diff --git a/.github/actions/build-cache/action.yml b/.github/actions/build-cache/action.yml index 5d6ce349ab..dc51b9695f 100644 --- a/.github/actions/build-cache/action.yml +++ b/.github/actions/build-cache/action.yml @@ -31,5 +31,14 @@ runs: restore-keys: | ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} ${{ runner.os }}-maven- + - name: Restore NPM Cache + id: cache-npm + uses: actions/cache/restore@v4 + with: + path: .hermit/node/cache/_cacache + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + ${{ runner.os }}-npm- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a6e3db134..774e2e95f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,8 @@ jobs: uses: ./.github/actions/build-cache - name: golangci-lint run: golangci-lint run --new-from-rev="$(git merge-base origin/main HEAD)" --out-format github-actions ./... + - name: lint-commit-or-rollback + run: lint-commit-or-rollback ./backend/... 2>&1 | to-annotation - name: go-check-sumtype shell: bash run: go-check-sumtype ./... 2>&1 | to-annotation @@ -177,7 +179,7 @@ jobs: - run: cd docs && zola build integration-shard: name: Shard Integration Tests - if: github.event_name != 'pull_request' || github.event.action == 'enqueued' + if: github.event_name != 'pull_request' || github.event.action == 'enqueued' || contains( github.event.pull_request.labels.*.name, 'run-integration') runs-on: ubuntu-latest outputs: matrix: ${{ steps.extract-tests.outputs.matrix }} @@ -193,7 +195,7 @@ jobs: echo "matrix={\"test\":$(jq -c -n '$ARGS.positional' --args $(git grep -l '^//go:build integration' | xargs grep '^func Test' | awk '{print $2}' | cut -d'(' -f1))}" >> "$GITHUB_OUTPUT" integration-run: name: Integration Test - if: github.event_name != 'pull_request' || github.event.action == 'enqueued' + if: github.event_name != 'pull_request' || github.event.action == 'enqueued' || contains( github.event.pull_request.labels.*.name, 'run-integration') needs: integration-shard runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/writecache.yml b/.github/workflows/writecache.yml index 3d1d4ade50..959a9c6e12 100644 --- a/.github/workflows/writecache.yml +++ b/.github/workflows/writecache.yml @@ -23,10 +23,13 @@ jobs: run: just build-all - name: Download Go Dependencies run: go mod download -x + - name: Download NPM Dependencies + run: find . -name package-lock.json -execdir npm ci \; - id: find-go-build-cache shell: bash run: echo "cache=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" - - uses: actions/cache/save@v4 + - name: Save Go Module Cache + uses: actions/cache/save@v4 with: path: | ~/go/pkg/mod @@ -38,3 +41,9 @@ jobs: with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + - name: Save NPM Modules Cache + id: cache-npm + uses: actions/cache/save@v4 + with: + path: .hermit/node/cache/_cacache + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 990e03fba2..42c509c032 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,9 +12,11 @@ This guide is for you. ## Development Prerequisites We recommend that you use OrbStack instead of Docker desktop when developing on this project: + ``` brew install orbstack ``` + or [OrbStack Website](https://orbstack.dev/) The tools used by this project are managed by @@ -56,9 +58,18 @@ $ ftl dev --recreate ./examples/go ### Code reviews -Because we're a geographically distributed team, we use a review-after-merge development flow. That is, if a PR is urgent, minor, or the developer has high confidence, we encourage merging without waiting for review in order to decrease friction. Conversely, if a change is more complex, or needs more eyes, we encourage developers to wait for review if it will make them feel more comfortable. Use your best judgement. +Our goal is to maintain velocity while keeping code quality high. To that end, the process is an exercise in trust on the part of the reviewer, and responsibility on the part of the PR author. + +In practice, the **reviewer** will review _and_ approve the PR at the same time, trusting that the author will apply the feedback before merging. + +On the **author's** side, they are responsible for reading and understanding all feedback, and applying that feedback where they think it is appropriate. If either side doesn't understand something and it's important, comment accordingly, or do a quick pairing to resolve it. The author should feel free to re-request a review. + +Additional points of note: -We discourage bike-shedding. Code and documentation are easy to change, we can always adjust it later. +- We discourage bike-shedding. Code and documentation are easy to change, we can always adjust it later. +- Keep your PRs digestible, large PRs are very difficult to comprehend and review. +- Changing code is cheap, we can fix it later. The only caveat here is data storage. +- Reviewing code is everybody's responsibility. ### Design process @@ -135,6 +146,7 @@ just build-sqlc ``` We use [dbmate](https://github.com/amacneil/dbmate) to manage migrations. To create a migration file, run `dbmate new` with the name of your migration. Example: + ``` dbmate new create_users_table ``` @@ -191,15 +203,19 @@ For an in-line replacement of `ftl dev `, use the command: just debug ``` -This command compiles a binary with debug information, runs `ftl dev ` using this binary, and provides an endpoint to attach a remote debugger at __127.0.0.1:2345__. +This command compiles a binary with debug information, runs `ftl dev ` using this binary, and provides an endpoint to attach a remote debugger at **127.0.0.1:2345**. You do not need to run `FTL_DEBUG=true just build ftl` separately when using this command. + ### Attaching a Debugger By running `just debug ` and then attaching a remote debugger, you can debug the FTL infrastructure while running your project. #### IntelliJ + Run `Debug FTL` from the `Run/Debug Configurations` dropdown while in the FTL project. + #### VSCode + Run `Debug FTL` from the `Run and Debug` dropdown while in the FTL project. ## Useful links diff --git a/Justfile b/Justfile index a2d09bc4e1..1d1441cc7a 100644 --- a/Justfile +++ b/Justfile @@ -31,6 +31,7 @@ clean: rm -rf frontend/node_modules find . -name '*.zip' -exec rm {} \; mvn -f kotlin-runtime/ftl-runtime clean + mvn -f java-runtime/ftl-runtime clean # Live rebuild the ftl binary whenever source changes. live-rebuild: @@ -41,7 +42,7 @@ dev *args: watchexec -r {{WATCHEXEC_ARGS}} -- "just build-sqlc && ftl dev {{args}}" # Build everything -build-all: build-protos-unconditionally build-frontend build-generate build-sqlc build-zips lsp-generate +build-all: build-protos-unconditionally build-frontend build-generate build-sqlc build-zips lsp-generate build-java @just build ftl ftl-controller ftl-runner ftl-initdb # Run "go generate" on all packages @@ -64,6 +65,9 @@ build +tools: build-protos build-zips build-frontend build-backend: just build ftl ftl-controller ftl-runner +build-java: + mvn -f java-runtime/ftl-runtime install + export DATABASE_URL := "postgres://postgres:secret@localhost:15432/ftl?sslmode=disable" # Explicitly initialise the database @@ -107,9 +111,10 @@ build-kt-runtime: @cd build/template && zip -q --symlinks -r ../../{{RUNNER_TEMPLATE_ZIP}} . # Install Node dependencies +# npm install fails intermittently due to network issues, so we retry a few times. npm-install: - @mk frontend/node_modules : frontend/package.json -- "cd frontend && npm install" - @mk extensions/vscode/node_modules : extensions/vscode/package.json extensions/vscode/src -- "cd extensions/vscode && npm install" + @mk frontend/node_modules : frontend/package.json -- "cd frontend && for i in {1..3}; do npm install && break || sleep 5; done" + @mk extensions/vscode/node_modules : extensions/vscode/package.json extensions/vscode/src -- "cd extensions/vscode && for i in {1..3}; do npm install && break || sleep 5; done" # Regenerate protos build-protos: npm-install @@ -158,6 +163,7 @@ lint-frontend: build-frontend # Lint the backend lint-backend: @golangci-lint run --new-from-rev=$(git merge-base origin/main HEAD) ./... + @lint-commit-or-rollback ./backend/... lint-scripts: @shellcheck -f gcc -e SC2016 $(find scripts -type f -not -path scripts/tests) | to-annotation diff --git a/backend/controller/admin/local_client_test.go b/backend/controller/admin/local_client_test.go index 1d4f9eab4e..97e0a66e54 100644 --- a/backend/controller/admin/local_client_test.go +++ b/backend/controller/admin/local_client_test.go @@ -6,15 +6,18 @@ import ( "context" "testing" + "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" + cf "github.com/TBD54566975/ftl/common/configuration" in "github.com/TBD54566975/ftl/integration" "github.com/TBD54566975/ftl/internal/log" - "github.com/alecthomas/assert/v2" - "github.com/alecthomas/types/optional" ) func TestDiskSchemaRetrieverWithBuildArtefact(t *testing.T) { - in.RunWithoutController(t, "ftl-project-dr.toml", + in.Run(t, + in.WithFTLConfig("ftl-project-dr.toml"), + in.WithoutController(), in.CopyModule("dischema"), in.Build("dischema"), func(t testing.TB, ic in.TestContext) { @@ -30,7 +33,9 @@ func TestDiskSchemaRetrieverWithBuildArtefact(t *testing.T) { } func TestDiskSchemaRetrieverWithNoSchema(t *testing.T) { - in.RunWithoutController(t, "ftl-project-dr.toml", + in.Run(t, + in.WithFTLConfig("ftl-project-dr.toml"), + in.WithoutController(), in.CopyModule("dischema"), func(t testing.TB, ic in.TestContext) { dsr := &diskSchemaRetriever{} diff --git a/backend/controller/console/console.go b/backend/controller/console/console.go index 23cbd9d3d2..67d9ea296b 100644 --- a/backend/controller/console/console.go +++ b/backend/controller/console/console.go @@ -195,7 +195,7 @@ func (c *ConsoleService) GetEvents(ctx context.Context, req *connect.Request[pbc // Get 1 more than the requested limit to determine if there are more results. limitPlusOne := limit + 1 - results, err := c.dal.QueryEvents(ctx, limitPlusOne, query...) + results, err := c.dal.QueryTimeline(ctx, limitPlusOne, query...) if err != nil { return nil, err } @@ -241,7 +241,7 @@ func (c *ConsoleService) StreamEvents(ctx context.Context, req *connect.Request[ newQuery = append(newQuery, dal.FilterTimeRange(thisRequestTime, lastEventTime)) } - events, err := c.dal.QueryEvents(ctx, int(req.Msg.Query.Limit), newQuery...) + events, err := c.dal.QueryTimeline(ctx, int(req.Msg.Query.Limit), newQuery...) if err != nil { return err } @@ -264,8 +264,8 @@ func (c *ConsoleService) StreamEvents(ctx context.Context, req *connect.Request[ } } -func eventsQueryProtoToDAL(pb *pbconsole.EventsQuery) ([]dal.EventFilter, error) { - var query []dal.EventFilter +func eventsQueryProtoToDAL(pb *pbconsole.EventsQuery) ([]dal.TimelineFilter, error) { + var query []dal.TimelineFilter if pb.Order == pbconsole.EventsQuery_DESC { query = append(query, dal.FilterDescending()) @@ -357,7 +357,7 @@ func eventsQueryProtoToDAL(pb *pbconsole.EventsQuery) ([]dal.EventFilter, error) return query, nil } -func eventDALToProto(event dal.Event) *pbconsole.Event { +func eventDALToProto(event dal.TimelineEvent) *pbconsole.Event { switch event := event.(type) { case *dal.CallEvent: var requestKey *string diff --git a/backend/controller/console/console_integration_test.go b/backend/controller/console/console_integration_test.go index b3d1baa882..22513f3610 100644 --- a/backend/controller/console/console_integration_test.go +++ b/backend/controller/console/console_integration_test.go @@ -6,9 +6,10 @@ import ( "testing" "connectrpc.com/connect" + "github.com/alecthomas/assert/v2" + pbconsole "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/console" in "github.com/TBD54566975/ftl/integration" - "github.com/alecthomas/assert/v2" ) // GetModules calls console service GetModules and returns the response. @@ -24,7 +25,7 @@ func GetModules(onResponse func(t testing.TB, resp *connect.Response[pbconsole.G } func TestConsoleGetModules(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("console"), in.Deploy("console"), GetModules(func(t testing.TB, resp *connect.Response[pbconsole.GetModulesResponse]) { diff --git a/backend/controller/console/testdata/go/console/go.mod b/backend/controller/console/testdata/go/console/go.mod index 467d787aa7..39b93f7a22 100644 --- a/backend/controller/console/testdata/go/console/go.mod +++ b/backend/controller/console/testdata/go/console/go.mod @@ -7,6 +7,7 @@ require github.com/TBD54566975/ftl v0.189.0 require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.1 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect @@ -33,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/console/testdata/go/console/go.sum b/backend/controller/console/testdata/go/console/go.sum index fbefaba633..9fbb9ebc36 100644 --- a/backend/controller/console/testdata/go/console/go.sum +++ b/backend/controller/console/testdata/go/console/go.sum @@ -2,6 +2,8 @@ connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= @@ -103,23 +105,27 @@ go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -134,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 28399102a9..3b79041c1c 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -4,6 +4,7 @@ import ( "bytes" "context" sha "crypto/sha256" + "database/sql" "encoding/binary" "encoding/json" "errors" @@ -25,7 +26,6 @@ import ( "github.com/alecthomas/types/either" "github.com/alecthomas/types/optional" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" "github.com/jellydator/ttlcache/v3" "github.com/jpillora/backoff" "golang.org/x/exp/maps" @@ -55,7 +55,6 @@ import ( cf "github.com/TBD54566975/ftl/common/configuration" frontend "github.com/TBD54566975/ftl/frontend" "github.com/TBD54566975/ftl/internal/cors" - "github.com/TBD54566975/ftl/internal/encryption" ftlhttp "github.com/TBD54566975/ftl/internal/http" "github.com/TBD54566975/ftl/internal/log" ftlmaps "github.com/TBD54566975/ftl/internal/maps" @@ -85,40 +84,6 @@ func (c *CommonConfig) Validate() error { return nil } -type EncryptionKeys struct { - Logs string `name:"log-key" help:"Key for sensitive log data in internal FTL tables." env:"FTL_LOG_ENCRYPTION_KEY"` - Async string `name:"async-key" help:"Key for sensitive async call data in internal FTL tables." env:"FTL_ASYNC_ENCRYPTION_KEY"` -} - -func (e EncryptionKeys) Encryptors(required bool) (*dal.Encryptors, error) { - encryptors := dal.Encryptors{} - if e.Logs != "" { - enc, err := encryption.NewForKeyOrURI(e.Logs) - if err != nil { - return nil, fmt.Errorf("could not create log encryptor: %w", err) - } - encryptors.Logs = enc - } else if required { - return nil, fmt.Errorf("FTL_LOG_ENCRYPTION_KEY is required") - } else { - encryptors.Logs = encryption.NoOpEncryptor{} - } - - if e.Async != "" { - enc, err := encryption.NewForKeyOrURI(e.Async) - if err != nil { - return nil, fmt.Errorf("could not create async calls encryptor: %w", err) - } - encryptors.Async = enc - } else if required { - return nil, fmt.Errorf("FTL_ASYNC_ENCRYPTION_KEY is required") - } else { - encryptors.Async = encryption.NoOpEncryptor{} - } - - return &encryptors, nil -} - type Config struct { Bind *url.URL `help:"Socket to bind to." default:"http://127.0.0.1:8892" env:"FTL_CONTROLLER_BIND"` IngressBind *url.URL `help:"Socket to bind to for ingress." default:"http://127.0.0.1:8891" env:"FTL_CONTROLLER_INGRESS_BIND"` @@ -133,7 +98,7 @@ type Config struct { ModuleUpdateFrequency time.Duration `help:"Frequency to send module updates." default:"30s"` EventLogRetention *time.Duration `help:"Delete call logs after this time period. 0 to disable" env:"FTL_EVENT_LOG_RETENTION" default:"24h"` ArtefactChunkSize int `help:"Size of each chunk streamed to the client." default:"1048576"` - EncryptionKeys + KMSURI *string `help:"URI for KMS key e.g. with fake-kms:// or aws-kms://arn:aws:kms:ap-southeast-2:12345:key/0000-1111" env:"FTL_KMS_URI"` CommonConfig } @@ -147,7 +112,7 @@ func (c *Config) SetDefaults() { } // Start the Controller. Blocks until the context is cancelled. -func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScaling, pool *pgxpool.Pool, encryptors *dal.Encryptors) error { +func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScaling, conn *sql.DB) error { config.SetDefaults() logger := log.FromContext(ctx) @@ -168,7 +133,7 @@ func Start(ctx context.Context, config Config, runnerScaling scaling.RunnerScali logger.Infof("Web console available at: %s", config.Bind) } - svc, err := New(ctx, pool, config, runnerScaling, encryptors) + svc, err := New(ctx, conn, config, runnerScaling) if err != nil { return err } @@ -226,7 +191,7 @@ type ControllerListListener interface { } type Service struct { - pool *pgxpool.Pool + conn *sql.DB dal *dal.DAL key model.ControllerKey deploymentLogsSink *deploymentLogsSink @@ -250,7 +215,7 @@ type Service struct { asyncCallsLock sync.Mutex } -func New(ctx context.Context, pool *pgxpool.Pool, config Config, runnerScaling scaling.RunnerScaling, encryptors *dal.Encryptors) (*Service, error) { +func New(ctx context.Context, conn *sql.DB, config Config, runnerScaling scaling.RunnerScaling) (*Service, error) { key := config.Key if config.Key.IsZero() { key = model.NewControllerKey(config.Bind.Hostname(), config.Bind.Port()) @@ -264,7 +229,7 @@ func New(ctx context.Context, pool *pgxpool.Pool, config Config, runnerScaling s config.ControllerTimeout = time.Second * 5 } - db, err := dal.New(ctx, pool, encryptors) + db, err := dal.New(ctx, conn, optional.Ptr[string](config.KMSURI)) if err != nil { return nil, fmt.Errorf("failed to create DAL: %w", err) } @@ -272,7 +237,7 @@ func New(ctx context.Context, pool *pgxpool.Pool, config Config, runnerScaling s svc := &Service{ tasks: scheduledtask.New(ctx, key, db), dal: db, - pool: pool, + conn: conn, key: key, deploymentLogsSink: newDeploymentLogsSink(ctx, db), clients: ttlcache.New(ttlcache.WithTTL[string, clients](time.Minute)), @@ -283,7 +248,7 @@ func New(ctx context.Context, pool *pgxpool.Pool, config Config, runnerScaling s svc.routes.Store(map[string][]dal.Route{}) svc.schema.Store(&schema.Schema{}) - cronSvc := cronjobs.New(ctx, key, svc.config.Advertise.Host, cronjobs.Config{Timeout: config.CronJobTimeout}, pool, svc.tasks, svc.callWithRequest) + cronSvc := cronjobs.New(ctx, key, svc.config.Advertise.Host, cronjobs.Config{Timeout: config.CronJobTimeout}, conn, svc.tasks, svc.callWithRequest) svc.cronJobs = cronSvc svc.controllerListListeners = append(svc.controllerListListeners, cronSvc) @@ -336,22 +301,27 @@ func New(ctx context.Context, pool *pgxpool.Pool, config Config, runnerScaling s } func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + start := time.Now() + routes, err := s.dal.GetIngressRoutes(r.Context(), r.Method) if err != nil { if errors.Is(err, dalerrs.ErrNotFound) { http.NotFound(w, r) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.None[*schemapb.Ref](), start, optional.Some("route not found in dal")) return } http.Error(w, err.Error(), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.None[*schemapb.Ref](), start, optional.Some("failed to resolve route from dal")) return } sch, err := s.dal.GetActiveSchema(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.None[*schemapb.Ref](), start, optional.Some("could not get active schema")) return } requestKey := model.NewRequestKey(model.OriginIngress, fmt.Sprintf("%s %s", r.Method, r.URL.Path)) - ingress.Handle(sch, requestKey, routes, w, r, s.callWithRequest) + ingress.Handle(start, sch, requestKey, routes, w, r, s.callWithRequest) } func (s *Service) ProcessList(ctx context.Context, req *connect.Request[ftlv1.ProcessListRequest]) (*connect.Response[ftlv1.ProcessListResponse], error) { @@ -1484,7 +1454,7 @@ func (s *Service) catchAsyncCall(ctx context.Context, logger *log.Logger, call * originalResult := either.RightOf[[]byte](originalError) request := map[string]any{ - "request": call.Request, + "request": json.RawMessage(call.Request), "error": originalError, } body, err := json.Marshal(request) diff --git a/backend/controller/cronjobs/cronjobs.go b/backend/controller/cronjobs/cronjobs.go index 1f555a1f79..c892c7000f 100644 --- a/backend/controller/cronjobs/cronjobs.go +++ b/backend/controller/cronjobs/cronjobs.go @@ -2,6 +2,7 @@ package cronjobs import ( "context" + "database/sql" "encoding/json" "errors" "fmt" @@ -12,7 +13,6 @@ import ( "github.com/alecthomas/types/optional" "github.com/alecthomas/types/pubsub" "github.com/benbjohnson/clock" - "github.com/jackc/pgx/v5/pgxpool" "github.com/jpillora/backoff" "github.com/serialx/hashring" @@ -97,8 +97,8 @@ type Service struct { hashRingState atomic.Value[*hashRingState] } -func New(ctx context.Context, key model.ControllerKey, requestSource string, config Config, pool *pgxpool.Pool, scheduler Scheduler, call ExecuteCallFunc) *Service { - return NewForTesting(ctx, key, requestSource, config, dal.New(pool), scheduler, call, clock.New()) +func New(ctx context.Context, key model.ControllerKey, requestSource string, config Config, conn *sql.DB, scheduler Scheduler, call ExecuteCallFunc) *Service { + return NewForTesting(ctx, key, requestSource, config, dal.New(conn), scheduler, call, clock.New()) } func NewForTesting(ctx context.Context, key model.ControllerKey, requestSource string, config Config, dal DAL, scheduler Scheduler, call ExecuteCallFunc, clock clock.Clock) *Service { diff --git a/backend/controller/cronjobs/cronjobs_integration_test.go b/backend/controller/cronjobs/cronjobs_integration_test.go index 61c250e34a..5824acbbf5 100644 --- a/backend/controller/cronjobs/cronjobs_integration_test.go +++ b/backend/controller/cronjobs/cronjobs_integration_test.go @@ -9,13 +9,15 @@ import ( "testing" "time" + "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" + "github.com/benbjohnson/clock" + db "github.com/TBD54566975/ftl/backend/controller/cronjobs/dal" parentdb "github.com/TBD54566975/ftl/backend/controller/dal" "github.com/TBD54566975/ftl/backend/controller/sql/sqltest" in "github.com/TBD54566975/ftl/integration" "github.com/TBD54566975/ftl/internal/log" - "github.com/alecthomas/assert/v2" - "github.com/benbjohnson/clock" ) func TestServiceWithRealDal(t *testing.T) { @@ -26,7 +28,7 @@ func TestServiceWithRealDal(t *testing.T) { conn := sqltest.OpenForTesting(ctx, t) dal := db.New(conn) - parentDAL, err := parentdb.New(ctx, conn, parentdb.NoOpEncryptors()) + parentDAL, err := parentdb.New(ctx, conn, optional.None[string]()) assert.NoError(t, err) // Using a real clock because real db queries use db clock @@ -51,7 +53,7 @@ func TestCron(t *testing.T) { t.Cleanup(func() { _ = os.Remove(tmpFile) }) - in.Run(t, "", + in.Run(t, in.CopyModule("cron"), in.Deploy("cron"), func(t testing.TB, ic in.TestContext) { diff --git a/backend/controller/cronjobs/cronjobs_test.go b/backend/controller/cronjobs/cronjobs_test.go index bba46f5a42..715476c932 100644 --- a/backend/controller/cronjobs/cronjobs_test.go +++ b/backend/controller/cronjobs/cronjobs_test.go @@ -37,7 +37,7 @@ func TestServiceWithMockDal(t *testing.T) { attemptCountMap: map[string]int{}, } conn := sqltest.OpenForTesting(ctx, t) - parentDAL, err := db.New(ctx, conn, db.NoOpEncryptors()) + parentDAL, err := db.New(ctx, conn, optional.None[string]()) assert.NoError(t, err) testServiceWithDal(ctx, t, mockDal, parentDAL, clk) diff --git a/backend/controller/cronjobs/dal/dal.go b/backend/controller/cronjobs/dal/dal.go index 9408465204..9499717fe6 100644 --- a/backend/controller/cronjobs/dal/dal.go +++ b/backend/controller/cronjobs/dal/dal.go @@ -6,9 +6,8 @@ import ( "fmt" "time" - "github.com/jackc/pgx/v5/pgxpool" - "github.com/TBD54566975/ftl/backend/controller/cronjobs/sql" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/internal/model" @@ -19,8 +18,8 @@ type DAL struct { db sql.DBI } -func New(pool *pgxpool.Pool) *DAL { - return &DAL{db: sql.NewDB(pool)} +func New(conn sql.ConnI) *DAL { + return &DAL{db: sql.NewDB(conn)} } func cronJobFromRow(row sql.GetCronJobsRow) model.CronJob { @@ -92,7 +91,7 @@ func (d *DAL) EndCronJob(ctx context.Context, job model.CronJob, next time.Time) // GetStaleCronJobs returns a list of cron jobs that have been executing longer than the duration func (d *DAL) GetStaleCronJobs(ctx context.Context, duration time.Duration) ([]model.CronJob, error) { - rows, err := d.db.GetStaleCronJobs(ctx, duration) + rows, err := d.db.GetStaleCronJobs(ctx, sqltypes.Duration(duration)) if err != nil { return nil, fmt.Errorf("failed to get stale cron jobs: %w", dalerrs.TranslatePGError(err)) } diff --git a/backend/controller/cronjobs/sql/db.go b/backend/controller/cronjobs/sql/db.go index c4b45fb311..0e0973111c 100644 --- a/backend/controller/cronjobs/sql/db.go +++ b/backend/controller/cronjobs/sql/db.go @@ -1,20 +1,19 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql import ( "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" + "database/sql" ) type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { @@ -25,7 +24,7 @@ type Queries struct { db DBTX } -func (q *Queries) WithTx(tx pgx.Tx) *Queries { +func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ db: tx, } diff --git a/backend/controller/cronjobs/sql/models.go b/backend/controller/cronjobs/sql/models.go index e67f71b017..5c2780936a 100644 --- a/backend/controller/cronjobs/sql/models.go +++ b/backend/controller/cronjobs/sql/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql @@ -11,10 +11,12 @@ import ( "time" "github.com/TBD54566975/ftl/backend/controller/leases" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" "github.com/google/uuid" + "github.com/sqlc-dev/pqtype" ) type AsyncCallState string @@ -380,12 +382,12 @@ type AsyncCall struct { Response []byte Error optional.Option[string] RemainingAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] Catching bool ParentRequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage } type Controller struct { @@ -415,7 +417,7 @@ type Deployment struct { ModuleID int64 Key model.DeploymentKey Schema *schema.Module - Labels []byte + Labels json.RawMessage MinReplicas int32 } @@ -427,18 +429,10 @@ type DeploymentArtefact struct { Path string } -type Event struct { - ID int64 - TimeStamp time.Time - DeploymentID int64 - RequestID optional.Option[int64] - Type EventType - CustomKey1 optional.Option[string] - CustomKey2 optional.Option[string] - CustomKey3 optional.Option[string] - CustomKey4 optional.Option[string] - Payload json.RawMessage - ParentRequestID optional.Option[string] +type EncryptionKey struct { + ID int64 + Key []byte + CreatedAt time.Time } type FsmInstance struct { @@ -467,7 +461,7 @@ type Lease struct { Key leases.Key CreatedAt time.Time ExpiresAt time.Time - Metadata []byte + Metadata pqtype.NullRawMessage } type Module struct { @@ -481,7 +475,7 @@ type ModuleConfiguration struct { CreatedAt time.Time Module optional.Option[string] Name string - Value []byte + Value json.RawMessage } type ModuleSecret struct { @@ -509,7 +503,21 @@ type Runner struct { Endpoint string ModuleName optional.Option[string] DeploymentID optional.Option[int64] - Labels []byte + Labels json.RawMessage +} + +type Timeline struct { + ID int64 + TimeStamp time.Time + DeploymentID int64 + RequestID optional.Option[int64] + Type EventType + CustomKey1 optional.Option[string] + CustomKey2 optional.Option[string] + CustomKey3 optional.Option[string] + CustomKey4 optional.Option[string] + Payload []byte + ParentRequestID optional.Option[string] } type Topic struct { @@ -530,7 +538,7 @@ type TopicEvent struct { Payload []byte Caller optional.Option[string] RequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage } type TopicSubscriber struct { @@ -541,8 +549,8 @@ type TopicSubscriber struct { DeploymentID int64 Sink schema.RefKey RetryAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] } diff --git a/backend/controller/cronjobs/sql/querier.go b/backend/controller/cronjobs/sql/querier.go index 9f8cb6a55a..2bfa010982 100644 --- a/backend/controller/cronjobs/sql/querier.go +++ b/backend/controller/cronjobs/sql/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql @@ -8,6 +8,7 @@ import ( "context" "time" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/internal/model" ) @@ -15,7 +16,7 @@ type Querier interface { CreateCronJob(ctx context.Context, arg CreateCronJobParams) error EndCronJob(ctx context.Context, nextExecution time.Time, key model.CronJobKey, startTime time.Time) (EndCronJobRow, error) GetCronJobs(ctx context.Context) ([]GetCronJobsRow, error) - GetStaleCronJobs(ctx context.Context, dollar_1 time.Duration) ([]GetStaleCronJobsRow, error) + GetStaleCronJobs(ctx context.Context, dollar_1 sqltypes.Duration) ([]GetStaleCronJobsRow, error) StartCronJobs(ctx context.Context, keys []string) ([]StartCronJobsRow, error) } diff --git a/backend/controller/cronjobs/sql/queries.sql.go b/backend/controller/cronjobs/sql/queries.sql.go index 5199dc158a..1064924f55 100644 --- a/backend/controller/cronjobs/sql/queries.sql.go +++ b/backend/controller/cronjobs/sql/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: queries.sql package sql @@ -9,7 +9,9 @@ import ( "context" "time" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/internal/model" + "github.com/lib/pq" ) const createCronJob = `-- name: CreateCronJob :exec @@ -35,7 +37,7 @@ type CreateCronJobParams struct { } func (q *Queries) CreateCronJob(ctx context.Context, arg CreateCronJobParams) error { - _, err := q.db.Exec(ctx, createCronJob, + _, err := q.db.ExecContext(ctx, createCronJob, arg.Key, arg.DeploymentKey, arg.ModuleName, @@ -75,7 +77,7 @@ type EndCronJobRow struct { } func (q *Queries) EndCronJob(ctx context.Context, nextExecution time.Time, key model.CronJobKey, startTime time.Time) (EndCronJobRow, error) { - row := q.db.QueryRow(ctx, endCronJob, nextExecution, key, startTime) + row := q.db.QueryRowContext(ctx, endCronJob, nextExecution, key, startTime) var i EndCronJobRow err := row.Scan( &i.Key, @@ -109,7 +111,7 @@ type GetCronJobsRow struct { } func (q *Queries) GetCronJobs(ctx context.Context) ([]GetCronJobsRow, error) { - rows, err := q.db.Query(ctx, getCronJobs) + rows, err := q.db.QueryContext(ctx, getCronJobs) if err != nil { return nil, err } @@ -131,6 +133,9 @@ func (q *Queries) GetCronJobs(ctx context.Context) ([]GetCronJobsRow, error) { } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -156,8 +161,8 @@ type GetStaleCronJobsRow struct { State model.CronJobState } -func (q *Queries) GetStaleCronJobs(ctx context.Context, dollar_1 time.Duration) ([]GetStaleCronJobsRow, error) { - rows, err := q.db.Query(ctx, getStaleCronJobs, dollar_1) +func (q *Queries) GetStaleCronJobs(ctx context.Context, dollar_1 sqltypes.Duration) ([]GetStaleCronJobsRow, error) { + rows, err := q.db.QueryContext(ctx, getStaleCronJobs, dollar_1) if err != nil { return nil, err } @@ -179,6 +184,9 @@ func (q *Queries) GetStaleCronJobs(ctx context.Context, dollar_1 time.Duration) } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -221,7 +229,7 @@ type StartCronJobsRow struct { } func (q *Queries) StartCronJobs(ctx context.Context, keys []string) ([]StartCronJobsRow, error) { - rows, err := q.db.Query(ctx, startCronJobs, keys) + rows, err := q.db.QueryContext(ctx, startCronJobs, pq.Array(keys)) if err != nil { return nil, err } @@ -245,6 +253,9 @@ func (q *Queries) StartCronJobs(ctx context.Context, keys []string) ([]StartCron } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } diff --git a/backend/controller/cronjobs/testdata/go/cron/go.mod b/backend/controller/cronjobs/testdata/go/cron/go.mod index d7700833c2..8668d5c713 100644 --- a/backend/controller/cronjobs/testdata/go/cron/go.mod +++ b/backend/controller/cronjobs/testdata/go/cron/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/cronjobs/testdata/go/cron/go.sum b/backend/controller/cronjobs/testdata/go/cron/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/backend/controller/cronjobs/testdata/go/cron/go.sum +++ b/backend/controller/cronjobs/testdata/go/cron/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/dal/async_calls.go b/backend/controller/dal/async_calls.go index 02c576f932..e4ab2f4688 100644 --- a/backend/controller/dal/async_calls.go +++ b/backend/controller/dal/async_calls.go @@ -2,7 +2,6 @@ package dal import ( "context" - "encoding/json" "errors" "fmt" "time" @@ -12,8 +11,10 @@ import ( "github.com/alecthomas/types/optional" "github.com/TBD54566975/ftl/backend/controller/sql" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/encryption" ) type asyncOriginParseRoot struct { @@ -76,7 +77,7 @@ type AsyncCall struct { Origin AsyncOrigin Verb schema.RefKey CatchVerb optional.Option[schema.RefKey] - Request json.RawMessage + Request []byte ScheduledAt time.Time QueueDepth int64 ParentRequestKey optional.Option[string] @@ -101,7 +102,7 @@ func (d *DAL) AcquireAsyncCall(ctx context.Context) (call *AsyncCall, err error) defer tx.CommitOrRollback(ctx, &err) ttl := time.Second * 5 - row, err := tx.db.AcquireAsyncCall(ctx, ttl) + row, err := tx.db.AcquireAsyncCall(ctx, sqltypes.Duration(ttl)) if err != nil { err = dalerrs.TranslatePGError(err) if errors.Is(err, dalerrs.ErrNotFound) { @@ -114,8 +115,7 @@ func (d *DAL) AcquireAsyncCall(ctx context.Context) (call *AsyncCall, err error) return nil, fmt.Errorf("failed to parse origin key %q: %w", row.Origin, err) } - var decryptedRequest json.RawMessage - err = d.encryptors.Async.DecryptJSON(row.Request, &decryptedRequest) + decryptedRequest, err := d.decrypt(encryption.AsyncSubKey, row.Request) if err != nil { return nil, fmt.Errorf("failed to decrypt async call request: %w", err) } @@ -131,11 +131,11 @@ func (d *DAL) AcquireAsyncCall(ctx context.Context) (call *AsyncCall, err error) ScheduledAt: row.ScheduledAt, QueueDepth: row.QueueDepth, ParentRequestKey: row.ParentRequestKey, - TraceContext: row.TraceContext, + TraceContext: row.TraceContext.RawMessage, RemainingAttempts: row.RemainingAttempts, Error: row.Error, - Backoff: row.Backoff, - MaxBackoff: row.MaxBackoff, + Backoff: time.Duration(row.Backoff), + MaxBackoff: time.Duration(row.MaxBackoff), Catching: row.Catching, }, nil } @@ -158,7 +158,11 @@ func (d *DAL) CompleteAsyncCall(ctx context.Context, didScheduleAnotherCall = false switch result := result.(type) { case either.Left[[]byte, string]: // Successful response. - _, err = tx.db.SucceedAsyncCall(ctx, result.Get(), call.ID) + encryptedResult, err := d.encrypt(encryption.AsyncSubKey, result.Get()) + if err != nil { + return false, fmt.Errorf("failed to encrypt async call result: %w", err) + } + _, err = tx.db.SucceedAsyncCall(ctx, encryptedResult, call.ID) if err != nil { return false, dalerrs.TranslatePGError(err) //nolint:wrapcheck } @@ -169,8 +173,8 @@ func (d *DAL) CompleteAsyncCall(ctx context.Context, ID: call.ID, Error: result.Get(), RemainingAttempts: call.RemainingAttempts - 1, - Backoff: min(call.Backoff*2, call.MaxBackoff), - MaxBackoff: call.MaxBackoff, + Backoff: sqltypes.Duration(min(call.Backoff*2, call.MaxBackoff)), + MaxBackoff: sqltypes.Duration(call.MaxBackoff), ScheduledAt: time.Now().Add(call.Backoff), }) if err != nil { @@ -190,8 +194,8 @@ func (d *DAL) CompleteAsyncCall(ctx context.Context, ID: call.ID, Error: result.Get(), RemainingAttempts: 0, - Backoff: call.Backoff, // maintain backoff - MaxBackoff: call.MaxBackoff, + Backoff: sqltypes.Duration(call.Backoff), // maintain backoff + MaxBackoff: sqltypes.Duration(call.MaxBackoff), ScheduledAt: scheduledAt, Catching: true, OriginalError: optional.Some(originalError), @@ -223,10 +227,14 @@ func (d *DAL) LoadAsyncCall(ctx context.Context, id int64) (*AsyncCall, error) { if err != nil { return nil, fmt.Errorf("failed to parse origin key %q: %w", row.Origin, err) } + request, err := d.decrypt(encryption.AsyncSubKey, row.Request) + if err != nil { + return nil, fmt.Errorf("failed to decrypt async call request: %w", err) + } return &AsyncCall{ ID: row.ID, Verb: row.Verb, Origin: origin, - Request: row.Request, + Request: request, }, nil } diff --git a/backend/controller/dal/async_calls_test.go b/backend/controller/dal/async_calls_test.go index 7aab05128f..965a3d6a24 100644 --- a/backend/controller/dal/async_calls_test.go +++ b/backend/controller/dal/async_calls_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/alecthomas/types/optional" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltest" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/internal/log" @@ -13,7 +15,7 @@ import ( func TestNoCallToAcquire(t *testing.T) { ctx := log.ContextWithNewDefaultLogger(context.Background()) conn := sqltest.OpenForTesting(ctx, t) - dal, err := New(ctx, conn, NoOpEncryptors()) + dal, err := New(ctx, conn, optional.None[string]()) assert.NoError(t, err) _, err = dal.AcquireAsyncCall(ctx) diff --git a/backend/controller/dal/dal.go b/backend/controller/dal/dal.go index 1ea5f6daf0..7172fe71dc 100644 --- a/backend/controller/dal/dal.go +++ b/backend/controller/dal/dal.go @@ -3,6 +3,7 @@ package dal import ( "context" + stdsql "database/sql" "encoding/json" "errors" "fmt" @@ -13,10 +14,10 @@ import ( "github.com/alecthomas/types/optional" "github.com/alecthomas/types/pubsub" sets "github.com/deckarep/golang-set/v2" - "github.com/jackc/pgx/v5/pgxpool" "google.golang.org/protobuf/proto" "github.com/TBD54566975/ftl/backend/controller/sql" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" dalerrs "github.com/TBD54566975/ftl/backend/dal" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/schema" @@ -209,41 +210,30 @@ func WithReservation(ctx context.Context, reservation Reservation, fn func() err return reservation.Commit(ctx) } -func New(ctx context.Context, pool *pgxpool.Pool, encryptors *Encryptors) (*DAL, error) { - _, err := pool.Acquire(ctx) - if err != nil { - return nil, fmt.Errorf("could not acquire connection: %w", err) - } - dal := &DAL{ - db: sql.NewDB(pool), +func New(ctx context.Context, conn *stdsql.DB, kmsURL optional.Option[string]) (*DAL, error) { + d := &DAL{ + db: sql.NewDB(conn), DeploymentChanges: pubsub.New[DeploymentNotification](), - encryptors: encryptors, + kmsURL: kmsURL, } - return dal, nil + if err := d.setupEncryptor(ctx); err != nil { + return nil, fmt.Errorf("failed to setup encryptor: %w", err) + } + + return d, nil } type DAL struct { - db sql.DBI - encryptors *Encryptors + db sql.DBI + + kmsURL optional.Option[string] + encryptor encryption.DataEncryptor // DeploymentChanges is a Topic that receives changes to the deployments table. DeploymentChanges *pubsub.Topic[DeploymentNotification] } -type Encryptors struct { - Logs encryption.Encryptable - Async encryption.Encryptable -} - -// NoOpEncryptors do not encrypt potentially sensitive data. -func NoOpEncryptors() *Encryptors { - return &Encryptors{ - Logs: encryption.NoOpEncryptor{}, - Async: encryption.NoOpEncryptor{}, - } -} - // Tx is DAL within a transaction. type Tx struct { *DAL @@ -290,7 +280,8 @@ func (d *DAL) Begin(ctx context.Context) (*Tx, error) { return &Tx{&DAL{ db: tx, DeploymentChanges: d.DeploymentChanges, - encryptors: d.encryptors, + kmsURL: d.kmsURL, + encryptor: d.encryptor, }}, nil } @@ -602,13 +593,13 @@ func (d *DAL) UpsertRunner(ctx context.Context, runner Runner) error { // KillStaleRunners deletes runners that have not had heartbeats for the given duration. func (d *DAL) KillStaleRunners(ctx context.Context, age time.Duration) (int64, error) { - count, err := d.db.KillStaleRunners(ctx, age) + count, err := d.db.KillStaleRunners(ctx, sqltypes.Duration(age)) return count, err } // KillStaleControllers deletes controllers that have not had heartbeats for the given duration. func (d *DAL) KillStaleControllers(ctx context.Context, age time.Duration) (int64, error) { - count, err := d.db.KillStaleControllers(ctx, age) + count, err := d.db.KillStaleControllers(ctx, sqltypes.Duration(age)) return count, err } @@ -689,7 +680,7 @@ func (p *postgresClaim) Rollback(ctx context.Context) error { func (p *postgresClaim) Runner() Runner { return p.runner } // SetDeploymentReplicas activates the given deployment. -func (d *DAL) SetDeploymentReplicas(ctx context.Context, key model.DeploymentKey, minReplicas int) error { +func (d *DAL) SetDeploymentReplicas(ctx context.Context, key model.DeploymentKey, minReplicas int) (err error) { // Start the transaction tx, err := d.db.Begin(ctx) if err != nil { @@ -718,14 +709,14 @@ func (d *DAL) SetDeploymentReplicas(ctx context.Context, key model.DeploymentKey return dalerrs.TranslatePGError(err) } } - payload, err := d.encryptors.Logs.EncryptJSON(map[string]any{ + payload, err := d.encryptJSON(encryption.TimelineSubKey, map[string]interface{}{ "prev_min_replicas": deployment.MinReplicas, "min_replicas": minReplicas, }) if err != nil { return fmt.Errorf("failed to encrypt payload for InsertDeploymentUpdatedEvent: %w", err) } - err = tx.InsertDeploymentUpdatedEvent(ctx, sql.InsertDeploymentUpdatedEventParams{ + err = tx.InsertTimelineDeploymentUpdatedEvent(ctx, sql.InsertTimelineDeploymentUpdatedEventParams{ DeploymentKey: key, Payload: payload, }) @@ -791,7 +782,7 @@ func (d *DAL) ReplaceDeployment(ctx context.Context, newDeploymentKey model.Depl } } - payload, err := d.encryptors.Logs.EncryptJSON(map[string]any{ + payload, err := d.encryptJSON(encryption.TimelineSubKey, map[string]any{ "min_replicas": int32(minReplicas), "replaced": replacedDeploymentKey, }) @@ -799,7 +790,7 @@ func (d *DAL) ReplaceDeployment(ctx context.Context, newDeploymentKey model.Depl return fmt.Errorf("replace deployment failed to encrypt payload: %w", err) } - err = tx.InsertDeploymentCreatedEvent(ctx, sql.InsertDeploymentCreatedEventParams{ + err = tx.InsertTimelineDeploymentCreatedEvent(ctx, sql.InsertTimelineDeploymentCreatedEventParams{ DeploymentKey: newDeploymentKey, Language: newDeployment.Language, ModuleName: newDeployment.ModuleName, @@ -946,7 +937,7 @@ func (d *DAL) GetProcessList(ctx context.Context) ([]Process, error) { var runner optional.Option[ProcessRunner] if endpoint, ok := row.Endpoint.Get(); ok { var labels model.Labels - if err := json.Unmarshal(row.RunnerLabels, &labels); err != nil { + if err := json.Unmarshal(row.RunnerLabels.RawMessage, &labels); err != nil { return Process{}, fmt.Errorf("invalid labels JSON for runner %s: %w", row.RunnerKey, err) } @@ -1066,11 +1057,11 @@ func (d *DAL) InsertLogEvent(ctx context.Context, log *LogEvent) error { "error": log.Error, "stack": log.Stack, } - encryptedPayload, err := d.encryptors.Logs.EncryptJSON(payload) + encryptedPayload, err := d.encryptJSON(encryption.TimelineSubKey, payload) if err != nil { return fmt.Errorf("failed to encrypt log payload: %w", err) } - return dalerrs.TranslatePGError(d.db.InsertLogEvent(ctx, sql.InsertLogEventParams{ + return dalerrs.TranslatePGError(d.db.InsertTimelineLogEvent(ctx, sql.InsertTimelineLogEventParams{ DeploymentKey: log.DeploymentKey, RequestKey: requestKey, TimeStamp: log.Time, @@ -1146,7 +1137,7 @@ func (d *DAL) InsertCallEvent(ctx context.Context, call *CallEvent) error { if pr, ok := call.ParentRequestKey.Get(); ok { parentRequestKey = optional.Some(pr.String()) } - payload, err := d.encryptors.Logs.EncryptJSON(map[string]any{ + payload, err := d.encryptJSON(encryption.TimelineSubKey, map[string]any{ "duration_ms": call.Duration.Milliseconds(), "request": call.Request, "response": call.Response, @@ -1156,7 +1147,7 @@ func (d *DAL) InsertCallEvent(ctx context.Context, call *CallEvent) error { if err != nil { return fmt.Errorf("failed to encrypt call payload: %w", err) } - return dalerrs.TranslatePGError(d.db.InsertCallEvent(ctx, sql.InsertCallEventParams{ + return dalerrs.TranslatePGError(d.db.InsertTimelineCallEvent(ctx, sql.InsertTimelineCallEventParams{ DeploymentKey: call.DeploymentKey, RequestKey: requestKey, ParentRequestKey: parentRequestKey, @@ -1170,7 +1161,7 @@ func (d *DAL) InsertCallEvent(ctx context.Context, call *CallEvent) error { } func (d *DAL) DeleteOldEvents(ctx context.Context, eventType EventType, age time.Duration) (int64, error) { - count, err := d.db.DeleteOldEvents(ctx, age, eventType) + count, err := d.db.DeleteOldTimelineEvents(ctx, sqltypes.Duration(age), eventType) return count, dalerrs.TranslatePGError(err) } diff --git a/backend/controller/dal/dal_test.go b/backend/controller/dal/dal_test.go index 0f3be9a3a9..43ed5e9c4c 100644 --- a/backend/controller/dal/dal_test.go +++ b/backend/controller/dal/dal_test.go @@ -25,7 +25,7 @@ import ( func TestDAL(t *testing.T) { ctx := log.ContextWithNewDefaultLogger(context.Background()) conn := sqltest.OpenForTesting(ctx, t) - dal, err := New(ctx, conn, NoOpEncryptors()) + dal, err := New(ctx, conn, optional.None[string]()) assert.NoError(t, err) assert.NotZero(t, dal) var testContent = bytes.Repeat([]byte("sometestcontentthatislongerthanthereadbuffer"), 100) @@ -235,7 +235,7 @@ func TestDAL(t *testing.T) { DeploymentKey: deploymentKey, RequestKey: optional.Some(requestKey), Request: []byte("{}"), - Response: []byte(`{"time": "now"}`), + Response: []byte(`{"time":"now"}`), DestVerb: schema.Ref{Module: "time", Name: "time"}, } t.Run("InsertCallEvent", func(t *testing.T) { @@ -263,39 +263,39 @@ func TestDAL(t *testing.T) { t.Run("QueryEvents", func(t *testing.T) { t.Run("Limit", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, 1) + events, err := dal.QueryTimeline(ctx, 1) assert.NoError(t, err) assert.Equal(t, 1, len(events)) }) t.Run("NoFilters", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, 1000) + events, err := dal.QueryTimeline(ctx, 1000) assert.NoError(t, err) - assertEventsEqual(t, []Event{expectedDeploymentUpdatedEvent, callEvent, logEvent}, events) + assertEventsEqual(t, []TimelineEvent{expectedDeploymentUpdatedEvent, callEvent, logEvent}, events) }) t.Run("ByDeployment", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, 1000, FilterDeployments(deploymentKey)) + events, err := dal.QueryTimeline(ctx, 1000, FilterDeployments(deploymentKey)) assert.NoError(t, err) - assertEventsEqual(t, []Event{expectedDeploymentUpdatedEvent, callEvent, logEvent}, events) + assertEventsEqual(t, []TimelineEvent{expectedDeploymentUpdatedEvent, callEvent, logEvent}, events) }) t.Run("ByCall", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, 1000, FilterTypes(EventTypeCall), FilterCall(optional.None[string](), "time", optional.None[string]())) + events, err := dal.QueryTimeline(ctx, 1000, FilterTypes(EventTypeCall), FilterCall(optional.None[string](), "time", optional.None[string]())) assert.NoError(t, err) - assertEventsEqual(t, []Event{callEvent}, events) + assertEventsEqual(t, []TimelineEvent{callEvent}, events) }) t.Run("ByLogLevel", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, 1000, FilterTypes(EventTypeLog), FilterLogLevel(log.Trace)) + events, err := dal.QueryTimeline(ctx, 1000, FilterTypes(EventTypeLog), FilterLogLevel(log.Trace)) assert.NoError(t, err) - assertEventsEqual(t, []Event{logEvent}, events) + assertEventsEqual(t, []TimelineEvent{logEvent}, events) }) t.Run("ByRequests", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, 1000, FilterRequests(requestKey)) + events, err := dal.QueryTimeline(ctx, 1000, FilterRequests(requestKey)) assert.NoError(t, err) - assertEventsEqual(t, []Event{callEvent, logEvent}, events) + assertEventsEqual(t, []TimelineEvent{callEvent, logEvent}, events) }) }) @@ -386,7 +386,7 @@ func TestRunnerStateFromProto(t *testing.T) { assert.Equal(t, RunnerStateIdle, RunnerStateFromProto(state)) } -func normaliseEvents(events []Event) []Event { +func normaliseEvents(events []TimelineEvent) []TimelineEvent { for i := range len(events) { event := events[i] re := reflect.Indirect(reflect.ValueOf(event)) @@ -396,10 +396,11 @@ func normaliseEvents(events []Event) []Event { f.Set(reflect.Zero(f.Type())) events[i] = event } + return events } -func assertEventsEqual(t *testing.T, expected, actual []Event) { +func assertEventsEqual(t *testing.T, expected, actual []TimelineEvent) { t.Helper() assert.Equal(t, normaliseEvents(expected), normaliseEvents(actual)) } @@ -407,7 +408,7 @@ func assertEventsEqual(t *testing.T, expected, actual []Event) { func TestDeleteOldEvents(t *testing.T) { ctx := log.ContextWithNewDefaultLogger(context.Background()) conn := sqltest.OpenForTesting(ctx, t) - dal, err := New(ctx, conn, NoOpEncryptors()) + dal, err := New(ctx, conn, optional.None[string]()) assert.NoError(t, err) var testContent = bytes.Repeat([]byte("sometestcontentthatislongerthanthereadbuffer"), 100) diff --git a/backend/controller/dal/encryption.go b/backend/controller/dal/encryption.go new file mode 100644 index 0000000000..3bedc9476a --- /dev/null +++ b/backend/controller/dal/encryption.go @@ -0,0 +1,106 @@ +package dal + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/TBD54566975/ftl/backend/dal" + "github.com/TBD54566975/ftl/internal/encryption" + "github.com/TBD54566975/ftl/internal/log" +) + +func (d *DAL) encrypt(subKey encryption.SubKey, cleartext []byte) ([]byte, error) { + if d.encryptor == nil { + return nil, fmt.Errorf("encryptor not set") + } + + v, err := d.encryptor.Encrypt(subKey, cleartext) + if err != nil { + return nil, fmt.Errorf("failed to encrypt binary with subkey %s: %w", subKey, err) + } + + return v, nil +} + +func (d *DAL) decrypt(subKey encryption.SubKey, encrypted []byte) ([]byte, error) { + if d.encryptor == nil { + return nil, fmt.Errorf("encryptor not set") + } + + v, err := d.encryptor.Decrypt(subKey, encrypted) + if err != nil { + return nil, fmt.Errorf("failed to decrypt binary with subkey %s: %w", subKey, err) + } + + return v, nil +} + +func (d *DAL) encryptJSON(subKey encryption.SubKey, v any) ([]byte, error) { + serialized, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("failed to marshal JSON: %w", err) + } + + return d.encrypt(subKey, serialized) +} + +func (d *DAL) decryptJSON(subKey encryption.SubKey, encrypted []byte, v any) error { //nolint:unparam + decrypted, err := d.decrypt(subKey, encrypted) + if err != nil { + return fmt.Errorf("failed to decrypt json with subkey %s: %w", subKey, err) + } + + if err = json.Unmarshal(decrypted, v); err != nil { + return fmt.Errorf("failed to unmarshal JSON: %w", err) + } + + return nil +} + +// setupEncryptor sets up the encryptor for the DAL. +// It will either create a key or load the existing one. +// If the KMS URL is not set, it will use a NoOpEncryptor which does not encrypt anything. +func (d *DAL) setupEncryptor(ctx context.Context) (err error) { + logger := log.FromContext(ctx) + tx, err := d.Begin(ctx) + if err != nil { + return fmt.Errorf("failed to begin transaction: %w", err) + } + defer tx.CommitOrRollback(ctx, &err) + + url, ok := d.kmsURL.Get() + if !ok { + logger.Infof("KMS URL not set, encryption not enabled") + d.encryptor = encryption.NewNoOpEncryptor() + return nil + } + + encryptedKey, err := tx.db.GetOnlyEncryptionKey(ctx) + if err != nil { + if dal.IsNotFound(err) { + logger.Infof("No encryption key found, generating a new one") + encryptor, err := encryption.NewKMSEncryptorGenerateKey(url, nil) + if err != nil { + return fmt.Errorf("failed to create encryptor for generation: %w", err) + } + d.encryptor = encryptor + + if err = tx.db.CreateOnlyEncryptionKey(ctx, encryptor.GetEncryptedKeyset()); err != nil { + return fmt.Errorf("failed to create only encryption key: %w", err) + } + + return nil + } + return fmt.Errorf("failed to get only encryption key: %w", err) + } + + logger.Debugf("Encryption key found, using it") + encryptor, err := encryption.NewKMSEncryptorWithKMS(url, nil, encryptedKey) + if err != nil { + return fmt.Errorf("failed to create encryptor with encrypted key: %w", err) + } + d.encryptor = encryptor + + return nil +} diff --git a/backend/controller/dal/events.go b/backend/controller/dal/events.go index 7250e047ac..1ae4282b11 100644 --- a/backend/controller/dal/events.go +++ b/backend/controller/dal/events.go @@ -2,17 +2,18 @@ package dal import ( "context" + stdsql "database/sql" "encoding/json" "fmt" "strconv" "time" "github.com/alecthomas/types/optional" - "github.com/jackc/pgx/v5" "github.com/TBD54566975/ftl/backend/controller/sql" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/encryption" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" ) @@ -27,10 +28,10 @@ const ( EventTypeDeploymentUpdated = sql.EventTypeDeploymentUpdated ) -// Event types. +// TimelineEvent types. // //sumtype:decl -type Event interface { +type TimelineEvent interface { GetID() int64 event() } @@ -111,9 +112,9 @@ type eventFilter struct { descending bool } -type EventFilter func(query *eventFilter) +type TimelineFilter func(query *eventFilter) -func FilterLogLevel(level log.Level) EventFilter { +func FilterLogLevel(level log.Level) TimelineFilter { return func(query *eventFilter) { query.level = &level } @@ -122,19 +123,19 @@ func FilterLogLevel(level log.Level) EventFilter { // FilterCall filters call events between the given modules. // // May be called multiple times. -func FilterCall(sourceModule optional.Option[string], destModule string, destVerb optional.Option[string]) EventFilter { +func FilterCall(sourceModule optional.Option[string], destModule string, destVerb optional.Option[string]) TimelineFilter { return func(query *eventFilter) { query.calls = append(query.calls, &eventFilterCall{sourceModule: sourceModule, destModule: destModule, destVerb: destVerb}) } } -func FilterDeployments(deploymentKeys ...model.DeploymentKey) EventFilter { +func FilterDeployments(deploymentKeys ...model.DeploymentKey) TimelineFilter { return func(query *eventFilter) { query.deployments = append(query.deployments, deploymentKeys...) } } -func FilterRequests(requestKeys ...model.RequestKey) EventFilter { +func FilterRequests(requestKeys ...model.RequestKey) TimelineFilter { return func(query *eventFilter) { for _, request := range requestKeys { query.requests = append(query.requests, request.String()) @@ -142,7 +143,7 @@ func FilterRequests(requestKeys ...model.RequestKey) EventFilter { } } -func FilterTypes(types ...sql.EventType) EventFilter { +func FilterTypes(types ...sql.EventType) TimelineFilter { return func(query *eventFilter) { query.types = append(query.types, types...) } @@ -151,7 +152,7 @@ func FilterTypes(types ...sql.EventType) EventFilter { // FilterTimeRange filters events between the given times, inclusive. // // Either maybe be zero to indicate no upper or lower bound. -func FilterTimeRange(olderThan, newerThan time.Time) EventFilter { +func FilterTimeRange(olderThan, newerThan time.Time) TimelineFilter { return func(query *eventFilter) { query.newerThan = newerThan query.olderThan = olderThan @@ -159,7 +160,7 @@ func FilterTimeRange(olderThan, newerThan time.Time) EventFilter { } // FilterIDRange filters events between the given IDs, inclusive. -func FilterIDRange(higherThan, lowerThan int64) EventFilter { +func FilterIDRange(higherThan, lowerThan int64) TimelineFilter { return func(query *eventFilter) { query.idHigherThan = higherThan query.idLowerThan = lowerThan @@ -167,7 +168,7 @@ func FilterIDRange(higherThan, lowerThan int64) EventFilter { } // FilterDescending returns events in descending order. -func FilterDescending() EventFilter { +func FilterDescending() TimelineFilter { return func(query *eventFilter) { query.descending = true } @@ -200,12 +201,12 @@ type eventDeploymentUpdatedJSON struct { } type eventRow struct { - sql.Event + sql.Timeline DeploymentKey model.DeploymentKey RequestKey optional.Option[model.RequestKey] } -func (d *DAL) QueryEvents(ctx context.Context, limit int, filters ...EventFilter) ([]Event, error) { +func (d *DAL) QueryTimeline(ctx context.Context, limit int, filters ...TimelineFilter) ([]TimelineEvent, error) { if limit < 1 { return nil, fmt.Errorf("limit must be >= 1, got %d", limit) } @@ -221,7 +222,7 @@ func (d *DAL) QueryEvents(ctx context.Context, limit int, filters ...EventFilter e.custom_key_4, e.type, e.payload - FROM events e + FROM timeline e LEFT JOIN requests ir on e.request_id = ir.id WHERE true -- The "true" is to simplify the ANDs below. ` @@ -255,15 +256,16 @@ func (d *DAL) QueryEvents(ctx context.Context, limit int, filters ...EventFilter deploymentArgs := []any{} if len(filter.deployments) != 0 { // Unfortunately, if we use a join here, PG will do a sequential scan on - // events and deployments, making a 7ms query into a 700ms query. + // timeline and deployments, making a 7ms query into a 700ms query. // https://www.pgexplain.dev/plan/ecd44488-6060-4ad1-a9b4-49d092c3de81 deploymentQuery += ` WHERE key = ANY($1::TEXT[])` deploymentArgs = append(deploymentArgs, filter.deployments) } - rows, err := d.db.Conn().Query(ctx, deploymentQuery, deploymentArgs...) + rows, err := d.db.Conn().QueryContext(ctx, deploymentQuery, deploymentArgs...) if err != nil { return nil, dalerrs.TranslatePGError(err) } + defer rows.Close() // nolint:errcheck deploymentIDs := []int64{} for rows.Next() { var id int64 @@ -315,21 +317,21 @@ func (d *DAL) QueryEvents(ctx context.Context, limit int, filters ...EventFilter q += fmt.Sprintf(" LIMIT %d", limit) // Issue query. - rows, err = d.db.Conn().Query(ctx, q, args...) + rows, err = d.db.Conn().QueryContext(ctx, q, args...) if err != nil { return nil, fmt.Errorf("%s: %w", q, dalerrs.TranslatePGError(err)) } defer rows.Close() - events, err := d.transformRowsToEvents(deploymentKeys, rows) + events, err := d.transformRowsToTimelineEvents(deploymentKeys, rows) if err != nil { return nil, err } return events, nil } -func (d *DAL) transformRowsToEvents(deploymentKeys map[int64]model.DeploymentKey, rows pgx.Rows) ([]Event, error) { - var out []Event +func (d *DAL) transformRowsToTimelineEvents(deploymentKeys map[int64]model.DeploymentKey, rows *stdsql.Rows) ([]TimelineEvent, error) { + var out []TimelineEvent for rows.Next() { row := eventRow{} var deploymentID int64 @@ -347,7 +349,7 @@ func (d *DAL) transformRowsToEvents(deploymentKeys map[int64]model.DeploymentKey switch row.Type { case sql.EventTypeLog: var jsonPayload eventLogJSON - if err := d.encryptors.Logs.DecryptJSON(row.Payload, &jsonPayload); err != nil { + if err := d.decryptJSON(encryption.TimelineSubKey, row.Payload, &jsonPayload); err != nil { return nil, fmt.Errorf("failed to decrypt log event: %w", err) } @@ -369,7 +371,7 @@ func (d *DAL) transformRowsToEvents(deploymentKeys map[int64]model.DeploymentKey case sql.EventTypeCall: var jsonPayload eventCallJSON - if err := d.encryptors.Logs.DecryptJSON(row.Payload, &jsonPayload); err != nil { + if err := d.decryptJSON(encryption.TimelineSubKey, row.Payload, &jsonPayload); err != nil { return nil, fmt.Errorf("failed to decrypt call event: %w", err) } var sourceVerb optional.Option[schema.Ref] @@ -394,7 +396,7 @@ func (d *DAL) transformRowsToEvents(deploymentKeys map[int64]model.DeploymentKey case sql.EventTypeDeploymentCreated: var jsonPayload eventDeploymentCreatedJSON - if err := d.encryptors.Logs.DecryptJSON(row.Payload, &jsonPayload); err != nil { + if err := d.decryptJSON(encryption.TimelineSubKey, row.Payload, &jsonPayload); err != nil { return nil, fmt.Errorf("failed to decrypt call event: %w", err) } out = append(out, &DeploymentCreatedEvent{ @@ -409,7 +411,7 @@ func (d *DAL) transformRowsToEvents(deploymentKeys map[int64]model.DeploymentKey case sql.EventTypeDeploymentUpdated: var jsonPayload eventDeploymentUpdatedJSON - if err := d.encryptors.Logs.DecryptJSON(row.Payload, &jsonPayload); err != nil { + if err := d.decryptJSON(encryption.TimelineSubKey, row.Payload, &jsonPayload); err != nil { return nil, fmt.Errorf("failed to decrypt call event: %w", err) } out = append(out, &DeploymentUpdatedEvent{ diff --git a/backend/controller/dal/fsm.go b/backend/controller/dal/fsm.go index 750a94825c..1311c76ed5 100644 --- a/backend/controller/dal/fsm.go +++ b/backend/controller/dal/fsm.go @@ -12,8 +12,10 @@ import ( "github.com/TBD54566975/ftl/backend/controller/leases" "github.com/TBD54566975/ftl/backend/controller/observability" "github.com/TBD54566975/ftl/backend/controller/sql" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/encryption" ) // StartFSMTransition sends an event to an executing instance of an FSM. @@ -30,7 +32,7 @@ import ( // // Note: no validation of the FSM is performed. func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, executionKey string, destinationState schema.RefKey, request json.RawMessage, retryParams schema.RetryParams) (err error) { - encryptedRequest, err := d.encryptors.Async.EncryptJSON(request) + encryptedRequest, err := d.encryptJSON(encryption.AsyncSubKey, request) if err != nil { return fmt.Errorf("failed to encrypt FSM request: %w", err) } @@ -42,8 +44,8 @@ func (d *DAL) StartFSMTransition(ctx context.Context, fsm schema.RefKey, executi Origin: origin.String(), Request: encryptedRequest, RemainingAttempts: int32(retryParams.Count), - Backoff: retryParams.MinBackoff, - MaxBackoff: retryParams.MaxBackoff, + Backoff: sqltypes.Duration(retryParams.MinBackoff), + MaxBackoff: sqltypes.Duration(retryParams.MaxBackoff), CatchVerb: retryParams.Catch, }) observability.AsyncCalls.Created(ctx, destinationState, retryParams.Catch, origin.String(), int64(retryParams.Count), err) diff --git a/backend/controller/dal/fsm_integration_test.go b/backend/controller/dal/fsm_integration_test.go index a783e79f59..2fee9701c3 100644 --- a/backend/controller/dal/fsm_integration_test.go +++ b/backend/controller/dal/fsm_integration_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" - in "github.com/TBD54566975/ftl/integration" "github.com/alecthomas/assert/v2" + + in "github.com/TBD54566975/ftl/integration" ) func TestFSM(t *testing.T) { @@ -22,7 +23,7 @@ func TestFSM(t *testing.T) { WHERE fsm = 'fsm.fsm' AND key = '%s' `, instance), status, state) } - in.Run(t, "", + in.Run(t, in.CopyModule("fsm"), in.Deploy("fsm"), @@ -81,7 +82,7 @@ func TestFSMRetry(t *testing.T) { } } - in.Run(t, "", + in.Run(t, in.CopyModule("fsmretry"), in.Build("fsmretry"), in.Deploy("fsmretry"), @@ -98,7 +99,7 @@ func TestFSMRetry(t *testing.T) { in.Call("fsmretry", "startTransitionToThree", in.Obj{"id": "2"}, func(t testing.TB, response any) {}), in.Call("fsmretry", "startTransitionToTwo", in.Obj{"id": "3", "failCatch": true}, func(t testing.TB, response any) {}), - in.Sleep(9*time.Second), //6s is longest run of retries + in.Sleep(7*time.Second), //6s is longest run of retries // First two FSMs instances should have failed // Third one will not as it is still catching @@ -127,7 +128,7 @@ func TestFSMRetry(t *testing.T) { func TestFSMGoTests(t *testing.T) { logFilePath := filepath.Join(t.TempDir(), "fsm.log") t.Setenv("FSM_LOG_FILE", logFilePath) - in.Run(t, "", + in.Run(t, in.CopyModule("fsm"), in.Build("fsm"), in.ExecModuleTest("fsm"), diff --git a/backend/controller/dal/fsm_test.go b/backend/controller/dal/fsm_test.go index 684768be90..7f1eabe032 100644 --- a/backend/controller/dal/fsm_test.go +++ b/backend/controller/dal/fsm_test.go @@ -2,6 +2,7 @@ package dal import ( "context" + "github.com/alecthomas/types/optional" "testing" "time" @@ -17,7 +18,7 @@ import ( func TestSendFSMEvent(t *testing.T) { ctx := log.ContextWithNewDefaultLogger(context.Background()) conn := sqltest.OpenForTesting(ctx, t) - dal, err := New(ctx, conn, NoOpEncryptors()) + dal, err := New(ctx, conn, optional.None[string]()) assert.NoError(t, err) _, err = dal.AcquireAsyncCall(ctx) diff --git a/backend/controller/dal/lease.go b/backend/controller/dal/lease.go index d29625764c..8bbc1fd5b0 100644 --- a/backend/controller/dal/lease.go +++ b/backend/controller/dal/lease.go @@ -9,9 +9,11 @@ import ( "github.com/alecthomas/types/optional" "github.com/google/uuid" + "github.com/sqlc-dev/pqtype" "github.com/TBD54566975/ftl/backend/controller/leases" "github.com/TBD54566975/ftl/backend/controller/sql" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/internal/log" ) @@ -44,7 +46,7 @@ func (l *Lease) renew(ctx context.Context, cancelCtx context.CancelFunc) { case <-time.After(leaseRenewalInterval): logger.Tracef("Renewing lease") ctx, cancel := context.WithTimeout(ctx, leaseRenewalInterval) - _, err := l.db.RenewLease(ctx, l.ttl, l.idempotencyKey, l.key) + _, err := l.db.RenewLease(ctx, sqltypes.Duration(l.ttl), l.idempotencyKey, l.key) cancel() if err != nil { @@ -94,7 +96,7 @@ func (d *DAL) AcquireLease(ctx context.Context, key leases.Key, ttl time.Duratio return nil, nil, fmt.Errorf("failed to marshal lease metadata: %w", err) } } - idempotencyKey, err := d.db.NewLease(ctx, key, ttl, metadataBytes) + idempotencyKey, err := d.db.NewLease(ctx, key, sqltypes.Duration(ttl), pqtype.NullRawMessage{RawMessage: metadataBytes, Valid: true}) if err != nil { err = dalerrs.TranslatePGError(err) if errors.Is(err, dalerrs.ErrConflict) { @@ -128,8 +130,10 @@ func (d *DAL) GetLeaseInfo(ctx context.Context, key leases.Key, metadata any) (e if err != nil { return expiry, dalerrs.TranslatePGError(err) } - if err := json.Unmarshal(l.Metadata, metadata); err != nil { - return expiry, fmt.Errorf("could not unmarshal lease metadata: %w", err) + if l.Metadata.Valid { + if err := json.Unmarshal(l.Metadata.RawMessage, metadata); err != nil { + return expiry, fmt.Errorf("could not unmarshal lease metadata: %w", err) + } } return l.ExpiresAt, nil } diff --git a/backend/controller/dal/lease_test.go b/backend/controller/dal/lease_test.go index 5b73d84bbe..9e2370d72f 100644 --- a/backend/controller/dal/lease_test.go +++ b/backend/controller/dal/lease_test.go @@ -21,7 +21,7 @@ func leaseExists(t *testing.T, conn sql.ConnI, idempotencyKey uuid.UUID, key lea t.Helper() var count int err := dalerrs.TranslatePGError(conn. - QueryRow(context.Background(), "SELECT COUNT(*) FROM leases WHERE idempotency_key = $1 AND key = $2", idempotencyKey, key). + QueryRowContext(context.Background(), "SELECT COUNT(*) FROM leases WHERE idempotency_key = $1 AND key = $2", idempotencyKey, key). Scan(&count)) if errors.Is(err, dalerrs.ErrNotFound) { return false @@ -36,7 +36,7 @@ func TestLease(t *testing.T) { } ctx := log.ContextWithNewDefaultLogger(context.Background()) conn := sqltest.OpenForTesting(ctx, t) - dal, err := New(ctx, conn, NoOpEncryptors()) + dal, err := New(ctx, conn, optional.None[string]()) assert.NoError(t, err) // TTL is too short, expect an error @@ -71,7 +71,7 @@ func TestExpireLeases(t *testing.T) { } ctx := log.ContextWithNewDefaultLogger(context.Background()) conn := sqltest.OpenForTesting(ctx, t) - dal, err := New(ctx, conn, NoOpEncryptors()) + dal, err := New(ctx, conn, optional.None[string]()) assert.NoError(t, err) leasei, _, err := dal.AcquireLease(ctx, leases.SystemKey("test"), time.Second*5, optional.None[any]()) diff --git a/backend/controller/dal/pubsub.go b/backend/controller/dal/pubsub.go index 699799daee..aed6077479 100644 --- a/backend/controller/dal/pubsub.go +++ b/backend/controller/dal/pubsub.go @@ -2,7 +2,6 @@ package dal import ( "context" - "encoding/json" "fmt" "strings" "time" @@ -11,8 +10,10 @@ import ( "github.com/TBD54566975/ftl/backend/controller/observability" "github.com/TBD54566975/ftl/backend/controller/sql" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/encryption" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" "github.com/TBD54566975/ftl/internal/rpc" @@ -20,7 +21,7 @@ import ( ) func (d *DAL) PublishEventForTopic(ctx context.Context, module, topic, caller string, payload []byte) error { - encryptedPayload, err := d.encryptors.Async.EncryptJSON(json.RawMessage(payload)) + encryptedPayload, err := d.encrypt(encryption.AsyncSubKey, payload) if err != nil { return fmt.Errorf("failed to encrypt payload: %w", err) } @@ -91,7 +92,7 @@ func (d *DAL) ProgressSubscriptions(ctx context.Context, eventConsumptionDelay t successful := 0 for _, subscription := range subs { - nextCursor, err := tx.db.GetNextEventForSubscription(ctx, eventConsumptionDelay, subscription.Topic, subscription.Cursor) + nextCursor, err := tx.db.GetNextEventForSubscription(ctx, sqltypes.Duration(eventConsumptionDelay), subscription.Topic, subscription.Cursor) if err != nil { observability.PubSub.PropagationFailed(ctx, "GetNextEventForSubscription", subscription.Topic.Payload, nextCursor.Caller, subscriptionRef(subscription), optional.None[schema.RefKey]()) return 0, fmt.Errorf("failed to get next cursor: %w", dalerrs.TranslatePGError(err)) @@ -133,7 +134,7 @@ func (d *DAL) ProgressSubscriptions(ctx context.Context, eventConsumptionDelay t Backoff: subscriber.Backoff, MaxBackoff: subscriber.MaxBackoff, ParentRequestKey: nextCursor.RequestKey, - TraceContext: nextCursor.TraceContext, + TraceContext: nextCursor.TraceContext.RawMessage, CatchVerb: subscriber.CatchVerb, }) observability.AsyncCalls.Created(ctx, subscriber.Sink, subscriber.CatchVerb, origin.String(), int64(subscriber.RetryAttempts), err) @@ -300,8 +301,8 @@ func (d *DAL) createSubscribers(ctx context.Context, tx *sql.Tx, key model.Deplo Deployment: key, Sink: sinkRef, RetryAttempts: int32(retryParams.Count), - Backoff: retryParams.MinBackoff, - MaxBackoff: retryParams.MaxBackoff, + Backoff: sqltypes.Duration(retryParams.MinBackoff), + MaxBackoff: sqltypes.Duration(retryParams.MaxBackoff), CatchVerb: retryParams.Catch, }) if err != nil { diff --git a/backend/controller/dal/testdata/go/fsm/go.mod b/backend/controller/dal/testdata/go/fsm/go.mod index f56c99aee7..3242beebbb 100644 --- a/backend/controller/dal/testdata/go/fsm/go.mod +++ b/backend/controller/dal/testdata/go/fsm/go.mod @@ -44,19 +44,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/dal/testdata/go/fsm/go.sum b/backend/controller/dal/testdata/go/fsm/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/backend/controller/dal/testdata/go/fsm/go.sum +++ b/backend/controller/dal/testdata/go/fsm/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/dal/testdata/go/fsmretry/go.mod b/backend/controller/dal/testdata/go/fsmretry/go.mod index 2cc9c7bc6a..8d4e381536 100644 --- a/backend/controller/dal/testdata/go/fsmretry/go.mod +++ b/backend/controller/dal/testdata/go/fsmretry/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/dal/testdata/go/fsmretry/go.sum b/backend/controller/dal/testdata/go/fsmretry/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/backend/controller/dal/testdata/go/fsmretry/go.sum +++ b/backend/controller/dal/testdata/go/fsmretry/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/ingress/handler.go b/backend/controller/ingress/handler.go index 3201bb9767..47207dacc0 100644 --- a/backend/controller/ingress/handler.go +++ b/backend/controller/ingress/handler.go @@ -5,11 +5,13 @@ import ( "encoding/json" "errors" "net/http" + "time" "connectrpc.com/connect" "github.com/alecthomas/types/optional" "github.com/TBD54566975/ftl/backend/controller/dal" + "github.com/TBD54566975/ftl/backend/controller/observability" dalerrs "github.com/TBD54566975/ftl/backend/dal" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema" @@ -20,6 +22,7 @@ import ( // Handle HTTP ingress routes. func Handle( + startTime time.Time, sch *schema.Schema, requestKey model.RequestKey, routes []dal.IngressRoute, @@ -33,24 +36,29 @@ func Handle( if err != nil { if errors.Is(err, dalerrs.ErrNotFound) { http.NotFound(w, r) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.None[*schemapb.Ref](), startTime, optional.Some("route not found")) return } logger.Errorf(err, "failed to resolve route for %s %s", r.Method, r.URL.Path) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.None[*schemapb.Ref](), startTime, optional.Some("failed to resolve route")) return } + verbRef := &schemapb.Ref{Module: route.Module, Name: route.Verb} + body, err := BuildRequestBody(route, r, sch) if err != nil { // Only log at debug, as this is a client side error logger.Debugf("bad request: %s", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("bad request")) return } creq := connect.NewRequest(&ftlv1.CallRequest{ Metadata: &ftlv1.Metadata{}, - Verb: &schemapb.Ref{Module: route.Module, Name: route.Verb}, + Verb: verbRef, Body: body, }) @@ -60,8 +68,10 @@ func Handle( if connectErr := new(connect.Error); errors.As(err, &connectErr) { httpCode := connectCodeToHTTP(connectErr.Code()) http.Error(w, http.StatusText(httpCode), httpCode) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("failed to call verb: connect error")) } else { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("failed to call verb: internal server error")) } return } @@ -72,6 +82,7 @@ func Handle( if err != nil { logger.Errorf(err, "could not resolve schema type for verb %s", route.Verb) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("could not resolve schema type for verb")) return } var responseBody []byte @@ -81,6 +92,7 @@ func Handle( if err := json.Unmarshal(msg.Body, &response); err != nil { logger.Errorf(err, "could not unmarhal response for verb %s", verb) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("could not unmarhal response for verb")) return } @@ -89,6 +101,7 @@ func Handle( if err != nil { logger.Errorf(err, "could not create response for verb %s", verb) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("could not create response for verb")) return } @@ -105,12 +118,16 @@ func Handle( responseBody = msg.Body } _, err = w.Write(responseBody) - if err != nil { + if err == nil { + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.None[string]()) + } else { logger.Errorf(err, "Could not write response body") + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("could not write response body")) } case *ftlv1.CallResponse_Error_: http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + observability.Ingress.Request(r.Context(), r.Method, r.URL.Path, optional.Some(verbRef), startTime, optional.Some("call response: internal server error")) } } diff --git a/backend/controller/ingress/handler_test.go b/backend/controller/ingress/handler_test.go index 527992c04d..7251e88860 100644 --- a/backend/controller/ingress/handler_test.go +++ b/backend/controller/ingress/handler_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "net/url" "testing" + "time" "connectrpc.com/connect" "github.com/alecthomas/assert/v2" @@ -99,7 +100,7 @@ func TestIngress(t *testing.T) { req := httptest.NewRequest(test.method, test.path, bytes.NewBuffer(test.payload)).WithContext(ctx) req.URL.RawQuery = test.query.Encode() reqKey := model.NewRequestKey(model.OriginIngress, "test") - ingress.Handle(sch, reqKey, routes, rec, req, func(ctx context.Context, r *connect.Request[ftlv1.CallRequest], requestKey optional.Option[model.RequestKey], parentRequestKey optional.Option[model.RequestKey], requestSource string) (*connect.Response[ftlv1.CallResponse], error) { + ingress.Handle(time.Now(), sch, reqKey, routes, rec, req, func(ctx context.Context, r *connect.Request[ftlv1.CallRequest], requestKey optional.Option[model.RequestKey], parentRequestKey optional.Option[model.RequestKey], requestSource string) (*connect.Response[ftlv1.CallResponse], error) { body, err := encoding.Marshal(response) assert.NoError(t, err) return connect.NewResponse(&ftlv1.CallResponse{Response: &ftlv1.CallResponse_Body{Body: body}}), nil diff --git a/backend/controller/ingress/ingress_integration_test.go b/backend/controller/ingress/ingress_integration_test.go index e5474d314e..4679ce16a0 100644 --- a/backend/controller/ingress/ingress_integration_test.go +++ b/backend/controller/ingress/ingress_integration_test.go @@ -14,7 +14,7 @@ import ( ) func TestHttpIngress(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("httpingress"), in.Deploy("httpingress"), in.HttpCall(http.MethodGet, "/users/123/posts/456", nil, in.JsonData(t, in.Obj{}), func(t testing.TB, resp *in.HTTPResponse) { @@ -152,7 +152,7 @@ func TestHttpIngress(t *testing.T) { func TestHttpIngressWithCors(t *testing.T) { os.Setenv("FTL_CONTROLLER_ALLOW_ORIGIN", "http://localhost:8892") os.Setenv("FTL_CONTROLLER_ALLOW_HEADERS", "x-forwarded-capabilities") - in.Run(t, "", + in.Run(t, in.CopyModule("httpingress"), in.Deploy("httpingress"), // A correct CORS preflight request diff --git a/backend/controller/ingress/testdata/go/httpingress/go.mod b/backend/controller/ingress/testdata/go/httpingress/go.mod index 0e4c18a6c7..0b01b36277 100644 --- a/backend/controller/ingress/testdata/go/httpingress/go.mod +++ b/backend/controller/ingress/testdata/go/httpingress/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/ingress/testdata/go/httpingress/go.sum b/backend/controller/ingress/testdata/go/httpingress/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/backend/controller/ingress/testdata/go/httpingress/go.sum +++ b/backend/controller/ingress/testdata/go/httpingress/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/leader/leader.go b/backend/controller/leader/leader.go index a12b6f018a..79c4081a2b 100644 --- a/backend/controller/leader/leader.go +++ b/backend/controller/leader/leader.go @@ -182,7 +182,9 @@ func (c *Coordinator[P]) createFollower() (out P, err error) { } return out, fmt.Errorf("could not get lease for %s: %w", c.key, err) } - if urlString == c.advertise.String() { + if urlString == "" { + return out, fmt.Errorf("%s leader lease missing url in metadata", c.key) + } else if urlString == c.advertise.String() { // This prevents endless loops after a lease breaks. // If we create a follower pointing locally, the receiver will likely try to then call the leader, which starts the loop again. return out, fmt.Errorf("could not follow %s leader at own url: %s", c.key, urlString) @@ -220,3 +222,71 @@ func (c *Coordinator[P]) retireFollower() { f.cancelCtx() c.follower = optional.None[*follower[P]]() } + +// ErrorFilter allows uses of leases to decide if an error might be due to the master falling over, +// or is something else that will not resolve itself after the TTL +type ErrorFilter struct { + leaseTTL time.Duration + // Error reporting utilities + // If a controller has failed over we don't want error logs while we are waiting for the lease to expire. + // This records error state and allows us to filter errors until we are past lease timeout + // and only reports the error if it persists + // firstErrorTime is the time of the first error, used to lower log levels if the errors all occur within a lease window + firstErrorTime optional.Option[time.Time] + recordedSuccessTime optional.Option[time.Time] + + // errorMutex protects firstErrorTime + errorMutex sync.Mutex +} + +func NewErrorFilter(leaseTTL time.Duration) *ErrorFilter { + return &ErrorFilter{ + errorMutex: sync.Mutex{}, + leaseTTL: leaseTTL, + } +} + +// ReportLeaseError reports that an operation that relies on the leader being up has failed +// If this is either the first report or the error is within the lease timeout duration from +// the time of the first report it will return false, indicating that this may be a transient error +// If it returns true then the error has persisted over the length of a lease, and is probably serious +// this will also return true if some operations are succeeding and some are failing, indicating a non-lease +// related transient error +func (c *ErrorFilter) ReportLeaseError() bool { + c.errorMutex.Lock() + defer c.errorMutex.Unlock() + errorTime, ok := c.firstErrorTime.Get() + if !ok { + c.firstErrorTime = optional.Some(time.Now()) + return false + } + // We have seen a success recorded, and a previous error + // within the lease timeout, this indicates transient errors are happening + if c.recordedSuccessTime.Ok() { + return true + } + if errorTime.Add(c.leaseTTL).After(time.Now()) { + // Within the lease window, it will probably be resolved when a new leader is elected + return false + } + return true +} + +// ReportOperationSuccess reports that an operation that relies on the leader being up has succeeded +// it is used to decide if an error is transient and will be fixed with a new leader, or if the error is persistent +func (c *ErrorFilter) ReportOperationSuccess() { + c.errorMutex.Lock() + defer c.errorMutex.Unlock() + errorTime, ok := c.firstErrorTime.Get() + if !ok { + // Normal operation, no errors + return + } + if errorTime.Add(c.leaseTTL).After(time.Now()) { + c.recordedSuccessTime = optional.Some(time.Now()) + } else { + // Outside the lease window, clear our state + c.recordedSuccessTime = optional.None[time.Time]() + c.firstErrorTime = optional.None[time.Time]() + } +} diff --git a/backend/controller/leader/leader_test.go b/backend/controller/leader/leader_test.go index 7d239b8a41..dd6f9ef295 100644 --- a/backend/controller/leader/leader_test.go +++ b/backend/controller/leader/leader_test.go @@ -125,6 +125,33 @@ func TestSingleLeader(t *testing.T) { ctxSlicesLock.Unlock() } +func TestLeaseErrorFilteringPersistentError(t *testing.T) { + filter := NewErrorFilter(time.Millisecond * 200) + assert.False(t, filter.ReportLeaseError(), "first error should be filtered") + assert.False(t, filter.ReportLeaseError(), "second error should be filtered") + assert.False(t, filter.ReportLeaseError(), "third error should be filtered") + time.Sleep(time.Millisecond * 201) + assert.True(t, filter.ReportLeaseError(), "third error should be reported") +} + +func TestLeaseErrorFilteringTransientError(t *testing.T) { + filter := NewErrorFilter(time.Millisecond * 200) + assert.False(t, filter.ReportLeaseError(), "first error should be filtered") + assert.False(t, filter.ReportLeaseError(), "second error should be filtered") + assert.False(t, filter.ReportLeaseError(), "third error should be filtered") + time.Sleep(time.Millisecond * 201) + filter.ReportOperationSuccess() + assert.False(t, filter.ReportLeaseError(), "first error again") +} + +func TestLeaseErrorTransientErrors(t *testing.T) { + filter := NewErrorFilter(time.Millisecond * 200) + assert.False(t, filter.ReportLeaseError(), "first error should be filtered") + assert.False(t, filter.ReportLeaseError(), "second error should be filtered") + filter.ReportOperationSuccess() + assert.True(t, filter.ReportLeaseError(), "success and failure within the TTL should be reported") +} + func leaderFromCoordinators(t *testing.T, coordinators []*Coordinator[string]) (leaderIdx int, leaderStr string) { t.Helper() diff --git a/backend/controller/leases/lease_integration_test.go b/backend/controller/leases/lease_integration_test.go index 002fb62f64..3af001258c 100644 --- a/backend/controller/leases/lease_integration_test.go +++ b/backend/controller/leases/lease_integration_test.go @@ -18,7 +18,7 @@ import ( ) func TestLease(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("leases"), in.Build("leases"), // checks if leases work in a unit test environment diff --git a/backend/controller/leases/testdata/go/leases/go.mod b/backend/controller/leases/testdata/go/leases/go.mod index 46a9d65323..50739f8650 100644 --- a/backend/controller/leases/testdata/go/leases/go.mod +++ b/backend/controller/leases/testdata/go/leases/go.mod @@ -7,7 +7,7 @@ replace github.com/TBD54566975/ftl => ./../../../../../.. require ( github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000 github.com/alecthomas/assert/v2 v2.10.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 ) require ( @@ -45,17 +45,18 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/leases/testdata/go/leases/go.sum b/backend/controller/leases/testdata/go/leases/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/backend/controller/leases/testdata/go/leases/go.sum +++ b/backend/controller/leases/testdata/go/leases/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/observability/ingress.go b/backend/controller/observability/ingress.go new file mode 100644 index 0000000000..ebae91ad80 --- /dev/null +++ b/backend/controller/observability/ingress.go @@ -0,0 +1,76 @@ +package observability + +import ( + "context" + "fmt" + "time" + + "github.com/alecthomas/types/optional" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" + + schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema" + "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/observability" +) + +const ( + ingressMeterName = "ftl.ingress" + ingressMethodAttr = "ftl.ingress.method" + ingressPathAttr = "ftl.ingress.path" + ingressVerbRefAttr = "ftl.ingress.verb.ref" + ingressFailureModeAttr = "ftl.ingress.failure_mode" + ingressRunTimeBucketAttr = "ftl.ingress.run_time_ms.bucket" +) + +type IngressMetrics struct { + requests metric.Int64Counter + msToComplete metric.Int64Histogram +} + +func initIngressMetrics() (*IngressMetrics, error) { + result := &IngressMetrics{ + requests: noop.Int64Counter{}, + msToComplete: noop.Int64Histogram{}, + } + + var err error + meter := otel.Meter(ingressMeterName) + + signalName := fmt.Sprintf("%s.requests", ingressMeterName) + if result.requests, err = meter.Int64Counter(signalName, metric.WithUnit("1"), + metric.WithDescription("the number of ingress requests that the FTL controller receives")); err != nil { + return nil, wrapErr(signalName, err) + } + + signalName = fmt.Sprintf("%s.ms_to_complete", ingressMeterName) + if result.msToComplete, err = meter.Int64Histogram(signalName, metric.WithUnit("ms"), + metric.WithDescription("duration in ms to complete an ingress request")); err != nil { + return nil, wrapErr(signalName, err) + } + + return result, nil +} + +func (m *IngressMetrics) Request(ctx context.Context, method string, path string, verb optional.Option[*schemapb.Ref], startTime time.Time, failureMode optional.Option[string]) { + attrs := []attribute.KeyValue{ + attribute.String(ingressMethodAttr, method), + attribute.String(ingressPathAttr, path), + } + if v, ok := verb.Get(); ok { + attrs = append(attrs, + attribute.String(observability.ModuleNameAttribute, v.Module), + attribute.String(ingressVerbRefAttr, schema.RefFromProto(v).String())) + } + if f, ok := failureMode.Get(); ok { + attrs = append(attrs, attribute.String(ingressFailureModeAttr, f)) + } + + msToComplete := timeSinceMS(startTime) + m.msToComplete.Record(ctx, msToComplete, metric.WithAttributes(attrs...)) + + attrs = append(attrs, attribute.String(ingressRunTimeBucketAttr, logBucket(2, msToComplete))) + m.requests.Add(ctx, 1, metric.WithAttributes(attrs...)) +} diff --git a/backend/controller/observability/observability.go b/backend/controller/observability/observability.go index ca952c2fb7..57851b0e0e 100644 --- a/backend/controller/observability/observability.go +++ b/backend/controller/observability/observability.go @@ -12,6 +12,7 @@ var ( Calls *CallMetrics Deployment *DeploymentMetrics FSM *FSMMetrics + Ingress *IngressMetrics PubSub *PubSubMetrics Cron *CronMetrics ) @@ -28,6 +29,8 @@ func init() { errs = errors.Join(errs, err) FSM, err = initFSMMetrics() errs = errors.Join(errs, err) + Ingress, err = initIngressMetrics() + errs = errors.Join(errs, err) PubSub, err = initPubSubMetrics() errs = errors.Join(errs, err) Cron, err = initCronMetrics() diff --git a/backend/controller/pubsub/integration_test.go b/backend/controller/pubsub/integration_test.go index 9438a3d2ba..879db011d1 100644 --- a/backend/controller/pubsub/integration_test.go +++ b/backend/controller/pubsub/integration_test.go @@ -15,7 +15,7 @@ import ( func TestPubSub(t *testing.T) { calls := 20 events := calls * 10 - in.Run(t, "", + in.Run(t, in.CopyModule("publisher"), in.CopyModule("subscriber"), in.Deploy("publisher"), @@ -34,13 +34,13 @@ func TestPubSub(t *testing.T) { WHERE state = 'success' AND origin = '%s' - `, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "test_subscription"}}.String()), + `, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "testSubscription"}}.String()), events), ) } func TestConsumptionDelay(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("publisher"), in.CopyModule("subscriber"), in.Deploy("publisher"), @@ -83,7 +83,7 @@ func TestConsumptionDelay(t *testing.T) { func TestRetry(t *testing.T) { retriesPerCall := 2 - in.Run(t, "", + in.Run(t, in.CopyModule("publisher"), in.CopyModule("subscriber"), in.Deploy("publisher"), @@ -103,7 +103,7 @@ func TestRetry(t *testing.T) { state = 'error' AND catching = false AND origin = '%s' - `, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "doomed_subscription"}}.String()), + `, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "doomedSubscription"}}.String()), 1+retriesPerCall), // check that there is one failed attempt to catch (we purposely fail the first one) @@ -116,7 +116,7 @@ func TestRetry(t *testing.T) { AND error = 'call to verb subscriber.catch failed: catching error' AND catching = true AND origin = '%s' - `, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "doomed_subscription"}}.String()), + `, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "doomedSubscription"}}.String()), 1), // check that there is one successful attempt to catch (we succeed the second one as long as we receive the correct error in the request) @@ -129,13 +129,13 @@ func TestRetry(t *testing.T) { AND error IS NULL AND catching = true AND origin = '%s' -`, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "doomed_subscription"}}.String()), +`, dal.AsyncOriginPubSub{Subscription: schema.RefKey{Module: "subscriber", Name: "doomedSubscription"}}.String()), 1), ) } func TestExternalPublishRuntimeCheck(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("publisher"), in.CopyModule("subscriber"), in.Deploy("publisher"), diff --git a/backend/controller/pubsub/testdata/go/publisher/go.mod b/backend/controller/pubsub/testdata/go/publisher/go.mod index 08ca162da5..e9df110a1f 100644 --- a/backend/controller/pubsub/testdata/go/publisher/go.mod +++ b/backend/controller/pubsub/testdata/go/publisher/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/pubsub/testdata/go/publisher/go.sum b/backend/controller/pubsub/testdata/go/publisher/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/backend/controller/pubsub/testdata/go/publisher/go.sum +++ b/backend/controller/pubsub/testdata/go/publisher/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/pubsub/testdata/go/publisher/publisher.go b/backend/controller/pubsub/testdata/go/publisher/publisher.go index 38af05f989..e74ecdf191 100644 --- a/backend/controller/pubsub/testdata/go/publisher/publisher.go +++ b/backend/controller/pubsub/testdata/go/publisher/publisher.go @@ -9,7 +9,7 @@ import ( ) //ftl:export -var TestTopic = ftl.Topic[PubSubEvent]("test_topic") +var TestTopic = ftl.Topic[PubSubEvent]("testTopic") type PubSubEvent struct { Time time.Time @@ -38,7 +38,7 @@ func PublishOne(ctx context.Context) error { } //ftl:export -var Topic2 = ftl.Topic[PubSubEvent]("topic_2") +var Topic2 = ftl.Topic[PubSubEvent]("topic2") //ftl:verb func PublishOneToTopic2(ctx context.Context) error { diff --git a/backend/controller/pubsub/testdata/go/subscriber/go.mod b/backend/controller/pubsub/testdata/go/subscriber/go.mod index d77cc53f8a..0d558421b0 100644 --- a/backend/controller/pubsub/testdata/go/subscriber/go.mod +++ b/backend/controller/pubsub/testdata/go/subscriber/go.mod @@ -38,13 +38,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/pubsub/testdata/go/subscriber/go.sum b/backend/controller/pubsub/testdata/go/subscriber/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/backend/controller/pubsub/testdata/go/subscriber/go.sum +++ b/backend/controller/pubsub/testdata/go/subscriber/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/controller/pubsub/testdata/go/subscriber/subscriber.go b/backend/controller/pubsub/testdata/go/subscriber/subscriber.go index 068671661a..bc719e49e4 100644 --- a/backend/controller/pubsub/testdata/go/subscriber/subscriber.go +++ b/backend/controller/pubsub/testdata/go/subscriber/subscriber.go @@ -3,30 +3,32 @@ package subscriber import ( "context" "fmt" - "ftl/builtin" - "ftl/publisher" "strings" "time" - "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. + "ftl/builtin" + "ftl/publisher" + "github.com/alecthomas/atomic" + + "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. ) -var _ = ftl.Subscription(publisher.TestTopic, "test_subscription") +var _ = ftl.Subscription(publisher.TestTopic, "testSubscription") var catchCount atomic.Value[int] //ftl:verb -//ftl:subscribe test_subscription +//ftl:subscribe testSubscription func Consume(ctx context.Context, req publisher.PubSubEvent) error { ftl.LoggerFromContext(ctx).Infof("Subscriber is consuming %v", req.Time) return nil } -var _ = ftl.Subscription(publisher.Topic2, "doomed_subscription") +var _ = ftl.Subscription(publisher.Topic2, "doomedSubscription") //ftl:verb -//ftl:subscribe doomed_subscription +//ftl:subscribe doomedSubscription //ftl:retry 2 1s 1s catch catch func ConsumeButFailAndRetry(ctx context.Context, req publisher.PubSubEvent) error { return fmt.Errorf("always error: event %v", req.Time) diff --git a/backend/controller/scaling/localscaling/local_scaling.go b/backend/controller/scaling/localscaling/local_scaling.go index f8ba1850ae..99eaf1bb7d 100644 --- a/backend/controller/scaling/localscaling/local_scaling.go +++ b/backend/controller/scaling/localscaling/local_scaling.go @@ -92,7 +92,7 @@ func (l *LocalScaling) SetReplicas(ctx context.Context, replicas int, idleRunner simpleName := fmt.Sprintf("runner%d", keySuffix) if err := kong.ApplyDefaults(&config, kong.Vars{ "deploymentdir": filepath.Join(l.cacheDir, "ftl-runner", simpleName, "deployments"), - "language": "go,kotlin,rust", + "language": "go,kotlin,rust,java", }); err != nil { return err } diff --git a/backend/controller/sql/conn.go b/backend/controller/sql/conn.go index 5a2498536e..d3a29c87cf 100644 --- a/backend/controller/sql/conn.go +++ b/backend/controller/sql/conn.go @@ -2,10 +2,9 @@ package sql import ( "context" + "database/sql" "errors" "fmt" - - "github.com/jackc/pgx/v5" ) type DBI interface { @@ -16,7 +15,7 @@ type DBI interface { type ConnI interface { DBTX - Begin(ctx context.Context) (pgx.Tx, error) + Begin() (*sql.Tx, error) } type DB struct { @@ -31,32 +30,36 @@ func NewDB(conn ConnI) *DB { func (d *DB) Conn() ConnI { return d.conn } func (d *DB) Begin(ctx context.Context) (*Tx, error) { - tx, err := d.conn.Begin(ctx) + tx, err := d.conn.Begin() if err != nil { return nil, err } return &Tx{tx: tx, Queries: New(tx)}, nil } +type noopSubConn struct { + DBTX +} + +func (noopSubConn) Begin() (*sql.Tx, error) { + return nil, errors.New("sql: not implemented") +} + type Tx struct { - tx pgx.Tx + tx *sql.Tx *Queries } -func (t *Tx) Conn() ConnI { return t.tx } +func (t *Tx) Conn() ConnI { return noopSubConn{t.tx} } -func (t *Tx) Tx() pgx.Tx { return t.tx } +func (t *Tx) Tx() *sql.Tx { return t.tx } func (t *Tx) Begin(ctx context.Context) (*Tx, error) { - _, err := t.tx.Begin(ctx) - if err != nil { - return nil, fmt.Errorf("beginning transaction: %w", err) - } - return &Tx{tx: t.tx, Queries: t.Queries}, nil + return nil, fmt.Errorf("cannot nest transactions") } func (t *Tx) Commit(ctx context.Context) error { - err := t.tx.Commit(ctx) + err := t.tx.Commit() if err != nil { return fmt.Errorf("committing transaction: %w", err) } @@ -65,7 +68,7 @@ func (t *Tx) Commit(ctx context.Context) error { } func (t *Tx) Rollback(ctx context.Context) error { - err := t.tx.Rollback(ctx) + err := t.tx.Rollback() if err != nil { return fmt.Errorf("rolling back transaction: %w", err) } diff --git a/backend/controller/sql/database_integration_test.go b/backend/controller/sql/database_integration_test.go index 57e355beb2..a82cdb84b1 100644 --- a/backend/controller/sql/database_integration_test.go +++ b/backend/controller/sql/database_integration_test.go @@ -10,7 +10,8 @@ import ( ) func TestDatabase(t *testing.T) { - in.Run(t, "database/ftl-project.toml", + in.Run(t, + in.WithFTLConfig("database/ftl-project.toml"), // deploy real module against "testdb" in.CopyModule("database"), in.CreateDBAction("database", "testdb", false), @@ -33,7 +34,8 @@ func TestMigrate(t *testing.T) { return in.QueryRow(dbName, "SELECT version FROM schema_migrations WHERE version = '20240704103403'", "20240704103403") } - in.RunWithoutController(t, "", + in.Run(t, + in.WithoutController(), in.DropDBAction(t, dbName), in.Fail(q(), "Should fail because the database does not exist."), in.Exec("ftl", "migrate", "--dsn", dbUri), diff --git a/backend/controller/sql/databasetesting/devel.go b/backend/controller/sql/databasetesting/devel.go index 96f6cb44c9..a52f3fb4c2 100644 --- a/backend/controller/sql/databasetesting/devel.go +++ b/backend/controller/sql/databasetesting/devel.go @@ -2,11 +2,13 @@ package databasetesting import ( "context" + stdsql "database/sql" "fmt" + "net/url" + "strings" "time" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" + _ "github.com/jackc/pgx/v5/stdlib" // pgx driver "github.com/TBD54566975/ftl/backend/controller/sql" "github.com/TBD54566975/ftl/internal/log" @@ -15,20 +17,21 @@ import ( // CreateForDevel creates and migrates a new database for development or testing. // // If "recreate" is true, the database will be dropped and recreated. -func CreateForDevel(ctx context.Context, dsn string, recreate bool) (*pgxpool.Pool, error) { +func CreateForDevel(ctx context.Context, dsn string, recreate bool) (*stdsql.DB, error) { logger := log.FromContext(ctx) - config, err := pgx.ParseConfig(dsn) + config, err := url.Parse(dsn) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse DSN: %w", err) } - noDBDSN := config.Copy() - noDBDSN.Database = "" - var conn *pgx.Conn + noDBDSN := *config + noDBDSN.Path = "" // Remove the database name. + + var conn *stdsql.DB for range 10 { - conn, err = pgx.ConnectConfig(ctx, noDBDSN) + conn, err = stdsql.Open("pgx", noDBDSN.String()) if err == nil { - defer conn.Close(ctx) + defer conn.Close() break } logger.Debugf("Waiting for database to be ready: %v", err) @@ -43,39 +46,41 @@ func CreateForDevel(ctx context.Context, dsn string, recreate bool) (*pgxpool.Po return nil, fmt.Errorf("database not ready after 10 tries: %w", err) } + dbName := strings.TrimPrefix(config.Path, "/") + if recreate { // Terminate any dangling connections. - _, err = conn.Exec(ctx, ` + _, err = conn.ExecContext(ctx, ` SELECT pid, pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()`, - config.Database) + dbName) if err != nil { return nil, err } - _, err = conn.Exec(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS %q", config.Database)) + _, err = conn.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS %q", dbName)) if err != nil { return nil, err } } - _, _ = conn.Exec(ctx, fmt.Sprintf("CREATE DATABASE %q", config.Database)) //nolint:errcheck // PG doesn't support "IF NOT EXISTS" so instead we just ignore any error. + _, _ = conn.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE %q", dbName)) //nolint:errcheck // PG doesn't support "IF NOT EXISTS" so instead we just ignore any error. err = sql.Migrate(ctx, dsn, log.Debug) if err != nil { return nil, err } - realConn, err := pgxpool.New(ctx, dsn) + realConn, err := stdsql.Open("pgx", dsn) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to open database: %w", err) } // Reset transient state in the database to a clean state for development purposes. // This includes things like resetting the state of async calls, leases, // controller/runner registration, etc. but not anything more. if !recreate { - _, err = realConn.Exec(ctx, ` + _, err = realConn.ExecContext(ctx, ` WITH deleted AS ( DELETE FROM async_calls RETURNING 1 diff --git a/backend/controller/sql/db.go b/backend/controller/sql/db.go index c4b45fb311..0e0973111c 100644 --- a/backend/controller/sql/db.go +++ b/backend/controller/sql/db.go @@ -1,20 +1,19 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql import ( "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" + "database/sql" ) type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { @@ -25,7 +24,7 @@ type Queries struct { db DBTX } -func (q *Queries) WithTx(tx pgx.Tx) *Queries { +func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ db: tx, } diff --git a/backend/controller/sql/migrate/migrate.go b/backend/controller/sql/migrate/migrate.go new file mode 100644 index 0000000000..57f8522e9d --- /dev/null +++ b/backend/controller/sql/migrate/migrate.go @@ -0,0 +1,155 @@ +// Package migrate supports a dbmate-compatible superset of migration files. +// +// The superset is that in addition to a migration being a .sql file, it can +// also be a Go function which is called to execute the migration. +package migrate + +import ( + "context" + "database/sql" + "errors" + "fmt" + "io/fs" + "path/filepath" + "regexp" + "sort" + "time" + + "github.com/TBD54566975/ftl/backend/dal" + "github.com/TBD54566975/ftl/internal/log" +) + +var migrationFileNameRe = regexp.MustCompile(`^.*(\d{14})_(.*)(\.sql)?$`) + +type migrateOptions struct { + logLevel log.Level + migrations map[string]MigrationFunc +} + +// Option is a configuration option for Migrate. +type Option func(*migrateOptions) + +// Migration adds a named migration function to the migration set. +// +// "version" must be in the form "
". +func Migration(version, name string, migration MigrationFunc) Option { + return func(opts *migrateOptions) { + opts.migrations[version+"_"+name] = migration + } +} + +// LogLevel sets the logging level of the migrator. +func LogLevel(level log.Level) Option { + return func(opts *migrateOptions) { + opts.logLevel = level + } +} + +type MigrationFunc func(ctx context.Context, db *sql.Tx) error + +type namedMigration struct { + name string + version string + migration MigrationFunc +} + +func (m namedMigration) String() string { return m.name } + +// Migrate applies all migrations in the provided fs.FS and migration functions +// to the provided database. +func Migrate(ctx context.Context, db *sql.DB, migrationFiles fs.FS, options ...Option) error { + // Create schema_migrations table if it doesn't exist. + // This table structure is compatible with dbmate. + _, _ = db.ExecContext(ctx, `CREATE TABLE schema_migrations (version TEXT PRIMARY KEY)`) //nolint:errcheck + + sqlFiles, err := fs.Glob(migrationFiles, "*.sql") + if err != nil { + return fmt.Errorf("failed to read migration files: %w", err) + } + + opts := migrateOptions{ + logLevel: log.Debug, + migrations: make(map[string]MigrationFunc), + } + for _, opt := range options { + opt(&opts) + } + + migrations := make([]namedMigration, 0, len(sqlFiles)+len(opts.migrations)) + + // Collect .sql files. + for _, sqlFile := range sqlFiles { + name := filepath.Base(sqlFile) + groups := migrationFileNameRe.FindStringSubmatch(name) + if groups == nil { + return fmt.Errorf("invalid migration file name %q, must be in the form
_.sql", sqlFile) + } + version := groups[1] + migrations = append(migrations, namedMigration{name, version, func(ctx context.Context, db *sql.Tx) error { + sqlMigration, err := fs.ReadFile(migrationFiles, sqlFile) + if err != nil { + return fmt.Errorf("failed to read migration file %q: %w", sqlFile, err) + } + return migrateSQLFile(ctx, db, sqlFile, sqlMigration) + }}) + } + for name, migration := range opts.migrations { + groups := migrationFileNameRe.FindStringSubmatch(name) + if groups == nil { + return fmt.Errorf("invalid migration name %q, must be in the form
_", name) + } + version := groups[1] + migrations = append(migrations, namedMigration{name, version, migration}) + } + sort.Slice(migrations, func(i, j int) bool { + return migrations[i].version < migrations[j].version + }) + for _, migration := range migrations { + tx, err := db.Begin() + if err != nil { + return fmt.Errorf("migration %s: failed to begin transaction: %w", migration, err) + } + err = applyMigration(ctx, opts.logLevel, tx, migration) + if err != nil { + if txerr := tx.Rollback(); txerr != nil { + return fmt.Errorf("migration %s: failed to rollback transaction: %w", migration, txerr) + } + return fmt.Errorf("migration %s: %w", migration, err) + } + err = tx.Commit() + if err != nil { + return fmt.Errorf("migration %s: failed to commit transaction: %w", migration, err) + } + } + return nil +} + +func applyMigration(ctx context.Context, level log.Level, tx *sql.Tx, migration namedMigration) error { + start := time.Now() + logger := log.FromContext(ctx).Scope("migrate") + _, err := tx.ExecContext(ctx, "INSERT INTO schema_migrations (version) VALUES ($1)", migration.version) + err = dal.TranslatePGError(err) + if errors.Is(err, dal.ErrConflict) { + if txerr := tx.Rollback(); txerr != nil { + return fmt.Errorf("failed to rollback transaction: %w", txerr) + } + logger.Logf(level, "Skipping: %s", migration) + return nil + } else if err != nil { + return fmt.Errorf("failed to insert migration: %w", err) + } + logger.Logf(level, "Applying: %s", migration) + if err := migration.migration(ctx, tx); err != nil { + return fmt.Errorf("migration failed: %w", err) + } + logger.Logf(level, "Applied: %s in %s", migration, time.Since(start)) + return nil +} + +func migrateSQLFile(ctx context.Context, db *sql.Tx, name string, sqlMigration []byte) error { + _, err := db.ExecContext(ctx, string(sqlMigration)) + if err != nil { + return fmt.Errorf("failed to execute migration %q: %w", name, err) + } + return nil +} diff --git a/backend/controller/sql/migrate/migrate_test.go b/backend/controller/sql/migrate/migrate_test.go new file mode 100644 index 0000000000..17cc026c7d --- /dev/null +++ b/backend/controller/sql/migrate/migrate_test.go @@ -0,0 +1,46 @@ +package migrate + +import ( + "context" + "embed" + "io/fs" + "testing" + + "github.com/alecthomas/assert/v2" + + "github.com/TBD54566975/ftl/backend/controller/sql/migrate/migrationtest" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltest" + "github.com/TBD54566975/ftl/internal/log" +) + +func TestMigrate(t *testing.T) { + ctx := log.ContextWithNewDefaultLogger(context.Background()) + db := sqltest.OpenForTesting(ctx, t) + + mfs, err := fs.Sub(migrations, "migrationtest") + assert.NoError(t, err) + + err = Migrate(ctx, db, mfs, Migration("30280103000000", "split_name_age", migrationtest.MigrateSplitNameAge)) + assert.NoError(t, err) + + rows, err := db.QueryContext(ctx, "SELECT name, age FROM test") + assert.NoError(t, err) + defer rows.Close() + type user struct { + name string + age int + } + actual := []user{} + for rows.Next() { + var u user + assert.NoError(t, rows.Scan(&u.name, &u.age)) + actual = append(actual, u) + } + expected := []user{ + {"Alice", 30}, + } + assert.Equal(t, expected, actual) +} + +//go:embed migrationtest +var migrations embed.FS diff --git a/backend/controller/sql/migrate/migrationtest/30280101000000_init.sql b/backend/controller/sql/migrate/migrationtest/30280101000000_init.sql new file mode 100644 index 0000000000..02a2a2d064 --- /dev/null +++ b/backend/controller/sql/migrate/migrationtest/30280101000000_init.sql @@ -0,0 +1,6 @@ +CREATE TABLE test ( + id SERIAL PRIMARY KEY, + name_and_age TEXT NOT NULL +); + +INSERT INTO test (name_and_age) VALUES ('Alice 30'); \ No newline at end of file diff --git a/backend/controller/sql/migrate/migrationtest/30280102000000_add_name_age.sql b/backend/controller/sql/migrate/migrationtest/30280102000000_add_name_age.sql new file mode 100644 index 0000000000..c80dd07b9f --- /dev/null +++ b/backend/controller/sql/migrate/migrationtest/30280102000000_add_name_age.sql @@ -0,0 +1,3 @@ + ALTER TABLE test + ADD COLUMN name TEXT, + ADD COLUMN age INT; \ No newline at end of file diff --git a/backend/controller/sql/migrate/migrationtest/30280103000000_split.go b/backend/controller/sql/migrate/migrationtest/30280103000000_split.go new file mode 100644 index 0000000000..7a21b05001 --- /dev/null +++ b/backend/controller/sql/migrate/migrationtest/30280103000000_split.go @@ -0,0 +1,55 @@ +package migrationtest + +import ( + "context" + "database/sql" + "fmt" + "strconv" + "strings" +) + +func MigrateSplitNameAge(ctx context.Context, tx *sql.Tx) error { + rows, err := tx.QueryContext(ctx, "SELECT id, name_and_age FROM test") + if err != nil { + return fmt.Errorf("failed to query test: %w", err) + } + defer rows.Close() + type userUpdate struct { + name string + age int64 + } + updates := map[int]userUpdate{} + for rows.Next() { + var id int + var name string + var age int64 + err = rows.Scan(&id, &name) + if err != nil { + return fmt.Errorf("failed to scan user: %w", err) + } + nameAge := strings.Fields(name) + name = nameAge[0] + switch len(nameAge) { + case 1: + case 2: + age, err = strconv.ParseInt(nameAge[1], 10, 64) + if err != nil { + return fmt.Errorf("failed to parse age: %w", err) + } + default: + return fmt.Errorf("invalid name %q", name) + } + // We can't update the table while iterating over it, so we store the updates in a map. + updates[id] = userUpdate{name, age} + } + if err := rows.Close(); err != nil { + return fmt.Errorf("failed to close rows: %w", err) + } + for id, update := range updates { + _, err = tx.ExecContext(ctx, "UPDATE test SET name = $1, age = $2 WHERE id = $3", update.name, update.age, id) + if err != nil { + return fmt.Errorf("failed to update user %d: %w", id, err) + } + } + return nil +} diff --git a/backend/controller/sql/migrate/migrationtest/30280104000000_drop_column.sql b/backend/controller/sql/migrate/migrationtest/30280104000000_drop_column.sql new file mode 100644 index 0000000000..93221ae0bd --- /dev/null +++ b/backend/controller/sql/migrate/migrationtest/30280104000000_drop_column.sql @@ -0,0 +1,4 @@ +ALTER TABLE test + DROP COLUMN name_and_age, + ALTER COLUMN name SET NOT NULL, + ALTER COLUMN age SET NOT NULL \ No newline at end of file diff --git a/backend/controller/sql/models.go b/backend/controller/sql/models.go index e67f71b017..5c2780936a 100644 --- a/backend/controller/sql/models.go +++ b/backend/controller/sql/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql @@ -11,10 +11,12 @@ import ( "time" "github.com/TBD54566975/ftl/backend/controller/leases" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" "github.com/google/uuid" + "github.com/sqlc-dev/pqtype" ) type AsyncCallState string @@ -380,12 +382,12 @@ type AsyncCall struct { Response []byte Error optional.Option[string] RemainingAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] Catching bool ParentRequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage } type Controller struct { @@ -415,7 +417,7 @@ type Deployment struct { ModuleID int64 Key model.DeploymentKey Schema *schema.Module - Labels []byte + Labels json.RawMessage MinReplicas int32 } @@ -427,18 +429,10 @@ type DeploymentArtefact struct { Path string } -type Event struct { - ID int64 - TimeStamp time.Time - DeploymentID int64 - RequestID optional.Option[int64] - Type EventType - CustomKey1 optional.Option[string] - CustomKey2 optional.Option[string] - CustomKey3 optional.Option[string] - CustomKey4 optional.Option[string] - Payload json.RawMessage - ParentRequestID optional.Option[string] +type EncryptionKey struct { + ID int64 + Key []byte + CreatedAt time.Time } type FsmInstance struct { @@ -467,7 +461,7 @@ type Lease struct { Key leases.Key CreatedAt time.Time ExpiresAt time.Time - Metadata []byte + Metadata pqtype.NullRawMessage } type Module struct { @@ -481,7 +475,7 @@ type ModuleConfiguration struct { CreatedAt time.Time Module optional.Option[string] Name string - Value []byte + Value json.RawMessage } type ModuleSecret struct { @@ -509,7 +503,21 @@ type Runner struct { Endpoint string ModuleName optional.Option[string] DeploymentID optional.Option[int64] - Labels []byte + Labels json.RawMessage +} + +type Timeline struct { + ID int64 + TimeStamp time.Time + DeploymentID int64 + RequestID optional.Option[int64] + Type EventType + CustomKey1 optional.Option[string] + CustomKey2 optional.Option[string] + CustomKey3 optional.Option[string] + CustomKey4 optional.Option[string] + Payload []byte + ParentRequestID optional.Option[string] } type Topic struct { @@ -530,7 +538,7 @@ type TopicEvent struct { Payload []byte Caller optional.Option[string] RequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage } type TopicSubscriber struct { @@ -541,8 +549,8 @@ type TopicSubscriber struct { DeploymentID int64 Sink schema.RefKey RetryAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] } diff --git a/backend/controller/sql/querier.go b/backend/controller/sql/querier.go index 1331f7e476..0f2602688a 100644 --- a/backend/controller/sql/querier.go +++ b/backend/controller/sql/querier.go @@ -1,24 +1,27 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql import ( "context" + "encoding/json" "time" "github.com/TBD54566975/ftl/backend/controller/leases" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" "github.com/google/uuid" + "github.com/sqlc-dev/pqtype" ) type Querier interface { // Reserve a pending async call for execution, returning the associated lease // reservation key and accompanying metadata. - AcquireAsyncCall(ctx context.Context, ttl time.Duration) (AcquireAsyncCallRow, error) + AcquireAsyncCall(ctx context.Context, ttl sqltypes.Duration) (AcquireAsyncCallRow, error) AssociateArtefactWithDeployment(ctx context.Context, arg AssociateArtefactWithDeploymentParams) error AsyncCallQueueDepth(ctx context.Context) (int64, error) BeginConsumingTopicEvent(ctx context.Context, subscription model.SubscriptionKey, event model.TopicEventKey) error @@ -29,8 +32,9 @@ type Querier interface { CreateCronJob(ctx context.Context, arg CreateCronJobParams) error CreateDeployment(ctx context.Context, moduleName string, schema []byte, key model.DeploymentKey) error CreateIngressRoute(ctx context.Context, arg CreateIngressRouteParams) error + CreateOnlyEncryptionKey(ctx context.Context, key []byte) error CreateRequest(ctx context.Context, origin Origin, key model.RequestKey, sourceAddr string) error - DeleteOldEvents(ctx context.Context, timeout time.Duration, type_ EventType) (int64, error) + DeleteOldTimelineEvents(ctx context.Context, timeout sqltypes.Duration, type_ EventType) (int64, error) DeleteSubscribers(ctx context.Context, deployment model.DeploymentKey) ([]model.SubscriberKey, error) DeleteSubscriptions(ctx context.Context, deployment model.DeploymentKey) ([]model.SubscriptionKey, error) DeregisterRunner(ctx context.Context, key model.RunnerKey) (int64, error) @@ -62,12 +66,13 @@ type Querier interface { GetDeploymentsWithMinReplicas(ctx context.Context) ([]GetDeploymentsWithMinReplicasRow, error) GetExistingDeploymentForModule(ctx context.Context, name string) (GetExistingDeploymentForModuleRow, error) GetFSMInstance(ctx context.Context, fsm schema.RefKey, key string) (FsmInstance, error) - GetIdleRunners(ctx context.Context, labels []byte, limit int64) ([]Runner, error) + GetIdleRunners(ctx context.Context, labels json.RawMessage, limit int64) ([]Runner, error) // Get the runner endpoints corresponding to the given ingress route. GetIngressRoutes(ctx context.Context, method string) ([]GetIngressRoutesRow, error) GetLeaseInfo(ctx context.Context, key leases.Key) (GetLeaseInfoRow, error) GetModulesByID(ctx context.Context, ids []int64) ([]Module, error) - GetNextEventForSubscription(ctx context.Context, consumptionDelay time.Duration, topic model.TopicKey, cursor optional.Option[model.TopicEventKey]) (GetNextEventForSubscriptionRow, error) + GetNextEventForSubscription(ctx context.Context, consumptionDelay sqltypes.Duration, topic model.TopicKey, cursor optional.Option[model.TopicEventKey]) (GetNextEventForSubscriptionRow, error) + GetOnlyEncryptionKey(ctx context.Context) ([]byte, error) GetProcessList(ctx context.Context) ([]GetProcessListRow, error) GetRandomSubscriber(ctx context.Context, key model.SubscriptionKey) (GetRandomSubscriberRow, error) // Retrieve routing information for a runner. @@ -77,7 +82,7 @@ type Querier interface { GetRunnerState(ctx context.Context, key model.RunnerKey) (RunnerState, error) GetRunnersForDeployment(ctx context.Context, key model.DeploymentKey) ([]GetRunnersForDeploymentRow, error) GetSchemaForDeployment(ctx context.Context, key model.DeploymentKey) (*schema.Module, error) - GetStaleCronJobs(ctx context.Context, dollar_1 time.Duration) ([]GetStaleCronJobsRow, error) + GetStaleCronJobs(ctx context.Context, dollar_1 sqltypes.Duration) ([]GetStaleCronJobsRow, error) GetSubscription(ctx context.Context, column1 string, column2 string) (TopicSubscription, error) // Results may not be ready to be scheduled yet due to event consumption delay // Sorting ensures that brand new events (that may not be ready for consumption) @@ -85,22 +90,22 @@ type Querier interface { GetSubscriptionsNeedingUpdate(ctx context.Context) ([]GetSubscriptionsNeedingUpdateRow, error) GetTopic(ctx context.Context, dollar_1 int64) (Topic, error) GetTopicEvent(ctx context.Context, dollar_1 int64) (TopicEvent, error) - InsertCallEvent(ctx context.Context, arg InsertCallEventParams) error - InsertDeploymentCreatedEvent(ctx context.Context, arg InsertDeploymentCreatedEventParams) error - InsertDeploymentUpdatedEvent(ctx context.Context, arg InsertDeploymentUpdatedEventParams) error - InsertEvent(ctx context.Context, arg InsertEventParams) error - InsertLogEvent(ctx context.Context, arg InsertLogEventParams) error InsertSubscriber(ctx context.Context, arg InsertSubscriberParams) error + InsertTimelineCallEvent(ctx context.Context, arg InsertTimelineCallEventParams) error + InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error + InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error + InsertTimelineEvent(ctx context.Context, arg InsertTimelineEventParams) error + InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error // Mark any controller entries that haven't been updated recently as dead. - KillStaleControllers(ctx context.Context, timeout time.Duration) (int64, error) - KillStaleRunners(ctx context.Context, timeout time.Duration) (int64, error) + KillStaleControllers(ctx context.Context, timeout sqltypes.Duration) (int64, error) + KillStaleRunners(ctx context.Context, timeout sqltypes.Duration) (int64, error) LoadAsyncCall(ctx context.Context, id int64) (AsyncCall, error) - NewLease(ctx context.Context, key leases.Key, ttl time.Duration, metadata []byte) (uuid.UUID, error) + NewLease(ctx context.Context, key leases.Key, ttl sqltypes.Duration, metadata pqtype.NullRawMessage) (uuid.UUID, error) PublishEventForTopic(ctx context.Context, arg PublishEventForTopicParams) error ReleaseLease(ctx context.Context, idempotencyKey uuid.UUID, key leases.Key) (bool, error) - RenewLease(ctx context.Context, ttl time.Duration, idempotencyKey uuid.UUID, key leases.Key) (bool, error) + RenewLease(ctx context.Context, ttl sqltypes.Duration, idempotencyKey uuid.UUID, key leases.Key) (bool, error) // Find an idle runner and reserve it for the given deployment. - ReserveRunner(ctx context.Context, reservationTimeout time.Time, deploymentKey model.DeploymentKey, labels []byte) (Runner, error) + ReserveRunner(ctx context.Context, reservationTimeout time.Time, deploymentKey model.DeploymentKey, labels json.RawMessage) (Runner, error) SetDeploymentDesiredReplicas(ctx context.Context, key model.DeploymentKey, minReplicas int32) error SetSubscriptionCursor(ctx context.Context, column1 model.SubscriptionKey, column2 model.TopicEventKey) error StartCronJobs(ctx context.Context, keys []string) ([]StartCronJobsRow, error) diff --git a/backend/controller/sql/queries.sql b/backend/controller/sql/queries.sql index 031a46e964..386d180fba 100644 --- a/backend/controller/sql/queries.sql +++ b/backend/controller/sql/queries.sql @@ -270,8 +270,8 @@ WITH rows AS ( SELECT COUNT(*) FROM rows; --- name: InsertLogEvent :exec -INSERT INTO events ( +-- name: InsertTimelineLogEvent :exec +INSERT INTO timeline ( deployment_id, request_id, time_stamp, @@ -293,8 +293,8 @@ VALUES ( sqlc.arg('payload') ); --- name: InsertDeploymentCreatedEvent :exec -INSERT INTO events ( +-- name: InsertTimelineDeploymentCreatedEvent :exec +INSERT INTO timeline ( deployment_id, type, custom_key_1, @@ -302,7 +302,7 @@ INSERT INTO events ( payload ) VALUES ( - ( + ( SELECT id FROM deployments WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key @@ -313,8 +313,8 @@ VALUES ( sqlc.arg('payload') ); --- name: InsertDeploymentUpdatedEvent :exec -INSERT INTO events ( +-- name: InsertTimelineDeploymentUpdatedEvent :exec +INSERT INTO timeline ( deployment_id, type, custom_key_1, @@ -333,8 +333,8 @@ VALUES ( sqlc.arg('payload') ); --- name: InsertCallEvent :exec -INSERT INTO events ( +-- name: InsertTimelineCallEvent :exec +INSERT INTO timeline ( deployment_id, request_id, parent_request_id, @@ -365,9 +365,9 @@ VALUES ( sqlc.arg('payload') ); --- name: DeleteOldEvents :one +-- name: DeleteOldTimelineEvents :one WITH deleted AS ( - DELETE FROM events + DELETE FROM timeline WHERE time_stamp < (NOW() AT TIME ZONE 'utc') - sqlc.arg('timeout')::INTERVAL AND type = sqlc.arg('type') RETURNING 1 @@ -423,8 +423,8 @@ FROM ingress_routes ir WHERE d.min_replicas > 0; --- name: InsertEvent :exec -INSERT INTO events (deployment_id, request_id, parent_request_id, type, +-- name: InsertTimelineEvent :exec +INSERT INTO timeline (deployment_id, request_id, parent_request_id, type, custom_key_1, custom_key_2, custom_key_3, custom_key_4, payload) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) @@ -542,7 +542,7 @@ RETURNING UPDATE async_calls SET state = 'success'::async_call_state, - response = @response::JSONB, + response = @response, error = null WHERE id = @id RETURNING true; @@ -667,8 +667,8 @@ VALUES ( sqlc.arg('name')::TEXT, sqlc.arg('event_type')::TEXT ) -ON CONFLICT (name, module_id) DO -UPDATE SET +ON CONFLICT (name, module_id) DO +UPDATE SET type = sqlc.arg('event_type')::TEXT RETURNING id; @@ -693,12 +693,12 @@ VALUES ( sqlc.arg('name')::TEXT ) ON CONFLICT (name, module_id) DO -UPDATE SET +UPDATE SET topic_id = excluded.topic_id, deployment_id = (SELECT id FROM deployments WHERE key = sqlc.arg('deployment')::deployment_key) -RETURNING +RETURNING id, - CASE + CASE WHEN xmax = 0 THEN true ELSE false END AS inserted; @@ -866,8 +866,7 @@ WITH event AS ( WHERE "key" = $2::topic_event_key ) UPDATE topic_subscriptions -SET state = 'idle', - cursor = (SELECT id FROM event) +SET cursor = (SELECT id FROM event) WHERE key = $1::subscription_key; -- name: GetTopic :one @@ -879,3 +878,12 @@ WHERE id = $1::BIGINT; SELECT * FROM topic_events WHERE id = $1::BIGINT; + +-- name: GetOnlyEncryptionKey :one +SELECT key +FROM encryption_keys +WHERE id = 1; + +-- name: CreateOnlyEncryptionKey :exec +INSERT INTO encryption_keys (id, key) +VALUES (1, $1); diff --git a/backend/controller/sql/queries.sql.go b/backend/controller/sql/queries.sql.go index a7815e1184..79b2a680dd 100644 --- a/backend/controller/sql/queries.sql.go +++ b/backend/controller/sql/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: queries.sql package sql @@ -11,10 +11,13 @@ import ( "time" "github.com/TBD54566975/ftl/backend/controller/leases" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" "github.com/google/uuid" + "github.com/lib/pq" + "github.com/sqlc-dev/pqtype" ) const acquireAsyncCall = `-- name: AcquireAsyncCall :one @@ -68,17 +71,17 @@ type AcquireAsyncCallRow struct { ScheduledAt time.Time RemainingAttempts int32 Error optional.Option[string] - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration ParentRequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage Catching bool } // Reserve a pending async call for execution, returning the associated lease // reservation key and accompanying metadata. -func (q *Queries) AcquireAsyncCall(ctx context.Context, ttl time.Duration) (AcquireAsyncCallRow, error) { - row := q.db.QueryRow(ctx, acquireAsyncCall, ttl) +func (q *Queries) AcquireAsyncCall(ctx context.Context, ttl sqltypes.Duration) (AcquireAsyncCallRow, error) { + row := q.db.QueryRowContext(ctx, acquireAsyncCall, ttl) var i AcquireAsyncCallRow err := row.Scan( &i.AsyncCallID, @@ -114,7 +117,7 @@ type AssociateArtefactWithDeploymentParams struct { } func (q *Queries) AssociateArtefactWithDeployment(ctx context.Context, arg AssociateArtefactWithDeploymentParams) error { - _, err := q.db.Exec(ctx, associateArtefactWithDeployment, + _, err := q.db.ExecContext(ctx, associateArtefactWithDeployment, arg.Key, arg.ArtefactID, arg.Executable, @@ -130,7 +133,7 @@ WHERE state = 'pending' AND scheduled_at <= (NOW() AT TIME ZONE 'utc') ` func (q *Queries) AsyncCallQueueDepth(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, asyncCallQueueDepth) + row := q.db.QueryRowContext(ctx, asyncCallQueueDepth) var count int64 err := row.Scan(&count) return count, err @@ -149,7 +152,7 @@ WHERE key = $1::subscription_key ` func (q *Queries) BeginConsumingTopicEvent(ctx context.Context, subscription model.SubscriptionKey, event model.TopicEventKey) error { - _, err := q.db.Exec(ctx, beginConsumingTopicEvent, subscription, event) + _, err := q.db.ExecContext(ctx, beginConsumingTopicEvent, subscription, event) return err } @@ -166,7 +169,7 @@ WHERE name = $1::TEXT ` func (q *Queries) CompleteEventForSubscription(ctx context.Context, name string, module string) error { - _, err := q.db.Exec(ctx, completeEventForSubscription, name, module) + _, err := q.db.ExecContext(ctx, completeEventForSubscription, name, module) return err } @@ -178,7 +181,7 @@ RETURNING id // Create a new artefact and return the artefact ID. func (q *Queries) CreateArtefact(ctx context.Context, digest []byte, content []byte) (int64, error) { - row := q.db.QueryRow(ctx, createArtefact, digest, content) + row := q.db.QueryRowContext(ctx, createArtefact, digest, content) var id int64 err := row.Scan(&id) return id, err @@ -215,15 +218,15 @@ type CreateAsyncCallParams struct { Origin string Request []byte RemainingAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] ParentRequestKey optional.Option[string] - TraceContext []byte + TraceContext json.RawMessage } func (q *Queries) CreateAsyncCall(ctx context.Context, arg CreateAsyncCallParams) (int64, error) { - row := q.db.QueryRow(ctx, createAsyncCall, + row := q.db.QueryRowContext(ctx, createAsyncCall, arg.Verb, arg.Origin, arg.Request, @@ -262,7 +265,7 @@ type CreateCronJobParams struct { } func (q *Queries) CreateCronJob(ctx context.Context, arg CreateCronJobParams) error { - _, err := q.db.Exec(ctx, createCronJob, + _, err := q.db.ExecContext(ctx, createCronJob, arg.Key, arg.DeploymentKey, arg.ModuleName, @@ -280,7 +283,7 @@ VALUES ((SELECT id FROM modules WHERE name = $1::TEXT LIMIT 1), $2::BYTEA, $3::d ` func (q *Queries) CreateDeployment(ctx context.Context, moduleName string, schema []byte, key model.DeploymentKey) error { - _, err := q.db.Exec(ctx, createDeployment, moduleName, schema, key) + _, err := q.db.ExecContext(ctx, createDeployment, moduleName, schema, key) return err } @@ -298,7 +301,7 @@ type CreateIngressRouteParams struct { } func (q *Queries) CreateIngressRoute(ctx context.Context, arg CreateIngressRouteParams) error { - _, err := q.db.Exec(ctx, createIngressRoute, + _, err := q.db.ExecContext(ctx, createIngressRoute, arg.Key, arg.Module, arg.Verb, @@ -308,19 +311,29 @@ func (q *Queries) CreateIngressRoute(ctx context.Context, arg CreateIngressRoute return err } +const createOnlyEncryptionKey = `-- name: CreateOnlyEncryptionKey :exec +INSERT INTO encryption_keys (id, key) +VALUES (1, $1) +` + +func (q *Queries) CreateOnlyEncryptionKey(ctx context.Context, key []byte) error { + _, err := q.db.ExecContext(ctx, createOnlyEncryptionKey, key) + return err +} + const createRequest = `-- name: CreateRequest :exec INSERT INTO requests (origin, "key", source_addr) VALUES ($1, $2, $3) ` func (q *Queries) CreateRequest(ctx context.Context, origin Origin, key model.RequestKey, sourceAddr string) error { - _, err := q.db.Exec(ctx, createRequest, origin, key, sourceAddr) + _, err := q.db.ExecContext(ctx, createRequest, origin, key, sourceAddr) return err } -const deleteOldEvents = `-- name: DeleteOldEvents :one +const deleteOldTimelineEvents = `-- name: DeleteOldTimelineEvents :one WITH deleted AS ( - DELETE FROM events + DELETE FROM timeline WHERE time_stamp < (NOW() AT TIME ZONE 'utc') - $1::INTERVAL AND type = $2 RETURNING 1 @@ -329,8 +342,8 @@ SELECT COUNT(*) FROM deleted ` -func (q *Queries) DeleteOldEvents(ctx context.Context, timeout time.Duration, type_ EventType) (int64, error) { - row := q.db.QueryRow(ctx, deleteOldEvents, timeout, type_) +func (q *Queries) DeleteOldTimelineEvents(ctx context.Context, timeout sqltypes.Duration, type_ EventType) (int64, error) { + row := q.db.QueryRowContext(ctx, deleteOldTimelineEvents, timeout, type_) var count int64 err := row.Scan(&count) return count, err @@ -347,7 +360,7 @@ RETURNING topic_subscribers.key ` func (q *Queries) DeleteSubscribers(ctx context.Context, deployment model.DeploymentKey) ([]model.SubscriberKey, error) { - rows, err := q.db.Query(ctx, deleteSubscribers, deployment) + rows, err := q.db.QueryContext(ctx, deleteSubscribers, deployment) if err != nil { return nil, err } @@ -360,6 +373,9 @@ func (q *Queries) DeleteSubscribers(ctx context.Context, deployment model.Deploy } items = append(items, key) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -377,7 +393,7 @@ RETURNING topic_subscriptions.key ` func (q *Queries) DeleteSubscriptions(ctx context.Context, deployment model.DeploymentKey) ([]model.SubscriptionKey, error) { - rows, err := q.db.Query(ctx, deleteSubscriptions, deployment) + rows, err := q.db.QueryContext(ctx, deleteSubscriptions, deployment) if err != nil { return nil, err } @@ -390,6 +406,9 @@ func (q *Queries) DeleteSubscriptions(ctx context.Context, deployment model.Depl } items = append(items, key) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -408,7 +427,7 @@ FROM matches ` func (q *Queries) DeregisterRunner(ctx context.Context, key model.RunnerKey) (int64, error) { - row := q.db.QueryRow(ctx, deregisterRunner, key) + row := q.db.QueryRowContext(ctx, deregisterRunner, key) var count int64 err := row.Scan(&count) return count, err @@ -442,7 +461,7 @@ type EndCronJobRow struct { } func (q *Queries) EndCronJob(ctx context.Context, nextExecution time.Time, key model.CronJobKey, startTime time.Time) (EndCronJobRow, error) { - row := q.db.QueryRow(ctx, endCronJob, nextExecution, key, startTime) + row := q.db.QueryRowContext(ctx, endCronJob, nextExecution, key, startTime) var i EndCronJobRow err := row.Scan( &i.Key, @@ -468,7 +487,7 @@ FROM expired ` func (q *Queries) ExpireLeases(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, expireLeases) + row := q.db.QueryRowContext(ctx, expireLeases) var count int64 err := row.Scan(&count) return count, err @@ -488,7 +507,7 @@ FROM rows ` func (q *Queries) ExpireRunnerReservations(ctx context.Context) (int64, error) { - row := q.db.QueryRow(ctx, expireRunnerReservations) + row := q.db.QueryRowContext(ctx, expireRunnerReservations) var count int64 err := row.Scan(&count) return count, err @@ -504,7 +523,7 @@ RETURNING true ` func (q *Queries) FailAsyncCall(ctx context.Context, error string, iD int64) (bool, error) { - row := q.db.QueryRow(ctx, failAsyncCall, error, iD) + row := q.db.QueryRowContext(ctx, failAsyncCall, error, iD) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -547,8 +566,8 @@ RETURNING true type FailAsyncCallWithRetryParams struct { RemainingAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration ScheduledAt time.Time Catching bool OriginalError optional.Option[string] @@ -557,7 +576,7 @@ type FailAsyncCallWithRetryParams struct { } func (q *Queries) FailAsyncCallWithRetry(ctx context.Context, arg FailAsyncCallWithRetryParams) (bool, error) { - row := q.db.QueryRow(ctx, failAsyncCallWithRetry, + row := q.db.QueryRowContext(ctx, failAsyncCallWithRetry, arg.RemainingAttempts, arg.Backoff, arg.MaxBackoff, @@ -585,7 +604,7 @@ RETURNING true ` func (q *Queries) FailFSMInstance(ctx context.Context, fsm schema.RefKey, key string) (bool, error) { - row := q.db.QueryRow(ctx, failFSMInstance, fsm, key) + row := q.db.QueryRowContext(ctx, failFSMInstance, fsm, key) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -605,7 +624,7 @@ RETURNING true // Mark an FSM transition as completed, updating the current state and clearing the async call ID. func (q *Queries) FinishFSMTransition(ctx context.Context, fsm schema.RefKey, key string) (bool, error) { - row := q.db.QueryRow(ctx, finishFSMTransition, fsm, key) + row := q.db.QueryRowContext(ctx, finishFSMTransition, fsm, key) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -619,7 +638,7 @@ ORDER BY c.key ` func (q *Queries) GetActiveControllers(ctx context.Context) ([]Controller, error) { - rows, err := q.db.Query(ctx, getActiveControllers) + rows, err := q.db.QueryContext(ctx, getActiveControllers) if err != nil { return nil, err } @@ -639,6 +658,9 @@ func (q *Queries) GetActiveControllers(ctx context.Context) ([]Controller, error } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -655,7 +677,7 @@ type GetActiveDeploymentSchemasRow struct { } func (q *Queries) GetActiveDeploymentSchemas(ctx context.Context) ([]GetActiveDeploymentSchemasRow, error) { - rows, err := q.db.Query(ctx, getActiveDeploymentSchemas) + rows, err := q.db.QueryContext(ctx, getActiveDeploymentSchemas) if err != nil { return nil, err } @@ -668,6 +690,9 @@ func (q *Queries) GetActiveDeploymentSchemas(ctx context.Context) ([]GetActiveDe } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -692,7 +717,7 @@ type GetActiveDeploymentsRow struct { } func (q *Queries) GetActiveDeployments(ctx context.Context) ([]GetActiveDeploymentsRow, error) { - rows, err := q.db.Query(ctx, getActiveDeployments) + rows, err := q.db.QueryContext(ctx, getActiveDeployments) if err != nil { return nil, err } @@ -716,6 +741,9 @@ func (q *Queries) GetActiveDeployments(ctx context.Context) ([]GetActiveDeployme } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -738,7 +766,7 @@ type GetActiveIngressRoutesRow struct { } func (q *Queries) GetActiveIngressRoutes(ctx context.Context) ([]GetActiveIngressRoutesRow, error) { - rows, err := q.db.Query(ctx, getActiveIngressRoutes) + rows, err := q.db.QueryContext(ctx, getActiveIngressRoutes) if err != nil { return nil, err } @@ -757,6 +785,9 @@ func (q *Queries) GetActiveIngressRoutes(ctx context.Context) ([]GetActiveIngres } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -783,14 +814,14 @@ type GetActiveRunnersRow struct { RunnerKey model.RunnerKey Endpoint string State RunnerState - Labels []byte + Labels json.RawMessage LastSeen time.Time ModuleName optional.Option[string] DeploymentKey optional.Option[string] } func (q *Queries) GetActiveRunners(ctx context.Context) ([]GetActiveRunnersRow, error) { - rows, err := q.db.Query(ctx, getActiveRunners) + rows, err := q.db.QueryContext(ctx, getActiveRunners) if err != nil { return nil, err } @@ -811,6 +842,9 @@ func (q *Queries) GetActiveRunners(ctx context.Context) ([]GetActiveRunnersRow, } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -824,7 +858,7 @@ WHERE a.id = $3 ` func (q *Queries) GetArtefactContentRange(ctx context.Context, start int32, count int32, iD int64) ([]byte, error) { - row := q.db.QueryRow(ctx, getArtefactContentRange, start, count, iD) + row := q.db.QueryRowContext(ctx, getArtefactContentRange, start, count, iD) var content []byte err := row.Scan(&content) return content, err @@ -843,7 +877,7 @@ type GetArtefactDigestsRow struct { // Return the digests that exist in the database. func (q *Queries) GetArtefactDigests(ctx context.Context, digests [][]byte) ([]GetArtefactDigestsRow, error) { - rows, err := q.db.Query(ctx, getArtefactDigests, digests) + rows, err := q.db.QueryContext(ctx, getArtefactDigests, pq.Array(digests)) if err != nil { return nil, err } @@ -856,6 +890,9 @@ func (q *Queries) GetArtefactDigests(ctx context.Context, digests [][]byte) ([]G } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -881,7 +918,7 @@ type GetCronJobsRow struct { } func (q *Queries) GetCronJobs(ctx context.Context) ([]GetCronJobsRow, error) { - rows, err := q.db.Query(ctx, getCronJobs) + rows, err := q.db.QueryContext(ctx, getCronJobs) if err != nil { return nil, err } @@ -903,6 +940,9 @@ func (q *Queries) GetCronJobs(ctx context.Context) ([]GetCronJobsRow, error) { } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -924,7 +964,7 @@ type GetDeploymentRow struct { } func (q *Queries) GetDeployment(ctx context.Context, key model.DeploymentKey) (GetDeploymentRow, error) { - row := q.db.QueryRow(ctx, getDeployment, key) + row := q.db.QueryRowContext(ctx, getDeployment, key) var i GetDeploymentRow err := row.Scan( &i.Deployment.ID, @@ -959,7 +999,7 @@ type GetDeploymentArtefactsRow struct { // Get all artefacts matching the given digests. func (q *Queries) GetDeploymentArtefacts(ctx context.Context, deploymentID int64) ([]GetDeploymentArtefactsRow, error) { - rows, err := q.db.Query(ctx, getDeploymentArtefacts, deploymentID) + rows, err := q.db.QueryContext(ctx, getDeploymentArtefacts, deploymentID) if err != nil { return nil, err } @@ -979,6 +1019,9 @@ func (q *Queries) GetDeploymentArtefacts(ctx context.Context, deploymentID int64 } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -992,7 +1035,7 @@ WHERE id = ANY ($1::BIGINT[]) ` func (q *Queries) GetDeploymentsByID(ctx context.Context, ids []int64) ([]Deployment, error) { - rows, err := q.db.Query(ctx, getDeploymentsByID, ids) + rows, err := q.db.QueryContext(ctx, getDeploymentsByID, pq.Array(ids)) if err != nil { return nil, err } @@ -1013,6 +1056,9 @@ func (q *Queries) GetDeploymentsByID(ctx context.Context, ids []int64) ([]Deploy } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1042,7 +1088,7 @@ type GetDeploymentsNeedingReconciliationRow struct { // Get deployments that have a mismatch between the number of assigned and required replicas. func (q *Queries) GetDeploymentsNeedingReconciliation(ctx context.Context) ([]GetDeploymentsNeedingReconciliationRow, error) { - rows, err := q.db.Query(ctx, getDeploymentsNeedingReconciliation) + rows, err := q.db.QueryContext(ctx, getDeploymentsNeedingReconciliation) if err != nil { return nil, err } @@ -1061,6 +1107,9 @@ func (q *Queries) GetDeploymentsNeedingReconciliation(ctx context.Context) ([]Ge } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1091,7 +1140,7 @@ type GetDeploymentsWithArtefactsRow struct { // Get all deployments that have artefacts matching the given digests. func (q *Queries) GetDeploymentsWithArtefacts(ctx context.Context, digests [][]byte, schema []byte, count int64) ([]GetDeploymentsWithArtefactsRow, error) { - rows, err := q.db.Query(ctx, getDeploymentsWithArtefacts, digests, schema, count) + rows, err := q.db.QueryContext(ctx, getDeploymentsWithArtefacts, pq.Array(digests), schema, count) if err != nil { return nil, err } @@ -1110,6 +1159,9 @@ func (q *Queries) GetDeploymentsWithArtefacts(ctx context.Context, digests [][]b } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1131,7 +1183,7 @@ type GetDeploymentsWithMinReplicasRow struct { } func (q *Queries) GetDeploymentsWithMinReplicas(ctx context.Context) ([]GetDeploymentsWithMinReplicasRow, error) { - rows, err := q.db.Query(ctx, getDeploymentsWithMinReplicas) + rows, err := q.db.QueryContext(ctx, getDeploymentsWithMinReplicas) if err != nil { return nil, err } @@ -1154,6 +1206,9 @@ func (q *Queries) GetDeploymentsWithMinReplicas(ctx context.Context) ([]GetDeplo } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1175,7 +1230,7 @@ type GetExistingDeploymentForModuleRow struct { ModuleID int64 Key model.DeploymentKey Schema *schema.Module - Labels []byte + Labels json.RawMessage MinReplicas int32 ID_2 int64 Language string @@ -1183,7 +1238,7 @@ type GetExistingDeploymentForModuleRow struct { } func (q *Queries) GetExistingDeploymentForModule(ctx context.Context, name string) (GetExistingDeploymentForModuleRow, error) { - row := q.db.QueryRow(ctx, getExistingDeploymentForModule, name) + row := q.db.QueryRowContext(ctx, getExistingDeploymentForModule, name) var i GetExistingDeploymentForModuleRow err := row.Scan( &i.ID, @@ -1207,7 +1262,7 @@ WHERE fsm = $1::schema_ref AND key = $2 ` func (q *Queries) GetFSMInstance(ctx context.Context, fsm schema.RefKey, key string) (FsmInstance, error) { - row := q.db.QueryRow(ctx, getFSMInstance, fsm, key) + row := q.db.QueryRowContext(ctx, getFSMInstance, fsm, key) var i FsmInstance err := row.Scan( &i.ID, @@ -1231,8 +1286,8 @@ WHERE labels @> $1::jsonb LIMIT $2 ` -func (q *Queries) GetIdleRunners(ctx context.Context, labels []byte, limit int64) ([]Runner, error) { - rows, err := q.db.Query(ctx, getIdleRunners, labels, limit) +func (q *Queries) GetIdleRunners(ctx context.Context, labels json.RawMessage, limit int64) ([]Runner, error) { + rows, err := q.db.QueryContext(ctx, getIdleRunners, labels, limit) if err != nil { return nil, err } @@ -1256,6 +1311,9 @@ func (q *Queries) GetIdleRunners(ctx context.Context, labels []byte, limit int64 } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1282,7 +1340,7 @@ type GetIngressRoutesRow struct { // Get the runner endpoints corresponding to the given ingress route. func (q *Queries) GetIngressRoutes(ctx context.Context, method string) ([]GetIngressRoutesRow, error) { - rows, err := q.db.Query(ctx, getIngressRoutes, method) + rows, err := q.db.QueryContext(ctx, getIngressRoutes, method) if err != nil { return nil, err } @@ -1302,6 +1360,9 @@ func (q *Queries) GetIngressRoutes(ctx context.Context, method string) ([]GetIng } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1314,11 +1375,11 @@ SELECT expires_at, metadata FROM leases WHERE key = $1::lease_key type GetLeaseInfoRow struct { ExpiresAt time.Time - Metadata []byte + Metadata pqtype.NullRawMessage } func (q *Queries) GetLeaseInfo(ctx context.Context, key leases.Key) (GetLeaseInfoRow, error) { - row := q.db.QueryRow(ctx, getLeaseInfo, key) + row := q.db.QueryRowContext(ctx, getLeaseInfo, key) var i GetLeaseInfoRow err := row.Scan(&i.ExpiresAt, &i.Metadata) return i, err @@ -1331,7 +1392,7 @@ WHERE id = ANY ($1::BIGINT[]) ` func (q *Queries) GetModulesByID(ctx context.Context, ids []int64) ([]Module, error) { - rows, err := q.db.Query(ctx, getModulesByID, ids) + rows, err := q.db.QueryContext(ctx, getModulesByID, pq.Array(ids)) if err != nil { return nil, err } @@ -1344,6 +1405,9 @@ func (q *Queries) GetModulesByID(ctx context.Context, ids []int64) ([]Module, er } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1379,12 +1443,12 @@ type GetNextEventForSubscriptionRow struct { CreatedAt optional.Option[time.Time] Caller optional.Option[string] RequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage Ready bool } -func (q *Queries) GetNextEventForSubscription(ctx context.Context, consumptionDelay time.Duration, topic model.TopicKey, cursor optional.Option[model.TopicEventKey]) (GetNextEventForSubscriptionRow, error) { - row := q.db.QueryRow(ctx, getNextEventForSubscription, consumptionDelay, topic, cursor) +func (q *Queries) GetNextEventForSubscription(ctx context.Context, consumptionDelay sqltypes.Duration, topic model.TopicKey, cursor optional.Option[model.TopicEventKey]) (GetNextEventForSubscriptionRow, error) { + row := q.db.QueryRowContext(ctx, getNextEventForSubscription, consumptionDelay, topic, cursor) var i GetNextEventForSubscriptionRow err := row.Scan( &i.Event, @@ -1398,6 +1462,19 @@ func (q *Queries) GetNextEventForSubscription(ctx context.Context, consumptionDe return i, err } +const getOnlyEncryptionKey = `-- name: GetOnlyEncryptionKey :one +SELECT key +FROM encryption_keys +WHERE id = 1 +` + +func (q *Queries) GetOnlyEncryptionKey(ctx context.Context) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getOnlyEncryptionKey) + var key []byte + err := row.Scan(&key) + return key, err +} + const getProcessList = `-- name: GetProcessList :many SELECT d.min_replicas, d.key AS deployment_key, @@ -1414,14 +1491,14 @@ ORDER BY d.key type GetProcessListRow struct { MinReplicas int32 DeploymentKey model.DeploymentKey - DeploymentLabels []byte + DeploymentLabels json.RawMessage RunnerKey optional.Option[model.RunnerKey] Endpoint optional.Option[string] - RunnerLabels []byte + RunnerLabels pqtype.NullRawMessage } func (q *Queries) GetProcessList(ctx context.Context) ([]GetProcessListRow, error) { - rows, err := q.db.Query(ctx, getProcessList) + rows, err := q.db.QueryContext(ctx, getProcessList) if err != nil { return nil, err } @@ -1441,6 +1518,9 @@ func (q *Queries) GetProcessList(ctx context.Context) ([]GetProcessListRow, erro } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1464,13 +1544,13 @@ LIMIT 1 type GetRandomSubscriberRow struct { Sink schema.RefKey RetryAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] } func (q *Queries) GetRandomSubscriber(ctx context.Context, key model.SubscriptionKey) (GetRandomSubscriberRow, error) { - row := q.db.QueryRow(ctx, getRandomSubscriber, key) + row := q.db.QueryRowContext(ctx, getRandomSubscriber, key) var i GetRandomSubscriberRow err := row.Scan( &i.Sink, @@ -1499,7 +1579,7 @@ type GetRouteForRunnerRow struct { // Retrieve routing information for a runner. func (q *Queries) GetRouteForRunner(ctx context.Context, key model.RunnerKey) (GetRouteForRunnerRow, error) { - row := q.db.QueryRow(ctx, getRouteForRunner, key) + row := q.db.QueryRowContext(ctx, getRouteForRunner, key) var i GetRouteForRunnerRow err := row.Scan( &i.Endpoint, @@ -1528,7 +1608,7 @@ type GetRoutingTableRow struct { } func (q *Queries) GetRoutingTable(ctx context.Context, modules []string) ([]GetRoutingTableRow, error) { - rows, err := q.db.Query(ctx, getRoutingTable, modules) + rows, err := q.db.QueryContext(ctx, getRoutingTable, pq.Array(modules)) if err != nil { return nil, err } @@ -1546,6 +1626,9 @@ func (q *Queries) GetRoutingTable(ctx context.Context, modules []string) ([]GetR } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1571,14 +1654,14 @@ type GetRunnerRow struct { RunnerKey model.RunnerKey Endpoint string State RunnerState - Labels []byte + Labels json.RawMessage LastSeen time.Time ModuleName optional.Option[string] DeploymentKey optional.Option[string] } func (q *Queries) GetRunner(ctx context.Context, key model.RunnerKey) (GetRunnerRow, error) { - row := q.db.QueryRow(ctx, getRunner, key) + row := q.db.QueryRowContext(ctx, getRunner, key) var i GetRunnerRow err := row.Scan( &i.RunnerKey, @@ -1599,7 +1682,7 @@ WHERE key = $1::runner_key ` func (q *Queries) GetRunnerState(ctx context.Context, key model.RunnerKey) (RunnerState, error) { - row := q.db.QueryRow(ctx, getRunnerState, key) + row := q.db.QueryRowContext(ctx, getRunnerState, key) var state RunnerState err := row.Scan(&state) return state, err @@ -1623,18 +1706,18 @@ type GetRunnersForDeploymentRow struct { Endpoint string ModuleName optional.Option[string] DeploymentID optional.Option[int64] - Labels []byte + Labels json.RawMessage ID_2 int64 CreatedAt time.Time ModuleID int64 Key_2 model.DeploymentKey Schema *schema.Module - Labels_2 []byte + Labels_2 json.RawMessage MinReplicas int32 } func (q *Queries) GetRunnersForDeployment(ctx context.Context, key model.DeploymentKey) ([]GetRunnersForDeploymentRow, error) { - rows, err := q.db.Query(ctx, getRunnersForDeployment, key) + rows, err := q.db.QueryContext(ctx, getRunnersForDeployment, key) if err != nil { return nil, err } @@ -1665,6 +1748,9 @@ func (q *Queries) GetRunnersForDeployment(ctx context.Context, key model.Deploym } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1676,7 +1762,7 @@ SELECT schema FROM deployments WHERE key = $1::deployment_key ` func (q *Queries) GetSchemaForDeployment(ctx context.Context, key model.DeploymentKey) (*schema.Module, error) { - row := q.db.QueryRow(ctx, getSchemaForDeployment, key) + row := q.db.QueryRowContext(ctx, getSchemaForDeployment, key) var schema *schema.Module err := row.Scan(&schema) return schema, err @@ -1701,8 +1787,8 @@ type GetStaleCronJobsRow struct { State model.CronJobState } -func (q *Queries) GetStaleCronJobs(ctx context.Context, dollar_1 time.Duration) ([]GetStaleCronJobsRow, error) { - rows, err := q.db.Query(ctx, getStaleCronJobs, dollar_1) +func (q *Queries) GetStaleCronJobs(ctx context.Context, dollar_1 sqltypes.Duration) ([]GetStaleCronJobsRow, error) { + rows, err := q.db.QueryContext(ctx, getStaleCronJobs, dollar_1) if err != nil { return nil, err } @@ -1724,6 +1810,9 @@ func (q *Queries) GetStaleCronJobs(ctx context.Context, dollar_1 time.Duration) } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1743,7 +1832,7 @@ WHERE name = $1::TEXT ` func (q *Queries) GetSubscription(ctx context.Context, column1 string, column2 string) (TopicSubscription, error) { - row := q.db.QueryRow(ctx, getSubscription, column1, column2) + row := q.db.QueryRowContext(ctx, getSubscription, column1, column2) var i TopicSubscription err := row.Scan( &i.ID, @@ -1786,7 +1875,7 @@ type GetSubscriptionsNeedingUpdateRow struct { // Sorting ensures that brand new events (that may not be ready for consumption) // don't prevent older events from being consumed func (q *Queries) GetSubscriptionsNeedingUpdate(ctx context.Context) ([]GetSubscriptionsNeedingUpdateRow, error) { - rows, err := q.db.Query(ctx, getSubscriptionsNeedingUpdate) + rows, err := q.db.QueryContext(ctx, getSubscriptionsNeedingUpdate) if err != nil { return nil, err } @@ -1804,6 +1893,9 @@ func (q *Queries) GetSubscriptionsNeedingUpdate(ctx context.Context) ([]GetSubsc } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -1817,7 +1909,7 @@ WHERE id = $1::BIGINT ` func (q *Queries) GetTopic(ctx context.Context, dollar_1 int64) (Topic, error) { - row := q.db.QueryRow(ctx, getTopic, dollar_1) + row := q.db.QueryRowContext(ctx, getTopic, dollar_1) var i Topic err := row.Scan( &i.ID, @@ -1838,7 +1930,7 @@ WHERE id = $1::BIGINT ` func (q *Queries) GetTopicEvent(ctx context.Context, dollar_1 int64) (TopicEvent, error) { - row := q.db.QueryRow(ctx, getTopicEvent, dollar_1) + row := q.db.QueryRowContext(ctx, getTopicEvent, dollar_1) var i TopicEvent err := row.Scan( &i.ID, @@ -1853,8 +1945,64 @@ func (q *Queries) GetTopicEvent(ctx context.Context, dollar_1 int64) (TopicEvent return i, err } -const insertCallEvent = `-- name: InsertCallEvent :exec -INSERT INTO events ( +const insertSubscriber = `-- name: InsertSubscriber :exec +INSERT INTO topic_subscribers ( + key, + topic_subscriptions_id, + deployment_id, + sink, + retry_attempts, + backoff, + max_backoff, + catch_verb +) +VALUES ( + $1::subscriber_key, + ( + SELECT topic_subscriptions.id as id + FROM topic_subscriptions + INNER JOIN modules ON topic_subscriptions.module_id = modules.id + WHERE modules.name = $2::TEXT + AND topic_subscriptions.name = $3::TEXT + ), + (SELECT id FROM deployments WHERE key = $4::deployment_key), + $5, + $6, + $7::interval, + $8::interval, + $9 +) +` + +type InsertSubscriberParams struct { + Key model.SubscriberKey + Module string + SubscriptionName string + Deployment model.DeploymentKey + Sink schema.RefKey + RetryAttempts int32 + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration + CatchVerb optional.Option[schema.RefKey] +} + +func (q *Queries) InsertSubscriber(ctx context.Context, arg InsertSubscriberParams) error { + _, err := q.db.ExecContext(ctx, insertSubscriber, + arg.Key, + arg.Module, + arg.SubscriptionName, + arg.Deployment, + arg.Sink, + arg.RetryAttempts, + arg.Backoff, + arg.MaxBackoff, + arg.CatchVerb, + ) + return err +} + +const insertTimelineCallEvent = `-- name: InsertTimelineCallEvent :exec +INSERT INTO timeline ( deployment_id, request_id, parent_request_id, @@ -1886,7 +2034,7 @@ VALUES ( ) ` -type InsertCallEventParams struct { +type InsertTimelineCallEventParams struct { DeploymentKey model.DeploymentKey RequestKey optional.Option[string] ParentRequestKey optional.Option[string] @@ -1895,11 +2043,11 @@ type InsertCallEventParams struct { SourceVerb optional.Option[string] DestModule string DestVerb string - Payload json.RawMessage + Payload []byte } -func (q *Queries) InsertCallEvent(ctx context.Context, arg InsertCallEventParams) error { - _, err := q.db.Exec(ctx, insertCallEvent, +func (q *Queries) InsertTimelineCallEvent(ctx context.Context, arg InsertTimelineCallEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineCallEvent, arg.DeploymentKey, arg.RequestKey, arg.ParentRequestKey, @@ -1913,8 +2061,8 @@ func (q *Queries) InsertCallEvent(ctx context.Context, arg InsertCallEventParams return err } -const insertDeploymentCreatedEvent = `-- name: InsertDeploymentCreatedEvent :exec -INSERT INTO events ( +const insertTimelineDeploymentCreatedEvent = `-- name: InsertTimelineDeploymentCreatedEvent :exec +INSERT INTO timeline ( deployment_id, type, custom_key_1, @@ -1922,7 +2070,7 @@ INSERT INTO events ( payload ) VALUES ( - ( + ( SELECT id FROM deployments WHERE deployments.key = $1::deployment_key @@ -1934,15 +2082,15 @@ VALUES ( ) ` -type InsertDeploymentCreatedEventParams struct { +type InsertTimelineDeploymentCreatedEventParams struct { DeploymentKey model.DeploymentKey Language string ModuleName string - Payload json.RawMessage + Payload []byte } -func (q *Queries) InsertDeploymentCreatedEvent(ctx context.Context, arg InsertDeploymentCreatedEventParams) error { - _, err := q.db.Exec(ctx, insertDeploymentCreatedEvent, +func (q *Queries) InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineDeploymentCreatedEvent, arg.DeploymentKey, arg.Language, arg.ModuleName, @@ -1951,8 +2099,8 @@ func (q *Queries) InsertDeploymentCreatedEvent(ctx context.Context, arg InsertDe return err } -const insertDeploymentUpdatedEvent = `-- name: InsertDeploymentUpdatedEvent :exec -INSERT INTO events ( +const insertTimelineDeploymentUpdatedEvent = `-- name: InsertTimelineDeploymentUpdatedEvent :exec +INSERT INTO timeline ( deployment_id, type, custom_key_1, @@ -1972,15 +2120,15 @@ VALUES ( ) ` -type InsertDeploymentUpdatedEventParams struct { +type InsertTimelineDeploymentUpdatedEventParams struct { DeploymentKey model.DeploymentKey Language string ModuleName string - Payload json.RawMessage + Payload []byte } -func (q *Queries) InsertDeploymentUpdatedEvent(ctx context.Context, arg InsertDeploymentUpdatedEventParams) error { - _, err := q.db.Exec(ctx, insertDeploymentUpdatedEvent, +func (q *Queries) InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineDeploymentUpdatedEvent, arg.DeploymentKey, arg.Language, arg.ModuleName, @@ -1989,15 +2137,15 @@ func (q *Queries) InsertDeploymentUpdatedEvent(ctx context.Context, arg InsertDe return err } -const insertEvent = `-- name: InsertEvent :exec -INSERT INTO events (deployment_id, request_id, parent_request_id, type, +const insertTimelineEvent = `-- name: InsertTimelineEvent :exec +INSERT INTO timeline (deployment_id, request_id, parent_request_id, type, custom_key_1, custom_key_2, custom_key_3, custom_key_4, payload) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id ` -type InsertEventParams struct { +type InsertTimelineEventParams struct { DeploymentID int64 RequestID optional.Option[int64] ParentRequestID optional.Option[string] @@ -2006,11 +2154,11 @@ type InsertEventParams struct { CustomKey2 optional.Option[string] CustomKey3 optional.Option[string] CustomKey4 optional.Option[string] - Payload json.RawMessage + Payload []byte } -func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error { - _, err := q.db.Exec(ctx, insertEvent, +func (q *Queries) InsertTimelineEvent(ctx context.Context, arg InsertTimelineEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineEvent, arg.DeploymentID, arg.RequestID, arg.ParentRequestID, @@ -2024,8 +2172,8 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error return err } -const insertLogEvent = `-- name: InsertLogEvent :exec -INSERT INTO events ( +const insertTimelineLogEvent = `-- name: InsertTimelineLogEvent :exec +INSERT INTO timeline ( deployment_id, request_id, time_stamp, @@ -2048,16 +2196,16 @@ VALUES ( ) ` -type InsertLogEventParams struct { +type InsertTimelineLogEventParams struct { DeploymentKey model.DeploymentKey RequestKey optional.Option[string] TimeStamp time.Time Level int32 - Payload json.RawMessage + Payload []byte } -func (q *Queries) InsertLogEvent(ctx context.Context, arg InsertLogEventParams) error { - _, err := q.db.Exec(ctx, insertLogEvent, +func (q *Queries) InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineLogEvent, arg.DeploymentKey, arg.RequestKey, arg.TimeStamp, @@ -2067,62 +2215,6 @@ func (q *Queries) InsertLogEvent(ctx context.Context, arg InsertLogEventParams) return err } -const insertSubscriber = `-- name: InsertSubscriber :exec -INSERT INTO topic_subscribers ( - key, - topic_subscriptions_id, - deployment_id, - sink, - retry_attempts, - backoff, - max_backoff, - catch_verb -) -VALUES ( - $1::subscriber_key, - ( - SELECT topic_subscriptions.id as id - FROM topic_subscriptions - INNER JOIN modules ON topic_subscriptions.module_id = modules.id - WHERE modules.name = $2::TEXT - AND topic_subscriptions.name = $3::TEXT - ), - (SELECT id FROM deployments WHERE key = $4::deployment_key), - $5, - $6, - $7::interval, - $8::interval, - $9 -) -` - -type InsertSubscriberParams struct { - Key model.SubscriberKey - Module string - SubscriptionName string - Deployment model.DeploymentKey - Sink schema.RefKey - RetryAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration - CatchVerb optional.Option[schema.RefKey] -} - -func (q *Queries) InsertSubscriber(ctx context.Context, arg InsertSubscriberParams) error { - _, err := q.db.Exec(ctx, insertSubscriber, - arg.Key, - arg.Module, - arg.SubscriptionName, - arg.Deployment, - arg.Sink, - arg.RetryAttempts, - arg.Backoff, - arg.MaxBackoff, - arg.CatchVerb, - ) - return err -} - const killStaleControllers = `-- name: KillStaleControllers :one WITH matches AS ( UPDATE controller @@ -2134,8 +2226,8 @@ FROM matches ` // Mark any controller entries that haven't been updated recently as dead. -func (q *Queries) KillStaleControllers(ctx context.Context, timeout time.Duration) (int64, error) { - row := q.db.QueryRow(ctx, killStaleControllers, timeout) +func (q *Queries) KillStaleControllers(ctx context.Context, timeout sqltypes.Duration) (int64, error) { + row := q.db.QueryRowContext(ctx, killStaleControllers, timeout) var count int64 err := row.Scan(&count) return count, err @@ -2152,8 +2244,8 @@ SELECT COUNT(*) FROM matches ` -func (q *Queries) KillStaleRunners(ctx context.Context, timeout time.Duration) (int64, error) { - row := q.db.QueryRow(ctx, killStaleRunners, timeout) +func (q *Queries) KillStaleRunners(ctx context.Context, timeout sqltypes.Duration) (int64, error) { + row := q.db.QueryRowContext(ctx, killStaleRunners, timeout) var count int64 err := row.Scan(&count) return count, err @@ -2166,7 +2258,7 @@ WHERE id = $1 ` func (q *Queries) LoadAsyncCall(ctx context.Context, id int64) (AsyncCall, error) { - row := q.db.QueryRow(ctx, loadAsyncCall, id) + row := q.db.QueryRowContext(ctx, loadAsyncCall, id) var i AsyncCall err := row.Scan( &i.ID, @@ -2206,8 +2298,8 @@ VALUES ( RETURNING idempotency_key ` -func (q *Queries) NewLease(ctx context.Context, key leases.Key, ttl time.Duration, metadata []byte) (uuid.UUID, error) { - row := q.db.QueryRow(ctx, newLease, key, ttl, metadata) +func (q *Queries) NewLease(ctx context.Context, key leases.Key, ttl sqltypes.Duration, metadata pqtype.NullRawMessage) (uuid.UUID, error) { + row := q.db.QueryRowContext(ctx, newLease, key, ttl, metadata) var idempotency_key uuid.UUID err := row.Scan(&idempotency_key) return idempotency_key, err @@ -2245,11 +2337,11 @@ type PublishEventForTopicParams struct { Caller string Payload []byte RequestKey string - TraceContext []byte + TraceContext json.RawMessage } func (q *Queries) PublishEventForTopic(ctx context.Context, arg PublishEventForTopicParams) error { - _, err := q.db.Exec(ctx, publishEventForTopic, + _, err := q.db.ExecContext(ctx, publishEventForTopic, arg.Key, arg.Module, arg.Topic, @@ -2268,7 +2360,7 @@ RETURNING true ` func (q *Queries) ReleaseLease(ctx context.Context, idempotencyKey uuid.UUID, key leases.Key) (bool, error) { - row := q.db.QueryRow(ctx, releaseLease, idempotencyKey, key) + row := q.db.QueryRowContext(ctx, releaseLease, idempotencyKey, key) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -2281,8 +2373,8 @@ WHERE idempotency_key = $2 AND key = $3::lease_key RETURNING true ` -func (q *Queries) RenewLease(ctx context.Context, ttl time.Duration, idempotencyKey uuid.UUID, key leases.Key) (bool, error) { - row := q.db.QueryRow(ctx, renewLease, ttl, idempotencyKey, key) +func (q *Queries) RenewLease(ctx context.Context, ttl sqltypes.Duration, idempotencyKey uuid.UUID, key leases.Key) (bool, error) { + row := q.db.QueryRowContext(ctx, renewLease, ttl, idempotencyKey, key) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -2307,8 +2399,8 @@ RETURNING runners.id, runners.key, runners.created, runners.last_seen, runners.r ` // Find an idle runner and reserve it for the given deployment. -func (q *Queries) ReserveRunner(ctx context.Context, reservationTimeout time.Time, deploymentKey model.DeploymentKey, labels []byte) (Runner, error) { - row := q.db.QueryRow(ctx, reserveRunner, reservationTimeout, deploymentKey, labels) +func (q *Queries) ReserveRunner(ctx context.Context, reservationTimeout time.Time, deploymentKey model.DeploymentKey, labels json.RawMessage) (Runner, error) { + row := q.db.QueryRowContext(ctx, reserveRunner, reservationTimeout, deploymentKey, labels) var i Runner err := row.Scan( &i.ID, @@ -2333,7 +2425,7 @@ RETURNING 1 ` func (q *Queries) SetDeploymentDesiredReplicas(ctx context.Context, key model.DeploymentKey, minReplicas int32) error { - _, err := q.db.Exec(ctx, setDeploymentDesiredReplicas, key, minReplicas) + _, err := q.db.ExecContext(ctx, setDeploymentDesiredReplicas, key, minReplicas) return err } @@ -2344,13 +2436,12 @@ WITH event AS ( WHERE "key" = $2::topic_event_key ) UPDATE topic_subscriptions -SET state = 'idle', - cursor = (SELECT id FROM event) +SET cursor = (SELECT id FROM event) WHERE key = $1::subscription_key ` func (q *Queries) SetSubscriptionCursor(ctx context.Context, column1 model.SubscriptionKey, column2 model.TopicEventKey) error { - _, err := q.db.Exec(ctx, setSubscriptionCursor, column1, column2) + _, err := q.db.ExecContext(ctx, setSubscriptionCursor, column1, column2) return err } @@ -2390,7 +2481,7 @@ type StartCronJobsRow struct { } func (q *Queries) StartCronJobs(ctx context.Context, keys []string) ([]StartCronJobsRow, error) { - rows, err := q.db.Query(ctx, startCronJobs, keys) + rows, err := q.db.QueryContext(ctx, startCronJobs, pq.Array(keys)) if err != nil { return nil, err } @@ -2414,6 +2505,9 @@ func (q *Queries) StartCronJobs(ctx context.Context, keys []string) ([]StartCron } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -2454,7 +2548,7 @@ type StartFSMTransitionParams struct { // // "key" is the unique identifier for the FSM execution. func (q *Queries) StartFSMTransition(ctx context.Context, arg StartFSMTransitionParams) (FsmInstance, error) { - row := q.db.QueryRow(ctx, startFSMTransition, + row := q.db.QueryRowContext(ctx, startFSMTransition, arg.Fsm, arg.Key, arg.DestinationState, @@ -2479,14 +2573,14 @@ const succeedAsyncCall = `-- name: SucceedAsyncCall :one UPDATE async_calls SET state = 'success'::async_call_state, - response = $1::JSONB, + response = $1, error = null WHERE id = $2 RETURNING true ` func (q *Queries) SucceedAsyncCall(ctx context.Context, response []byte, iD int64) (bool, error) { - row := q.db.QueryRow(ctx, succeedAsyncCall, response, iD) + row := q.db.QueryRowContext(ctx, succeedAsyncCall, response, iD) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -2506,7 +2600,7 @@ RETURNING true ` func (q *Queries) SucceedFSMInstance(ctx context.Context, fsm schema.RefKey, key string) (bool, error) { - row := q.db.QueryRow(ctx, succeedFSMInstance, fsm, key) + row := q.db.QueryRowContext(ctx, succeedFSMInstance, fsm, key) var column_1 bool err := row.Scan(&column_1) return column_1, err @@ -2522,7 +2616,7 @@ RETURNING id ` func (q *Queries) UpsertController(ctx context.Context, key model.ControllerKey, endpoint string) (int64, error) { - row := q.db.QueryRow(ctx, upsertController, key, endpoint) + row := q.db.QueryRowContext(ctx, upsertController, key, endpoint) var id int64 err := row.Scan(&id) return id, err @@ -2536,7 +2630,7 @@ RETURNING id ` func (q *Queries) UpsertModule(ctx context.Context, language string, name string) (int64, error) { - row := q.db.QueryRow(ctx, upsertModule, language, name) + row := q.db.QueryRowContext(ctx, upsertModule, language, name) var id int64 err := row.Scan(&id) return id, err @@ -2571,7 +2665,7 @@ type UpsertRunnerParams struct { Key model.RunnerKey Endpoint string State RunnerState - Labels []byte + Labels json.RawMessage DeploymentKey optional.Option[model.DeploymentKey] } @@ -2581,7 +2675,7 @@ type UpsertRunnerParams struct { // there is no corresponding deployment, then the deployment ID is -1 // and the parent statement will fail due to a foreign key constraint. func (q *Queries) UpsertRunner(ctx context.Context, arg UpsertRunnerParams) (optional.Option[int64], error) { - row := q.db.QueryRow(ctx, upsertRunner, + row := q.db.QueryRowContext(ctx, upsertRunner, arg.Key, arg.Endpoint, arg.State, @@ -2614,12 +2708,12 @@ VALUES ( $6::TEXT ) ON CONFLICT (name, module_id) DO -UPDATE SET +UPDATE SET topic_id = excluded.topic_id, deployment_id = (SELECT id FROM deployments WHERE key = $5::deployment_key) -RETURNING +RETURNING id, - CASE + CASE WHEN xmax = 0 THEN true ELSE false END AS inserted @@ -2640,7 +2734,7 @@ type UpsertSubscriptionRow struct { } func (q *Queries) UpsertSubscription(ctx context.Context, arg UpsertSubscriptionParams) (UpsertSubscriptionRow, error) { - row := q.db.QueryRow(ctx, upsertSubscription, + row := q.db.QueryRowContext(ctx, upsertSubscription, arg.Key, arg.TopicModule, arg.TopicName, @@ -2661,8 +2755,8 @@ VALUES ( $3::TEXT, $4::TEXT ) -ON CONFLICT (name, module_id) DO -UPDATE SET +ON CONFLICT (name, module_id) DO +UPDATE SET type = $4::TEXT RETURNING id ` @@ -2675,7 +2769,7 @@ type UpsertTopicParams struct { } func (q *Queries) UpsertTopic(ctx context.Context, arg UpsertTopicParams) error { - _, err := q.db.Exec(ctx, upsertTopic, + _, err := q.db.ExecContext(ctx, upsertTopic, arg.Topic, arg.Module, arg.Name, diff --git a/backend/controller/sql/schema/20240812011321_derive_encryption.sql b/backend/controller/sql/schema/20240812011321_derive_encryption.sql new file mode 100644 index 0000000000..bdb8b23962 --- /dev/null +++ b/backend/controller/sql/schema/20240812011321_derive_encryption.sql @@ -0,0 +1,19 @@ +-- migrate:up + +CREATE TABLE encryption_keys ( + id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + key bytea NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc') +); + +ALTER TABLE events + ALTER COLUMN payload TYPE bytea + USING payload::text::bytea; + +ALTER TABLE async_calls + ALTER COLUMN request TYPE bytea + USING request::text::bytea, + ALTER COLUMN response TYPE bytea + USING response::text::bytea; + +-- migrate:down diff --git a/backend/controller/sql/schema/20240814060154_rename_events_to_timeline.sql b/backend/controller/sql/schema/20240814060154_rename_events_to_timeline.sql new file mode 100644 index 0000000000..579ed8faa9 --- /dev/null +++ b/backend/controller/sql/schema/20240814060154_rename_events_to_timeline.sql @@ -0,0 +1,6 @@ +-- migrate:up + +ALTER TABLE events RENAME TO timeline; + +-- migrate:down + diff --git a/backend/controller/sql/sqltest/testing.go b/backend/controller/sql/sqltest/testing.go index 0202d62f88..a2affb00fa 100644 --- a/backend/controller/sql/sqltest/testing.go +++ b/backend/controller/sql/sqltest/testing.go @@ -2,13 +2,13 @@ package sqltest import ( "context" + "database/sql" "os" "path/filepath" "testing" "time" "github.com/alecthomas/assert/v2" - "github.com/jackc/pgx/v5/pgxpool" "github.com/TBD54566975/ftl/backend/controller/sql/databasetesting" "github.com/TBD54566975/ftl/internal/flock" @@ -16,16 +16,17 @@ import ( // OpenForTesting opens a database connection for testing, recreating the // database beforehand. -func OpenForTesting(ctx context.Context, t testing.TB) *pgxpool.Pool { +func OpenForTesting(ctx context.Context, t testing.TB) *sql.DB { t.Helper() // Acquire lock for this DB. lockPath := filepath.Join(os.TempDir(), "ftl-db-test.lock") - release, err := flock.Acquire(ctx, lockPath, 10*time.Second) + release, err := flock.Acquire(ctx, lockPath, 20*time.Second) assert.NoError(t, err) t.Cleanup(func() { _ = release() }) //nolint:errcheck testDSN := "postgres://localhost:15432/ftl-test?user=postgres&password=secret&sslmode=disable" conn, err := databasetesting.CreateForDevel(ctx, testDSN, true) assert.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, conn.Close()) }) return conn } diff --git a/backend/controller/sql/sqltypes/sqltypes.go b/backend/controller/sql/sqltypes/sqltypes.go new file mode 100644 index 0000000000..4fd49d120e --- /dev/null +++ b/backend/controller/sql/sqltypes/sqltypes.go @@ -0,0 +1,32 @@ +package sqltypes + +import ( + "database/sql/driver" + "fmt" + "strings" + "time" +) + +type Duration time.Duration + +func (d Duration) Value() (driver.Value, error) { + return time.Duration(d).String(), nil +} + +func (d *Duration) Scan(value interface{}) error { + switch v := value.(type) { + case string: + // Convert format of hh:mm:ss into format parseable by time.ParseDuration() + v = strings.Replace(v, ":", "h", 1) + v = strings.Replace(v, ":", "m", 1) + v += "s" + dur, err := time.ParseDuration(v) + if err != nil { + return fmt.Errorf("failed to parse duration %q: %w", v, err) + } + *d = Duration(dur) + return nil + default: + return fmt.Errorf("cannot scan duration %v", value) + } +} diff --git a/backend/controller/sql/testdata/go/database/go.mod b/backend/controller/sql/testdata/go/database/go.mod index dcf1bb9920..46957abc44 100644 --- a/backend/controller/sql/testdata/go/database/go.mod +++ b/backend/controller/sql/testdata/go/database/go.mod @@ -42,19 +42,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/backend/controller/sql/testdata/go/database/go.sum b/backend/controller/sql/testdata/go/database/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/backend/controller/sql/testdata/go/database/go.sum +++ b/backend/controller/sql/testdata/go/database/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/backend/runner/runner.go b/backend/runner/runner.go index fc68bb8b6a..d48d0c8636 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -49,7 +49,7 @@ type Config struct { TemplateDir string `help:"Template directory to copy into each deployment, if any." type:"existingdir"` DeploymentDir string `help:"Directory to store deployments in." default:"${deploymentdir}"` DeploymentKeepHistory int `help:"Number of deployments to keep history for." default:"3"` - Language []string `short:"l" help:"Languages the runner supports." env:"FTL_LANGUAGE" default:"go,kotlin,rust"` + Language []string `short:"l" help:"Languages the runner supports." env:"FTL_LANGUAGE" default:"go,kotlin,rust,java"` HeartbeatPeriod time.Duration `help:"Minimum period between heartbeats." default:"3s"` HeartbeatJitter time.Duration `help:"Jitter to add to heartbeat period." default:"2s"` RunnerStartDelay time.Duration `help:"Time in seconds for a runner to wait before contacting the controller. This can be needed in istio environments to work around initialization races." env:"FTL_RUNNER_START_DELAY" default:"0s"` diff --git a/backend/schema/metadatatypemap.go b/backend/schema/metadatatypemap.go index 432fd4f3c3..997e16c952 100644 --- a/backend/schema/metadatatypemap.go +++ b/backend/schema/metadatatypemap.go @@ -11,7 +11,7 @@ import ( type MetadataTypeMap struct { Pos Position `parser:"" protobuf:"1,optional"` - Runtime string `parser:"'+' 'typemap' @('go' | 'kotlin')" protobuf:"2"` + Runtime string `parser:"'+' 'typemap' @('go' | 'kotlin' | 'java')" protobuf:"2"` NativeName string `parser:"@String" protobuf:"3"` } diff --git a/bin/.act-0.2.64.pkg b/bin/.act-0.2.65.pkg similarity index 100% rename from bin/.act-0.2.64.pkg rename to bin/.act-0.2.65.pkg diff --git a/bin/.buf-1.35.1.pkg b/bin/.buf-1.36.0.pkg similarity index 100% rename from bin/.buf-1.35.1.pkg rename to bin/.buf-1.36.0.pkg diff --git a/bin/.go-1.22.5.pkg b/bin/.go-1.22.6.pkg similarity index 100% rename from bin/.go-1.22.5.pkg rename to bin/.go-1.22.6.pkg diff --git a/bin/.just-1.32.0.pkg b/bin/.just-1.34.0.pkg similarity index 100% rename from bin/.just-1.32.0.pkg rename to bin/.just-1.34.0.pkg diff --git a/bin/.k3d-5.7.2.pkg b/bin/.k3d-5.7.3.pkg similarity index 100% rename from bin/.k3d-5.7.2.pkg rename to bin/.k3d-5.7.3.pkg diff --git a/bin/.kotlin-2.0.0.pkg b/bin/.kotlin-2.0.10.pkg similarity index 100% rename from bin/.kotlin-2.0.0.pkg rename to bin/.kotlin-2.0.10.pkg diff --git a/bin/.pre-commit-3.7.1.pkg b/bin/.pre-commit-3.8.0.pkg similarity index 100% rename from bin/.pre-commit-3.7.1.pkg rename to bin/.pre-commit-3.8.0.pkg diff --git a/bin/.protoc-27.2.pkg b/bin/.protoc-27.3.pkg similarity index 100% rename from bin/.protoc-27.2.pkg rename to bin/.protoc-27.3.pkg diff --git a/bin/.sqlc-1.26.0.pkg b/bin/.sqlc-1.27.0.pkg similarity index 100% rename from bin/.sqlc-1.26.0.pkg rename to bin/.sqlc-1.27.0.pkg diff --git a/bin/.yq-4.44.2.pkg b/bin/.yq-4.44.3.pkg similarity index 100% rename from bin/.yq-4.44.2.pkg rename to bin/.yq-4.44.3.pkg diff --git a/bin/act b/bin/act index 27148c2126..c6d9c6303e 120000 --- a/bin/act +++ b/bin/act @@ -1 +1 @@ -.act-0.2.64.pkg \ No newline at end of file +.act-0.2.65.pkg \ No newline at end of file diff --git a/bin/buf b/bin/buf index 5324fa3114..578de2e3be 120000 --- a/bin/buf +++ b/bin/buf @@ -1 +1 @@ -.buf-1.35.1.pkg \ No newline at end of file +.buf-1.36.0.pkg \ No newline at end of file diff --git a/bin/go b/bin/go index 5c26cb9eda..fec23e291d 120000 --- a/bin/go +++ b/bin/go @@ -1 +1 @@ -.go-1.22.5.pkg \ No newline at end of file +.go-1.22.6.pkg \ No newline at end of file diff --git a/bin/gofmt b/bin/gofmt index 5c26cb9eda..fec23e291d 120000 --- a/bin/gofmt +++ b/bin/gofmt @@ -1 +1 @@ -.go-1.22.5.pkg \ No newline at end of file +.go-1.22.6.pkg \ No newline at end of file diff --git a/bin/just b/bin/just index 681ed58773..bf5e8a7ce2 120000 --- a/bin/just +++ b/bin/just @@ -1 +1 @@ -.just-1.32.0.pkg \ No newline at end of file +.just-1.34.0.pkg \ No newline at end of file diff --git a/bin/k3d b/bin/k3d index 5a8dc5fc0d..e5d6063fc8 120000 --- a/bin/k3d +++ b/bin/k3d @@ -1 +1 @@ -.k3d-5.7.2.pkg \ No newline at end of file +.k3d-5.7.3.pkg \ No newline at end of file diff --git a/bin/kapt b/bin/kapt index c3a07a1884..863697944d 120000 --- a/bin/kapt +++ b/bin/kapt @@ -1 +1 @@ -.kotlin-2.0.0.pkg \ No newline at end of file +.kotlin-2.0.10.pkg \ No newline at end of file diff --git a/bin/kotlin b/bin/kotlin index c3a07a1884..863697944d 120000 --- a/bin/kotlin +++ b/bin/kotlin @@ -1 +1 @@ -.kotlin-2.0.0.pkg \ No newline at end of file +.kotlin-2.0.10.pkg \ No newline at end of file diff --git a/bin/kotlin-dce-js b/bin/kotlin-dce-js index c3a07a1884..863697944d 120000 --- a/bin/kotlin-dce-js +++ b/bin/kotlin-dce-js @@ -1 +1 @@ -.kotlin-2.0.0.pkg \ No newline at end of file +.kotlin-2.0.10.pkg \ No newline at end of file diff --git a/bin/kotlinc-js b/bin/kotlinc-js index c3a07a1884..863697944d 120000 --- a/bin/kotlinc-js +++ b/bin/kotlinc-js @@ -1 +1 @@ -.kotlin-2.0.0.pkg \ No newline at end of file +.kotlin-2.0.10.pkg \ No newline at end of file diff --git a/bin/kotlinc-jvm b/bin/kotlinc-jvm index c3a07a1884..863697944d 120000 --- a/bin/kotlinc-jvm +++ b/bin/kotlinc-jvm @@ -1 +1 @@ -.kotlin-2.0.0.pkg \ No newline at end of file +.kotlin-2.0.10.pkg \ No newline at end of file diff --git a/bin/pre-commit b/bin/pre-commit index 47a53ea153..f942433653 120000 --- a/bin/pre-commit +++ b/bin/pre-commit @@ -1 +1 @@ -.pre-commit-3.7.1.pkg \ No newline at end of file +.pre-commit-3.8.0.pkg \ No newline at end of file diff --git a/bin/protoc b/bin/protoc index 72f813a0d0..0182c62110 120000 --- a/bin/protoc +++ b/bin/protoc @@ -1 +1 @@ -.protoc-27.2.pkg \ No newline at end of file +.protoc-27.3.pkg \ No newline at end of file diff --git a/bin/protoc-gen-buf-breaking b/bin/protoc-gen-buf-breaking index 5324fa3114..578de2e3be 120000 --- a/bin/protoc-gen-buf-breaking +++ b/bin/protoc-gen-buf-breaking @@ -1 +1 @@ -.buf-1.35.1.pkg \ No newline at end of file +.buf-1.36.0.pkg \ No newline at end of file diff --git a/bin/protoc-gen-buf-lint b/bin/protoc-gen-buf-lint index 5324fa3114..578de2e3be 120000 --- a/bin/protoc-gen-buf-lint +++ b/bin/protoc-gen-buf-lint @@ -1 +1 @@ -.buf-1.35.1.pkg \ No newline at end of file +.buf-1.36.0.pkg \ No newline at end of file diff --git a/bin/sqlc b/bin/sqlc index 355549249e..2346bedaf1 120000 --- a/bin/sqlc +++ b/bin/sqlc @@ -1 +1 @@ -.sqlc-1.26.0.pkg \ No newline at end of file +.sqlc-1.27.0.pkg \ No newline at end of file diff --git a/bin/yq b/bin/yq index 8b69e6dea6..e7f0a63c8e 120000 --- a/bin/yq +++ b/bin/yq @@ -1 +1 @@ -.yq-4.44.2.pkg \ No newline at end of file +.yq-4.44.3.pkg \ No newline at end of file diff --git a/buildengine/build.go b/buildengine/build.go index a2a32a43df..aff120ed65 100644 --- a/buildengine/build.go +++ b/buildengine/build.go @@ -47,6 +47,8 @@ func buildModule(ctx context.Context, projectRootDir string, sch *schema.Schema, switch module.Config.Language { case "go": err = buildGoModule(ctx, projectRootDir, sch, module, filesTransaction) + case "java": + err = buildJavaModule(ctx, module) case "kotlin": err = buildKotlinModule(ctx, sch, module) case "rust": diff --git a/buildengine/build_java.go b/buildengine/build_java.go new file mode 100644 index 0000000000..57b3d79c44 --- /dev/null +++ b/buildengine/build_java.go @@ -0,0 +1,23 @@ +package buildengine + +import ( + "context" + "fmt" + + "github.com/TBD54566975/ftl/internal/exec" + "github.com/TBD54566975/ftl/internal/log" +) + +func buildJavaModule(ctx context.Context, module Module) error { + logger := log.FromContext(ctx) + if err := SetPOMProperties(ctx, module.Config.Dir); err != nil { + return fmt.Errorf("unable to update ftl.version in %s: %w", module.Config.Dir, err) + } + logger.Infof("Using build command '%s'", module.Config.Build) + err := exec.Command(ctx, log.Debug, module.Config.Dir, "bash", "-c", module.Config.Build).RunBuffered(ctx) + if err != nil { + return fmt.Errorf("failed to build module %q: %w", module.Config.Module, err) + } + + return nil +} diff --git a/buildengine/build_kotlin.go b/buildengine/build_kotlin.go index 595976186f..5e81c1664c 100644 --- a/buildengine/build_kotlin.go +++ b/buildengine/build_kotlin.go @@ -43,16 +43,14 @@ func buildKotlinModule(ctx context.Context, sch *schema.Schema, module Module) e if err := SetPOMProperties(ctx, module.Config.Dir); err != nil { return fmt.Errorf("unable to update ftl.version in %s: %w", module.Config.Dir, err) } - if err := generateExternalModules(ctx, module, sch); err != nil { return fmt.Errorf("unable to generate external modules for %s: %w", module.Config.Module, err) } - if err := prepareFTLRoot(module); err != nil { return fmt.Errorf("unable to prepare FTL root for %s: %w", module.Config.Module, err) } - logger.Debugf("Using build command '%s'", module.Config.Build) + logger.Infof("Using build command '%s'", module.Config.Build) err := exec.Command(ctx, log.Debug, module.Config.Dir, "bash", "-c", module.Config.Build).RunBuffered(ctx) if err != nil { return fmt.Errorf("failed to build module %q: %w", module.Config.Module, err) diff --git a/buildengine/build_test.go b/buildengine/build_test.go index e29e244ea2..fba4cf4089 100644 --- a/buildengine/build_test.go +++ b/buildengine/build_test.go @@ -93,7 +93,7 @@ func testBuildClearsBuildDir(t *testing.T, bctx buildContext) { projectRoot := t.TempDir() // generate stubs to create the shared modules directory - err = GenerateStubs(ctx, projectRoot, bctx.sch.Modules, []moduleconfig.ModuleConfig{{Dir: bctx.moduleDir}}) + err = GenerateStubs(ctx, projectRoot, bctx.sch.Modules, []moduleconfig.ModuleConfig{{Dir: bctx.moduleDir, Language: "go"}}) assert.NoError(t, err) // build to generate the build directory diff --git a/buildengine/deps.go b/buildengine/deps.go index c989c45fc7..2f4bf4a5a1 100644 --- a/buildengine/deps.go +++ b/buildengine/deps.go @@ -51,6 +51,9 @@ func extractDependencies(module Module) ([]string, error) { case "kotlin": return extractKotlinFTLImports(module.Config.Module, module.Config.Dir) + case "java": + return extractJavaFTLImports(module.Config.Module, module.Config.Dir) + case "rust": return extractRustFTLImports(module.Config.Module, module.Config.Dir) @@ -140,6 +143,55 @@ func extractKotlinFTLImports(self, dir string) ([]string, error) { return modules, nil } +func extractJavaFTLImports(self, dir string) ([]string, error) { + dependencies := map[string]bool{} + // We also attempt to look at kotlin files + // As the Java module supports both + kotin, kotlinErr := extractKotlinFTLImports(self, dir) + if kotlinErr == nil { + // We don't really care about the error case, its probably a Java project + for _, imp := range kotin { + dependencies[imp] = true + } + } + javaImportRegex := regexp.MustCompile(`^import ftl\.([A-Za-z0-9_.]+)`) + + err := filepath.WalkDir(filepath.Join(dir, "src/main/java"), func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("failed to walk directory: %w", err) + } + if d.IsDir() || !(strings.HasSuffix(path, ".java")) { + return nil + } + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + matches := javaImportRegex.FindStringSubmatch(scanner.Text()) + if len(matches) > 1 { + module := strings.Split(matches[1], ".")[0] + if module == self { + continue + } + dependencies[module] = true + } + } + return scanner.Err() + }) + + // We only error out if they both failed + if err != nil && kotlinErr != nil { + return nil, fmt.Errorf("%s: failed to extract dependencies from Java module: %w", self, err) + } + modules := maps.Keys(dependencies) + sort.Strings(modules) + return modules, nil +} + func extractRustFTLImports(self, dir string) ([]string, error) { fmt.Fprintf(os.Stderr, "RUST TODO extractRustFTLImports\n") diff --git a/buildengine/discover_test.go b/buildengine/discover_test.go index dc7b241daa..26dbb8db02 100644 --- a/buildengine/discover_test.go +++ b/buildengine/discover_test.go @@ -73,7 +73,7 @@ func TestDiscoverModules(t *testing.T) { Language: "kotlin", Realm: "home", Module: "echo", - Build: "mvn -B compile", + Build: "mvn -B package", Deploy: []string{ "main", "classes", @@ -116,7 +116,7 @@ func TestDiscoverModules(t *testing.T) { Language: "kotlin", Realm: "home", Module: "externalkotlin", - Build: "mvn -B compile", + Build: "mvn -B package", Deploy: []string{ "main", "classes", diff --git a/buildengine/stubs.go b/buildengine/stubs.go index 646348b4d4..5992e4ff65 100644 --- a/buildengine/stubs.go +++ b/buildengine/stubs.go @@ -3,6 +3,8 @@ package buildengine import ( "context" "fmt" + "os" + "path/filepath" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/common/moduleconfig" @@ -13,7 +15,11 @@ import ( // // Currently, only Go stubs are supported. Kotlin and other language stubs can be added in the future. func GenerateStubs(ctx context.Context, projectRoot string, modules []*schema.Module, moduleConfigs []moduleconfig.ModuleConfig) error { - return generateGoStubs(ctx, projectRoot, modules, moduleConfigs) + err := generateGoStubs(ctx, projectRoot, modules, moduleConfigs) + if err != nil { + return err + } + return writeGenericSchemaFiles(ctx, projectRoot, modules, moduleConfigs) } // CleanStubs removes all generated stubs. @@ -37,6 +43,39 @@ func generateGoStubs(ctx context.Context, projectRoot string, modules []*schema. return nil } +func writeGenericSchemaFiles(ctx context.Context, projectRoot string, modules []*schema.Module, moduleConfigs []moduleconfig.ModuleConfig) error { + sch := &schema.Schema{Modules: modules} + for _, module := range moduleConfigs { + if module.GeneratedSchemaDir == "" { + continue + } + + modPath := module.Abs().GeneratedSchemaDir + err := os.MkdirAll(modPath, 0750) + if err != nil { + return fmt.Errorf("failed to create directory %s: %w", modPath, err) + } + + for _, mod := range sch.Modules { + if mod.Name == module.Module { + continue + } + data, err := schema.ModuleToBytes(mod) + if err != nil { + return fmt.Errorf("failed to export module schema for module %s %w", mod.Name, err) + } + err = os.WriteFile(filepath.Join(modPath, mod.Name+".pb"), data, 0600) + if err != nil { + return fmt.Errorf("failed to write schema file for module %s %w", mod.Name, err) + } + } + } + err := compile.GenerateStubsForModules(ctx, projectRoot, moduleConfigs, sch) + if err != nil { + return fmt.Errorf("failed to generate go stubs: %w", err) + } + return nil +} func cleanGoStubs(ctx context.Context, projectRoot string) error { err := compile.CleanStubs(ctx, projectRoot) if err != nil { diff --git a/buildengine/stubs_test.go b/buildengine/stubs_test.go index c4c1efe5b5..3f3d32856d 100644 --- a/buildengine/stubs_test.go +++ b/buildengine/stubs_test.go @@ -6,10 +6,11 @@ import ( "path/filepath" "testing" + "github.com/alecthomas/assert/v2" + "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/common/moduleconfig" "github.com/TBD54566975/ftl/internal/log" - "github.com/alecthomas/assert/v2" ) func TestGenerateGoStubs(t *testing.T) { @@ -180,7 +181,7 @@ func init() { ctx := log.ContextWithNewDefaultLogger(context.Background()) projectRoot := t.TempDir() - err := GenerateStubs(ctx, projectRoot, modules, []moduleconfig.ModuleConfig{}) + err := GenerateStubs(ctx, projectRoot, modules, []moduleconfig.ModuleConfig{{Language: "go"}}) assert.NoError(t, err) generatedPath := filepath.Join(projectRoot, ".ftl/go/modules/other/external_module.go") @@ -240,7 +241,7 @@ func Call(context.Context, Req) (Resp, error) { ` ctx := log.ContextWithNewDefaultLogger(context.Background()) projectRoot := t.TempDir() - err := GenerateStubs(ctx, projectRoot, modules, []moduleconfig.ModuleConfig{}) + err := GenerateStubs(ctx, projectRoot, modules, []moduleconfig.ModuleConfig{{Language: "go"}}) assert.NoError(t, err) generatedPath := filepath.Join(projectRoot, ".ftl/go/modules/test/external_module.go") diff --git a/buildengine/testdata/alpha/go.mod b/buildengine/testdata/alpha/go.mod index cf678d0394..d12184a069 100644 --- a/buildengine/testdata/alpha/go.mod +++ b/buildengine/testdata/alpha/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/buildengine/testdata/alpha/go.sum b/buildengine/testdata/alpha/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/buildengine/testdata/alpha/go.sum +++ b/buildengine/testdata/alpha/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/buildengine/testdata/another/go.mod b/buildengine/testdata/another/go.mod index 3733382e1a..4924aab959 100644 --- a/buildengine/testdata/another/go.mod +++ b/buildengine/testdata/another/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/buildengine/testdata/another/go.sum b/buildengine/testdata/another/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/buildengine/testdata/another/go.sum +++ b/buildengine/testdata/another/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/buildengine/testdata/echokotlin/pom.xml b/buildengine/testdata/echokotlin/pom.xml index 5b541258e1..edabd9c0c0 100644 --- a/buildengine/testdata/echokotlin/pom.xml +++ b/buildengine/testdata/echokotlin/pom.xml @@ -9,7 +9,7 @@ 1.0-SNAPSHOT 1.8 - 2.0.0 + 2.0.10 true ${java.version} ${java.version} diff --git a/buildengine/testdata/externalkotlin/pom.xml b/buildengine/testdata/externalkotlin/pom.xml index feedfac278..33f05ecccc 100644 --- a/buildengine/testdata/externalkotlin/pom.xml +++ b/buildengine/testdata/externalkotlin/pom.xml @@ -9,7 +9,7 @@ 1.0-SNAPSHOT 1.8 - 2.0.0 + 2.0.10 true ${java.version} ${java.version} diff --git a/buildengine/testdata/other/go.mod b/buildengine/testdata/other/go.mod index ae7638c902..4502b1ec23 100644 --- a/buildengine/testdata/other/go.mod +++ b/buildengine/testdata/other/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/buildengine/testdata/other/go.sum b/buildengine/testdata/other/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/buildengine/testdata/other/go.sum +++ b/buildengine/testdata/other/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/cmd/ftl-controller/main.go b/cmd/ftl-controller/main.go index 53ca9bc530..42b0a54d01 100644 --- a/cmd/ftl-controller/main.go +++ b/cmd/ftl-controller/main.go @@ -2,15 +2,16 @@ package main import ( "context" + "database/sql" "fmt" "os" "strconv" "time" "github.com/alecthomas/kong" + "github.com/alecthomas/types/optional" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" - "github.com/jackc/pgx/v5/pgxpool" "github.com/TBD54566975/ftl" "github.com/TBD54566975/ftl/backend/controller" @@ -43,17 +44,18 @@ func main() { ) cli.ControllerConfig.SetDefaults() - encryptors, err := cli.ControllerConfig.EncryptionKeys.Encryptors(true) - kctx.FatalIfErrorf(err, "failed to create encryptors") + if cli.ControllerConfig.KMSURI == nil { + kctx.Fatalf("KMSURI is required") + } ctx := log.ContextWithLogger(context.Background(), log.Configure(os.Stderr, cli.LogConfig)) err = observability.Init(ctx, false, "", "ftl-controller", ftl.Version, cli.ObservabilityConfig) kctx.FatalIfErrorf(err, "failed to initialize observability") // The FTL controller currently only supports DB as a configuration provider/resolver. - conn, err := pgxpool.New(ctx, cli.ControllerConfig.DSN) + conn, err := sql.Open("pgx", cli.ControllerConfig.DSN) kctx.FatalIfErrorf(err) - dal, err := dal.New(ctx, conn, encryptors) + dal, err := dal.New(ctx, conn, optional.Some[string](*cli.ControllerConfig.KMSURI)) kctx.FatalIfErrorf(err) configDal, err := cfdal.New(ctx, conn) @@ -74,6 +76,6 @@ func main() { kctx.FatalIfErrorf(err) ctx = cf.ContextWithSecrets(ctx, sm) - err = controller.Start(ctx, cli.ControllerConfig, scaling.NewK8sScaling(), conn, encryptors) + err = controller.Start(ctx, cli.ControllerConfig, scaling.NewK8sScaling(), conn) kctx.FatalIfErrorf(err) } diff --git a/cmd/ftl/cmd_box_run.go b/cmd/ftl/cmd_box_run.go index 884f114e22..971df8a915 100644 --- a/cmd/ftl/cmd_box_run.go +++ b/cmd/ftl/cmd_box_run.go @@ -2,11 +2,12 @@ package main import ( "context" + "database/sql" "fmt" "net/url" "time" - "github.com/jackc/pgx/v5/pgxpool" + _ "github.com/jackc/pgx/v5/stdlib" // pgx driver "github.com/jpillora/backoff" "golang.org/x/sync/errgroup" @@ -57,18 +58,14 @@ func (b *boxRunCmd) Run(ctx context.Context, projConfig projectconfig.Config) er } // Bring up the DB connection and DAL. - pool, err := pgxpool.New(ctx, config.DSN) + conn, err := sql.Open("pgx", config.DSN) if err != nil { return fmt.Errorf("failed to bring up DB connection: %w", err) } - encryptors, err := config.EncryptionKeys.Encryptors(false) - if err != nil { - return fmt.Errorf("failed to create encryptors: %w", err) - } wg := errgroup.Group{} wg.Go(func() error { - return controller.Start(ctx, config, runnerScaling, pool, encryptors) + return controller.Start(ctx, config, runnerScaling, conn) }) // Wait for the controller to come up. diff --git a/cmd/ftl/cmd_new.go b/cmd/ftl/cmd_new.go index 7f5c2fc58c..7a93c15df6 100644 --- a/cmd/ftl/cmd_new.go +++ b/cmd/ftl/cmd_new.go @@ -79,7 +79,7 @@ func (i newGoCmd) Run(ctx context.Context) error { return err } } - if err := maybeGitAdd(ctx, i.Dir, filepath.Join(path, "*")); err != nil { + if err := maybeGitAdd(ctx, i.Dir, filepath.Join(i.Name, "*")); err != nil { return err } } diff --git a/cmd/ftl/cmd_schema_diff.go b/cmd/ftl/cmd_schema_diff.go index 4572103f73..8576eb4356 100644 --- a/cmd/ftl/cmd_schema_diff.go +++ b/cmd/ftl/cmd_schema_diff.go @@ -33,7 +33,9 @@ func (d *schemaDiffCmd) Run(ctx context.Context, currentURL *url.URL, projConfig var other *schema.Schema var err error sameModulesOnly := false - if d.OtherEndpoint.String() == "" { + otherEndpoint := d.OtherEndpoint.String() + if otherEndpoint == "" { + otherEndpoint = "Local Changes" sameModulesOnly = true other, err = localSchema(ctx, projConfig) } else { @@ -61,8 +63,8 @@ func (d *schemaDiffCmd) Run(ctx context.Context, currentURL *url.URL, projConfig } } - edits := myers.ComputeEdits(span.URIFromPath(""), other.String(), current.String()) - diff := fmt.Sprint(gotextdiff.ToUnified(d.OtherEndpoint.String(), currentURL.String(), other.String(), edits)) + edits := myers.ComputeEdits(span.URIFromPath(""), current.String(), other.String()) + diff := fmt.Sprint(gotextdiff.ToUnified(currentURL.String(), otherEndpoint, current.String(), edits)) color := d.Color || isatty.IsTerminal(os.Stdout.Fd()) if color { diff --git a/cmd/ftl/cmd_serve.go b/cmd/ftl/cmd_serve.go index 2a037b3b91..62bfd843c6 100644 --- a/cmd/ftl/cmd_serve.go +++ b/cmd/ftl/cmd_serve.go @@ -2,6 +2,7 @@ package main import ( "context" + "database/sql" "errors" "fmt" "net" @@ -15,7 +16,7 @@ import ( "connectrpc.com/connect" "github.com/alecthomas/types/optional" - "github.com/jackc/pgx/v5/pgxpool" + _ "github.com/jackc/pgx/v5/stdlib" // pgx driver "golang.org/x/sync/errgroup" "github.com/TBD54566975/ftl" @@ -147,17 +148,13 @@ func (s *serveCmd) run(ctx context.Context, projConfig projectconfig.Config, ini controllerCtx = cf.ContextWithSecrets(controllerCtx, sm) // Bring up the DB connection and DAL. - pool, err := pgxpool.New(ctx, config.DSN) + conn, err := sql.Open("pgx", config.DSN) if err != nil { return fmt.Errorf("failed to bring up DB connection: %w", err) } - encryptors, err := config.EncryptionKeys.Encryptors(false) - if err != nil { - return fmt.Errorf("failed to create encryptors: %w", err) - } wg.Go(func() error { - if err := controller.Start(controllerCtx, config, runnerScaling, pool, encryptors); err != nil { + if err := controller.Start(controllerCtx, config, runnerScaling, conn); err != nil { logger.Errorf(err, "controller%d failed: %v", i, err) return fmt.Errorf("controller%d failed: %w", i, err) } diff --git a/cmd/ftl/integration_test.go b/cmd/ftl/integration_test.go index 015fa07e23..75b19e426e 100644 --- a/cmd/ftl/integration_test.go +++ b/cmd/ftl/integration_test.go @@ -24,7 +24,8 @@ func TestBox(t *testing.T) { ctx := log.ContextWithNewDefaultLogger(context.Background()) err := exec.Command(ctx, log.Debug, "../..", "docker", "build", "-t", "ftl0/ftl-box:latest", "--progress=plain", "--platform=linux/amd64", "-f", "Dockerfile.box", ".").Run() assert.NoError(t, err) - RunWithoutController(t, "", + Run(t, + WithoutController(), CopyModule("time"), CopyModule("echo"), Exec("ftl", "box", "echo", "--compose=echo-compose.yml"), @@ -35,17 +36,17 @@ func TestBox(t *testing.T) { } func TestConfigsWithController(t *testing.T) { - Run(t, "", configActions(t)...) + Run(t, configActions(t)...) } func TestConfigsWithoutController(t *testing.T) { - RunWithoutController(t, "", configActions(t)...) + Run(t, configActions(t, WithoutController())...) } -func configActions(t *testing.T) []Action { +func configActions(t *testing.T, prepend ...ActionOrOption) []ActionOrOption { t.Helper() - return []Action{ + return append(prepend, // test setting value without --json flag Exec("ftl", "config", "set", "test.one", "hello world", "--inline"), ExecWithExpectedOutput("\"hello world\"\n", "ftl", "config", "get", "test.one"), @@ -58,18 +59,18 @@ func configActions(t *testing.T) []Action { ExecWithOutput("ftl", []string{"config", "get", "test.one"}, func(output string) {}), "failed to get from config manager: not found", ), - } + ) } func TestSecretsWithController(t *testing.T) { - Run(t, "", secretActions(t)...) + Run(t, secretActions(t)...) } func TestSecretsWithoutController(t *testing.T) { - RunWithoutController(t, "", secretActions(t)...) + Run(t, secretActions(t, WithoutController())...) } -func secretActions(t *testing.T) []Action { +func secretActions(t *testing.T, prepend ...ActionOrOption) []ActionOrOption { t.Helper() // can not easily use Exec() to enter secure text, using secret import instead @@ -78,7 +79,7 @@ func secretActions(t *testing.T) []Action { secretsPath2, err := filepath.Abs("testdata/secrets2.json") assert.NoError(t, err) - return []Action{ + return append(prepend, // test setting secret without --json flag Exec("ftl", "secret", "import", "--inline", secretsPath1), ExecWithExpectedOutput("\"hello world\"\n", "ftl", "secret", "get", "test.one"), @@ -91,7 +92,7 @@ func secretActions(t *testing.T) []Action { ExecWithOutput("ftl", []string{"secret", "get", "test.one"}, func(output string) {}), "failed to get from secret manager: not found", ), - } + ) } func TestSecretImportExport(t *testing.T) { @@ -116,7 +117,8 @@ func testImportExport(t *testing.T, object string) { blank := "" exported := &blank - RunWithoutController(t, "", + Run(t, + WithoutController(), // duplicate project file in the temp directory Exec("cp", firstProjFile, secondProjFile), // import into first project file @@ -152,7 +154,7 @@ func NewFunction(ctx context.Context, req TimeRequest) (TimeResponse, error) { return TimeResponse{Time: time.Now()}, nil } ` - Run(t, "", + Run(t, CopyModule("time"), Deploy("time"), ExecWithOutput("ftl", []string{"schema", "diff"}, func(output string) { @@ -199,7 +201,7 @@ func TestResetSubscription(t *testing.T) { `, module, subscription), cursor) } - Run(t, "", + Run(t, CopyModule("time"), CopyModule("echo"), Deploy("time"), diff --git a/cmd/ftl/testdata/go/echo/go.mod b/cmd/ftl/testdata/go/echo/go.mod index 69df181706..5db8a0dc9b 100644 --- a/cmd/ftl/testdata/go/echo/go.mod +++ b/cmd/ftl/testdata/go/echo/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/cmd/ftl/testdata/go/echo/go.sum b/cmd/ftl/testdata/go/echo/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/cmd/ftl/testdata/go/echo/go.sum +++ b/cmd/ftl/testdata/go/echo/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/cmd/ftl/testdata/go/time/go.mod b/cmd/ftl/testdata/go/time/go.mod index ce2a3974c8..45efe0f1a2 100644 --- a/cmd/ftl/testdata/go/time/go.mod +++ b/cmd/ftl/testdata/go/time/go.mod @@ -3,3 +3,45 @@ module ftl/time go 1.22.2 replace github.com/TBD54566975/ftl => ../../../../.. + +require github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000 + +require ( + connectrpc.com/connect v1.16.2 // indirect + connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.1 // indirect + github.com/alecthomas/atomic v0.1.0-alpha2 // indirect + github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/alessio/shellescape v1.4.2 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + github.com/zalando/go-keyring v0.2.5 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/cmd/ftl/testdata/go/time/go.sum b/cmd/ftl/testdata/go/time/go.sum index e69de29bb2..9fbb9ebc36 100644 --- a/cmd/ftl/testdata/go/time/go.sum +++ b/cmd/ftl/testdata/go/time/go.sum @@ -0,0 +1,148 @@ +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= +github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= +github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= +github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= +github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/cmd/lint-commit-or-rollback/go.mod b/cmd/lint-commit-or-rollback/go.mod new file mode 100644 index 0000000000..351e6edef6 --- /dev/null +++ b/cmd/lint-commit-or-rollback/go.mod @@ -0,0 +1,9 @@ +module github.com/tbdeng/pfi/cmd/lint-commit-or-rollback + +go 1.22.0 + +require golang.org/x/tools v0.24.0 + +require golang.org/x/sync v0.8.0 // indirect + +require golang.org/x/mod v0.20.0 // indirect diff --git a/cmd/lint-commit-or-rollback/go.sum b/cmd/lint-commit-or-rollback/go.sum new file mode 100644 index 0000000000..5b7f1f0a60 --- /dev/null +++ b/cmd/lint-commit-or-rollback/go.sum @@ -0,0 +1,8 @@ +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/cmd/lint-commit-or-rollback/main.go b/cmd/lint-commit-or-rollback/main.go new file mode 100644 index 0000000000..ab7dab3b11 --- /dev/null +++ b/cmd/lint-commit-or-rollback/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/singlechecker" +) + +var Analyzer = &analysis.Analyzer{ + Name: "commitorrollback", + Doc: "Detects misues of dal.TX.CommitOrRollback", + Run: run, +} + +// Detect that any use of dal.TX.CommitOrRollback is in a defer statement and +// takes a reference to a named error return parameter. +// +// ie. Must be in the following form +// +// func myFunc() (err error) { +// // ... +// defer tx.CommitOrRollback(&err) +// } +func run(pass *analysis.Pass) (interface{}, error) { + var inspect func(n ast.Node) bool + funcStack := []*ast.FuncType{} + inspect = func(n ast.Node) bool { + switch n := n.(type) { + case nil: + return false + + case *ast.FuncLit: + funcStack = append(funcStack, n.Type) + ast.Inspect(n.Body, inspect) + funcStack = funcStack[:len(funcStack)-1] + return false + + case *ast.FuncDecl: + funcStack = append(funcStack, n.Type) + ast.Inspect(n.Body, inspect) + funcStack = funcStack[:len(funcStack)-1] + return false + + case *ast.CallExpr: + sel, ok := n.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + x, ok := sel.X.(*ast.Ident) + if !ok || x.Name != "tx" || sel.Sel.Name != "CommitOrRollback" || len(n.Args) != 2 { + return true + } + arg0, ok := n.Args[1].(*ast.UnaryExpr) + if !ok || arg0.Op != token.AND { + return true + } + arg0Ident, ok := arg0.X.(*ast.Ident) + if !ok { + return true + } + funcDecl := funcStack[len(funcStack)-1] + funcPos := pass.Fset.Position(funcDecl.Func) + if funcDecl.Results == nil { + pass.Reportf(arg0.Pos(), "defer tx.CommitOrRollback(ctx, &err) should be deferred with a named error return parameter but the function at %s has no named return parameters", funcPos) + return true + } + for _, field := range funcDecl.Results.List { + if result, ok := field.Type.(*ast.Ident); ok && result.Name == "error" { + if len(field.Names) == 0 { + pass.Reportf(arg0.Pos(), "defer tx.CommitOrRollback(ctx, &err) should be deferred with a reference to a named error return parameter, but the function at %s has no named return parameters", funcPos) + } + for _, name := range field.Names { + if name.Name != arg0Ident.Name { + namePos := pass.Fset.Position(name.NamePos) + pass.Reportf(arg0.Pos(), "defer tx.CommitOrRollback(&ctx, %s) should be deferred with the named error return parameter here %s", arg0Ident.Name, namePos) + } + } + } + } + } + return true + } + for _, file := range pass.Files { + funcStack = []*ast.FuncType{} + ast.Inspect(file, inspect) + } + return nil, nil +} + +func main() { + singlechecker.Main(Analyzer) +} diff --git a/common/configuration/asm.go b/common/configuration/asm.go index 4136409a2a..bcab586a00 100644 --- a/common/configuration/asm.go +++ b/common/configuration/asm.go @@ -52,18 +52,19 @@ func newASMForTesting(ctx context.Context, secretsClient *secretsmanager.Client, return override, nil } rpcClient := rpc.Dial(ftlv1connect.NewAdminServiceClient, url.String(), log.Error) - return newASMFollower(rpcClient, url.String()), nil + return newASMFollower(rpcClient, url.String(), time.Second*10), nil } + coordinator := leader.NewCoordinator[asmClient]( + ctx, + advertise, + leases.SystemKey("asm"), + leaser, + time.Second*10, + leaderFactory, + followerFactory, + ) return &ASM{ - coordinator: leader.NewCoordinator[asmClient]( - ctx, - advertise, - leases.SystemKey("asm"), - leaser, - time.Second*10, - leaderFactory, - followerFactory, - ), + coordinator: coordinator, } } diff --git a/common/configuration/asm_follower.go b/common/configuration/asm_follower.go index 60346483b7..de4710624a 100644 --- a/common/configuration/asm_follower.go +++ b/common/configuration/asm_follower.go @@ -2,6 +2,7 @@ package configuration import ( "context" + "errors" "fmt" "net/url" "time" @@ -9,26 +10,27 @@ import ( "connectrpc.com/connect" "github.com/puzpuzpuz/xsync/v3" + "github.com/TBD54566975/ftl/backend/controller/leader" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect" + "github.com/TBD54566975/ftl/internal/log" ) const asmFollowerSyncInterval = time.Second * 10 // asmFollower uses AdminService to get/set secrets from the leader type asmFollower struct { - leaderName string - + errorFilter *leader.ErrorFilter + leaderName string // client requests/responses use unobfuscated values client ftlv1connect.AdminServiceClient } -var _ asmClient = &asmFollower{} - -func newASMFollower(rpcClient ftlv1connect.AdminServiceClient, leaderName string) *asmFollower { +func newASMFollower(rpcClient ftlv1connect.AdminServiceClient, leaderName string, leaseTTL time.Duration) *asmFollower { f := &asmFollower{ - leaderName: leaderName, - client: rpcClient, + errorFilter: leader.NewErrorFilter(leaseTTL), + leaderName: leaderName, + client: rpcClient, } return f } @@ -43,6 +45,7 @@ func (f *asmFollower) syncInterval() time.Duration { func (f *asmFollower) sync(ctx context.Context, values *xsync.MapOf[Ref, SyncedValue]) error { // values must store obfuscated values, but f.client gives unobfuscated values + logger := log.FromContext(ctx) obfuscator := Secrets{}.obfuscator() module := "" includeValues := true @@ -51,8 +54,17 @@ func (f *asmFollower) sync(ctx context.Context, values *xsync.MapOf[Ref, SyncedV IncludeValues: &includeValues, })) if err != nil { + if connectErr := new(connect.Error); errors.As(err, &connectErr) { + if connectErr.Code() == connect.CodeInternal || connectErr.Code() == connect.CodeUnavailable { + if !f.errorFilter.ReportLeaseError() { + logger.Warnf("error getting secrets list from leader, possible leader failover %s", err.Error()) + return nil + } + } + } return fmt.Errorf("error getting secrets list from leader: %w", err) } + f.errorFilter.ReportOperationSuccess() visited := map[Ref]bool{} for _, s := range resp.Msg.Secrets { ref, err := ParseRef(s.RefPath) diff --git a/common/configuration/asm_test.go b/common/configuration/asm_test.go index 489b9b0f01..78086d9c3c 100644 --- a/common/configuration/asm_test.go +++ b/common/configuration/asm_test.go @@ -11,19 +11,19 @@ import ( "path" "sort" "testing" + "time" - "connectrpc.com/connect" "github.com/TBD54566975/ftl/backend/controller/leases" ftlv1 "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/slices" + "github.com/TBD54566975/ftl/testutils" + "connectrpc.com/connect" "github.com/alecthomas/assert/v2" "github.com/alecthomas/types/optional" . "github.com/alecthomas/types/optional" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" ) @@ -38,10 +38,7 @@ func setUp(ctx context.Context, t *testing.T, router optional.Option[Router[Secr router = optional.Some[Router[Secrets]](ProjectConfigResolver[Secrets]{Config: projectPath}) } - cc := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider("test", "test", "")) - cfg, err := config.LoadDefaultConfig(ctx, config.WithCredentialsProvider(cc), config.WithRegion("us-west-2")) - assert.NoError(t, err) - + cfg := testutils.NewLocalstackConfig(t, ctx) externalClient := secretsmanager.NewFromConfig(cfg, func(o *secretsmanager.Options) { o.BaseEndpoint = aws.String("http://localhost:4566") }) @@ -181,7 +178,7 @@ func TestFollowerSync(t *testing.T) { // fakeRPCClient connects the follower to the leader fakeRPCClient := &fakeAdminClient{sm: leaderManager} - follower := newASMFollower(fakeRPCClient, "fake") + follower := newASMFollower(fakeRPCClient, "fake", time.Second) followerASM := newASMForTesting(ctx, externalClient, URL("http://localhost:1235"), leaser, optional.Some[asmClient](follower)) asmClient, err := followerASM.coordinator.Get() diff --git a/common/configuration/dal/dal.go b/common/configuration/dal/dal.go index b8e106e6ba..54a2db3083 100644 --- a/common/configuration/dal/dal.go +++ b/common/configuration/dal/dal.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/alecthomas/types/optional" - "github.com/jackc/pgx/v5/pgxpool" dalerrs "github.com/TBD54566975/ftl/backend/dal" "github.com/TBD54566975/ftl/common/configuration/sql" @@ -16,8 +15,8 @@ type DAL struct { db sql.DBI } -func New(ctx context.Context, pool *pgxpool.Pool) (*DAL, error) { - dal := &DAL{db: sql.NewDB(pool)} +func New(ctx context.Context, conn sql.ConnI) (*DAL, error) { + dal := &DAL{db: sql.NewDB(conn)} return dal, nil } diff --git a/common/configuration/sql/db.go b/common/configuration/sql/db.go index c4b45fb311..0e0973111c 100644 --- a/common/configuration/sql/db.go +++ b/common/configuration/sql/db.go @@ -1,20 +1,19 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql import ( "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" + "database/sql" ) type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { @@ -25,7 +24,7 @@ type Queries struct { db DBTX } -func (q *Queries) WithTx(tx pgx.Tx) *Queries { +func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ db: tx, } diff --git a/common/configuration/sql/models.go b/common/configuration/sql/models.go index e67f71b017..5c2780936a 100644 --- a/common/configuration/sql/models.go +++ b/common/configuration/sql/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql @@ -11,10 +11,12 @@ import ( "time" "github.com/TBD54566975/ftl/backend/controller/leases" + "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" "github.com/google/uuid" + "github.com/sqlc-dev/pqtype" ) type AsyncCallState string @@ -380,12 +382,12 @@ type AsyncCall struct { Response []byte Error optional.Option[string] RemainingAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] Catching bool ParentRequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage } type Controller struct { @@ -415,7 +417,7 @@ type Deployment struct { ModuleID int64 Key model.DeploymentKey Schema *schema.Module - Labels []byte + Labels json.RawMessage MinReplicas int32 } @@ -427,18 +429,10 @@ type DeploymentArtefact struct { Path string } -type Event struct { - ID int64 - TimeStamp time.Time - DeploymentID int64 - RequestID optional.Option[int64] - Type EventType - CustomKey1 optional.Option[string] - CustomKey2 optional.Option[string] - CustomKey3 optional.Option[string] - CustomKey4 optional.Option[string] - Payload json.RawMessage - ParentRequestID optional.Option[string] +type EncryptionKey struct { + ID int64 + Key []byte + CreatedAt time.Time } type FsmInstance struct { @@ -467,7 +461,7 @@ type Lease struct { Key leases.Key CreatedAt time.Time ExpiresAt time.Time - Metadata []byte + Metadata pqtype.NullRawMessage } type Module struct { @@ -481,7 +475,7 @@ type ModuleConfiguration struct { CreatedAt time.Time Module optional.Option[string] Name string - Value []byte + Value json.RawMessage } type ModuleSecret struct { @@ -509,7 +503,21 @@ type Runner struct { Endpoint string ModuleName optional.Option[string] DeploymentID optional.Option[int64] - Labels []byte + Labels json.RawMessage +} + +type Timeline struct { + ID int64 + TimeStamp time.Time + DeploymentID int64 + RequestID optional.Option[int64] + Type EventType + CustomKey1 optional.Option[string] + CustomKey2 optional.Option[string] + CustomKey3 optional.Option[string] + CustomKey4 optional.Option[string] + Payload []byte + ParentRequestID optional.Option[string] } type Topic struct { @@ -530,7 +538,7 @@ type TopicEvent struct { Payload []byte Caller optional.Option[string] RequestKey optional.Option[string] - TraceContext []byte + TraceContext pqtype.NullRawMessage } type TopicSubscriber struct { @@ -541,8 +549,8 @@ type TopicSubscriber struct { DeploymentID int64 Sink schema.RefKey RetryAttempts int32 - Backoff time.Duration - MaxBackoff time.Duration + Backoff sqltypes.Duration + MaxBackoff sqltypes.Duration CatchVerb optional.Option[schema.RefKey] } diff --git a/common/configuration/sql/querier.go b/common/configuration/sql/querier.go index adfbe2f0b1..ac45674c28 100644 --- a/common/configuration/sql/querier.go +++ b/common/configuration/sql/querier.go @@ -1,21 +1,22 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package sql import ( "context" + "encoding/json" "github.com/alecthomas/types/optional" ) type Querier interface { - GetModuleConfiguration(ctx context.Context, module optional.Option[string], name string) ([]byte, error) + GetModuleConfiguration(ctx context.Context, module optional.Option[string], name string) (json.RawMessage, error) GetModuleSecretURL(ctx context.Context, module optional.Option[string], name string) (string, error) ListModuleConfiguration(ctx context.Context) ([]ModuleConfiguration, error) ListModuleSecrets(ctx context.Context) ([]ModuleSecret, error) - SetModuleConfiguration(ctx context.Context, module optional.Option[string], name string, value []byte) error + SetModuleConfiguration(ctx context.Context, module optional.Option[string], name string, value json.RawMessage) error SetModuleSecretURL(ctx context.Context, module optional.Option[string], name string, url string) error UnsetModuleConfiguration(ctx context.Context, module optional.Option[string], name string) error UnsetModuleSecret(ctx context.Context, module optional.Option[string], name string) error diff --git a/common/configuration/sql/queries.sql.go b/common/configuration/sql/queries.sql.go index f611ce598c..51c86f6116 100644 --- a/common/configuration/sql/queries.sql.go +++ b/common/configuration/sql/queries.sql.go @@ -1,12 +1,13 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: queries.sql package sql import ( "context" + "encoding/json" "github.com/alecthomas/types/optional" ) @@ -21,9 +22,9 @@ ORDER BY module NULLS LAST LIMIT 1 ` -func (q *Queries) GetModuleConfiguration(ctx context.Context, module optional.Option[string], name string) ([]byte, error) { - row := q.db.QueryRow(ctx, getModuleConfiguration, module, name) - var value []byte +func (q *Queries) GetModuleConfiguration(ctx context.Context, module optional.Option[string], name string) (json.RawMessage, error) { + row := q.db.QueryRowContext(ctx, getModuleConfiguration, module, name) + var value json.RawMessage err := row.Scan(&value) return value, err } @@ -39,7 +40,7 @@ LIMIT 1 ` func (q *Queries) GetModuleSecretURL(ctx context.Context, module optional.Option[string], name string) (string, error) { - row := q.db.QueryRow(ctx, getModuleSecretURL, module, name) + row := q.db.QueryRowContext(ctx, getModuleSecretURL, module, name) var url string err := row.Scan(&url) return url, err @@ -52,7 +53,7 @@ ORDER BY module, name ` func (q *Queries) ListModuleConfiguration(ctx context.Context) ([]ModuleConfiguration, error) { - rows, err := q.db.Query(ctx, listModuleConfiguration) + rows, err := q.db.QueryContext(ctx, listModuleConfiguration) if err != nil { return nil, err } @@ -71,6 +72,9 @@ func (q *Queries) ListModuleConfiguration(ctx context.Context) ([]ModuleConfigur } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -84,7 +88,7 @@ ORDER BY module, name ` func (q *Queries) ListModuleSecrets(ctx context.Context) ([]ModuleSecret, error) { - rows, err := q.db.Query(ctx, listModuleSecrets) + rows, err := q.db.QueryContext(ctx, listModuleSecrets) if err != nil { return nil, err } @@ -103,6 +107,9 @@ func (q *Queries) ListModuleSecrets(ctx context.Context) ([]ModuleSecret, error) } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -115,8 +122,8 @@ VALUES ($1, $2, $3) ON CONFLICT ((COALESCE(module, '')), name) DO UPDATE SET value = $3 ` -func (q *Queries) SetModuleConfiguration(ctx context.Context, module optional.Option[string], name string, value []byte) error { - _, err := q.db.Exec(ctx, setModuleConfiguration, module, name, value) +func (q *Queries) SetModuleConfiguration(ctx context.Context, module optional.Option[string], name string, value json.RawMessage) error { + _, err := q.db.ExecContext(ctx, setModuleConfiguration, module, name, value) return err } @@ -127,7 +134,7 @@ ON CONFLICT ((COALESCE(module, '')), name) DO UPDATE SET url = $3 ` func (q *Queries) SetModuleSecretURL(ctx context.Context, module optional.Option[string], name string, url string) error { - _, err := q.db.Exec(ctx, setModuleSecretURL, module, name, url) + _, err := q.db.ExecContext(ctx, setModuleSecretURL, module, name, url) return err } @@ -137,7 +144,7 @@ WHERE COALESCE(module, '') = COALESCE($1, '') AND name = $2 ` func (q *Queries) UnsetModuleConfiguration(ctx context.Context, module optional.Option[string], name string) error { - _, err := q.db.Exec(ctx, unsetModuleConfiguration, module, name) + _, err := q.db.ExecContext(ctx, unsetModuleConfiguration, module, name) return err } @@ -147,6 +154,6 @@ WHERE COALESCE(module, '') = COALESCE($1, '') AND name = $2 ` func (q *Queries) UnsetModuleSecret(ctx context.Context, module optional.Option[string], name string) error { - _, err := q.db.Exec(ctx, unsetModuleSecret, module, name) + _, err := q.db.ExecContext(ctx, unsetModuleSecret, module, name) return err } diff --git a/common/moduleconfig/moduleconfig.go b/common/moduleconfig/moduleconfig.go index 990e96b732..53ff6b2246 100644 --- a/common/moduleconfig/moduleconfig.go +++ b/common/moduleconfig/moduleconfig.go @@ -21,6 +21,9 @@ type ModuleGoConfig struct{} // ModuleKotlinConfig is language-specific configuration for Kotlin modules. type ModuleKotlinConfig struct{} +// ModuleJavaConfig is language-specific configuration for Java modules. +type ModuleJavaConfig struct{} + // ModuleConfig is the configuration for an FTL module. // // Module config files are currently TOML. @@ -37,6 +40,8 @@ type ModuleConfig struct { Deploy []string `toml:"deploy"` // DeployDir is the directory to deploy from, relative to the module directory. DeployDir string `toml:"deploy-dir"` + // GeneratedSchemaDir is the directory to generate protobuf schema files into. These can be picked up by language specific build tools + GeneratedSchemaDir string `toml:"generated-schema-dir"` // Schema is the name of the schema file relative to the DeployDir. Schema string `toml:"schema"` // Errors is the name of the error file relative to the DeployDir. @@ -46,6 +51,7 @@ type ModuleConfig struct { Go ModuleGoConfig `toml:"go,optional"` Kotlin ModuleKotlinConfig `toml:"kotlin,optional"` + Java ModuleJavaConfig `toml:"java,optional"` } // AbsModuleConfig is a ModuleConfig with all paths made absolute. @@ -84,6 +90,12 @@ func (c ModuleConfig) Abs() AbsModuleConfig { if !strings.HasPrefix(clone.DeployDir, clone.Dir) { panic(fmt.Sprintf("deploy-dir %q is not beneath module directory %q", clone.DeployDir, clone.Dir)) } + if clone.GeneratedSchemaDir != "" { + clone.GeneratedSchemaDir = filepath.Clean(filepath.Join(clone.Dir, clone.GeneratedSchemaDir)) + if !strings.HasPrefix(clone.GeneratedSchemaDir, clone.Dir) { + panic(fmt.Sprintf("generated-schema-dir %q is not beneath module directory %q", clone.GeneratedSchemaDir, clone.Dir)) + } + } clone.Schema = filepath.Clean(filepath.Join(clone.DeployDir, clone.Schema)) if !strings.HasPrefix(clone.Schema, clone.DeployDir) { panic(fmt.Sprintf("schema %q is not beneath deploy directory %q", clone.Schema, clone.DeployDir)) @@ -119,7 +131,7 @@ func setConfigDefaults(moduleDir string, config *ModuleConfig) error { switch config.Language { case "kotlin": if config.Build == "" { - config.Build = "mvn -B compile" + config.Build = "mvn -B package" } if config.DeployDir == "" { config.DeployDir = "target" @@ -130,7 +142,22 @@ func setConfigDefaults(moduleDir string, config *ModuleConfig) error { if len(config.Watch) == 0 { config.Watch = []string{"pom.xml", "src/**", "target/generated-sources"} } - + case "java": + if config.Build == "" { + config.Build = "mvn -B package" + } + if config.DeployDir == "" { + config.DeployDir = "target" + } + if config.GeneratedSchemaDir == "" { + config.GeneratedSchemaDir = "src/main/ftl-module-schema" + } + if len(config.Deploy) == 0 { + config.Deploy = []string{"main", "quarkus-app"} + } + if len(config.Watch) == 0 { + config.Watch = []string{"pom.xml", "src/**", "target/generated-sources"} + } case "go": if config.DeployDir == "" { config.DeployDir = ".ftl" diff --git a/common/projectconfig/integration_test.go b/common/projectconfig/integration_test.go index 2e79d26e31..f7b46df8dc 100644 --- a/common/projectconfig/integration_test.go +++ b/common/projectconfig/integration_test.go @@ -14,7 +14,8 @@ import ( ) func TestDefaultToRootWhenModuleDirsMissing(t *testing.T) { - in.Run(t, "no-module-dirs-ftl-project.toml", + in.Run(t, + in.WithFTLConfig("no-module-dirs-ftl-project.toml"), in.CopyModule("echo"), in.Exec("ftl", "build"), // Needs to be `ftl build`, not `ftl build echo` in.Deploy("echo"), @@ -25,7 +26,9 @@ func TestDefaultToRootWhenModuleDirsMissing(t *testing.T) { } func TestConfigCmdWithoutController(t *testing.T) { - in.RunWithoutController(t, "configs-ftl-project.toml", + in.Run(t, + in.WithFTLConfig("configs-ftl-project.toml"), + in.WithoutController(), in.ExecWithExpectedOutput("\"value\"\n", "ftl", "config", "get", "key"), ) } @@ -49,7 +52,8 @@ func TestFindConfig(t *testing.T) { assert.Equal(t, "test = \"test\"\n", string(output)) } } - in.RunWithoutController(t, "", + in.Run(t, + in.WithoutController(), in.CopyModule("findconfig"), checkConfig("findconfig"), checkConfig("findconfig/subdir"), @@ -61,7 +65,8 @@ func TestFindConfig(t *testing.T) { } func TestConfigValidation(t *testing.T) { - in.Run(t, "./validateconfig/ftl-project.toml", + in.Run(t, + in.WithFTLConfig("./validateconfig/ftl-project.toml"), in.CopyModule("validateconfig"), // Global sets never error. diff --git a/common/projectconfig/testdata/go/echo/go.mod b/common/projectconfig/testdata/go/echo/go.mod index c9547804c5..0be7eaa4c0 100644 --- a/common/projectconfig/testdata/go/echo/go.mod +++ b/common/projectconfig/testdata/go/echo/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/common/projectconfig/testdata/go/echo/go.sum b/common/projectconfig/testdata/go/echo/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/common/projectconfig/testdata/go/echo/go.sum +++ b/common/projectconfig/testdata/go/echo/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/common/projectconfig/testdata/go/findconfig/go.mod b/common/projectconfig/testdata/go/findconfig/go.mod index d01345f674..104c283923 100644 --- a/common/projectconfig/testdata/go/findconfig/go.mod +++ b/common/projectconfig/testdata/go/findconfig/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/common/projectconfig/testdata/go/findconfig/go.sum b/common/projectconfig/testdata/go/findconfig/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/common/projectconfig/testdata/go/findconfig/go.sum +++ b/common/projectconfig/testdata/go/findconfig/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/common/projectconfig/testdata/go/validateconfig/go.mod b/common/projectconfig/testdata/go/validateconfig/go.mod index de1641577d..4bca59c331 100644 --- a/common/projectconfig/testdata/go/validateconfig/go.mod +++ b/common/projectconfig/testdata/go/validateconfig/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/common/projectconfig/testdata/go/validateconfig/go.sum b/common/projectconfig/testdata/go/validateconfig/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/common/projectconfig/testdata/go/validateconfig/go.sum +++ b/common/projectconfig/testdata/go/validateconfig/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/deployment/base/ftl-runner/ftl-runner.yml b/deployment/base/ftl-runner/ftl-runner.yml index 365f806fef..71063b0b22 100644 --- a/deployment/base/ftl-runner/ftl-runner.yml +++ b/deployment/base/ftl-runner/ftl-runner.yml @@ -31,7 +31,7 @@ spec: - name: FTL_RUNNER_ADVERTISE value: "http://$(MY_POD_IP):8893" - name: FTL_LANGUAGE - value: "go,kotlin" + value: "go,kotlin,java" ports: - containerPort: 8893 readinessProbe: diff --git a/docker-compose.yml b/docker-compose.yml index fe58670b48..72389f101b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: db: - image: postgres:15.7 + image: postgres:15.8 command: postgres user: postgres # For local debugging diff --git a/docs/content/docs/reference/matrix.md b/docs/content/docs/reference/matrix.md new file mode 100644 index 0000000000..12172bd4de --- /dev/null +++ b/docs/content/docs/reference/matrix.md @@ -0,0 +1,43 @@ ++++ +title = "Feature Matrix" +description = "Matrix showing which FTL features are supported by each language" +date = 2021-05-01T08:20:00+00:00 +updated = 2021-05-01T08:20:00+00:00 +draft = false +weight = 120 +sort_by = "weight" +template = "docs/page.html" + +[extra] +toc = true +top = false ++++ + +| System | Feature | Go | JVM | Rust | +| :------------ | :-------------- | :-- | :-- | :--- | +| **Types** | Basic Types | ✔️ | ✔️ | ️ ✔️ | +| | Optional Type | ✔️ | ✔️ | | +| | Unit Type | ✔️ | ✔️ | | +| | Empty Type | ✔️ | ✔️ | | +| | Generic Types | ✔️ | ✔️ | | +| | Type Aliases | ✔️ | ️ | | +| | Value Enums | ✔️ | ️ | | +| | Type Enums | ✔️ | ️ | | +| | Visibility | ✔️ | ✔️ | | +| **Verbs** | Verb | ✔️ | ✔️ | ️✔️ | +| | Sink | ✔️ | ✔️ | | +| | Source | ✔️ | ✔️ | | +| | Empty | ✔️ | ✔️ | | +| | Visibility | ✔️ | ✔️ | | +| **Core** | FSM | ✔️ | ️ | | +| | Leases | ✔️ | ✔️ | | +| | Cron | ✔️ | ✔️ | | +| | Config | ✔️ | ✔️ | | +| | Secrets | ✔️ | ✔️ | | +| | HTTP Ingress | ✔️ | ✔️ | | +| **Resources** | PostgreSQL | ✔️ | ️ | | +| | MySQL | | | | +| | Kafka | | | | +| **PubSub** | Declaring Topic | ✔️ | ✔️ | | +| | Subscribing | ✔️ | ✔️ | | +| | Publishing | ✔️ | ✔️ | | diff --git a/docs/content/docs/reference/types.md b/docs/content/docs/reference/types.md index af7eb8350a..1971df7585 100644 --- a/docs/content/docs/reference/types.md +++ b/docs/content/docs/reference/types.md @@ -85,3 +85,72 @@ type UserToken = string --- +## Optional types + +FTL supports optional types, which are types that can be `None` or `Some` and can be declared via `ftl.Option[T]`. These types are provided by the `ftl` runtimes. For example, the following FTL type declaration in go, will provide an optional string type "Name": + +```go +type EchoResponse struct { + Name ftl.Option[string] `json:"name"` +} +``` + +The value of this type can be set to `Some` or `None`: + +```go +resp := EchoResponse{ + Name: ftl.Some("John"), +} + +resp := EchoResponse{ + Name: ftl.None(), +} +``` + +The value of the optional type can be accessed using `Get`, `MustGet`, or `Default` methods: + +```go +// Get returns the value and a boolean indicating if the Option contains a value. +if value, ok := resp.Name.Get(); ok { + resp.Name = ftl.Some(value) +} + +// MustGet returns the value or panics if the Option is None. +value := resp.Name.MustGet() + +// Default returns the value or a default value if the Option is None. +value := resp.Name.Default("default") +``` + +## Unit "void" type + +The `Unit` type is similar to the `void` type in other languages. It is used to indicate that a function does not return a value. For example: + +```go +//ftl:ingress GET /unit +func Unit(ctx context.Context, req builtin.HttpRequest[TimeRequest]) (builtin.HttpResponse[ftl.Unit, string], error) { + return builtin.HttpResponse[ftl.Unit, string]{Body: ftl.Some(ftl.Unit{})}, nil +} +``` + +This request will return an empty body with a status code of 200: + +```sh +curl http://localhost:8891/unit -i +``` + +```http +HTTP/1.1 200 OK +Date: Mon, 12 Aug 2024 17:58:22 GMT +Content-Length: 0 +``` + +## Builtin types + +FTL provides a set of builtin types that are automatically available in all FTL runtimes. These types are: + +- `builtin.HttpRequest[Body]` - Represents an HTTP request with a body of type `Body`. +- `builtin.HttpResponse[Body, Error]` - Represents an HTTP response with a body of type `Body` and an error of type `Error`. +- `builtin.Empty` - Represents an empty type. This equates to an empty structure `{}`. +- `builtin.CatchRequest` - Represents a request structure for catch verbs. + diff --git a/examples/go/echo/echo.go b/examples/go/echo/echo.go index 233b4850cb..8f4776749f 100644 --- a/examples/go/echo/echo.go +++ b/examples/go/echo/echo.go @@ -23,7 +23,7 @@ type EchoResponse struct { // Echo returns a greeting with the current time. // -//ftl:verb +//ftl:verb export func Echo(ctx context.Context, req EchoRequest) (EchoResponse, error) { tresp, err := ftl.Call(ctx, time.Time, time.TimeRequest{}) if err != nil { diff --git a/examples/go/echo/go.mod b/examples/go/echo/go.mod index 00c5d8e4db..272b973492 100644 --- a/examples/go/echo/go.mod +++ b/examples/go/echo/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/examples/go/echo/go.sum b/examples/go/echo/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/examples/go/echo/go.sum +++ b/examples/go/echo/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/examples/kotlin/echo/ftl.toml b/examples/kotlin/echo/ftl.toml index 700b9d8833..de92e831e1 100644 --- a/examples/kotlin/echo/ftl.toml +++ b/examples/kotlin/echo/ftl.toml @@ -1,2 +1,2 @@ module = "echo" -language = "kotlin" +language = "java" diff --git a/examples/kotlin/echo/pom.xml b/examples/kotlin/echo/pom.xml index 3945bbad4d..f4a3a46581 100644 --- a/examples/kotlin/echo/pom.xml +++ b/examples/kotlin/echo/pom.xml @@ -1,185 +1,182 @@ - + 4.0.0 - - ftl + xyz.block.ftl.examples echo - 1.0-SNAPSHOT + 1.0.0-SNAPSHOT 1.0-SNAPSHOT - 1.8 - 1.9.22 - true - ${java.version} - ${java.version} + 3.13.0 + 2.0.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.12.3 + true + 3.2.5 + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} + xyz.block + ftl-java-runtime + 1.0.0-SNAPSHOT - xyz.block - ftl-runtime - ${ftl.version} + io.quarkus + quarkus-kotlin - org.postgresql - postgresql - 42.7.2 + io.quarkus + quarkus-jackson + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-junit5 + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + io.rest-assured + kotlin-extensions + test - - - - kotlin-maven-plugin - org.jetbrains.kotlin - ${kotlin.version} - - - compile - - compile - - - - ${project.basedir}/src/main/kotlin - - - - - test-compile - - test-compile - - - - ${project.basedir}/src/test/kotlin - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.6.1 - - - - copy-dependencies - compile - - copy-dependencies - - - ${project.build.directory}/dependency - runtime - - - - - build-classpath - compile - - build-classpath - - - ${project.build.directory}/classpath.txt - dependency - - - - build-classpath-property - compile - - build-classpath - - - generated.classpath - ${project.build.directory}/dependency - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - - - generate-sources - - add-source - - - - ${project.build.directory}/generated-sources/ftl - - - - - - - com.github.ozsie - detekt-maven-plugin - 1.23.5 - - true - ${generated.classpath} - ${java.version} - ${java.home} - ${project.build.directory}/detekt.yml - - - ${project.build.directory}/dependency/ftl-runtime-${ftl.version}.jar - - - ${project.basedir}/src/main/kotlin,${project.build.directory}/generated-sources - - - - compile - - check-with-type-resolution - - - - - - xyz.block - ftl-runtime - ${ftl.version} - - - - - + src/main/kotlin + src/test/kotlin - kotlin-maven-plugin - org.jetbrains.kotlin + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + - org.apache.maven.plugins - maven-dependency-plugin + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + - - org.codehaus.mojo - build-helper-maven-plugin + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + - - com.github.ozsie - detekt-maven-plugin + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + true + 17 + + all-open + + + + + + + + - \ No newline at end of file + + + + native + + + native + + + + false + true + + + + diff --git a/examples/kotlin/echo/src/main/ftl-module-schema/builtin.pb b/examples/kotlin/echo/src/main/ftl-module-schema/builtin.pb new file mode 100644 index 0000000000..af6286fa85 Binary files /dev/null and b/examples/kotlin/echo/src/main/ftl-module-schema/builtin.pb differ diff --git a/examples/kotlin/echo/src/main/ftl-module-schema/time.pb b/examples/kotlin/echo/src/main/ftl-module-schema/time.pb new file mode 100644 index 0000000000..05389c8baa Binary files /dev/null and b/examples/kotlin/echo/src/main/ftl-module-schema/time.pb differ diff --git a/examples/kotlin/echo/src/main/kotlin/ftl/echo/Echo.kt b/examples/kotlin/echo/src/main/kotlin/ftl/echo/Echo.kt index 3bebf67f3d..ecf114f4a9 100644 --- a/examples/kotlin/echo/src/main/kotlin/ftl/echo/Echo.kt +++ b/examples/kotlin/echo/src/main/kotlin/ftl/echo/Echo.kt @@ -1,18 +1,15 @@ package ftl.echo -import ftl.builtin.Empty -import ftl.time.time -import xyz.block.ftl.Context +import ftl.time.TimeClient import xyz.block.ftl.Export - -class InvalidInput(val field: String) : Exception() +import xyz.block.ftl.Verb data class EchoRequest(val name: String?) data class EchoResponse(val message: String) -@Throws(InvalidInput::class) @Export -fun echo(context: Context, req: EchoRequest): EchoResponse { - val response = context.call(::time, Empty()) +@Verb +fun echo(req: EchoRequest, time: TimeClient): EchoResponse { + val response = time.call() return EchoResponse(message = "Hello, ${req.name ?: "anonymous"}! The time is ${response.time}.") } diff --git a/examples/kotlin/time/ftl.toml b/examples/kotlin/time/ftl.toml index 48033f28f8..e89ed11377 100644 --- a/examples/kotlin/time/ftl.toml +++ b/examples/kotlin/time/ftl.toml @@ -1,2 +1,2 @@ module = "time" -language = "kotlin" +language = "java" diff --git a/examples/kotlin/time/pom.xml b/examples/kotlin/time/pom.xml index 86748eceec..92190cd367 100644 --- a/examples/kotlin/time/pom.xml +++ b/examples/kotlin/time/pom.xml @@ -1,185 +1,182 @@ - + 4.0.0 - - ftl + xyz.block.ftl.examples time - 1.0-SNAPSHOT + 1.0.0-SNAPSHOT 1.0-SNAPSHOT - 1.8 - 1.9.22 - true - ${java.version} - ${java.version} + 3.13.0 + 2.0.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.12.3 + true + 3.2.5 + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} + xyz.block + ftl-java-runtime + 1.0.0-SNAPSHOT - xyz.block - ftl-runtime - ${ftl.version} + io.quarkus + quarkus-kotlin - org.postgresql - postgresql - 42.7.2 + io.quarkus + quarkus-jackson + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-junit5 + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + io.rest-assured + kotlin-extensions + test - - - - kotlin-maven-plugin - org.jetbrains.kotlin - ${kotlin.version} - - - compile - - compile - - - - ${project.basedir}/src/main/kotlin - - - - - test-compile - - test-compile - - - - ${project.basedir}/src/test/kotlin - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.6.1 - - - - copy-dependencies - compile - - copy-dependencies - - - ${project.build.directory}/dependency - runtime - - - - - build-classpath - compile - - build-classpath - - - ${project.build.directory}/classpath.txt - dependency - - - - build-classpath-property - compile - - build-classpath - - - generated.classpath - ${project.build.directory}/dependency - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - - - generate-sources - - add-source - - - - ${project.build.directory}/generated-sources/ftl - - - - - - - com.github.ozsie - detekt-maven-plugin - 1.23.5 - - true - ${generated.classpath} - ${java.version} - ${java.home} - ${project.build.directory}/detekt.yml - - - ${project.build.directory}/dependency/ftl-runtime-${ftl.version}.jar - - - ${project.basedir}/src/main/kotlin,${project.build.directory}/generated-sources - - - - compile - - check-with-type-resolution - - - - - - xyz.block - ftl-runtime - ${ftl.version} - - - - - + src/main/kotlin + src/test/kotlin - kotlin-maven-plugin - org.jetbrains.kotlin + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + - org.apache.maven.plugins - maven-dependency-plugin + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + - - org.codehaus.mojo - build-helper-maven-plugin + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + - - com.github.ozsie - detekt-maven-plugin + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + true + 17 + + all-open + + + + + + + + - \ No newline at end of file + + + + native + + + native + + + + false + true + + + + diff --git a/examples/kotlin/time/src/main/kotlin/ftl/time/Time.kt b/examples/kotlin/time/src/main/kotlin/ftl/time/Time.kt index 7158bc5a38..c6dd7f271c 100644 --- a/examples/kotlin/time/src/main/kotlin/ftl/time/Time.kt +++ b/examples/kotlin/time/src/main/kotlin/ftl/time/Time.kt @@ -1,13 +1,13 @@ package ftl.time -import ftl.builtin.Empty -import xyz.block.ftl.Context import xyz.block.ftl.Export +import xyz.block.ftl.Verb import java.time.OffsetDateTime data class TimeResponse(val time: OffsetDateTime) +@Verb @Export -fun time(context: Context, req: Empty): TimeResponse { +fun time(): TimeResponse { return TimeResponse(time = OffsetDateTime.now()) } diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json index 5c7561223e..9ae760b513 100644 --- a/extensions/vscode/package-lock.json +++ b/extensions/vscode/package-lock.json @@ -17,8 +17,8 @@ "@types/node": "20.x", "@types/semver": "^7.5.8", "@types/vscode": "^1.87.0", - "@typescript-eslint/eslint-plugin": "^7.5.0", - "@typescript-eslint/parser": "^7.5.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.3.9", "@vscode/vsce": "^2.29.0", @@ -599,9 +599,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz", - "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==", + "version": "20.14.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz", + "integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==", "dev": true, "license": "MIT", "dependencies": { @@ -615,39 +615,39 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.91.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.91.0.tgz", - "integrity": "sha512-PgPr+bUODjG3y+ozWUCyzttqR9EHny9sPAfJagddQjDwdtf66y2sDKJMnFZRuzBA2YtBGASqJGPil8VDUPvO6A==", + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.92.0.tgz", + "integrity": "sha512-DcZoCj17RXlzB4XJ7IfKdPTcTGDLYvTOcTNkvtjXWF+K2TlKzHHkBEXNWQRpBIXixNEUgx39cQeTFunY0E2msw==", "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", - "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", + "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/type-utils": "7.17.0", - "@typescript-eslint/utils": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -656,27 +656,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", - "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", + "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -685,17 +685,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", - "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0" + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -703,27 +703,24 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", - "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", + "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/utils": "7.17.0", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/utils": "8.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^8.56.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -731,13 +728,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", - "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", + "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -745,14 +742,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", - "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -761,7 +758,7 @@ "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -774,40 +771,40 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", - "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", + "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0" + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", - "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", + "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -5880,33 +5877,34 @@ } }, "node_modules/vscode-languageclient": { - "version": "10.0.0-next.9", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.9.tgz", - "integrity": "sha512-DHEpX7EOCnI2DI5MzE3WknHIcJ29f1mUf058KjFY5PTqfV7HFpBIFPp35LipQ6zYTZN4oAgXUoitfmzvmBqVpQ==", + "version": "10.0.0-next.11", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.11.tgz", + "integrity": "sha512-zoQrt0jtA6khVh/Oe+IURyayrOPlAyxkhlaCtKE5EaW6xwlLSsRKvk3GnR6zMRy7tEXbmhEGQsluV/ZAF7oi1A==", "license": "MIT", "dependencies": { "minimatch": "^9.0.3", "semver": "^7.6.0", - "vscode-languageserver-protocol": "3.17.6-next.7" + "vscode-languageserver-protocol": "3.17.6-next.9" }, "engines": { "vscode": "^1.91.0" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.7", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.7.tgz", - "integrity": "sha512-lx8BQ94x6jl6ZzZOrsN4RxwA1Xh0Ovpus+pWA9TXYF5A9EAAL+pTgYFRra3byucdjw3GDC6zbj7wviyfkMgYuA==", + "version": "3.17.6-next.9", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.9.tgz", + "integrity": "sha512-p4ddqAZa55xzkowM1zpMcQzAh0E1cUWBnQtr6lsXEJ4QupixMVWe53F9vbLbXCN6R/K5uNUaa861c2yS3tcEFw==", "license": "MIT", "dependencies": { "vscode-jsonrpc": "9.0.0-next.5", - "vscode-languageserver-types": "3.17.6-next.4" + "vscode-languageserver-types": "3.17.6-next.5" } }, "node_modules/vscode-languageserver-types": { - "version": "3.17.6-next.4", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.4.tgz", - "integrity": "sha512-SeJTpH/S14EbxOAVaOUoGVqPToqpRTld5QO5Ghig3AlbFJTFF9Wu7srHMfa85L0SX1RYAuuCSFKJVVCxDIk1/Q==" + "version": "3.17.6-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.5.tgz", + "integrity": "sha512-QFmf3Yl1tCgUQfA77N9Me/LXldJXkIVypQbty2rJ1DNHQkC+iwvm4Z2tXg9czSwlhvv0pD4pbF5mT7WhAglolw==", + "license": "MIT" }, "node_modules/watchpack": { "version": "2.4.1", diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 92bf2e0df1..79f4e3882b 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -88,8 +88,8 @@ "@types/node": "20.x", "@types/semver": "^7.5.8", "@types/vscode": "^1.87.0", - "@typescript-eslint/eslint-plugin": "^7.5.0", - "@typescript-eslint/parser": "^7.5.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.3.9", "eslint": "^8.57.0", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b51ea3e533..029edcf7d0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "json-schema-faker": "0.5.6", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.25.1", + "react-router-dom": "6.26.0", "react-use": "^17.5.0", "reactflow": "11.11.4", "tailwindcss": "^3.3.3", @@ -49,17 +49,17 @@ "@storybook/test": "^8.2.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "buffer": "^6.0.3", "chokidar": "3.6.0", "eslint": "^8.57.0", "eslint-plugin-react": "7.35.0", "eslint-plugin-storybook": "^0.8.0", "fast-glob": "3.3.2", - "lint-staged": "15.2.7", - "postcss": "8.4.40", - "postcss-nesting": "12.1.5", + "lint-staged": "15.2.8", + "postcss": "8.4.41", + "postcss-nesting": "13.0.0", "storybook": "^8.2.7", "storybook-dark-mode": "^4.0.2", "typed-css-modules": "0.9.1", @@ -2280,9 +2280,9 @@ "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" }, "node_modules/@codemirror/view": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.29.0.tgz", - "integrity": "sha512-ED4ims4fkf7eOA+HYLVP8VVg3NMllt1FPm9PEJBfYFnidKlRITBaua38u68L1F60eNtw2YNcDN5jsIzhKZwWQA==", + "version": "6.31.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.31.0.tgz", + "integrity": "sha512-pEqRyzcodpvIuWOhGSo0AIs3WKnewDl1EffAnaSZjqGtxk8HisBLY3VltYKA6Lr94VBH3B6O7G8G1tsQ/Wyi7w==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.4.0", @@ -2335,9 +2335,9 @@ } }, "node_modules/@csstools/selector-resolve-nested": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz", - "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-2.0.0.tgz", + "integrity": "sha512-oklSrRvOxNeeOW1yARd4WNCs/D09cQjunGZUgSq6vM8GpzFswN+8rBZyJA29YFZhOTQ6GFzxgLDNtVbt9wPZMA==", "dev": true, "funding": [ { @@ -2349,17 +2349,18 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.13" + "postcss-selector-parser": "^6.1.0" } }, "node_modules/@csstools/selector-specificity": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", - "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz", + "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==", "dev": true, "funding": [ { @@ -2371,11 +2372,12 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.13" + "postcss-selector-parser": "^6.1.0" } }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { @@ -4639,9 +4641,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", - "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -4708,10 +4710,11 @@ } }, "node_modules/@storybook/addon-actions": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.2.7.tgz", - "integrity": "sha512-wDnMGGmaogAForkNncfCx8BEDiwxeK8zC0lj8HkRPUuH6vTr81U5RIb12Wa2TnnNKLKMFAtyPSnofHf3OAfzZQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.2.8.tgz", + "integrity": "sha512-dyajqsMNAUktpi7aiml0Fsm4ey8Nh2YwRyTDuTJZ1iJFcFyARqfr5iKH4/qElq80y0FYXGgGRJB+dKJsCdefLw==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", @@ -4724,14 +4727,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-backgrounds": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.2.7.tgz", - "integrity": "sha512-kEL3kzYB0qNfpznchlGBnQm4iydyzdTYDPlCFsKUAxfUmJFnpz2H52Sl5lB+qJC/4OREp1Usltag7cUjeuyzMQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.2.8.tgz", + "integrity": "sha512-OqXGpq8KzWwAAQWPnby/v4ayWuUAB18Twgi6zeb+QNLEQdFnSp7kz6+4mP8ZVg8RS3ACGXD31nnvvlF7GYoJjQ==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3", @@ -4742,14 +4746,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-controls": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.2.7.tgz", - "integrity": "sha512-u3MruX0Zh6l1iNkoJdXwx+zPVqpDKypVrC0YdN3qQ3+mtTwqt35rgetYqtOkDnJ8mXKxo8A5giERKPIyzH9iBA==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.2.8.tgz", + "integrity": "sha512-adhg68CSFaR/r95rgyKU4ZzWwZz+MU0c4vr9hqrR1UGvg/zl33IZQQzb5j5v3Axo0O31yPMaY6LRty7pOv3+/Q==", "dev": true, + "license": "MIT", "dependencies": { "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -4760,21 +4765,22 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-docs": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.2.7.tgz", - "integrity": "sha512-icLbvUWp95WUxq2sY+0xgJ49MaQ2HqtWY9RUJUZswJ/ZPJTCCpIoa6HP/NOB9A90Oec9n8sW+1CdDL4CxfxfZg==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.2.8.tgz", + "integrity": "sha512-8hqUYYveJjR3e/XdXt0vduA7TxFRIFWgXoa9jN5axa63kqfiHcfkpFYPjM8jCRhsfDIRgdrwe2qxsA0wewO1pA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.2.7", - "@storybook/csf-plugin": "8.2.7", + "@storybook/blocks": "8.2.8", + "@storybook/csf-plugin": "8.2.8", "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "8.2.7", + "@storybook/react-dom-shim": "8.2.8", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "fs-extra": "^11.1.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -4788,24 +4794,25 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-essentials": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.2.7.tgz", - "integrity": "sha512-5qe7La9B2Z4Y9Fet3C35y8zOZwKgrqduNk8yAUmPRAOwopdo8SGKYpnFTnAtTfTCVk6Y+AZlRfQq0yLUk0Wl3g==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "8.2.7", - "@storybook/addon-backgrounds": "8.2.7", - "@storybook/addon-controls": "8.2.7", - "@storybook/addon-docs": "8.2.7", - "@storybook/addon-highlight": "8.2.7", - "@storybook/addon-measure": "8.2.7", - "@storybook/addon-outline": "8.2.7", - "@storybook/addon-toolbars": "8.2.7", - "@storybook/addon-viewport": "8.2.7", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.2.8.tgz", + "integrity": "sha512-NRbFv2ociM1l/Oi/1go/ZC5bUU41n9aKD1DzIbguEKBhUs/TGAES+f5x+7DvYnt3Hvd925/FyTXuMU+vNUeiUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/addon-actions": "8.2.8", + "@storybook/addon-backgrounds": "8.2.8", + "@storybook/addon-controls": "8.2.8", + "@storybook/addon-docs": "8.2.8", + "@storybook/addon-highlight": "8.2.8", + "@storybook/addon-measure": "8.2.8", + "@storybook/addon-outline": "8.2.8", + "@storybook/addon-toolbars": "8.2.8", + "@storybook/addon-viewport": "8.2.8", "ts-dedent": "^2.0.0" }, "funding": { @@ -4813,14 +4820,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-highlight": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.2.7.tgz", - "integrity": "sha512-YhiLtyJ3NBNV3FQoQo8RFjj59QGSmmeSwRvCjoac6No2DY5vkMW5a8mW6ORr6QYd7ratRNtd3AsPqksZIehRwQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.2.8.tgz", + "integrity": "sha512-IM1pPx6CCZbHV0bv3oB1qBCGDsr8soq7XLl93tc7mc4hstWSDFfNn7rx4CWycSlCqXlNTKh8cEkbrPrhV9cwbg==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0" }, @@ -4829,18 +4837,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-interactions": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.2.7.tgz", - "integrity": "sha512-WZXlwpBNLE483uKuR70A7nm+ZbcZNEmuVz/J1/u7dbi0BUWzmJUa9YIgVeQ/1KTwW8KTkxvB0TuUUH3aA4ZKlA==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.2.8.tgz", + "integrity": "sha512-ggctlrSlK72xMfhviHHRslZF5tr9aHr1VFwCG/tjF7s1lM3S7OGqgHLJpcja/wNREvq9GMEvX95ZSu5NMh5CtA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.2.7", - "@storybook/test": "8.2.7", + "@storybook/instrumenter": "8.2.8", + "@storybook/test": "8.2.8", "polished": "^4.2.2", "ts-dedent": "^2.2.0" }, @@ -4849,14 +4858,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-links": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.2.7.tgz", - "integrity": "sha512-BJdR+vdj7S6Rtx8XqBNQzLsRGH/FYHJ6B6BPWGp0awVx0jNWJnxepINQov8i+GAddUVQGCNG+r4LI3QSD3tNAA==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.2.8.tgz", + "integrity": "sha512-2igEaSdKAFjKjioT6LGdBxZulpbVCzmlmV//sTu3sQiVnnxRjjGFt77sEeLMajrsSvg9DB1RMbDsvJ4FJTzXfQ==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", "@storybook/global": "^5.0.0", @@ -4868,7 +4878,7 @@ }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.7" + "storybook": "^8.2.8" }, "peerDependenciesMeta": { "react": { @@ -4877,10 +4887,11 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.2.7.tgz", - "integrity": "sha512-cS5njwlzrgrUjigUKjhbgJMT8bhPmVDK3FwrQqGhw6xYP4cd9/YBJ4RLNPWhOgGJ+EUTz7eFZ/Rkli5mNrhYcQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.2.8.tgz", + "integrity": "sha512-oqZiX571F9NNy8o/oVyM1Pe2cJz3WJ/OpL0lVbepHrV4ir1f+SDYZdMI58jGBAtoM52cwFc2ZPbzXKQs7a513A==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "tiny-invariant": "^1.3.1" @@ -4890,14 +4901,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-onboarding": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-onboarding/-/addon-onboarding-8.2.7.tgz", - "integrity": "sha512-YgpQY0uhYRi+3vny2BjnFlY19ga9/GGZlcFTfaf2wCO7KhjXdES3bp4VTtdNkAwbVaXYY+b33ERd4x1UQ7jSkA==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-onboarding/-/addon-onboarding-8.2.8.tgz", + "integrity": "sha512-fKy3uwggIZKFQL9qo4niVYnAhMAdO/xBsEzJNj2ueTaWoJYO6c0jDWhVQW3pxlMw6yq/WdYT6tW/lsbHKFBUVQ==", "dev": true, + "license": "MIT", "dependencies": { "react-confetti": "^6.1.0" }, @@ -4906,14 +4918,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-outline": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.2.7.tgz", - "integrity": "sha512-oFSo3o5eEUSsdGUSPV22pGoJ5lL0PGcqjcee2hyl0Rc60ovsnB1BEGOoaGk7/bmkywMxRZm8D6j85V8HftA/kg==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.2.8.tgz", + "integrity": "sha512-Cbk4Z0ojggiXjpbS2c4WUP56yikQdT4O7+8AuBNNjVUHNvJQADWYovi6SvDmrS5dH1iyIkB+4saXMr0syp+BDw==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "ts-dedent": "^2.0.0" @@ -4923,7 +4936,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-styling": { @@ -5120,10 +5133,11 @@ } }, "node_modules/@storybook/addon-themes": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-themes/-/addon-themes-8.2.7.tgz", - "integrity": "sha512-LlQFHfC5CicQ7urPMOjhqGyKN8Tm+4mVw4mI7+FGnPkUUcBwxw85OcbtdXQgzfU4AlRauBkIz0CW/tbhSR8R/A==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-themes/-/addon-themes-8.2.8.tgz", + "integrity": "sha512-lJzLAAs2gPm0fbE+NB7oQSbetQoENe3jrlb+vUemUpGBJuhiiz+Aue47DEiFtWCzeNze6dhigMJKFlFacvLz+A==", "dev": true, + "license": "MIT", "dependencies": { "ts-dedent": "^2.0.0" }, @@ -5132,27 +5146,29 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-toolbars": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.2.7.tgz", - "integrity": "sha512-lEq0/uiogQSxS8pM5AqIexPiG2mudHUxgBiVWSspbTQDUbGBUxB64VYeYERat50N/GyS2iCymlfSkC+OUXaYLQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.2.8.tgz", + "integrity": "sha512-k64G3FUpX3H/mhJ7AG1r/4Drsk6cdUtxI3yVdgWb7O3Ka7v/OFZexRXRSiV03n5q/kaqVKDu96Tuog57+7EB4w==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/addon-viewport": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.2.7.tgz", - "integrity": "sha512-d4+klwM/duTukNED1WCeBgIMqL5Jvm/iUs2rUc5HI1FGMEDYnoLVR2ztjivQs+6f1cJWuGwWZD/toB5pKHuR/A==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.2.8.tgz", + "integrity": "sha512-/JZeIgB33yhryUvWaNO+3t9akcS8nGLyAUmlljPFr3LUDDYrO/0H9tE4CgjLqtwCXBq3k3s0HLzEJOrKI9Tmbw==", "dev": true, + "license": "MIT", "dependencies": { "memoizerific": "^1.11.3" }, @@ -5161,7 +5177,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/api": { @@ -5287,10 +5303,11 @@ } }, "node_modules/@storybook/blocks": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.2.7.tgz", - "integrity": "sha512-lZB4EzmY4ftgubkf7hmkALEhmfMhRkDRD5QjrgTZLRpdVXPzFUyljgLlTBhv34YTN+ZLYK618/4uSVJBpgoKeQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.2.8.tgz", + "integrity": "sha512-AHBXu9s73Xv9r1JageIL7C4eGf5XYEByai4Y6NYQsE+jF7b7e8oaSUoLW6fWSyLGuqvjRx+5P7GMNI2K1EngBA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", "@storybook/global": "^5.0.0", @@ -5314,7 +5331,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.7" + "storybook": "^8.2.8" }, "peerDependenciesMeta": { "react": { @@ -5330,6 +5347,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -5341,15 +5359,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@storybook/builder-vite": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.2.7.tgz", - "integrity": "sha512-CoEQjsfAQdZeAavfh1sBTMmC453kUFLKHr1zs6MZAlkejxky+U21t1Zb1qEU+IsEr/AlzvJr60pxUNL/dy6PVQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.2.8.tgz", + "integrity": "sha512-p9EJfZkX9ZsVi1Qr3jYyCJaZZ/2pt0KVTOYnDzNnhi3P/suU6O3Lp/YCV5+KOfAmlg2IgTND0EidqZinqPIBSg==", "dev": true, + "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "8.2.7", + "@storybook/csf-plugin": "8.2.8", "@types/find-cache-dir": "^3.2.1", "browser-assert": "^1.2.1", "es-module-lexer": "^1.5.0", @@ -5365,7 +5385,7 @@ }, "peerDependencies": { "@preact/preset-vite": "*", - "storybook": "^8.2.7", + "storybook": "^8.2.8", "typescript": ">= 4.3.x", "vite": "^4.0.0 || ^5.0.0", "vite-plugin-glimmerx": "*" @@ -5440,15 +5460,16 @@ } }, "node_modules/@storybook/codemod": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.2.7.tgz", - "integrity": "sha512-D2sJcZMUO6Y7DNja4LvdT6uBee4bZbQKB904kEG9Kpr0XF20IHAP9BbkfG8HEFaS0GbJwvGvE03Sg+S1y+vO6Q==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.2.8.tgz", + "integrity": "sha512-dqD4j6JTsS8BM2y1yHBIe5fHvsGM08qpJQXkE77aXJIm5UfUeuWC7rY0xAheX3fU5G98l3BJk0ySUGspQL5pNg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/preset-env": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/core": "8.2.7", + "@storybook/core": "8.2.8", "@storybook/csf": "0.1.11", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", @@ -5509,23 +5530,25 @@ } }, "node_modules/@storybook/components": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.2.7.tgz", - "integrity": "sha512-FXhnoHl9S+tKSFc62iUG3EWplQP9ojGQaSMhqP4QTus6xmo53oSsPzuTPQilKVHkGxFQW8eGgKKsfHw3G2NT2g==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.2.8.tgz", + "integrity": "sha512-d4fI7Clogx4rgLAM7vZVr9L2EFtAkGXvpkZFuB0H0eyYaxZSbuZYvDCzRglQGQGsqD8IA8URTgPVSXC3L3k6Bg==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/core": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.2.7.tgz", - "integrity": "sha512-vgw5MYN9Bq2/ZsObCOEHbBHwi4RpbYCHPFtKkr4kTnWID++FCSiSVd7jY3xPvcNxWqCxOyH6dThpBi+SsB/ZAA==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.2.8.tgz", + "integrity": "sha512-Wwm/Txh87hbxqU9OaxXwdGAmdRBjDn7rlZEPjNBx0tt43SQ11fKambY7nVWrWuw46YsJpdF9V/PQr4noNEXXEA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", "@types/express": "^4.17.21", @@ -5730,6 +5753,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -5744,10 +5768,11 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.2.7.tgz", - "integrity": "sha512-rBdplL6xcVJcuq+uM0eidomMQ5BtAlVAejYrOTNiqBk/zVh5JSvchYzYG9n6Fo2PdKLLKdlZ874zhsVuNriNBQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.2.8.tgz", + "integrity": "sha512-CEHY7xloBPE8d8h0wg2AM2kRaZkHK8/vkYMNZPbccqAYj6PQIdTuOcXZIBAhAGydyIBULZmsmmsASxM9RO5fKA==", "dev": true, + "license": "MIT", "dependencies": { "unplugin": "^1.3.1" }, @@ -5756,7 +5781,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/csf/node_modules/type-fest": { @@ -5791,10 +5816,11 @@ } }, "node_modules/@storybook/instrumenter": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.2.7.tgz", - "integrity": "sha512-Zm6Ty4uWFTNchKUviuJ9vfcMb7+qU8eyrFXVY80XRpr62JEWkYj4eCwx4OG8GzlQahTh9aSv9+hzV6p/5Ld4mw==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.2.8.tgz", + "integrity": "sha512-6Gk3CzoYQQXBXpW86PKqYSozOB/C9dSYiFvwPRo4XsEfjARDi8yglqkbOtG+FVqKDL66I5krcveB8bTWigqc9g==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "@vitest/utils": "^1.3.1", @@ -5805,20 +5831,21 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/manager-api": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.2.7.tgz", - "integrity": "sha512-BXjz6eNl1GyFcMwzRQTIokslcIY71AYblJUscPcy03X93oqI0GjFVa1xuSMwYw/oXWn7SHhKmqtqEG19lvBGRQ==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.2.8.tgz", + "integrity": "sha512-wzfRu3vrD9a99pN3W/RJXVtgNGNsy9PyvetjUfgQVtUZ9eXXDuA+tM7ITTu3xvONtV/rT2YEBwzOpowa+r1GNQ==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/node-logger": { @@ -5832,30 +5859,32 @@ } }, "node_modules/@storybook/preview-api": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.2.7.tgz", - "integrity": "sha512-lNZBTjZaYNSwBY8dEcDZdkOBvq1/JoVWpuvqDEKvGmp5usTe77xAOwGyncEb96Cx1BbXXkMiDrqbV5G23PFRYA==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.2.8.tgz", + "integrity": "sha512-BDt1lo5oEWAaTVCsl6JUHCBFtIWI/Za4qvIdn2Lx9eCA+Ae6IDliosmu273DcvGD9R4OPF6sm1dML3TXILGGcA==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/react": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.2.7.tgz", - "integrity": "sha512-Qkw1K1iBDk+E9dlHrEWOOkn0trUU6wSt4mvzyOekiApM290esnPtw6GYXvxfBgFwNXfXbaGG3QNYGAFevf7qHw==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.2.8.tgz", + "integrity": "sha512-Nln0DDTQ930P4J+SEkWbLSgaDe8eDd5gP6h3l4b5RwT7sRuSyHtTtYHPCnU9U7sLQ3AbMsclgtJukHXDitlccg==", "dev": true, + "license": "MIT", "dependencies": { - "@storybook/components": "^8.2.7", + "@storybook/components": "^8.2.8", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "^8.2.7", - "@storybook/preview-api": "^8.2.7", - "@storybook/react-dom-shim": "8.2.7", - "@storybook/theming": "^8.2.7", + "@storybook/manager-api": "^8.2.8", + "@storybook/preview-api": "^8.2.8", + "@storybook/react-dom-shim": "8.2.8", + "@storybook/theming": "^8.2.8", "@types/escodegen": "^0.0.6", "@types/estree": "^0.0.51", "@types/node": "^18.0.0", @@ -5882,7 +5911,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.7", + "storybook": "^8.2.8", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { @@ -5892,10 +5921,11 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.2.7.tgz", - "integrity": "sha512-9VI+NrC09DAr0QQZsFmU5Fd9eqdJp/1AHK+sm9BOZretGGGJwn22xS7UXhHIiFpfXJQnr3TNcYWRzXFyuaE/Sw==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.2.8.tgz", + "integrity": "sha512-2my3dGBOpBe30+FsSdQOIYCfxMyT68+SEq0qcXxfuax0BkhhJnZLpwvpqOna6EOVTgBD+Tk1TKmjpGwxuwp4rg==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" @@ -5903,19 +5933,20 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/react-vite": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-8.2.7.tgz", - "integrity": "sha512-OvC4c4VpMbMNWNR8zBfWPwSS+LilMZ5O7cUbFSFOsAAI5cYcReo1d0MqpiaWybQynkBFj81zRT+dr+B/5Y2Ajg==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-8.2.8.tgz", + "integrity": "sha512-xzXWyhFnLoFtJGgj8F5j/33QB4YTyEX61On6kolt7WFAjRFaUWJGYUC8cPPL4PNwsdouyCrnHvlJj77AvFlvfQ==", "dev": true, + "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.1", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "8.2.7", - "@storybook/react": "8.2.7", + "@storybook/builder-vite": "8.2.8", + "@storybook/react": "8.2.8", "find-up": "^5.0.0", "magic-string": "^0.30.0", "react-docgen": "^7.0.0", @@ -5932,7 +5963,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.7", + "storybook": "^8.2.8", "vite": "^4.0.0 || ^5.0.0" } }, @@ -5941,6 +5972,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -5958,6 +5990,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -5967,6 +6000,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -5979,6 +6013,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -6015,13 +6050,14 @@ } }, "node_modules/@storybook/test": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.2.7.tgz", - "integrity": "sha512-7xypGR0zjJaM5MkxIz513SYiGs5vDJZL1bbkG1YKeBMff+ZRpa8y8VDYn/WDWuDw76KcFEXoPsPzKwktGhvnpw==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.2.8.tgz", + "integrity": "sha512-Lbt4DHP8WhnakTPw981kP85DeoONKN+zVLjFPa5ptllyT+jazZANjIdGhNUlBdIzOw3oyDXhGlWIdtqztS3pSA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", - "@storybook/instrumenter": "8.2.7", + "@storybook/instrumenter": "8.2.8", "@testing-library/dom": "10.1.0", "@testing-library/jest-dom": "6.4.5", "@testing-library/user-event": "14.5.2", @@ -6034,20 +6070,21 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/theming": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.2.7.tgz", - "integrity": "sha512-+iqm0GfRkshrjjNSOzwl7AD2m+LtJGXJCr93ke1huDK497WUKbX1hbbw51h5E1tEkx0c2wIqUlaqCM+7XMYcpw==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.2.8.tgz", + "integrity": "sha512-jt5oUO82LN3z5aygNdHucBZcErSicIAwzhR5Kz9E/C9wUbhyZhbWsWyhpZaytu8LJUj2YWAIPS8kq/jGx+qLZA==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.7" + "storybook": "^8.2.8" } }, "node_modules/@storybook/types": { @@ -6872,32 +6909,32 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", - "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", + "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/type-utils": "7.17.0", - "@typescript-eslint/utils": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -6906,27 +6943,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", - "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", + "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -6935,17 +6972,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", - "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0" + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6953,27 +6990,24 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", - "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", + "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/utils": "7.17.0", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/utils": "8.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^8.56.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -6981,13 +7015,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", - "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", + "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -6995,14 +7029,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", - "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -7011,7 +7045,7 @@ "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -7024,40 +7058,40 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", - "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", + "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0" + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", - "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", + "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -7541,12 +7575,16 @@ } }, "node_modules/ansi-escapes": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", - "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8279,15 +8317,16 @@ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8944,9 +8983,10 @@ "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==" }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -9246,6 +9286,19 @@ "node": ">=4" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -12184,22 +12237,22 @@ } }, "node_modules/lint-staged": { - "version": "15.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.7.tgz", - "integrity": "sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==", + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", + "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "~5.3.0", "commander": "~12.1.0", - "debug": "~4.3.4", + "debug": "~4.3.6", "execa": "~8.0.1", - "lilconfig": "~3.1.1", - "listr2": "~8.2.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", "micromatch": "~4.0.7", "pidtree": "~0.6.0", "string-argv": "~0.3.2", - "yaml": "~2.4.2" + "yaml": "~2.5.0" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -12225,16 +12278,17 @@ } }, "node_modules/listr2": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz", - "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", "dev": true, + "license": "MIT", "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", - "log-update": "^6.0.0", - "rfdc": "^1.3.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" }, "engines": { @@ -12385,14 +12439,15 @@ } }, "node_modules/log-update": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", - "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-escapes": "^6.2.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^7.0.0", + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" }, @@ -12408,6 +12463,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -12420,6 +12476,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -12432,6 +12489,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, + "license": "MIT", "dependencies": { "get-east-asian-width": "^1.0.0" }, @@ -12447,6 +12505,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" @@ -12463,6 +12522,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -12691,6 +12751,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -13686,9 +13759,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", - "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "funding": [ { "type": "opencollective", @@ -13897,9 +13970,9 @@ } }, "node_modules/postcss-nesting": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.5.tgz", - "integrity": "sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.0.tgz", + "integrity": "sha512-TCGQOizyqvEkdeTPM+t6NYwJ3EJszYE/8t8ILxw/YoeUvz2rz7aM8XTAmBWh9/DJjfaaabL88fWrsVHSPF2zgA==", "dev": true, "funding": [ { @@ -13911,13 +13984,14 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^1.1.0", - "@csstools/selector-specificity": "^3.1.1", + "@csstools/selector-resolve-nested": "^2.0.0", + "@csstools/selector-specificity": "^4.0.0", "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, "peerDependencies": { "postcss": "^8.4" @@ -14010,6 +14084,7 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -14348,12 +14423,12 @@ } }, "node_modules/react-router": { - "version": "6.25.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", - "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.18.0" + "@remix-run/router": "1.19.0" }, "engines": { "node": ">=14.0.0" @@ -14363,13 +14438,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.25.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", - "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.18.0", - "react-router": "6.25.1" + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" }, "engines": { "node": ">=14.0.0" @@ -14751,51 +14826,38 @@ "dev": true }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -15364,15 +15426,16 @@ "dev": true }, "node_modules/storybook": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.2.7.tgz", - "integrity": "sha512-Jb9DXue1sr3tKkpuq66VP5ItOKTpxL6t99ze1wXDbjCvPiInTdPA5AyFEjBuKjOBIh28bayYoOZa6/xbMJV+Wg==", + "version": "8.2.8", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.2.8.tgz", + "integrity": "sha512-sh4CNCXkieVgJ5GXrCOESS0BjRbQ9wG7BVnurQPl6izNnB9zR8rag+aUmjPZWBwbj55V1BFA5A/vEsCov21qjg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/codemod": "8.2.7", - "@storybook/core": "8.2.7", + "@storybook/codemod": "8.2.8", + "@storybook/core": "8.2.8", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -15429,6 +15492,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -15444,6 +15508,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15460,6 +15525,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -15471,13 +15537,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/storybook/node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -15487,6 +15555,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -15510,6 +15579,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -15522,6 +15592,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", @@ -15542,6 +15613,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -15551,6 +15623,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -15560,6 +15633,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -15572,6 +15646,7 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -15581,6 +15656,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -15593,6 +15669,7 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -15608,6 +15685,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -15619,13 +15697,15 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/storybook/node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -15638,6 +15718,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -15647,6 +15728,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -16010,9 +16092,9 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/tailwindcss": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz", - "integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz", + "integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -16457,9 +16539,9 @@ } }, "node_modules/type-fest": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", - "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.24.0.tgz", + "integrity": "sha512-spAaHzc6qre0TlZQQ2aA/nGMe+2Z/wyGk5Z+Ru2VUfdNwT6kWO6TjevOlpebsATEG1EIQ2sOiDszud3lO5mt/Q==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -17337,6 +17419,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -17427,6 +17510,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -17439,6 +17523,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -17451,6 +17536,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -17489,6 +17575,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -17520,9 +17607,10 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "license": "ISC", "bin": { "yaml": "bin.mjs" }, diff --git a/frontend/package.json b/frontend/package.json index 2c5f26fa1e..b80b223eb0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,7 +44,7 @@ "json-schema-faker": "0.5.6", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.25.1", + "react-router-dom": "6.26.0", "react-use": "^17.5.0", "reactflow": "11.11.4", "tailwindcss": "^3.3.3", @@ -64,17 +64,17 @@ "@storybook/test": "^8.2.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "buffer": "^6.0.3", "chokidar": "3.6.0", "eslint": "^8.57.0", "eslint-plugin-react": "7.35.0", "eslint-plugin-storybook": "^0.8.0", "fast-glob": "3.3.2", - "lint-staged": "15.2.7", - "postcss": "8.4.40", - "postcss-nesting": "12.1.5", + "lint-staged": "15.2.8", + "postcss": "8.4.41", + "postcss-nesting": "13.0.0", "storybook": "^8.2.7", "storybook-dark-mode": "^4.0.2", "typed-css-modules": "0.9.1", diff --git a/frontend/src/components/CodeEditor.tsx b/frontend/src/components/CodeEditor.tsx index 6589f3b3cc..963e4cfc11 100644 --- a/frontend/src/components/CodeEditor.tsx +++ b/frontend/src/components/CodeEditor.tsx @@ -64,7 +64,9 @@ export const CodeEditor = ( const handleEditorTextChange = useCallback((state: EditorState) => { const currentText = state.doc.toString() - onTextChanged && onTextChanged(currentText) + if (onTextChanged) { + onTextChanged(currentText) + } }, [onTextChanged]) useEffect(() => { diff --git a/frontend/src/features/graph/create-layout.ts b/frontend/src/features/graph/create-layout.ts index 6ad9b5d5e6..354b78d87b 100644 --- a/frontend/src/features/graph/create-layout.ts +++ b/frontend/src/features/graph/create-layout.ts @@ -108,8 +108,6 @@ export const layoutNodes = (modules: Module[], topology: Topology | undefined) = style: { stroke: 'rgb(251 113 133)' }, animated: true, }) - call.name - call.module } }), ) diff --git a/ftl-project.toml b/ftl-project.toml index f196dc2f02..62f6c086db 100644 --- a/ftl-project.toml +++ b/ftl-project.toml @@ -1,9 +1,12 @@ name = "ftl" module-dirs = ["examples/go"] ftl-min-version = "" +hermit = false +no-git = false [global] [global.configuration] + CUSTOMER_TOKEN_TBD = "inline://InNvbWVDb25maWci" key = "inline://InZhbHVlIg" [modules] diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index f0d1e60382..d599035aaf 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -267,7 +267,7 @@ func CleanStubs(ctx context.Context, projectRoot string) error { // GenerateStubsForModules generates stubs for all modules in the schema. func GenerateStubsForModules(ctx context.Context, projectRoot string, moduleConfigs []moduleconfig.ModuleConfig, sch *schema.Schema) error { logger := log.FromContext(ctx) - logger.Debugf("Generating module stubs") + logger.Debugf("Generating go module stubs") sharedFtlDir := filepath.Join(projectRoot, buildDirName) @@ -275,12 +275,21 @@ func GenerateStubsForModules(ctx context.Context, projectRoot string, moduleConf if ftl.IsRelease(ftl.Version) { ftlVersion = ftl.Version } + hasGo := false + for _, mc := range moduleConfigs { + if mc.Language == "go" && mc.Module != "builtin" { + hasGo = true + } + } + if !hasGo { + return nil + } for _, module := range sch.Modules { var moduleConfig *moduleconfig.ModuleConfig for _, mc := range moduleConfigs { mcCopy := mc - if mc.Module == module.Name { + if mc.Module == module.Name && mc.Language == "go" { moduleConfig = &mcCopy break } @@ -292,12 +301,18 @@ func GenerateStubsForModules(ctx context.Context, projectRoot string, moduleConf // If there's no module config, use the go.mod file for the first config we find. if moduleConfig == nil { - if len(moduleConfigs) > 0 { - _, goModVersion, err = updateGoModule(filepath.Join(moduleConfigs[0].Dir, "go.mod")) + for _, mod := range moduleConfigs { + if mod.Language != "go" { + continue + } + goModPath := filepath.Join(mod.Dir, "go.mod") + _, goModVersion, err = updateGoModule(goModPath) if err != nil { - return err + logger.Debugf("could not read go.mod %s", goModPath) + continue } - } else { + } + if goModVersion == "" { // The best we can do here if we don't have a module to read from is to use the current Go version. goModVersion = runtime.Version()[2:] } @@ -347,6 +362,9 @@ func SyncGeneratedStubReferences(ctx context.Context, projectRootDir string, stu } sharedModulesPaths = append(sharedModulesPaths, filepath.Join(projectRootDir, buildDirName, "go", "modules", mod)) } + if moduleConfig.Language != "go" { + continue + } _, goModVersion, err := updateGoModule(filepath.Join(moduleConfig.Dir, "go.mod")) if err != nil { diff --git a/go-runtime/compile/compile_integration_test.go b/go-runtime/compile/compile_integration_test.go index 567eca4cc3..23091bbfd4 100644 --- a/go-runtime/compile/compile_integration_test.go +++ b/go-runtime/compile/compile_integration_test.go @@ -12,7 +12,7 @@ import ( ) func TestNonExportedDecls(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("time"), in.Deploy("time"), in.CopyModule("echo"), @@ -26,7 +26,7 @@ func TestNonExportedDecls(t *testing.T) { } func TestUndefinedExportedDecls(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("time"), in.Deploy("time"), in.CopyModule("echo"), @@ -40,7 +40,7 @@ func TestUndefinedExportedDecls(t *testing.T) { } func TestNonFTLTypes(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("external"), in.Deploy("external"), in.Call("external", "echo", in.Obj{"message": "hello"}, func(t testing.TB, response in.Obj) { @@ -50,7 +50,7 @@ func TestNonFTLTypes(t *testing.T) { } func TestNonStructRequestResponse(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("two"), in.Deploy("two"), in.CopyModule("one"), diff --git a/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl b/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl index 7288d73a1a..bff5bbb2fa 100644 --- a/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl +++ b/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl @@ -23,7 +23,7 @@ var _ = context.Background // {{- end}} {{- if is "Topic" .}} -var {{.Name|strippedCamel}} = ftl.Topic[{{type $.Module .Event}}]("{{.Name}}") +var {{.Name|title}} = ftl.Topic[{{type $.Module .Event}}]("{{.Name}}") {{- else if and (is "Enum" .) .IsValueEnum}} {{- $enumName := .Name}} //ftl:enum diff --git a/go-runtime/compile/testdata/go/echo/go.mod b/go-runtime/compile/testdata/go/echo/go.mod index 767d3bd24f..0f9dc388ce 100644 --- a/go-runtime/compile/testdata/go/echo/go.mod +++ b/go-runtime/compile/testdata/go/echo/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/compile/testdata/go/echo/go.sum b/go-runtime/compile/testdata/go/echo/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/compile/testdata/go/echo/go.sum +++ b/go-runtime/compile/testdata/go/echo/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/compile/testdata/go/notexportedverb/go.mod b/go-runtime/compile/testdata/go/notexportedverb/go.mod index 8b64312e83..2b8282d901 100644 --- a/go-runtime/compile/testdata/go/notexportedverb/go.mod +++ b/go-runtime/compile/testdata/go/notexportedverb/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/compile/testdata/go/notexportedverb/go.sum b/go-runtime/compile/testdata/go/notexportedverb/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/compile/testdata/go/notexportedverb/go.sum +++ b/go-runtime/compile/testdata/go/notexportedverb/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/compile/testdata/go/one/go.mod b/go-runtime/compile/testdata/go/one/go.mod index 73340f94f7..524102f091 100644 --- a/go-runtime/compile/testdata/go/one/go.mod +++ b/go-runtime/compile/testdata/go/one/go.mod @@ -7,9 +7,9 @@ replace github.com/TBD54566975/ftl => ../../../../.. require github.com/TBD54566975/ftl v0.150.3 require ( - connectrpc.com/connect v1.16.1 // indirect + connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect - connectrpc.com/otelconnect v0.7.0 // indirect + connectrpc.com/otelconnect v0.7.1 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect @@ -29,19 +29,19 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/puzpuzpuz/xsync/v3 v3.3.1 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/compile/testdata/go/one/go.sum b/go-runtime/compile/testdata/go/one/go.sum index 38d5d1435e..9fbb9ebc36 100644 --- a/go-runtime/compile/testdata/go/one/go.sum +++ b/go-runtime/compile/testdata/go/one/go.sum @@ -1,9 +1,9 @@ -connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= -connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= -connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= -connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= @@ -74,8 +74,8 @@ github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/puzpuzpuz/xsync/v3 v3.3.1 h1:vZPJk3OOfoaSjy3cdTX3BZxhDCUVp9SqdHnd+ilGlbQ= -github.com/puzpuzpuz/xsync/v3 v3.3.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -134,14 +134,14 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= -modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= -modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/compile/testdata/go/two/go.mod b/go-runtime/compile/testdata/go/two/go.mod index e009521704..9963efd3a7 100644 --- a/go-runtime/compile/testdata/go/two/go.mod +++ b/go-runtime/compile/testdata/go/two/go.mod @@ -7,9 +7,9 @@ replace github.com/TBD54566975/ftl => ../../../../.. require github.com/TBD54566975/ftl v0.150.3 require ( - connectrpc.com/connect v1.16.1 // indirect + connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect - connectrpc.com/otelconnect v0.7.0 // indirect + connectrpc.com/otelconnect v0.7.1 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect @@ -29,19 +29,19 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/puzpuzpuz/xsync/v3 v3.3.1 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/compile/testdata/go/two/go.sum b/go-runtime/compile/testdata/go/two/go.sum index 38d5d1435e..9fbb9ebc36 100644 --- a/go-runtime/compile/testdata/go/two/go.sum +++ b/go-runtime/compile/testdata/go/two/go.sum @@ -1,9 +1,9 @@ -connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= -connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= -connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= -connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= @@ -74,8 +74,8 @@ github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/puzpuzpuz/xsync/v3 v3.3.1 h1:vZPJk3OOfoaSjy3cdTX3BZxhDCUVp9SqdHnd+ilGlbQ= -github.com/puzpuzpuz/xsync/v3 v3.3.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -134,14 +134,14 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= -modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= -modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/compile/testdata/go/undefinedverb/go.mod b/go-runtime/compile/testdata/go/undefinedverb/go.mod index 8b64312e83..2b8282d901 100644 --- a/go-runtime/compile/testdata/go/undefinedverb/go.mod +++ b/go-runtime/compile/testdata/go/undefinedverb/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/compile/testdata/go/undefinedverb/go.sum b/go-runtime/compile/testdata/go/undefinedverb/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/compile/testdata/go/undefinedverb/go.sum +++ b/go-runtime/compile/testdata/go/undefinedverb/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/encoding/encoding_integration_test.go b/go-runtime/encoding/encoding_integration_test.go index d8157ba364..35cfa1586e 100644 --- a/go-runtime/encoding/encoding_integration_test.go +++ b/go-runtime/encoding/encoding_integration_test.go @@ -6,12 +6,13 @@ import ( "net/http" "testing" - in "github.com/TBD54566975/ftl/integration" "github.com/alecthomas/assert/v2" + + in "github.com/TBD54566975/ftl/integration" ) func TestHttpEncodeOmitempty(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("omitempty"), in.Deploy("omitempty"), in.HttpCall(http.MethodGet, "/get", nil, in.JsonData(t, in.Obj{}), func(t testing.TB, resp *in.HTTPResponse) { diff --git a/go-runtime/encoding/testdata/go/omitempty/go.mod b/go-runtime/encoding/testdata/go/omitempty/go.mod index 243cd594e7..334871780d 100644 --- a/go-runtime/encoding/testdata/go/omitempty/go.mod +++ b/go-runtime/encoding/testdata/go/omitempty/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/encoding/testdata/go/omitempty/go.sum b/go-runtime/encoding/testdata/go/omitempty/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/encoding/testdata/go/omitempty/go.sum +++ b/go-runtime/encoding/testdata/go/omitempty/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/ftl_integration_test.go b/go-runtime/ftl/ftl_integration_test.go index 06afc15ae5..1dff86c46d 100644 --- a/go-runtime/ftl/ftl_integration_test.go +++ b/go-runtime/ftl/ftl_integration_test.go @@ -6,14 +6,15 @@ import ( "strings" "testing" - in "github.com/TBD54566975/ftl/integration" "github.com/alecthomas/assert/v2" + in "github.com/TBD54566975/ftl/integration" + "github.com/alecthomas/repr" ) func TestLifecycle(t *testing.T) { - in.Run(t, "", + in.Run(t, in.GitInit(), in.Exec("rm", "ftl-project.toml"), in.Exec("ftl", "init", "test", "."), @@ -26,7 +27,7 @@ func TestLifecycle(t *testing.T) { } func TestInterModuleCall(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("echo"), in.CopyModule("time"), in.Deploy("time"), @@ -42,7 +43,7 @@ func TestInterModuleCall(t *testing.T) { } func TestSchemaGenerate(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyDir("../schema-generate", "schema-generate"), in.Mkdir("build/schema-generate"), in.Exec("ftl", "schema", "generate", "schema-generate", "build/schema-generate"), @@ -51,7 +52,7 @@ func TestSchemaGenerate(t *testing.T) { } func TestTypeRegistryUnitTest(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("typeregistry"), in.Deploy("typeregistry"), in.ExecModuleTest("typeregistry"), diff --git a/go-runtime/ftl/ftltest/ftltest_integration_test.go b/go-runtime/ftl/ftltest/ftltest_integration_test.go index d53a8cf0d8..5eb88500d6 100644 --- a/go-runtime/ftl/ftltest/ftltest_integration_test.go +++ b/go-runtime/ftl/ftltest/ftltest_integration_test.go @@ -9,7 +9,9 @@ import ( ) func TestModuleUnitTests(t *testing.T) { - in.RunWithoutController(t, "wrapped/ftl-project.toml", + in.Run(t, + in.WithFTLConfig("wrapped/ftl-project.toml"), + in.WithoutController(), in.GitInit(), in.CopyModule("time"), in.CopyModule("wrapped"), diff --git a/go-runtime/ftl/ftltest/testdata/go/outer/go.sum b/go-runtime/ftl/ftltest/testdata/go/outer/go.sum index 79591ce1b4..f7b40ba4c0 100644 --- a/go-runtime/ftl/ftltest/testdata/go/outer/go.sum +++ b/go-runtime/ftl/ftltest/testdata/go/outer/go.sum @@ -96,20 +96,20 @@ go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6b go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -122,8 +122,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/ftltest/testdata/go/pubsub/go.mod b/go-runtime/ftl/ftltest/testdata/go/pubsub/go.mod index ccd3eb15e3..12577fc3df 100644 --- a/go-runtime/ftl/ftltest/testdata/go/pubsub/go.mod +++ b/go-runtime/ftl/ftltest/testdata/go/pubsub/go.mod @@ -44,19 +44,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/ftltest/testdata/go/pubsub/go.sum b/go-runtime/ftl/ftltest/testdata/go/pubsub/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/go-runtime/ftl/ftltest/testdata/go/pubsub/go.sum +++ b/go-runtime/ftl/ftltest/testdata/go/pubsub/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/ftltest/testdata/go/subscriber/go.mod b/go-runtime/ftl/ftltest/testdata/go/subscriber/go.mod index 1c555bd415..1202106777 100644 --- a/go-runtime/ftl/ftltest/testdata/go/subscriber/go.mod +++ b/go-runtime/ftl/ftltest/testdata/go/subscriber/go.mod @@ -44,19 +44,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/ftltest/testdata/go/subscriber/go.sum b/go-runtime/ftl/ftltest/testdata/go/subscriber/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/go-runtime/ftl/ftltest/testdata/go/subscriber/go.sum +++ b/go-runtime/ftl/ftltest/testdata/go/subscriber/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod index 62da59be9f..cc87108068 100644 --- a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod +++ b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.mod @@ -44,18 +44,19 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum +++ b/go-runtime/ftl/ftltest/testdata/go/verbtypes/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/ftltest/testdata/go/wrapped/go.mod b/go-runtime/ftl/ftltest/testdata/go/wrapped/go.mod index 814d17f7d9..4c7cf792d3 100644 --- a/go-runtime/ftl/ftltest/testdata/go/wrapped/go.mod +++ b/go-runtime/ftl/ftltest/testdata/go/wrapped/go.mod @@ -42,19 +42,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/ftltest/testdata/go/wrapped/go.sum b/go-runtime/ftl/ftltest/testdata/go/wrapped/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/go-runtime/ftl/ftltest/testdata/go/wrapped/go.sum +++ b/go-runtime/ftl/ftltest/testdata/go/wrapped/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/integration_test.go b/go-runtime/ftl/integration_test.go index 155156a76a..e96d0ff0f9 100644 --- a/go-runtime/ftl/integration_test.go +++ b/go-runtime/ftl/integration_test.go @@ -9,7 +9,7 @@ import ( ) func TestFTLMap(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("mapper"), in.Build("mapper"), in.ExecModuleTest("mapper"), diff --git a/go-runtime/ftl/reflection/reflection_integration_test.go b/go-runtime/ftl/reflection/reflection_integration_test.go index e858feefc3..c91c9fdef2 100644 --- a/go-runtime/ftl/reflection/reflection_integration_test.go +++ b/go-runtime/ftl/reflection/reflection_integration_test.go @@ -9,7 +9,7 @@ import ( ) func TestRuntimeReflection(t *testing.T) { - in.Run(t, "", + in.Run(t, in.CopyModule("runtimereflection"), in.ExecModuleTest("runtimereflection"), ) diff --git a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod index 2f52025d2e..f190123044 100644 --- a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod +++ b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod @@ -39,13 +39,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum +++ b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/testdata/go/echo/go.mod b/go-runtime/ftl/testdata/go/echo/go.mod index 273a160e9f..b10462c9d7 100644 --- a/go-runtime/ftl/testdata/go/echo/go.mod +++ b/go-runtime/ftl/testdata/go/echo/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/testdata/go/echo/go.sum b/go-runtime/ftl/testdata/go/echo/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/ftl/testdata/go/echo/go.sum +++ b/go-runtime/ftl/testdata/go/echo/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/testdata/go/mapper/go.mod b/go-runtime/ftl/testdata/go/mapper/go.mod index f0e05325aa..144ee4c71e 100644 --- a/go-runtime/ftl/testdata/go/mapper/go.mod +++ b/go-runtime/ftl/testdata/go/mapper/go.mod @@ -44,19 +44,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/testdata/go/mapper/go.sum b/go-runtime/ftl/testdata/go/mapper/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/go-runtime/ftl/testdata/go/mapper/go.sum +++ b/go-runtime/ftl/testdata/go/mapper/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/ftl/testdata/go/typeregistry/go.mod b/go-runtime/ftl/testdata/go/typeregistry/go.mod index e122cb865e..d087998d1e 100644 --- a/go-runtime/ftl/testdata/go/typeregistry/go.mod +++ b/go-runtime/ftl/testdata/go/typeregistry/go.mod @@ -44,19 +44,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/sqlc-dev/pqtype v0.3.0 // indirect github.com/swaggest/jsonschema-go v0.3.72 // indirect github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.5 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/ftl/testdata/go/typeregistry/go.sum b/go-runtime/ftl/testdata/go/typeregistry/go.sum index d17b6aea5f..1ed06cc1b0 100644 --- a/go-runtime/ftl/testdata/go/typeregistry/go.sum +++ b/go-runtime/ftl/testdata/go/typeregistry/go.sum @@ -118,6 +118,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -147,21 +149,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -176,8 +178,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/internal/integration_test.go b/go-runtime/internal/integration_test.go index 9c7f07b566..5f2bca228b 100644 --- a/go-runtime/internal/integration_test.go +++ b/go-runtime/internal/integration_test.go @@ -11,7 +11,7 @@ import ( ) func TestRealMap(t *testing.T) { - Run(t, "", + Run(t, CopyModule("mapper"), Deploy("mapper"), Call("mapper", "get", Obj{}, func(t testing.TB, response Obj) { diff --git a/go-runtime/internal/testdata/go/mapper/go.mod b/go-runtime/internal/testdata/go/mapper/go.mod index ff497ddba4..08209801e2 100644 --- a/go-runtime/internal/testdata/go/mapper/go.mod +++ b/go-runtime/internal/testdata/go/mapper/go.mod @@ -38,12 +38,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/internal/testdata/go/mapper/go.sum b/go-runtime/internal/testdata/go/mapper/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/internal/testdata/go/mapper/go.sum +++ b/go-runtime/internal/testdata/go/mapper/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/scaffolding/bin/.go-1.22.5.pkg b/go-runtime/scaffolding/bin/.go-1.22.6.pkg similarity index 100% rename from go-runtime/scaffolding/bin/.go-1.22.5.pkg rename to go-runtime/scaffolding/bin/.go-1.22.6.pkg diff --git a/go-runtime/scaffolding/bin/go b/go-runtime/scaffolding/bin/go index 5c26cb9eda..fec23e291d 120000 --- a/go-runtime/scaffolding/bin/go +++ b/go-runtime/scaffolding/bin/go @@ -1 +1 @@ -.go-1.22.5.pkg \ No newline at end of file +.go-1.22.6.pkg \ No newline at end of file diff --git a/go-runtime/scaffolding/bin/gofmt b/go-runtime/scaffolding/bin/gofmt index 5c26cb9eda..fec23e291d 120000 --- a/go-runtime/scaffolding/bin/gofmt +++ b/go-runtime/scaffolding/bin/gofmt @@ -1 +1 @@ -.go-1.22.5.pkg \ No newline at end of file +.go-1.22.6.pkg \ No newline at end of file diff --git a/go-runtime/schema/common/directive.go b/go-runtime/schema/common/directive.go index 64db1e164d..ad5c37d92f 100644 --- a/go-runtime/schema/common/directive.go +++ b/go-runtime/schema/common/directive.go @@ -303,7 +303,7 @@ func (d *DirectiveExport) IsExported() bool { type DirectiveTypeMap struct { Pos token.Pos - Runtime string `parser:"'typemap' @('go' | 'kotlin')"` + Runtime string `parser:"'typemap' @('go' | 'kotlin' | 'java')"` NativeName string `parser:"@String"` } diff --git a/go-runtime/schema/extract.go b/go-runtime/schema/extract.go index d66eaf52cf..56091af5e2 100644 --- a/go-runtime/schema/extract.go +++ b/go-runtime/schema/extract.go @@ -41,43 +41,47 @@ import ( // It is a list of lists, where each list is a round of tasks dependent on the prior round's execution (e.g. an analyzer // in Extractors[1] will only execute once all analyzers in Extractors[0] complete). Elements of the same list // should be considered unordered and may run in parallel. -var Extractors = [][]*analysis.Analyzer{ - { - initialize.Analyzer, - inspect.Analyzer, - }, - { - metadata.Extractor, - }, - { - // must run before typeenumvariant.Extractor; typeenum.Extractor determines all possible discriminator - // interfaces and typeenumvariant.Extractor determines any types that implement these - typeenum.Extractor, - }, - { - configsecret.Extractor, - data.Extractor, - database.Extractor, - fsm.Extractor, - topic.Extractor, - typealias.Extractor, - typeenumvariant.Extractor, - valueenumvariant.Extractor, - verb.Extractor, - }, - { - call.Extractor, - // must run after valueenumvariant.Extractor and typeenumvariant.Extractor; - // visits a node and aggregates its enum variants if present - enum.Extractor, - subscription.Extractor, - }, - { - transitive.Extractor, - }, - { - finalize.Analyzer, - }, +var Extractors [][]*analysis.Analyzer + +func init() { + Extractors = [][]*analysis.Analyzer{ + { + initialize.Analyzer, + inspect.Analyzer, + }, + { + metadata.Extractor, + }, + { + // must run before typeenumvariant.Extractor; typeenum.Extractor determines all possible discriminator + // interfaces and typeenumvariant.Extractor determines any types that implement these + typeenum.Extractor, + }, + { + configsecret.Extractor, + data.Extractor, + database.Extractor, + fsm.Extractor, + topic.Extractor, + typealias.Extractor, + typeenumvariant.Extractor, + valueenumvariant.Extractor, + verb.Extractor, + }, + { + call.Extractor, + // must run after valueenumvariant.Extractor and typeenumvariant.Extractor; + // visits a node and aggregates its enum variants if present + enum.Extractor, + subscription.Extractor, + }, + { + transitive.Extractor, + }, + { + finalize.Analyzer, + }, + } } // NativeNames is a map of top-level declarations to their native Go names. @@ -297,7 +301,13 @@ func analyzersWithDependencies() []*analysis.Analyzer { func dependenciesBeforeIndex(idx int) []*analysis.Analyzer { var deps []*analysis.Analyzer for i := range idx { - deps = append(deps, Extractors[i]...) + for _, extractor := range Extractors[i] { + if extractor == nil { + panic(fmt.Sprintf("analyzer at Extractors[%d] not yet initialized", i)) + } + + deps = append(deps, extractor) + } } return deps } diff --git a/go-runtime/schema/schema_fuzz_test.go b/go-runtime/schema/schema_fuzz_test.go index ff504be410..7283d5aac3 100644 --- a/go-runtime/schema/schema_fuzz_test.go +++ b/go-runtime/schema/schema_fuzz_test.go @@ -192,7 +192,7 @@ func (ExportedVariant) exportedTag() {} var Topic = ftl.Topic[` + symbol + `]("topic") //ftl:export -var ExportedTopic = ftl.Topic[` + symbol + `]("exported_topic") +var ExportedTopic = ftl.Topic[` + symbol + `]("exportedTopic") var _ = ftl.Subscription(Topic, "subscription") @@ -334,7 +334,7 @@ module test { database postgres testDb - export topic exported_topic {{.TypeName}} + export topic exportedTopic {{.TypeName}} topic topic {{.TypeName}} subscription subscription test.topic diff --git a/go-runtime/schema/schema_test.go b/go-runtime/schema/schema_test.go index 6e33bdaa94..48d59a95e6 100644 --- a/go-runtime/schema/schema_test.go +++ b/go-runtime/schema/schema_test.go @@ -434,10 +434,10 @@ func TestExtractModulePubSub(t *testing.T) { actual := schema.Normalise(r.Module) expected := `module pubsub { topic payins pubsub.PayinEvent - // public_broadcast is a topic that broadcasts payin events to the public. + // publicBroadcast is a topic that broadcasts payin events to the public. // out of order with subscription registration to test ordering doesn't matter. - export topic public_broadcast pubsub.PayinEvent - subscription broadcastSubscription pubsub.public_broadcast + export topic publicBroadcast pubsub.PayinEvent + subscription broadcastSubscription pubsub.publicBroadcast subscription paymentProcessing pubsub.payins export data PayinEvent { @@ -469,7 +469,7 @@ func TestExtractModuleSubscriber(t *testing.T) { assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) expected := `module subscriber { - subscription subscriptionToExternalTopic pubsub.public_broadcast + subscription subscriptionToExternalTopic pubsub.publicBroadcast verb consumesSubscriptionFromExternalTopic(pubsub.PayinEvent) Unit +subscribe subscriptionToExternalTopic diff --git a/go-runtime/schema/subscription/analyzer.go b/go-runtime/schema/subscription/analyzer.go index ec5c4617e0..84dc481be6 100644 --- a/go-runtime/schema/subscription/analyzer.go +++ b/go-runtime/schema/subscription/analyzer.go @@ -63,7 +63,7 @@ func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr common.Errorf(pass, callExpr, "subscription registration must have a topic") return optional.None[*schema.Subscription]() } - name := strcase.ToLowerSnake(varName) + name := strcase.ToLowerCamel(varName) topicRef = &schema.Ref{ Module: moduleIdent.Name, Name: name, @@ -73,9 +73,15 @@ func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr return optional.None[*schema.Subscription]() } + subName := common.ExtractStringLiteralArg(pass, callExpr, 1) + expSubName := strcase.ToLowerCamel(subName) + if subName != expSubName { + common.Errorf(pass, node, "unsupported subscription name %q, did you mean to use %q?", subName, expSubName) + return optional.None[*schema.Subscription]() + } subscription := &schema.Subscription{ Pos: common.GoPosToSchemaPos(pass.Fset, callExpr.Pos()), - Name: common.ExtractStringLiteralArg(pass, callExpr, 1), + Name: subName, Topic: topicRef, } common.ApplyMetadata[*schema.Subscription](pass, obj, func(md *common.ExtractedMetadata) { diff --git a/go-runtime/schema/testdata/failing/go.mod b/go-runtime/schema/testdata/failing/go.mod index 20e11036ae..2b0089e252 100644 --- a/go-runtime/schema/testdata/failing/go.mod +++ b/go-runtime/schema/testdata/failing/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/failing/go.sum b/go-runtime/schema/testdata/failing/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/failing/go.sum +++ b/go-runtime/schema/testdata/failing/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/fsm/go.mod b/go-runtime/schema/testdata/fsm/go.mod index a36b3949cb..51d60813d7 100644 --- a/go-runtime/schema/testdata/fsm/go.mod +++ b/go-runtime/schema/testdata/fsm/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/fsm/go.sum b/go-runtime/schema/testdata/fsm/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/fsm/go.sum +++ b/go-runtime/schema/testdata/fsm/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/one/go.mod b/go-runtime/schema/testdata/one/go.mod index 7763d297af..33d7c3103c 100644 --- a/go-runtime/schema/testdata/one/go.mod +++ b/go-runtime/schema/testdata/one/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/one/go.sum b/go-runtime/schema/testdata/one/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/one/go.sum +++ b/go-runtime/schema/testdata/one/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/parent/go.mod b/go-runtime/schema/testdata/parent/go.mod index 24ee07a1d9..effb4395f9 100644 --- a/go-runtime/schema/testdata/parent/go.mod +++ b/go-runtime/schema/testdata/parent/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/parent/go.sum b/go-runtime/schema/testdata/parent/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/parent/go.sum +++ b/go-runtime/schema/testdata/parent/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/pubsub/go.mod b/go-runtime/schema/testdata/pubsub/go.mod index 9e1289cd7e..1cc90e2f41 100644 --- a/go-runtime/schema/testdata/pubsub/go.mod +++ b/go-runtime/schema/testdata/pubsub/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/pubsub/go.sum b/go-runtime/schema/testdata/pubsub/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/pubsub/go.sum +++ b/go-runtime/schema/testdata/pubsub/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/pubsub/pubsub.go b/go-runtime/schema/testdata/pubsub/pubsub.go index 2a729f4027..d2775b4d2f 100644 --- a/go-runtime/schema/testdata/pubsub/pubsub.go +++ b/go-runtime/schema/testdata/pubsub/pubsub.go @@ -32,11 +32,11 @@ var Payins = ftl.Topic[PayinEvent]("payins") var _ = ftl.Subscription(PublicBroadcast, "broadcastSubscription") -// public_broadcast is a topic that broadcasts payin events to the public. +// publicBroadcast is a topic that broadcasts payin events to the public. // out of order with subscription registration to test ordering doesn't matter. // //ftl:export -var PublicBroadcast = ftl.Topic[PayinEvent]("public_broadcast") +var PublicBroadcast = ftl.Topic[PayinEvent]("publicBroadcast") //ftl:verb export func Broadcast(ctx context.Context) error { diff --git a/go-runtime/schema/testdata/subscriber/go.mod b/go-runtime/schema/testdata/subscriber/go.mod index 35a7d9c389..31d5674483 100644 --- a/go-runtime/schema/testdata/subscriber/go.mod +++ b/go-runtime/schema/testdata/subscriber/go.mod @@ -36,13 +36,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/subscriber/go.sum b/go-runtime/schema/testdata/subscriber/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/subscriber/go.sum +++ b/go-runtime/schema/testdata/subscriber/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/two/go.mod b/go-runtime/schema/testdata/two/go.mod index df66aff063..3d275202bd 100644 --- a/go-runtime/schema/testdata/two/go.mod +++ b/go-runtime/schema/testdata/two/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/two/go.sum b/go-runtime/schema/testdata/two/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/two/go.sum +++ b/go-runtime/schema/testdata/two/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/testdata/validation/go.mod b/go-runtime/schema/testdata/validation/go.mod index 688040e8b0..5caa554e31 100644 --- a/go-runtime/schema/testdata/validation/go.mod +++ b/go-runtime/schema/testdata/validation/go.mod @@ -36,12 +36,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go-runtime/schema/testdata/validation/go.sum b/go-runtime/schema/testdata/validation/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/go-runtime/schema/testdata/validation/go.sum +++ b/go-runtime/schema/testdata/validation/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go-runtime/schema/topic/analyzer.go b/go-runtime/schema/topic/analyzer.go index ef87d9db1b..aa63036353 100644 --- a/go-runtime/schema/topic/analyzer.go +++ b/go-runtime/schema/topic/analyzer.go @@ -19,7 +19,7 @@ const ( // Extractor extracts topics. var Extractor = common.NewCallDeclExtractor[*schema.Topic]("topic", Extract, ftlTopicFuncPath) -// expects: var NameLiteral = ftl.Topic[EventType]("name_literal") +// Extract expects: var NameLiteral = ftl.Topic[EventType]("nameLiteral") func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, callPath string) optional.Option[*schema.Topic] { indexExpr, ok := callExpr.Fun.(*ast.IndexExpr) @@ -34,7 +34,7 @@ func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr } topicName := common.ExtractStringLiteralArg(pass, callExpr, 0) - expTopicName := strcase.ToLowerSnake(topicName) + expTopicName := strcase.ToLowerCamel(strcase.ToUpperStrippedCamel(topicName)) if topicName != expTopicName { common.Errorf(pass, node, "unsupported topic name %q, did you mean to use %q?", topicName, expTopicName) return optional.None[*schema.Topic]() @@ -43,7 +43,7 @@ func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr if len(node.Specs) > 0 { if t, ok := node.Specs[0].(*ast.ValueSpec); ok { varName := t.Names[0].Name - expVarName := strcase.ToUpperStrippedCamel(topicName) + expVarName := strcase.ToUpperCamel(topicName) if varName != expVarName { common.Errorf(pass, node, "unexpected topic variable name %q, did you mean %q?", varName, expVarName) return optional.None[*schema.Topic]() diff --git a/go.mod b/go.mod index 494f767c52..229522885f 100644 --- a/go.mod +++ b/go.mod @@ -18,9 +18,11 @@ require ( github.com/alecthomas/participle/v2 v2.1.1 github.com/alecthomas/types v0.16.0 github.com/amacneil/dbmate/v2 v2.19.0 + github.com/aws/aws-sdk-go v1.55.5 github.com/aws/aws-sdk-go-v2 v1.30.3 github.com/aws/aws-sdk-go-v2/config v1.27.27 github.com/aws/aws-sdk-go-v2/credentials v1.17.27 + github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 github.com/aws/smithy-go v1.20.3 github.com/beevik/etree v1.4.1 @@ -42,12 +44,14 @@ require ( github.com/radovskyb/watcher v1.0.7 github.com/rs/cors v1.11.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 + github.com/sqlc-dev/pqtype v0.3.0 github.com/swaggest/jsonschema-go v0.3.72 + github.com/tink-crypto/tink-go-awskms v0.0.0-20230616072154-ba4f9f22c3e9 github.com/tink-crypto/tink-go/v2 v2.2.0 github.com/titanous/json5 v1.0.0 - github.com/tliron/commonlog v0.2.17 + github.com/tliron/commonlog v0.2.18 github.com/tliron/glsp v0.2.2 - github.com/tliron/kutil v0.3.24 + github.com/tliron/kutil v0.3.25 github.com/tmc/langchaingo v0.1.12 github.com/zalando/go-keyring v0.2.5 go.opentelemetry.io/otel v1.28.0 @@ -59,12 +63,12 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 go.uber.org/automaxprocs v1.5.3 golang.org/x/exp v0.0.0-20240707233637-46b078467d37 - golang.org/x/mod v0.19.0 - golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 - golang.org/x/term v0.22.0 + golang.org/x/mod v0.20.0 + golang.org/x/net v0.28.0 + golang.org/x/sync v0.8.0 + golang.org/x/term v0.23.0 google.golang.org/protobuf v1.34.2 - modernc.org/sqlite v1.31.1 + modernc.org/sqlite v1.32.0 ) require ( @@ -86,6 +90,7 @@ require ( github.com/gorilla/websocket v1.5.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/iancoleman/strcase v0.3.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -127,16 +132,16 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect - github.com/lib/pq v1.10.9 // indirect + github.com/lib/pq v1.10.9 github.com/pelletier/go-toml v1.9.5 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b github.com/swaggest/refl v1.3.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/sys v0.22.0 - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.24.0 + golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240709173604-40e1e62336c5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5 // indirect google.golang.org/grpc v1.65.0 // indirect diff --git a/go.sum b/go.sum index 187085ef5a..93f56bfac0 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4u github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/amacneil/dbmate/v2 v2.19.0 h1:RqqkBv6/Jbupuv8GcpBXNdRdSjocddiwmfE56Oil6YA= github.com/amacneil/dbmate/v2 v2.19.0/go.mod h1:zyIkE4QZ+C+NxX0YNDb6zYlmEzCXMU7SGyYrtAJVDEk= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= @@ -56,6 +58,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvG github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= @@ -151,6 +155,10 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -227,6 +235,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/jsonrpc2 v0.2.0 h1:KjN/dC4fP6aN9030MZCJs9WQbTOjWHhrtKVpzzSrr/U= github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= +github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= +github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -240,16 +250,18 @@ github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNy github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/tink-crypto/tink-go-awskms v0.0.0-20230616072154-ba4f9f22c3e9 h1:MoIsYvBNJd8vkKZjLYloE3OK8bfcO10cMPw/EtydMBs= +github.com/tink-crypto/tink-go-awskms v0.0.0-20230616072154-ba4f9f22c3e9/go.mod h1:TTE4PoQLsYB5jQ1kK2g7WU4wzHg0Arn1CEozIUXiGSY= github.com/tink-crypto/tink-go/v2 v2.2.0 h1:L2Da0F2Udh2agtKztdr69mV/KpnY3/lGTkMgLTVIXlA= github.com/tink-crypto/tink-go/v2 v2.2.0/go.mod h1:JJ6PomeNPF3cJpfWC0lgyTES6zpJILkAX0cJNwlS3xU= github.com/titanous/json5 v1.0.0 h1:hJf8Su1d9NuI/ffpxgxQfxh/UiBFZX7bMPid0rIL/7s= github.com/titanous/json5 v1.0.0/go.mod h1:7JH1M8/LHKc6cyP5o5g3CSaRj+mBrIimTxzpvmckH8c= -github.com/tliron/commonlog v0.2.17 h1:GFVvzDZbNLkuQfT45IZeWkrR5AyqiX7Du8pWAtFuPTY= -github.com/tliron/commonlog v0.2.17/go.mod h1:J2Hb63/mMjYmkDzd7E+VL9wCHT6NFNSzV/IOjJWMJqc= +github.com/tliron/commonlog v0.2.18 h1:F0zY09VDGTasPCpP9KvE8xqqVNMUfwMJQ0Xvo5Y6BRs= +github.com/tliron/commonlog v0.2.18/go.mod h1:7f3OMSgVyGAFbRKwlvfUErnB6U75LgW8wa6NlWuswGg= github.com/tliron/glsp v0.2.2 h1:IKPfwpE8Lu8yB6Dayta+IyRMAbTVunudeauEgjXBt+c= github.com/tliron/glsp v0.2.2/go.mod h1:GMVWDNeODxHzmDPvYbYTCs7yHVaEATfYtXiYJ9w1nBg= -github.com/tliron/kutil v0.3.24 h1:LvaqizF4htpEef9tC0B//sqtvQzEjDu69A4a1HrY+ko= -github.com/tliron/kutil v0.3.24/go.mod h1:2iSIhOnOe1reqczZQy6TauVHhItsq6xRLV2rVBvodpk= +github.com/tliron/kutil v0.3.25 h1:oaPN6K0zsH3KcVnsocA3kAlfR0XYDzADob6xdjqe56k= +github.com/tliron/kutil v0.3.25/go.mod h1:ZvOJuF6PTGvjfHmn2dFcgz+EDEzRQqQUztK+7djlXIw= github.com/tmc/langchaingo v0.1.12 h1:yXwSu54f3b1IKw0jJ5/DWu+qFVH1NBblwC0xddBzGJE= github.com/tmc/langchaingo v0.1.12/go.mod h1:cd62xD6h+ouk8k/QQFhOsjRYBSA1JJ5UVKXSIgm7Ni4= github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= @@ -291,37 +303,37 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -345,6 +357,7 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -372,8 +385,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/integration/actions.go b/integration/actions.go index ebfe57d400..7875fd7429 100644 --- a/integration/actions.go +++ b/integration/actions.go @@ -51,13 +51,18 @@ func GitInit() Action { // Copy a module from the testdata directory to the working directory. // -// Ensures that replace directives are correctly handled. +// Ensures that any language-specific local modifications are made correctly, +// such as Go module file replace directives for FTL. func CopyModule(module string) Action { return Chain( CopyDir(module, module), func(t testing.TB, ic TestContext) { - err := ftlexec.Command(ic, log.Debug, filepath.Join(ic.workDir, module), "go", "mod", "edit", "-replace", "github.com/TBD54566975/ftl="+ic.RootDir).RunBuffered(ic) - assert.NoError(t, err) + root := filepath.Join(ic.workDir, module) + // TODO: Load the module configuration from the module itself and use that to determine the language-specific stuff. + if _, err := os.Stat(filepath.Join(root, "go.mod")); err == nil { + err := ftlexec.Command(ic, log.Debug, root, "go", "mod", "edit", "-replace", "github.com/TBD54566975/ftl="+ic.RootDir).RunBuffered(ic) + assert.NoError(t, err) + } }, ) } diff --git a/integration/harness.go b/integration/harness.go index da11527ae7..b9a192ecc2 100644 --- a/integration/harness.go +++ b/integration/harness.go @@ -43,39 +43,98 @@ func Infof(format string, args ...any) { var buildOnce sync.Once -// Run an integration test. -// ftlConfigPath: if FTL_CONFIG should be set for this test, then pass in the relative +// An Option for configuring the integration test harness. +type Option func(*options) + +// ActionOrOption is a type that can be either an Action or an Option. +type ActionOrOption any + +// WithLanguages is a Run* option that specifies the languages to test. // -// path based on ./testdata/go/ where "." denotes the directory containing the -// integration test (e.g. for "integration/harness_test.go" supplying -// "database/ftl-project.toml" would set FTL_CONFIG to -// "integration/testdata/go/database/ftl-project.toml"). -func Run(t *testing.T, ftlConfigPath string, actions ...Action) { - run(t, ftlConfigPath, true, actions...) +// Defaults to "go" if not provided. +func WithLanguages(languages ...string) Option { + return func(o *options) { + o.languages = languages + } } -// RunWithoutController runs an integration test without starting the controller. -// ftlConfigPath: if FTL_CONFIG should be set for this test, then pass in the relative +// WithFTLConfig is a Run* option that specifies the FTL config to use. // -// path based on ./testdata/go/ where "." denotes the directory containing the -// integration test (e.g. for "integration/harness_test.go" supplying -// "database/ftl-project.toml" would set FTL_CONFIG to -// "integration/testdata/go/database/ftl-project.toml"). -func RunWithoutController(t *testing.T, ftlConfigPath string, actions ...Action) { - run(t, ftlConfigPath, false, actions...) +// This will set FTL_CONFIG for this test, then pass in the relative +// path based on ./testdata/go/ where "." denotes the directory containing the +// integration test (e.g. for "integration/harness_test.go" supplying +// "database/ftl-project.toml" would set FTL_CONFIG to +// "integration/testdata/go/database/ftl-project.toml"). +func WithFTLConfig(path string) Option { + return func(o *options) { + o.ftlConfigPath = path + } +} + +// WithEnvar is a Run* option that specifies an environment variable to set. +func WithEnvar(key, value string) Option { + return func(o *options) { + o.envars[key] = value + } +} + +// WithJava is a Run* option that ensures the Java runtime is built. +func WithJava() Option { + return func(o *options) { + o.requireJava = true + } } -func RunWithEncryption(t *testing.T, ftlConfigPath string, actions ...Action) { - logKey := `{"primaryKeyId":1467957621,"key":[{"keyData":{"typeUrl":"type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey","value":"Eg4IgIBAECAYAyIECAMQIBog7t16YRvohzTJBKt0D4WcqFpoeWH0C20Hr09v+AxbOOE=","keyMaterialType":"SYMMETRIC"},"status":"ENABLED","keyId":1467957621,"outputPrefixType":"RAW"}]}` - asyncKey := `{"primaryKeyId":2710864232,"key":[{"keyData":{"typeUrl":"type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey","value":"Eg4IgIBAECAYAyIECAMQIBogTFCSLcJGRRazu74LrehNGL82J0sicjnjG5uNZcDyjGE=","keyMaterialType":"SYMMETRIC"},"status":"ENABLED","keyId":2710864232,"outputPrefixType":"RAW"}]}` +// WithoutController is a Run* option that disables starting the controller. +func WithoutController() Option { + return func(o *options) { + o.startController = false + } +} - t.Setenv("FTL_LOG_ENCRYPTION_KEY", logKey) - t.Setenv("FTL_ASYNC_ENCRYPTION_KEY", asyncKey) +type options struct { + languages []string + ftlConfigPath string + startController bool + requireJava bool + envars map[string]string +} - run(t, ftlConfigPath, true, actions...) +// Run an integration test. +func Run(t *testing.T, actionsOrOptions ...ActionOrOption) { + run(t, actionsOrOptions...) } -func run(t *testing.T, ftlConfigPath string, startController bool, actions ...Action) { +func run(t *testing.T, actionsOrOptions ...ActionOrOption) { + opts := options{ + startController: true, + languages: []string{"go"}, + envars: map[string]string{}, + } + actions := []Action{} + for _, opt := range actionsOrOptions { + switch o := opt.(type) { + case Action: + actions = append(actions, o) + + case func(t testing.TB, ic TestContext): + actions = append(actions, Action(o)) + + case Option: + o(&opts) + + case func(*options): + o(&opts) + + default: + panic(fmt.Sprintf("expected Option or Action, not %T", opt)) + } + } + + for key, value := range opts.envars { + t.Setenv(key, value) + } + tmpDir := t.TempDir() cwd, err := os.Getwd() @@ -84,12 +143,13 @@ func run(t *testing.T, ftlConfigPath string, startController bool, actions ...Ac rootDir, ok := internal.GitRoot("").Get() assert.True(t, ok) - if ftlConfigPath != "" { - ftlConfigPath = filepath.Join(cwd, "testdata", "go", ftlConfigPath) + if opts.ftlConfigPath != "" { + // TODO: We shouldn't be copying the shared config from the "go" testdata... + opts.ftlConfigPath = filepath.Join(cwd, "testdata", "go", opts.ftlConfigPath) projectPath := filepath.Join(tmpDir, "ftl-project.toml") // Copy the specified FTL config to the temporary directory. - err = copy.Copy(ftlConfigPath, projectPath) + err = copy.Copy(opts.ftlConfigPath, projectPath) if err == nil { t.Setenv("FTL_CONFIG", projectPath) } else { @@ -98,8 +158,8 @@ func run(t *testing.T, ftlConfigPath string, startController bool, actions ...Ac // can't be loaded until the module is copied over, and the config itself // is used by FTL during startup. // Some tests still rely on this behavior, so we can't remove it entirely. - t.Logf("Failed to copy %s to %s: %s", ftlConfigPath, projectPath, err) - t.Setenv("FTL_CONFIG", ftlConfigPath) + t.Logf("Failed to copy %s to %s: %s", opts.ftlConfigPath, projectPath, err) + t.Setenv("FTL_CONFIG", opts.ftlConfigPath) } } else { @@ -116,45 +176,53 @@ func run(t *testing.T, ftlConfigPath string, startController bool, actions ...Ac Infof("Building ftl") err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build", "ftl").RunBuffered(ctx) assert.NoError(t, err) + if opts.requireJava { + err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build-java").RunBuffered(ctx) + assert.NoError(t, err) + } }) - verbs := rpc.Dial(ftlv1connect.NewVerbServiceClient, "http://localhost:8892", log.Debug) - - var controller ftlv1connect.ControllerServiceClient - var console pbconsoleconnect.ConsoleServiceClient - if startController { - controller = rpc.Dial(ftlv1connect.NewControllerServiceClient, "http://localhost:8892", log.Debug) - console = rpc.Dial(pbconsoleconnect.NewConsoleServiceClient, "http://localhost:8892", log.Debug) - - Infof("Starting ftl cluster") - ctx = startProcess(ctx, t, filepath.Join(binDir, "ftl"), "serve", "--recreate") - } - - ic := TestContext{ - Context: ctx, - RootDir: rootDir, - testData: filepath.Join(cwd, "testdata", "go"), - workDir: tmpDir, - binDir: binDir, - Verbs: verbs, - } - - if startController { - ic.Controller = controller - ic.Console = console - - Infof("Waiting for controller to be ready") - ic.AssertWithRetry(t, func(t testing.TB, ic TestContext) { - _, err := ic.Controller.Status(ic, connect.NewRequest(&ftlv1.StatusRequest{})) - assert.NoError(t, err) + for _, language := range opts.languages { + t.Run(language, func(t *testing.T) { + verbs := rpc.Dial(ftlv1connect.NewVerbServiceClient, "http://localhost:8892", log.Debug) + + var controller ftlv1connect.ControllerServiceClient + var console pbconsoleconnect.ConsoleServiceClient + if opts.startController { + controller = rpc.Dial(ftlv1connect.NewControllerServiceClient, "http://localhost:8892", log.Debug) + console = rpc.Dial(pbconsoleconnect.NewConsoleServiceClient, "http://localhost:8892", log.Debug) + + Infof("Starting ftl cluster") + ctx = startProcess(ctx, t, filepath.Join(binDir, "ftl"), "serve", "--recreate") + } + + ic := TestContext{ + Context: ctx, + RootDir: rootDir, + testData: filepath.Join(cwd, "testdata", language), + workDir: tmpDir, + binDir: binDir, + Verbs: verbs, + } + + if opts.startController { + ic.Controller = controller + ic.Console = console + + Infof("Waiting for controller to be ready") + ic.AssertWithRetry(t, func(t testing.TB, ic TestContext) { + _, err := ic.Controller.Status(ic, connect.NewRequest(&ftlv1.StatusRequest{})) + assert.NoError(t, err) + }) + } + + Infof("Starting test") + + for _, action := range actions { + ic.AssertWithRetry(t, action) + } }) } - - Infof("Starting test") - - for _, action := range actions { - ic.AssertWithRetry(t, action) - } } type TestContext struct { @@ -237,7 +305,7 @@ func (l *logWriter) Write(p []byte) (n int, err error) { } } -// startProcess runs a binary in the background. +// startProcess runs a binary in the background and terminates it when the test completes. func startProcess(ctx context.Context, t testing.TB, args ...string) context.Context { t.Helper() ctx, cancel := context.WithCancel(ctx) diff --git a/internal/encryption/encryption.go b/internal/encryption/encryption.go index c3abe96726..9815b846bc 100644 --- a/internal/encryption/encryption.go +++ b/internal/encryption/encryption.go @@ -2,142 +2,206 @@ package encryption import ( "bytes" - "encoding/json" "fmt" - "io" "strings" - "github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset" + awsv1kms "github.com/aws/aws-sdk-go/service/kms" + "github.com/tink-crypto/tink-go-awskms/integration/awskms" + "github.com/tink-crypto/tink-go/v2/aead" + "github.com/tink-crypto/tink-go/v2/core/registry" + "github.com/tink-crypto/tink-go/v2/keyderivation" "github.com/tink-crypto/tink-go/v2/keyset" - "github.com/tink-crypto/tink-go/v2/streamingaead" + "github.com/tink-crypto/tink-go/v2/prf" + "github.com/tink-crypto/tink-go/v2/testing/fakekms" "github.com/tink-crypto/tink-go/v2/tink" ) -type Encryptable interface { - EncryptJSON(input any) (json.RawMessage, error) - DecryptJSON(input json.RawMessage, output any) error +type SubKey string + +const ( + TimelineSubKey SubKey = "timeline" + AsyncSubKey SubKey = "async" +) + +type DataEncryptor interface { + Encrypt(subKey SubKey, cleartext []byte) ([]byte, error) + Decrypt(subKey SubKey, encrypted []byte) ([]byte, error) } -func NewForKeyOrURI(keyOrURI string) (Encryptable, error) { - if len(keyOrURI) == 0 { - return NoOpEncryptor{}, nil - } +// NoOpEncryptorNext does not encrypt and just passes the input as is. +type NoOpEncryptorNext struct{} - // If keyOrUri is a JSON string, it is a clear text key set. - if strings.TrimSpace(keyOrURI)[0] == '{' { - return NewClearTextEncryptor(keyOrURI) - // Otherwise should be a URI for KMS. - // aws-kms://arn:aws:kms:[region]:[account-id]:key/[key-id] - } else if strings.HasPrefix(keyOrURI, "aws-kms://") { - return nil, fmt.Errorf("AWS KMS is not supported yet") - } - return nil, fmt.Errorf("unsupported key or uri: %s", keyOrURI) +func NewNoOpEncryptor() NoOpEncryptorNext { + return NoOpEncryptorNext{} +} + +func (n NoOpEncryptorNext) Encrypt(_ SubKey, cleartext []byte) ([]byte, error) { + return cleartext, nil } -// NoOpEncryptor does not encrypt and just passes the input as is. -type NoOpEncryptor struct { +func (n NoOpEncryptorNext) Decrypt(_ SubKey, encrypted []byte) ([]byte, error) { + return encrypted, nil } -func (n NoOpEncryptor) EncryptJSON(input any) (json.RawMessage, error) { - msg, err := json.Marshal(input) +// KMSEncryptor encrypts and decrypts using a KMS key via tink. +type KMSEncryptor struct { + root keyset.Handle + kekAEAD tink.AEAD + encryptedKeyset []byte + cachedDerived map[SubKey]tink.AEAD +} + +func newClientWithAEAD(uri string, kms *awsv1kms.KMS) (tink.AEAD, error) { + var client registry.KMSClient + var err error + + if strings.HasPrefix(strings.ToLower(uri), "fake-kms://") { + client, err = fakekms.NewClient(uri) + if err != nil { + return nil, fmt.Errorf("failed to create fake KMS client: %w", err) + } + + } else { + // tink does not support awsv2 yet + // https://github.com/tink-crypto/tink-go-awskms/issues/2 + var opts []awskms.ClientOption + if kms != nil { + opts = append(opts, awskms.WithKMS(kms)) + } + client, err = awskms.NewClientWithOptions(uri, opts...) + if err != nil { + return nil, fmt.Errorf("failed to create KMS client: %w", err) + } + } + + kekAEAD, err := client.GetAEAD(uri) if err != nil { - return nil, fmt.Errorf("failed to marshal input: %w", err) + return nil, fmt.Errorf("failed to get aead: %w", err) } - return msg, nil + return kekAEAD, nil } -func (n NoOpEncryptor) DecryptJSON(input json.RawMessage, output any) error { - err := json.Unmarshal(input, output) +func NewKMSEncryptorGenerateKey(uri string, v1client *awsv1kms.KMS) (*KMSEncryptor, error) { + kekAEAD, err := newClientWithAEAD(uri, v1client) if err != nil { - return fmt.Errorf("failed to unmarshal input: %w", err) + return nil, fmt.Errorf("failed to create KMS client: %w", err) } - return nil -} + // Create a PRF key template using HKDF-SHA256 + prfKeyTemplate := prf.HKDFSHA256PRFKeyTemplate() + + // Create an AES-256-GCM key template + aeadKeyTemplate := aead.AES256GCMKeyTemplate() + + template, err := keyderivation.CreatePRFBasedKeyTemplate(prfKeyTemplate, aeadKeyTemplate) + if err != nil { + return nil, fmt.Errorf("failed to create PRF based key template: %w", err) + } -func NewClearTextEncryptor(key string) (Encryptable, error) { - keySetHandle, err := insecurecleartextkeyset.Read( - keyset.NewJSONReader(bytes.NewBufferString(key))) + handle, err := keyset.NewHandle(template) if err != nil { - return nil, fmt.Errorf("failed to read clear text keyset: %w", err) + return nil, fmt.Errorf("failed to create keyset handle: %w", err) } - encryptor, err := NewEncryptor(*keySetHandle) + // Encrypt the keyset with the KEK AEAD. + buf := new(bytes.Buffer) + writer := keyset.NewBinaryWriter(buf) + err = handle.Write(writer, kekAEAD) if err != nil { - return nil, fmt.Errorf("failed to create clear text encryptor: %w", err) + return nil, fmt.Errorf("failed to encrypt DEK: %w", err) } + encryptedKeyset := buf.Bytes() - return encryptor, nil + return &KMSEncryptor{ + root: *handle, + kekAEAD: kekAEAD, + encryptedKeyset: encryptedKeyset, + cachedDerived: make(map[SubKey]tink.AEAD), + }, nil } -// NewEncryptor encrypts and decrypts JSON payloads using the provided key set. -// The key set must contain a primary key that is a streaming AEAD primitive. -func NewEncryptor(keySet keyset.Handle) (Encryptable, error) { - primitive, err := streamingaead.New(&keySet) +func NewKMSEncryptorWithKMS(uri string, v1client *awsv1kms.KMS, encryptedKeyset []byte) (*KMSEncryptor, error) { + kekAEAD, err := newClientWithAEAD(uri, v1client) if err != nil { - return nil, fmt.Errorf("failed to create primitive during encryption: %w", err) + return nil, fmt.Errorf("failed to create KMS client: %w", err) } - return Encryptor{keySetHandle: keySet, primitive: primitive}, nil -} + reader := keyset.NewBinaryReader(bytes.NewReader(encryptedKeyset)) + handle, err := keyset.Read(reader, kekAEAD) + if err != nil { + return nil, fmt.Errorf("failed to read keyset: %w", err) + } -type Encryptor struct { - keySetHandle keyset.Handle - primitive tink.StreamingAEAD + return &KMSEncryptor{ + root: *handle, + kekAEAD: kekAEAD, + encryptedKeyset: encryptedKeyset, + cachedDerived: make(map[SubKey]tink.AEAD), + }, nil } -type EncryptedPayload struct { - Encrypted []byte `json:"encrypted"` +func (k *KMSEncryptor) GetEncryptedKeyset() []byte { + return k.encryptedKeyset } -func (e Encryptor) EncryptJSON(input any) (json.RawMessage, error) { - msg, err := json.Marshal(input) +func deriveKeyset(root keyset.Handle, salt []byte) (*keyset.Handle, error) { + deriver, err := keyderivation.New(&root) if err != nil { - return nil, fmt.Errorf("failed to marshal input: %w", err) + return nil, fmt.Errorf("failed to create deriver: %w", err) } - encryptedBuffer := &bytes.Buffer{} - msgBuffer := bytes.NewBuffer(msg) - writer, err := e.primitive.NewEncryptingWriter(encryptedBuffer, nil) + derived, err := deriver.DeriveKeyset(salt) if err != nil { - return nil, fmt.Errorf("failed to create encrypting writer: %w", err) + return nil, fmt.Errorf("failed to derive keyset: %w", err) } - if _, err := io.Copy(writer, msgBuffer); err != nil { - return nil, fmt.Errorf("failed to copy encrypted data: %w", err) + return derived, nil +} + +func (k *KMSEncryptor) getDerivedPrimitive(subKey SubKey) (tink.AEAD, error) { + if primitive, ok := k.cachedDerived[subKey]; ok { + return primitive, nil } - if err := writer.Close(); err != nil { - return nil, fmt.Errorf("failed to close encrypted writer: %w", err) + + derived, err := deriveKeyset(k.root, []byte(subKey)) + if err != nil { + return nil, fmt.Errorf("failed to derive keyset: %w", err) } - out, err := json.Marshal(EncryptedPayload{Encrypted: encryptedBuffer.Bytes()}) + primitive, err := aead.New(derived) if err != nil { - return nil, fmt.Errorf("failed to marshal encrypted data: %w", err) + return nil, fmt.Errorf("failed to create primitive: %w", err) } - return out, nil + + k.cachedDerived[subKey] = primitive + return primitive, nil } -func (e Encryptor) DecryptJSON(input json.RawMessage, output any) error { - var payload EncryptedPayload - if err := json.Unmarshal(input, &payload); err != nil { - return fmt.Errorf("failed to unmarshal encrypted payload: %w", err) +func (k *KMSEncryptor) Encrypt(subKey SubKey, cleartext []byte) ([]byte, error) { + primitive, err := k.getDerivedPrimitive(subKey) + if err != nil { + return nil, fmt.Errorf("failed to get derived primitive: %w", err) } - inputBytesReader := bytes.NewReader(payload.Encrypted) - reader, err := e.primitive.NewDecryptingReader(inputBytesReader, nil) + encrypted, err := primitive.Encrypt(cleartext, nil) if err != nil { - return fmt.Errorf("failed to create decrypting reader: %w", err) + return nil, fmt.Errorf("failed to encrypt: %w", err) } - decryptedBuffer := &bytes.Buffer{} - if _, err := io.Copy(decryptedBuffer, reader); err != nil { - return fmt.Errorf("failed to copy decrypted data: %w", err) + return encrypted, nil +} + +func (k *KMSEncryptor) Decrypt(subKey SubKey, encrypted []byte) ([]byte, error) { + primitive, err := k.getDerivedPrimitive(subKey) + if err != nil { + return nil, fmt.Errorf("failed to get derived primitive: %w", err) } - if err := json.Unmarshal(decryptedBuffer.Bytes(), output); err != nil { - return fmt.Errorf("failed to unmarshal decrypted data: %w", err) + decrypted, err := primitive.Decrypt(encrypted, nil) + if err != nil { + return nil, fmt.Errorf("failed to decrypt: %w", err) } - return nil + return decrypted, nil } diff --git a/internal/encryption/encryption_test.go b/internal/encryption/encryption_test.go index ba707644c2..b30719ff17 100644 --- a/internal/encryption/encryption_test.go +++ b/internal/encryption/encryption_test.go @@ -1,46 +1,38 @@ package encryption import ( - "encoding/json" - "fmt" "testing" "github.com/alecthomas/assert/v2" ) -const key = `{ - "primaryKeyId": 1720777699, - "key": [{ - "keyData": { - "typeUrl": "type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey", - "keyMaterialType": "SYMMETRIC", - "value": "Eg0IgCAQIBgDIgQIAxAgGiDtesd/4gCnQdTrh+AXodwpm2b6BFJkp043n+8mqx0YGw==" - }, - "outputPrefixType": "RAW", - "keyId": 1720777699, - "status": "ENABLED" - }] - }` - -func TestNewEncryptor(t *testing.T) { - jsonInput := "\"hello\"" - - encryptor, err := NewForKeyOrURI(key) +func TestNoOpEncryptor(t *testing.T) { + encryptor := NoOpEncryptorNext{} + + encrypted, err := encryptor.Encrypt(TimelineSubKey, []byte("hunter2")) + assert.NoError(t, err) + + decrypted, err := encryptor.Decrypt(TimelineSubKey, encrypted) assert.NoError(t, err) - encrypted, err := encryptor.EncryptJSON(jsonInput) + assert.Equal(t, "hunter2", string(decrypted)) +} + +// echo -n "fake-kms://" && tinkey create-keyset --key-template AES128_GCM --out-format binary | base64 | tr '+/' '-_' | tr -d '=' +func TestKMSEncryptorFakeKMS(t *testing.T) { + uri := "fake-kms://CKbvh_ILElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEE6tD2yE5AWYOirhmkY-r3sYARABGKbvh_ILIAE" + + encryptor, err := NewKMSEncryptorGenerateKey(uri, nil) assert.NoError(t, err) - fmt.Printf("Encrypted: %s\n", encrypted) - var decrypted json.RawMessage - err = encryptor.DecryptJSON(encrypted, &decrypted) + encrypted, err := encryptor.Encrypt(TimelineSubKey, []byte("hunter2")) assert.NoError(t, err) - fmt.Printf("Decrypted: %s\n", decrypted) - var decryptedString string - err = json.Unmarshal(decrypted, &decryptedString) + decrypted, err := encryptor.Decrypt(TimelineSubKey, encrypted) assert.NoError(t, err) - fmt.Printf("Decrypted string: %s\n", decryptedString) + assert.Equal(t, "hunter2", string(decrypted)) - assert.Equal(t, jsonInput, decryptedString) + // Should fail to decrypt with the wrong subkey + _, err = encryptor.Decrypt(AsyncSubKey, encrypted) + assert.Error(t, err) } diff --git a/internal/encryption/integration_test.go b/internal/encryption/integration_test.go index fe956e3182..d56462e10c 100644 --- a/internal/encryption/integration_test.go +++ b/internal/encryption/integration_test.go @@ -3,19 +3,34 @@ package encryption import ( + "context" "fmt" "testing" "time" - "connectrpc.com/connect" pbconsole "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/console" in "github.com/TBD54566975/ftl/integration" + "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/slices" + "github.com/TBD54566975/ftl/testutils" + + "connectrpc.com/connect" "github.com/alecthomas/assert/v2" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kms" + awsv1 "github.com/aws/aws-sdk-go/aws" + awsv1credentials "github.com/aws/aws-sdk-go/aws/credentials" + awsv1session "github.com/aws/aws-sdk-go/aws/session" + awsv1kms "github.com/aws/aws-sdk-go/service/kms" ) +func WithEncryption() in.Option { + return in.WithEnvar("FTL_KMS_URI", "fake-kms://CKbvh_ILElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEE6tD2yE5AWYOirhmkY-r3sYARABGKbvh_ILIAE") +} + func TestEncryptionForLogs(t *testing.T) { - in.RunWithEncryption(t, "", + in.Run(t, + WithEncryption(), in.CopyModule("encryption"), in.Deploy("encryption"), in.Call[map[string]interface{}, any]("encryption", "echo", map[string]interface{}{"name": "Alice"}, nil), @@ -40,19 +55,19 @@ func TestEncryptionForLogs(t *testing.T) { }, // confirm that we can't find that raw request string in the table - in.QueryRow("ftl", "SELECT COUNT(*) FROM events WHERE type = 'call'", int64(1)), + in.QueryRow("ftl", "SELECT COUNT(*) FROM timeline WHERE type = 'call'", int64(1)), func(t testing.TB, ic in.TestContext) { - values := in.GetRow(t, ic, "ftl", "SELECT payload FROM events WHERE type = 'call' LIMIT 1", 1) + values := in.GetRow(t, ic, "ftl", "SELECT payload FROM timeline WHERE type = 'call' LIMIT 1", 1) payload, ok := values[0].([]byte) assert.True(t, ok, "could not convert payload to string") - assert.Contains(t, string(payload), "encrypted", "raw request string should not be stored in the table") assert.NotContains(t, string(payload), "Alice", "raw request string should not be stored in the table") }, ) } -func TestEncryptionForubSub(t *testing.T) { - in.RunWithEncryption(t, "", +func TestEncryptionForPubSub(t *testing.T) { + in.Run(t, + WithEncryption(), in.CopyModule("encryption"), in.Deploy("encryption"), in.Call[map[string]interface{}, any]("encryption", "publish", map[string]interface{}{"name": "AliceInWonderland"}, nil), @@ -65,7 +80,6 @@ func TestEncryptionForubSub(t *testing.T) { values := in.GetRow(t, ic, "ftl", "SELECT payload FROM topic_events", 1) payload, ok := values[0].([]byte) assert.True(t, ok, "could not convert payload to string") - assert.Contains(t, string(payload), "encrypted", "raw request string should not be stored in the table") assert.NotContains(t, string(payload), "AliceInWonderland", "raw request string should not be stored in the table") }, validateAsyncCall("consume", "AliceInWonderland"), @@ -73,7 +87,8 @@ func TestEncryptionForubSub(t *testing.T) { } func TestEncryptionForFSM(t *testing.T) { - in.RunWithEncryption(t, "", + in.Run(t, + WithEncryption(), in.CopyModule("encryption"), in.Deploy("encryption"), in.Call[map[string]interface{}, any]("encryption", "beginFsm", map[string]interface{}{"name": "Rosebud"}, nil), @@ -93,7 +108,43 @@ func validateAsyncCall(verb string, sensitive string) in.Action { values := in.GetRow(t, ic, "ftl", fmt.Sprintf("SELECT request FROM async_calls WHERE verb = 'encryption.%s' AND state = 'success'", verb), 1) request, ok := values[0].([]byte) assert.True(t, ok, "could not convert payload to string") - assert.Contains(t, string(request), "encrypted", "raw request string should not be stored in the table") assert.NotContains(t, string(request), sensitive, "raw request string should not be stored in the table") } } + +func TestKMSEncryptorLocalstack(t *testing.T) { + endpoint := "http://localhost:4566" + + ctx := log.ContextWithNewDefaultLogger(context.Background()) + cfg := testutils.NewLocalstackConfig(t, ctx) + v2client := kms.NewFromConfig(cfg, func(o *kms.Options) { + o.BaseEndpoint = aws.String(endpoint) + }) + createKey, err := v2client.CreateKey(ctx, &kms.CreateKeyInput{}) + assert.NoError(t, err) + uri := fmt.Sprintf("aws-kms://%s", *createKey.KeyMetadata.Arn) + fmt.Printf("URI: %s\n", uri) + + // tink does not support awsv2 yet so here be dragons + // https://github.com/tink-crypto/tink-go-awskms/issues/2 + s := awsv1session.Must(awsv1session.NewSession()) + v1client := awsv1kms.New(s, &awsv1.Config{ + Credentials: awsv1credentials.NewStaticCredentials("test", "test", ""), + Endpoint: awsv1.String(endpoint), + Region: awsv1.String("us-west-2"), + }) + + encryptor, err := NewKMSEncryptorGenerateKey(uri, v1client) + assert.NoError(t, err) + + encrypted, err := encryptor.Encrypt(TimelineSubKey, []byte("hunter2")) + assert.NoError(t, err) + + decrypted, err := encryptor.Decrypt(TimelineSubKey, encrypted) + assert.NoError(t, err) + assert.Equal(t, "hunter2", string(decrypted)) + + // Should fail to decrypt with the wrong subkey + _, err = encryptor.Decrypt(AsyncSubKey, encrypted) + assert.Error(t, err) +} diff --git a/internal/encryption/testdata/go/encryption/go.mod b/internal/encryption/testdata/go/encryption/go.mod index 0eabab575f..f4fcecbdf1 100644 --- a/internal/encryption/testdata/go/encryption/go.mod +++ b/internal/encryption/testdata/go/encryption/go.mod @@ -34,13 +34,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/internal/encryption/testdata/go/encryption/go.sum b/internal/encryption/testdata/go/encryption/go.sum index 359cfad1d6..9fbb9ebc36 100644 --- a/internal/encryption/testdata/go/encryption/go.sum +++ b/internal/encryption/testdata/go/encryption/go.sum @@ -111,21 +111,21 @@ go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnC go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -140,8 +140,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/modulecontext/database.go b/internal/modulecontext/database.go index a24080580b..fef41e6f54 100644 --- a/internal/modulecontext/database.go +++ b/internal/modulecontext/database.go @@ -21,7 +21,7 @@ type Database struct { func NewDatabase(dbType DBType, dsn string) (Database, error) { db, err := sql.Open("pgx", dsn) if err != nil { - return Database{}, err + return Database{}, fmt.Errorf("failed to bring up DB connection: %w", err) } return Database{ DSN: dsn, diff --git a/java-runtime/.gitignore b/java-runtime/.gitignore new file mode 100644 index 0000000000..08b1802abf --- /dev/null +++ b/java-runtime/.gitignore @@ -0,0 +1,6 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build +generated \ No newline at end of file diff --git a/java-runtime/README.md b/java-runtime/README.md new file mode 100644 index 0000000000..ffe6385512 --- /dev/null +++ b/java-runtime/README.md @@ -0,0 +1,22 @@ +# FTL Java Runtime + +This contains the code for the FTL Java runtime environment. + +## Tips + +### Debugging Maven commands with IntelliJ + +The Java runtime is built and packaged using Maven. If you would like to debug Maven commands using Intellij: + +1. Click `Run->Edit Configurations...` to bring up the run configurations window. + +2. Hit `+` to add a new configuration and select `Remove JVM Debug`. Provide the following configurations and save: +- `Debugger Mode`: `Attach to remote JVM` +- `Host`: `localhost` +- `Port`: `8000` +- `Command line arguments for remote JVM`: `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000` + +3. Run any `mvn` command, substituting `mvnDebug` in place of `mvn` (e.g. `mvnDebug compile`). This command should hang in the terminal, awaiting your remote debugger invocation. +4. Select the newly created debugger from the `Run / Debug Configurations` drop-down in the top right of your IntelliJ +window. With the debugger selected, hit the debug icon to run it. From here the `mvn` command will +execute, stopping at any breakpoints specified. \ No newline at end of file diff --git a/java-runtime/ftl-runtime/deployment/pom.xml b/java-runtime/ftl-runtime/deployment/pom.xml new file mode 100644 index 0000000000..db056f496e --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + xyz.block + ftl-java-runtime-parent + 1.0.0-SNAPSHOT + + ftl-java-runtime-deployment + Ftl Java Runtime - Deployment + + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-grpc-deployment + + + io.quarkus + quarkus-rest-jackson-deployment + + + + com.squareup + javapoet + + + + xyz.block + ftl-java-runtime + ${project.version} + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java new file mode 100644 index 0000000000..dce8144fed --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java @@ -0,0 +1,349 @@ +package xyz.block.ftl.deployment; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Stream; + +import javax.lang.model.element.Modifier; + +import org.eclipse.microprofile.config.Config; +import org.jboss.logging.Logger; +import org.jetbrains.annotations.NotNull; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; +import com.squareup.javapoet.WildcardTypeName; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.prebuild.CodeGenException; +import io.quarkus.deployment.CodeGenContext; +import io.quarkus.deployment.CodeGenProvider; +import xyz.block.ftl.GeneratedRef; +import xyz.block.ftl.Subscription; +import xyz.block.ftl.VerbClient; +import xyz.block.ftl.VerbClientDefinition; +import xyz.block.ftl.VerbClientEmpty; +import xyz.block.ftl.VerbClientSink; +import xyz.block.ftl.VerbClientSource; +import xyz.block.ftl.v1.schema.Module; +import xyz.block.ftl.v1.schema.Type; + +public class FTLCodeGenerator implements CodeGenProvider { + + private static final Logger log = Logger.getLogger(FTLCodeGenerator.class); + + public static final String CLIENT = "Client"; + public static final String PACKAGE_PREFIX = "ftl."; + String moduleName; + + @Override + public void init(ApplicationModel model, Map properties) { + CodeGenProvider.super.init(model, properties); + moduleName = model.getAppArtifact().getArtifactId(); + } + + @Override + public String providerId() { + return "ftl-clients"; + } + + @Override + public String inputDirectory() { + return "ftl-module-schema"; + } + + @Override + public boolean trigger(CodeGenContext context) throws CodeGenException { + if (!Files.isDirectory(context.inputDir())) { + return false; + } + + List modules = new ArrayList<>(); + + Map typeAliasMap = new HashMap<>(); + + try (Stream pathStream = Files.list(context.inputDir())) { + for (var file : pathStream.toList()) { + String fileName = file.getFileName().toString(); + if (!fileName.endsWith(".pb")) { + continue; + } + var module = Module.parseFrom(Files.readAllBytes(file)); + for (var decl : module.getDeclsList()) { + if (decl.hasTypeAlias()) { + var data = decl.getTypeAlias(); + typeAliasMap.put(new Key(module.getName(), data.getName()), data.getType()); + } + } + modules.add(module); + } + } catch (IOException e) { + throw new CodeGenException(e); + } + try { + for (var module : modules) { + String packageName = PACKAGE_PREFIX + module.getName(); + for (var decl : module.getDeclsList()) { + if (decl.hasVerb()) { + var verb = decl.getVerb(); + if (!verb.getExport()) { + continue; + } + + TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(className(verb.getName()) + CLIENT) + .addAnnotation(AnnotationSpec.builder(VerbClientDefinition.class) + .addMember("name", "\"" + verb.getName() + "\"") + .addMember("module", "\"" + module.getName() + "\"") + .build()) + .addModifiers(Modifier.PUBLIC); + if (verb.getRequest().hasUnit() && verb.getResponse().hasUnit()) { + typeBuilder.addSuperinterface(ClassName.get(VerbClientEmpty.class)); + } else if (verb.getRequest().hasUnit()) { + typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSource.class), + toJavaTypeName(verb.getResponse(), typeAliasMap))); + typeBuilder.addMethod(MethodSpec.methodBuilder("call") + .returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap)) + .addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build()); + } else if (verb.getResponse().hasUnit()) { + typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSink.class), + toJavaTypeName(verb.getRequest(), typeAliasMap))); + typeBuilder.addMethod(MethodSpec.methodBuilder("call").returns(TypeName.VOID) + .addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap), "value") + .addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build()); + } else { + typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClient.class), + toJavaTypeName(verb.getRequest(), typeAliasMap), + toJavaTypeName(verb.getResponse(), typeAliasMap))); + typeBuilder.addMethod(MethodSpec.methodBuilder("call") + .returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap)) + .addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap), "value") + .addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build()); + } + + TypeSpec helloWorld = typeBuilder + .build(); + + JavaFile javaFile = JavaFile.builder(packageName, helloWorld) + .build(); + + javaFile.writeTo(context.outDir()); + + } else if (decl.hasData()) { + var data = decl.getData(); + if (!data.getExport()) { + continue; + } + String thisType = className(data.getName()); + TypeSpec.Builder dataBuilder = TypeSpec.classBuilder(thisType) + .addAnnotation( + AnnotationSpec.builder(GeneratedRef.class) + .addMember("name", "\"" + data.getName() + "\"") + .addMember("module", "\"" + module.getName() + "\"").build()) + .addModifiers(Modifier.PUBLIC); + MethodSpec.Builder allConstructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); + + dataBuilder.addMethod(allConstructor.build()); + for (var param : data.getTypeParametersList()) { + dataBuilder.addTypeVariable(TypeVariableName.get(param.getName())); + } + Map sortedFields = new TreeMap<>(); + + for (var i : data.getFieldsList()) { + TypeName dataType = toAnnotatedJavaTypeName(i.getType(), typeAliasMap); + String name = i.getName(); + var fieldName = toJavaName(name); + dataBuilder.addField(dataType, fieldName, Modifier.PRIVATE); + sortedFields.put(fieldName, () -> { + allConstructor.addParameter(dataType, fieldName); + allConstructor.addCode("this.$L = $L;\n", fieldName, fieldName); + }); + String methodName = Character.toUpperCase(name.charAt(0)) + name.substring(1); + dataBuilder.addMethod(MethodSpec.methodBuilder("set" + methodName) + .addModifiers(Modifier.PUBLIC) + .addParameter(dataType, fieldName) + .returns(ClassName.get(packageName, thisType)) + .addCode("this.$L = $L;\n", fieldName, fieldName) + .addCode("return this;") + .build()); + if (i.getType().hasBool()) { + dataBuilder.addMethod(MethodSpec.methodBuilder("is" + methodName) + .addModifiers(Modifier.PUBLIC) + .returns(dataType) + .addCode("return $L;", fieldName) + .build()); + } else { + dataBuilder.addMethod(MethodSpec.methodBuilder("get" + methodName) + .addModifiers(Modifier.PUBLIC) + .returns(dataType) + .addCode("return $L;", fieldName) + .build()); + } + } + if (!sortedFields.isEmpty()) { + + for (var v : sortedFields.values()) { + v.run(); + } + dataBuilder.addMethod(allConstructor.build()); + + } + JavaFile javaFile = JavaFile.builder(packageName, dataBuilder.build()) + .build(); + + javaFile.writeTo(context.outDir()); + + } else if (decl.hasEnum()) { + var data = decl.getEnum(); + if (!data.getExport()) { + continue; + } + String thisType = className(data.getName()); + TypeSpec.Builder dataBuilder = TypeSpec.enumBuilder(thisType) + .addAnnotation( + AnnotationSpec.builder(GeneratedRef.class) + .addMember("name", "\"" + data.getName() + "\"") + .addMember("module", "\"" + module.getName() + "\"").build()) + .addModifiers(Modifier.PUBLIC); + + for (var i : data.getVariantsList()) { + dataBuilder.addEnumConstant(i.getName()); + } + + JavaFile javaFile = JavaFile.builder(packageName, dataBuilder.build()) + .build(); + + javaFile.writeTo(context.outDir()); + + } else if (decl.hasTopic()) { + var data = decl.getTopic(); + if (!data.getExport()) { + continue; + } + String thisType = className(data.getName() + "Subscription"); + + TypeSpec.Builder dataBuilder = TypeSpec.annotationBuilder(thisType) + .addModifiers(Modifier.PUBLIC); + if (data.getEvent().hasRef()) { + dataBuilder.addJavadoc("Subscription to the topic of type {@link $L}", + data.getEvent().getRef().getName()); + } + dataBuilder.addAnnotation(AnnotationSpec.builder(Retention.class) + .addMember("value", "java.lang.annotation.RetentionPolicy.RUNTIME").build()); + dataBuilder.addAnnotation(AnnotationSpec.builder(Subscription.class) + .addMember("topic", "\"" + data.getName() + "\"") + .addMember("module", "\"" + module.getName() + "\"") + .addMember("name", "\"" + data.getName() + "Subscription\"") + .build()); + + JavaFile javaFile = JavaFile.builder(packageName, dataBuilder.build()) + .build(); + + javaFile.writeTo(context.outDir()); + + } + } + } + + } catch (Exception e) { + throw new CodeGenException(e); + } + return true; + } + + private String toJavaName(String name) { + if (JAVA_KEYWORDS.contains(name)) { + return name + "_"; + } + return name; + } + + private TypeName toAnnotatedJavaTypeName(Type type, Map typeAliasMap) { + var results = toJavaTypeName(type, typeAliasMap); + if (type.hasRef() || type.hasArray() || type.hasBytes() || type.hasString() || type.hasMap() || type.hasTime()) { + return results.annotated(AnnotationSpec.builder(NotNull.class).build()); + } + return results; + } + + private TypeName toJavaTypeName(Type type, Map typeAliasMap) { + if (type.hasArray()) { + return ParameterizedTypeName.get(ClassName.get(List.class), + toJavaTypeName(type.getArray().getElement(), typeAliasMap)); + } else if (type.hasString()) { + return ClassName.get(String.class); + } else if (type.hasOptional()) { + return toJavaTypeName(type.getOptional().getType(), typeAliasMap); + } else if (type.hasRef()) { + if (type.getRef().getModule().isEmpty()) { + return TypeVariableName.get(type.getRef().getName()); + } + + Key key = new Key(type.getRef().getModule(), type.getRef().getName()); + if (typeAliasMap.containsKey(key)) { + return toJavaTypeName(typeAliasMap.get(key), typeAliasMap); + } + var params = type.getRef().getTypeParametersList(); + ClassName className = ClassName.get(PACKAGE_PREFIX + type.getRef().getModule(), type.getRef().getName()); + if (params.isEmpty()) { + return className; + } + List javaTypes = params.stream() + .map(s -> s.hasUnit() ? WildcardTypeName.subtypeOf(Object.class) : toJavaTypeName(s, typeAliasMap)) + .toList(); + return ParameterizedTypeName.get(className, javaTypes.toArray(new TypeName[javaTypes.size()])); + } else if (type.hasMap()) { + return ParameterizedTypeName.get(ClassName.get(Map.class), toJavaTypeName(type.getMap().getKey(), typeAliasMap), + toJavaTypeName(type.getMap().getValue(), typeAliasMap)); + } else if (type.hasTime()) { + return ClassName.get(Instant.class); + } else if (type.hasInt()) { + return TypeName.LONG; + } else if (type.hasUnit()) { + return TypeName.VOID; + } else if (type.hasBool()) { + return TypeName.BOOLEAN; + } else if (type.hasFloat()) { + return TypeName.DOUBLE; + } else if (type.hasBytes()) { + return ArrayTypeName.of(TypeName.BYTE); + } else if (type.hasAny()) { + return TypeName.OBJECT; + } + + throw new RuntimeException("Cannot generate Java type name: " + type); + } + + @Override + public boolean shouldRun(Path sourceDir, Config config) { + return true; + } + + record Key(String module, String name) { + } + + static String className(String in) { + return Character.toUpperCase(in.charAt(0)) + in.substring(1); + } + + private static final Set JAVA_KEYWORDS = Set.of("abstract", "continue", "for", "new", "switch", "assert", + "default", "goto", "package", "synchronized", "boolean", "do", "if", "private", "this", "break", "double", + "implements", "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum", "instanceof", + "return", "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", "void", + "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while"); +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java new file mode 100644 index 0000000000..538ecd739d --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java @@ -0,0 +1,705 @@ +package xyz.block.ftl.deployment; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.VoidType; +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.model.MethodParameter; +import org.jboss.resteasy.reactive.common.model.ParameterType; +import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; +import org.jboss.resteasy.reactive.server.mapping.URITemplate; +import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner; +import org.jetbrains.annotations.NotNull; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.builditem.ApplicationStartBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.builditem.SystemPropertyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.grpc.deployment.BindableServiceBuildItem; +import io.quarkus.netty.runtime.virtual.VirtualServerChannel; +import io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveResourceMethodEntriesBuildItem; +import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; +import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; +import io.quarkus.vertx.core.deployment.EventLoopCountBuildItem; +import io.quarkus.vertx.http.deployment.RequireVirtualHttpBuildItem; +import io.quarkus.vertx.http.deployment.WebsocketSubProtocolsBuildItem; +import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; +import io.quarkus.vertx.http.runtime.VertxHttpRecorder; +import xyz.block.ftl.Config; +import xyz.block.ftl.Cron; +import xyz.block.ftl.Export; +import xyz.block.ftl.GeneratedRef; +import xyz.block.ftl.LeaseClient; +import xyz.block.ftl.Retry; +import xyz.block.ftl.Secret; +import xyz.block.ftl.Subscription; +import xyz.block.ftl.Verb; +import xyz.block.ftl.VerbName; +import xyz.block.ftl.runtime.FTLController; +import xyz.block.ftl.runtime.FTLHttpHandler; +import xyz.block.ftl.runtime.FTLRecorder; +import xyz.block.ftl.runtime.JsonSerializationConfig; +import xyz.block.ftl.runtime.TopicHelper; +import xyz.block.ftl.runtime.VerbClientHelper; +import xyz.block.ftl.runtime.VerbHandler; +import xyz.block.ftl.runtime.VerbRegistry; +import xyz.block.ftl.runtime.builtin.HttpRequest; +import xyz.block.ftl.runtime.builtin.HttpResponse; +import xyz.block.ftl.v1.CallRequest; +import xyz.block.ftl.v1.schema.Array; +import xyz.block.ftl.v1.schema.Bool; +import xyz.block.ftl.v1.schema.Data; +import xyz.block.ftl.v1.schema.Decl; +import xyz.block.ftl.v1.schema.Field; +import xyz.block.ftl.v1.schema.Float; +import xyz.block.ftl.v1.schema.IngressPathComponent; +import xyz.block.ftl.v1.schema.IngressPathLiteral; +import xyz.block.ftl.v1.schema.IngressPathParameter; +import xyz.block.ftl.v1.schema.Int; +import xyz.block.ftl.v1.schema.Metadata; +import xyz.block.ftl.v1.schema.MetadataCalls; +import xyz.block.ftl.v1.schema.MetadataCronJob; +import xyz.block.ftl.v1.schema.MetadataIngress; +import xyz.block.ftl.v1.schema.MetadataRetry; +import xyz.block.ftl.v1.schema.MetadataSubscriber; +import xyz.block.ftl.v1.schema.Module; +import xyz.block.ftl.v1.schema.Optional; +import xyz.block.ftl.v1.schema.Ref; +import xyz.block.ftl.v1.schema.Time; +import xyz.block.ftl.v1.schema.Type; +import xyz.block.ftl.v1.schema.Unit; + +class FtlProcessor { + + private static final Logger log = Logger.getLogger(FtlProcessor.class); + + private static final String SCHEMA_OUT = "schema.pb"; + private static final String FEATURE = "ftl-java-runtime"; + public static final DotName EXPORT = DotName.createSimple(Export.class); + public static final DotName VERB = DotName.createSimple(Verb.class); + public static final DotName CRON = DotName.createSimple(Cron.class); + public static final DotName SUBSCRIPTION = DotName.createSimple(Subscription.class); + public static final String BUILTIN = "builtin"; + public static final DotName CONSUMER = DotName.createSimple(Consumer.class); + public static final DotName SECRET = DotName.createSimple(Secret.class); + public static final DotName CONFIG = DotName.createSimple(Config.class); + public static final DotName OFFSET_DATE_TIME = DotName.createSimple(OffsetDateTime.class.getName()); + public static final DotName GENERATED_REF = DotName.createSimple(GeneratedRef.class); + public static final DotName LEASE_CLIENT = DotName.createSimple(LeaseClient.class); + + @BuildStep + ModuleNameBuildItem moduleName(ApplicationInfoBuildItem applicationInfoBuildItem) { + return new ModuleNameBuildItem(applicationInfoBuildItem.getName()); + + } + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + BindableServiceBuildItem verbService() { + var ret = new BindableServiceBuildItem(DotName.createSimple(VerbHandler.class)); + ret.registerBlockingMethod("call"); + ret.registerBlockingMethod("publishEvent"); + ret.registerBlockingMethod("sendFSMEvent"); + ret.registerBlockingMethod("acquireLease"); + ret.registerBlockingMethod("getModuleContext"); + ret.registerBlockingMethod("ping"); + return ret; + } + + @BuildStep + AdditionalBeanBuildItem beans() { + return AdditionalBeanBuildItem.builder() + .addBeanClasses(VerbHandler.class, + VerbRegistry.class, FTLHttpHandler.class, FTLController.class, + TopicHelper.class, VerbClientHelper.class, JsonSerializationConfig.class) + .setUnremovable().build(); + } + + @BuildStep + AdditionalBeanBuildItem verbBeans(CombinedIndexBuildItem index) { + + var beans = AdditionalBeanBuildItem.builder().setUnremovable(); + for (var verb : index.getIndex().getAnnotations(VERB)) { + beans.addBeanClasses(verb.target().asMethod().declaringClass().name().toString()); + } + return beans.build(); + } + + @BuildStep + public SystemPropertyBuildItem moduleNameConfig(ApplicationInfoBuildItem applicationInfoBuildItem) { + return new SystemPropertyBuildItem("ftl.module.name", applicationInfoBuildItem.getName()); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public MethodScannerBuildItem methodScanners(TopicsBuildItem topics, + VerbClientBuildItem verbClients, FTLRecorder recorder) { + return new MethodScannerBuildItem(new MethodScanner() { + @Override + public ParameterExtractor handleCustomParameter(org.jboss.jandex.Type type, + Map annotations, boolean field, Map methodContext) { + try { + + if (annotations.containsKey(SECRET)) { + Class paramType = loadClass(type); + String name = annotations.get(SECRET).value().asString(); + return new VerbRegistry.SecretSupplier(name, paramType); + } else if (annotations.containsKey(CONFIG)) { + Class paramType = loadClass(type); + String name = annotations.get(CONFIG).value().asString(); + return new VerbRegistry.ConfigSupplier(name, paramType); + } else if (topics.getTopics().containsKey(type.name())) { + var topic = topics.getTopics().get(type.name()); + return recorder.topicParamExtractor(topic.generatedProducer()); + } else if (verbClients.getVerbClients().containsKey(type.name())) { + var client = verbClients.getVerbClients().get(type.name()); + return recorder.verbParamExtractor(client.generatedClient()); + } else if (LEASE_CLIENT.equals(type.name())) { + return recorder.leaseClientExtractor(); + } + return null; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + }); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public void registerVerbs(CombinedIndexBuildItem index, + FTLRecorder recorder, + OutputTargetBuildItem outputTargetBuildItem, + ResteasyReactiveResourceMethodEntriesBuildItem restEndpoints, + TopicsBuildItem topics, + VerbClientBuildItem verbClients, + ModuleNameBuildItem moduleNameBuildItem, + SubscriptionMetaAnnotationsBuildItem subscriptionMetaAnnotationsBuildItem) throws Exception { + String moduleName = moduleNameBuildItem.getModuleName(); + Module.Builder moduleBuilder = Module.newBuilder() + .setName(moduleName) + .setBuiltin(false); + Map dataElements = new HashMap<>(); + ExtractionContext extractionContext = new ExtractionContext(moduleName, index, recorder, moduleBuilder, dataElements, + new HashSet<>(), new HashSet<>(), topics.getTopics(), verbClients.getVerbClients()); + var beans = AdditionalBeanBuildItem.builder().setUnremovable(); + + //register all the topics we are defining in the module definition + + for (var topic : topics.getTopics().values()) { + extractionContext.moduleBuilder.addDecls(Decl.newBuilder().setTopic(xyz.block.ftl.v1.schema.Topic.newBuilder() + .setExport(topic.exported()) + .setName(topic.topicName()) + .setEvent(buildType(extractionContext, topic.eventType())).build())); + } + + handleVerbAnnotations(index, beans, extractionContext); + handleCronAnnotations(index, beans, extractionContext); + handleSubscriptionAnnotations(index, subscriptionMetaAnnotationsBuildItem, moduleName, moduleBuilder, extractionContext, + beans); + + //TODO: make this composable so it is not just one big method, build items should contribute to the schema + for (var endpoint : restEndpoints.getEntries()) { + //TODO: naming + var verbName = methodToName(endpoint.getMethodInfo()); + recorder.registerHttpIngress(moduleName, verbName); + + //TODO: handle type parameters properly + org.jboss.jandex.Type bodyParamType = VoidType.VOID; + MethodParameter[] parameters = endpoint.getResourceMethod().getParameters(); + for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) { + var param = parameters[i]; + if (param.parameterType.equals(ParameterType.BODY)) { + bodyParamType = endpoint.getMethodInfo().parameterType(i); + break; + } + } + + StringBuilder pathBuilder = new StringBuilder(); + if (endpoint.getBasicResourceClassInfo().getPath() != null) { + pathBuilder.append(endpoint.getBasicResourceClassInfo().getPath()); + } + if (endpoint.getResourceMethod().getPath() != null && !endpoint.getResourceMethod().getPath().isEmpty()) { + if (pathBuilder.charAt(pathBuilder.length() - 1) != '/' + && !endpoint.getResourceMethod().getPath().startsWith("/")) { + pathBuilder.append('/'); + } + pathBuilder.append(endpoint.getResourceMethod().getPath()); + } + String path = pathBuilder.toString(); + URITemplate template = new URITemplate(path, false); + List pathComponents = new ArrayList<>(); + for (var i : template.components) { + if (i.type == URITemplate.Type.CUSTOM_REGEX) { + throw new RuntimeException( + "Invalid path " + path + " on HTTP endpoint: " + endpoint.getActualClassInfo().name() + "." + + methodToName(endpoint.getMethodInfo()) + + " FTL does not support custom regular expressions"); + } else if (i.type == URITemplate.Type.LITERAL) { + if (i.literalText.equals("/")) { + continue; + } + pathComponents.add(IngressPathComponent.newBuilder() + .setIngressPathLiteral(IngressPathLiteral.newBuilder().setText(i.literalText.replace("/", ""))) + .build()); + } else { + pathComponents.add(IngressPathComponent.newBuilder() + .setIngressPathParameter(IngressPathParameter.newBuilder().setName(i.name.replace("/", ""))) + .build()); + } + } + + //TODO: process path properly + MetadataIngress.Builder ingressBuilder = MetadataIngress.newBuilder() + .setMethod(endpoint.getResourceMethod().getHttpMethod()); + for (var i : pathComponents) { + ingressBuilder.addPath(i); + } + Metadata ingressMetadata = Metadata.newBuilder() + .setIngress(ingressBuilder + .build()) + .build(); + Type requestTypeParam = buildType(extractionContext, bodyParamType); + Type responseTypeParam = buildType(extractionContext, endpoint.getMethodInfo().returnType()); + moduleBuilder + .addDecls(Decl.newBuilder().setVerb(xyz.block.ftl.v1.schema.Verb.newBuilder() + .addMetadata(ingressMetadata) + .setName(verbName) + .setExport(true) + .setRequest(Type.newBuilder() + .setRef(Ref.newBuilder().setModule(BUILTIN).setName(HttpRequest.class.getSimpleName()) + .addTypeParameters(requestTypeParam)) + .build()) + .setResponse(Type.newBuilder() + .setRef(Ref.newBuilder().setModule(BUILTIN).setName(HttpResponse.class.getSimpleName()) + .addTypeParameters(responseTypeParam) + .addTypeParameters(Type.newBuilder().setUnit(Unit.newBuilder()))) + .build())) + .build()); + } + + Path output = outputTargetBuildItem.getOutputDirectory().resolve(SCHEMA_OUT); + try (var out = Files.newOutputStream(output)) { + moduleBuilder.build().writeTo(out); + } + + output = outputTargetBuildItem.getOutputDirectory().resolve("main"); + try (var out = Files.newOutputStream(output)) { + out.write(""" + #!/bin/bash + exec java -jar quarkus-app/quarkus-run.jar""".getBytes(StandardCharsets.UTF_8)); + } + var perms = Files.getPosixFilePermissions(output); + EnumSet newPerms = EnumSet.copyOf(perms); + newPerms.add(PosixFilePermission.GROUP_EXECUTE); + newPerms.add(PosixFilePermission.OWNER_EXECUTE); + Files.setPosixFilePermissions(output, newPerms); + } + + private void handleVerbAnnotations(CombinedIndexBuildItem index, AdditionalBeanBuildItem.Builder beans, + ExtractionContext extractionContext) { + for (var verb : index.getIndex().getAnnotations(VERB)) { + boolean exported = verb.target().hasAnnotation(EXPORT); + var method = verb.target().asMethod(); + String className = method.declaringClass().name().toString(); + beans.addBeanClass(className); + + handleVerbMethod(extractionContext, method, className, exported, BodyType.ALLOWED, null); + } + } + + private void handleSubscriptionAnnotations(CombinedIndexBuildItem index, + SubscriptionMetaAnnotationsBuildItem subscriptionMetaAnnotationsBuildItem, String moduleName, + Module.Builder moduleBuilder, ExtractionContext extractionContext, AdditionalBeanBuildItem.Builder beans) { + for (var subscription : index.getIndex().getAnnotations(SUBSCRIPTION)) { + var info = SubscriptionMetaAnnotationsBuildItem.fromJandex(subscription, moduleName); + if (subscription.target().kind() != AnnotationTarget.Kind.METHOD) { + continue; + } + var method = subscription.target().asMethod(); + String className = method.declaringClass().name().toString(); + generateSubscription(moduleBuilder, extractionContext, beans, method, className, info); + } + for (var metaSub : subscriptionMetaAnnotationsBuildItem.getAnnotations().entrySet()) { + for (var subscription : index.getIndex().getAnnotations(metaSub.getKey())) { + if (subscription.target().kind() != AnnotationTarget.Kind.METHOD) { + log.warnf("Subscription annotation on non-method target: %s", subscription.target()); + continue; + } + var method = subscription.target().asMethod(); + generateSubscription(moduleBuilder, extractionContext, beans, method, + method.declaringClass().name().toString(), + metaSub.getValue()); + } + + } + } + + private void handleCronAnnotations(CombinedIndexBuildItem index, AdditionalBeanBuildItem.Builder beans, + ExtractionContext extractionContext) { + for (var cron : index.getIndex().getAnnotations(CRON)) { + var method = cron.target().asMethod(); + String className = method.declaringClass().name().toString(); + beans.addBeanClass(className); + handleVerbMethod(extractionContext, method, className, false, BodyType.DISALLOWED, (builder -> { + builder.addMetadata(Metadata.newBuilder() + .setCronJob(MetadataCronJob.newBuilder().setCron(cron.value().asString())).build()); + })); + } + } + + private void generateSubscription(Module.Builder moduleBuilder, ExtractionContext extractionContext, + AdditionalBeanBuildItem.Builder beans, MethodInfo method, String className, + SubscriptionMetaAnnotationsBuildItem.SubscriptionAnnotation info) { + beans.addBeanClass(className); + moduleBuilder.addDecls(Decl.newBuilder().setSubscription(xyz.block.ftl.v1.schema.Subscription.newBuilder() + .setName(info.name()).setTopic(Ref.newBuilder().setName(info.topic()).setModule(info.module()).build())) + .build()); + handleVerbMethod(extractionContext, method, className, false, BodyType.REQUIRED, (builder -> { + builder.addMetadata(Metadata.newBuilder().setSubscriber(MetadataSubscriber.newBuilder().setName(info.name()))); + if (method.hasAnnotation(Retry.class)) { + RetryRecord retry = RetryRecord.fromJandex(method.annotation(Retry.class), extractionContext.moduleName); + + MetadataRetry.Builder retryBuilder = MetadataRetry.newBuilder(); + if (!retry.catchVerb().isEmpty()) { + retryBuilder.setCatch(Ref.newBuilder().setModule(retry.catchModule()) + .setName(retry.catchVerb()).build()); + } + retryBuilder.setCount(retry.count()) + .setMaxBackoff(retry.maxBackoff()) + .setMinBackoff(retry.minBackoff()); + builder.addMetadata(Metadata.newBuilder().setRetry(retryBuilder).build()); + } + })); + } + + private void handleVerbMethod(ExtractionContext context, MethodInfo method, String className, + boolean exported, BodyType bodyType, Consumer metadataCallback) { + try { + List> parameterTypes = new ArrayList<>(); + List> paramMappers = new ArrayList<>(); + org.jboss.jandex.Type bodyParamType = null; + xyz.block.ftl.v1.schema.Verb.Builder verbBuilder = xyz.block.ftl.v1.schema.Verb.newBuilder(); + String verbName = methodToName(method); + MetadataCalls.Builder callsMetadata = MetadataCalls.newBuilder(); + for (var param : method.parameters()) { + if (param.hasAnnotation(Secret.class)) { + Class paramType = loadClass(param.type()); + parameterTypes.add(paramType); + String name = param.annotation(Secret.class).value().asString(); + paramMappers.add(new VerbRegistry.SecretSupplier(name, paramType)); + if (!context.knownSecrets.contains(name)) { + context.moduleBuilder.addDecls(Decl.newBuilder().setSecret(xyz.block.ftl.v1.schema.Secret.newBuilder() + .setType(buildType(context, param.type())).setName(name))); + context.knownSecrets.add(name); + } + } else if (param.hasAnnotation(Config.class)) { + Class paramType = loadClass(param.type()); + parameterTypes.add(paramType); + String name = param.annotation(Config.class).value().asString(); + paramMappers.add(new VerbRegistry.ConfigSupplier(name, paramType)); + if (!context.knownConfig.contains(name)) { + context.moduleBuilder.addDecls(Decl.newBuilder().setConfig(xyz.block.ftl.v1.schema.Config.newBuilder() + .setType(buildType(context, param.type())).setName(name))); + context.knownConfig.add(name); + } + } else if (context.knownTopics.containsKey(param.type().name())) { + var topic = context.knownTopics.get(param.type().name()); + Class paramType = loadClass(param.type()); + parameterTypes.add(paramType); + paramMappers.add(context.recorder().topicSupplier(topic.generatedProducer(), verbName)); + } else if (context.verbClients.containsKey(param.type().name())) { + var client = context.verbClients.get(param.type().name()); + Class paramType = loadClass(param.type()); + parameterTypes.add(paramType); + paramMappers.add(context.recorder().verbClientSupplier(client.generatedClient())); + callsMetadata.addCalls(Ref.newBuilder().setName(client.name()).setModule(client.module()).build()); + } else if (LEASE_CLIENT.equals(param.type().name())) { + parameterTypes.add(LeaseClient.class); + paramMappers.add(context.recorder().leaseClientSupplier()); + } else if (bodyType != BodyType.DISALLOWED && bodyParamType == null) { + bodyParamType = param.type(); + Class paramType = loadClass(param.type()); + parameterTypes.add(paramType); + //TODO: map and list types + paramMappers.add(new VerbRegistry.BodySupplier(paramType)); + } else { + throw new RuntimeException("Unknown parameter type " + param.type() + " on FTL method: " + + method.declaringClass().name() + "." + method.name()); + } + } + if (bodyParamType == null) { + if (bodyType == BodyType.REQUIRED) { + throw new RuntimeException("Missing required payload parameter"); + } + bodyParamType = VoidType.VOID; + } + if (callsMetadata.getCallsCount() > 0) { + verbBuilder.addMetadata(Metadata.newBuilder().setCalls(callsMetadata)); + } + + //TODO: we need better handling around Optional + context.recorder.registerVerb(context.moduleName(), verbName, method.name(), parameterTypes, + Class.forName(className, false, Thread.currentThread().getContextClassLoader()), paramMappers, + method.returnType() == VoidType.VOID); + verbBuilder + .setName(verbName) + .setExport(exported) + .setRequest(buildType(context, bodyParamType)) + .setResponse(buildType(context, method.returnType())); + + if (metadataCallback != null) { + metadataCallback.accept(verbBuilder); + } + context.moduleBuilder + .addDecls(Decl.newBuilder().setVerb(verbBuilder) + .build()); + + } catch (Exception e) { + throw new RuntimeException("Failed to process FTL method " + method.declaringClass().name() + "." + method.name(), + e); + } + } + + private static @NotNull String methodToName(MethodInfo method) { + if (method.hasAnnotation(VerbName.class)) { + return method.annotation(VerbName.class).value().asString(); + } + return method.name(); + } + + private static Class loadClass(org.jboss.jandex.Type param) throws ClassNotFoundException { + if (param.kind() == org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE) { + return Class.forName(param.asParameterizedType().name().toString(), false, + Thread.currentThread().getContextClassLoader()); + } else if (param.kind() == org.jboss.jandex.Type.Kind.CLASS) { + return Class.forName(param.name().toString(), false, Thread.currentThread().getContextClassLoader()); + } else if (param.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE) { + switch (param.asPrimitiveType().primitive()) { + case BOOLEAN: + return Boolean.TYPE; + case BYTE: + return Byte.TYPE; + case SHORT: + return Short.TYPE; + case INT: + return Integer.TYPE; + case LONG: + return Long.TYPE; + case FLOAT: + return java.lang.Float.TYPE; + case DOUBLE: + return java.lang.Double.TYPE; + case CHAR: + return Character.TYPE; + default: + throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive()); + } + } else { + throw new RuntimeException("Unknown type " + param.kind()); + } + } + + /** + * This is a huge hack that is needed until Quarkus supports both virtual and socket based HTTP + */ + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + void openSocket(ApplicationStartBuildItem start, + LaunchModeBuildItem launchMode, + CoreVertxBuildItem vertx, + ShutdownContextBuildItem shutdown, + BuildProducer reflectiveClass, + HttpBuildTimeConfig httpBuildTimeConfig, + java.util.Optional requireVirtual, + EventLoopCountBuildItem eventLoopCount, + List websocketSubProtocols, + Capabilities capabilities, + VertxHttpRecorder recorder) throws IOException { + reflectiveClass + .produce(ReflectiveClassBuildItem.builder(VirtualServerChannel.class) + .build()); + recorder.startServer(vertx.getVertx(), shutdown, + launchMode.getLaunchMode(), true, false, + eventLoopCount.getEventLoopCount(), + websocketSubProtocols.stream().map(bi -> bi.getWebsocketSubProtocols()) + .collect(Collectors.toList()), + launchMode.isAuxiliaryApplication(), !capabilities.isPresent(Capability.VERTX_WEBSOCKETS)); + } + + private Type buildType(ExtractionContext context, org.jboss.jandex.Type type) { + switch (type.kind()) { + case PRIMITIVE -> { + var prim = type.asPrimitiveType(); + switch (prim.primitive()) { + case INT, LONG, BYTE, SHORT -> { + return Type.newBuilder().setInt(Int.newBuilder().build()).build(); + } + case FLOAT, DOUBLE -> { + return Type.newBuilder().setFloat(Float.newBuilder().build()).build(); + } + case BOOLEAN -> { + return Type.newBuilder().setBool(Bool.newBuilder().build()).build(); + } + case CHAR -> { + return Type.newBuilder().setString(xyz.block.ftl.v1.schema.String.newBuilder().build()).build(); + } + default -> throw new RuntimeException("unknown primitive type: " + prim.primitive()); + } + } + case VOID -> { + return Type.newBuilder().setUnit(Unit.newBuilder().build()).build(); + } + case ARRAY -> { + return Type.newBuilder() + .setArray(Array.newBuilder().setElement(buildType(context, type.asArrayType().componentType())).build()) + .build(); + } + case CLASS -> { + var clazz = type.asClassType(); + var info = context.index().getComputingIndex().getClassByName(clazz.name()); + if (info != null && info.hasDeclaredAnnotation(GENERATED_REF)) { + var ref = info.declaredAnnotation(GENERATED_REF); + return Type.newBuilder() + .setRef(Ref.newBuilder().setName(ref.value("name").asString()) + .setModule(ref.value("module").asString())) + .build(); + } + if (clazz.name().equals(DotName.STRING_NAME)) { + return Type.newBuilder().setString(xyz.block.ftl.v1.schema.String.newBuilder().build()).build(); + } + if (clazz.name().equals(OFFSET_DATE_TIME)) { + return Type.newBuilder().setTime(Time.newBuilder().build()).build(); + } + var existing = context.dataElements.get(new TypeKey(clazz.name().toString(), List.of())); + if (existing != null) { + return Type.newBuilder().setRef(existing).build(); + } + Data.Builder data = Data.newBuilder(); + data.setName(clazz.name().local()); + data.setExport(type.hasAnnotation(EXPORT)); + buildDataElement(context, data, clazz.name()); + context.moduleBuilder.addDecls(Decl.newBuilder().setData(data).build()); + Ref ref = Ref.newBuilder().setName(data.getName()).setModule(context.moduleName).build(); + context.dataElements.put(new TypeKey(clazz.name().toString(), List.of()), ref); + return Type.newBuilder().setRef(ref).build(); + } + case PARAMETERIZED_TYPE -> { + var paramType = type.asParameterizedType(); + if (paramType.name().equals(DotName.createSimple(List.class))) { + return Type.newBuilder() + .setArray(Array.newBuilder().setElement(buildType(context, paramType.arguments().get(0)))).build(); + } else if (paramType.name().equals(DotName.createSimple(Map.class))) { + return Type.newBuilder().setMap(xyz.block.ftl.v1.schema.Map.newBuilder() + .setKey(buildType(context, paramType.arguments().get(0))) + .setValue(buildType(context, paramType.arguments().get(0)))) + .build(); + } else if (paramType.name().equals(DotNames.OPTIONAL)) { + return Type.newBuilder() + .setOptional(Optional.newBuilder().setType(buildType(context, paramType.arguments().get(0)))) + .build(); + } else if (paramType.name().equals(DotName.createSimple(HttpRequest.class))) { + return Type.newBuilder() + .setRef(Ref.newBuilder().setModule(BUILTIN).setName(HttpRequest.class.getSimpleName()) + .addTypeParameters(buildType(context, paramType.arguments().get(0)))) + .build(); + } else if (paramType.name().equals(DotName.createSimple(HttpResponse.class))) { + return Type.newBuilder() + .setRef(Ref.newBuilder().setModule(BUILTIN).setName(HttpResponse.class.getSimpleName()) + .addTypeParameters(buildType(context, paramType.arguments().get(0))) + .addTypeParameters(Type.newBuilder().setUnit(Unit.newBuilder().build()))) + .build(); + } else { + ClassInfo classByName = context.index().getComputingIndex().getClassByName(paramType.name()); + var cb = ClassType.builder(classByName.name()); + var main = buildType(context, cb.build()); + var builder = main.toBuilder(); + var refBuilder = builder.getRef().toBuilder(); + + for (var arg : paramType.arguments()) { + refBuilder.addTypeParameters(buildType(context, arg)); + } + builder.setRef(refBuilder); + return builder.build(); + } + } + } + + throw new RuntimeException("NOT YET IMPLEMENTED"); + } + + private void buildDataElement(ExtractionContext context, Data.Builder data, DotName className) { + if (className == null || className.equals(DotName.OBJECT_NAME)) { + return; + } + var clazz = context.index.getComputingIndex().getClassByName(className); + if (clazz == null) { + return; + } + //TODO: handle getters and setters properly, also Jackson annotations etc + for (var field : clazz.fields()) { + if (!Modifier.isStatic(field.flags())) { + data.addFields(Field.newBuilder().setName(field.name()).setType(buildType(context, field.type())).build()); + } + } + buildDataElement(context, data, clazz.superName()); + } + + private record TypeKey(String name, List typeParams) { + + } + + record ExtractionContext(String moduleName, CombinedIndexBuildItem index, FTLRecorder recorder, + Module.Builder moduleBuilder, + Map dataElements, Set knownSecrets, Set knownConfig, + Map knownTopics, + Map verbClients) { + } + + enum BodyType { + DISALLOWED, + ALLOWED, + REQUIRED + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/ModuleNameBuildItem.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/ModuleNameBuildItem.java new file mode 100644 index 0000000000..c47c10bcf5 --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/ModuleNameBuildItem.java @@ -0,0 +1,16 @@ +package xyz.block.ftl.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class ModuleNameBuildItem extends SimpleBuildItem { + + final String moduleName; + + public ModuleNameBuildItem(String moduleName) { + this.moduleName = moduleName; + } + + public String getModuleName() { + return moduleName; + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/RetryRecord.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/RetryRecord.java new file mode 100644 index 0000000000..8cf3621b68 --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/RetryRecord.java @@ -0,0 +1,19 @@ +package xyz.block.ftl.deployment; + +import org.jboss.jandex.AnnotationInstance; + +public record RetryRecord(int count, String minBackoff, String maxBackoff, String catchModule, String catchVerb) { + + public static RetryRecord fromJandex(AnnotationInstance nested, String currentModuleName) { + return new RetryRecord( + nested.value("count") != null ? nested.value("count").asInt() : 0, + nested.value("minBackoff") != null ? nested.value("minBackoff").asString() : "", + nested.value("maxBackoff") != null ? nested.value("maxBackoff").asString() : "", + nested.value("catchModule") != null ? nested.value("catchModule").asString() : currentModuleName, + nested.value("catchVerb") != null ? nested.value("catchVerb").asString() : ""); + } + + public boolean isEmpty() { + return count == 0 && minBackoff.isEmpty() && maxBackoff.isEmpty() && catchVerb.isEmpty(); + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/SubscriptionMetaAnnotationsBuildItem.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/SubscriptionMetaAnnotationsBuildItem.java new file mode 100644 index 0000000000..83e2d35cf5 --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/SubscriptionMetaAnnotationsBuildItem.java @@ -0,0 +1,35 @@ +package xyz.block.ftl.deployment; + +import java.util.Map; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class SubscriptionMetaAnnotationsBuildItem extends SimpleBuildItem { + + private final Map annotations; + + public SubscriptionMetaAnnotationsBuildItem(Map annotations) { + this.annotations = annotations; + } + + public Map getAnnotations() { + return annotations; + } + + public record SubscriptionAnnotation(String module, String topic, String name) { + } + + public static SubscriptionAnnotation fromJandex(AnnotationInstance subscriptions, String currentModuleName) { + AnnotationValue moduleValue = subscriptions.value("module"); + + return new SubscriptionMetaAnnotationsBuildItem.SubscriptionAnnotation( + moduleValue == null || moduleValue.asString().isEmpty() ? currentModuleName + : moduleValue.asString(), + subscriptions.value("topic").asString(), + subscriptions.value("name").asString()); + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsBuildItem.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsBuildItem.java new file mode 100644 index 0000000000..fcbbd78275 --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsBuildItem.java @@ -0,0 +1,26 @@ +package xyz.block.ftl.deployment; + +import java.util.HashMap; +import java.util.Map; + +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class TopicsBuildItem extends SimpleBuildItem { + + final Map topics; + + public TopicsBuildItem(Map topics) { + this.topics = new HashMap<>(topics); + } + + public Map getTopics() { + return topics; + } + + public record DiscoveredTopic(String topicName, String generatedProducer, Type eventType, boolean exported) { + + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java new file mode 100644 index 0000000000..367393890a --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java @@ -0,0 +1,97 @@ +package xyz.block.ftl.deployment; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.MethodDescriptor; +import xyz.block.ftl.Export; +import xyz.block.ftl.Subscription; +import xyz.block.ftl.Topic; +import xyz.block.ftl.TopicDefinition; +import xyz.block.ftl.runtime.TopicHelper; + +public class TopicsProcessor { + + public static final DotName TOPIC = DotName.createSimple(Topic.class); + + @BuildStep + TopicsBuildItem handleTopics(CombinedIndexBuildItem index, BuildProducer generatedTopicProducer) { + var topicDefinitions = index.getComputingIndex().getAnnotations(TopicDefinition.class); + Map topics = new HashMap<>(); + Set names = new HashSet<>(); + for (var topicDefinition : topicDefinitions) { + var iface = topicDefinition.target().asClass(); + if (!iface.isInterface()) { + throw new RuntimeException( + "@TopicDefinition can only be applied to interfaces " + iface.name() + " is not an interface"); + } + Type paramType = null; + for (var i : iface.interfaceTypes()) { + if (i.name().equals(TOPIC)) { + if (i.kind() == Type.Kind.PARAMETERIZED_TYPE) { + paramType = i.asParameterizedType().arguments().get(0); + } + } + + } + if (paramType == null) { + throw new RuntimeException("@TopicDefinition can only be applied to interfaces that directly extend " + TOPIC + + " with a concrete type parameter " + iface.name() + " does not extend this interface"); + } + + String name = topicDefinition.value("name").asString(); + if (names.contains(name)) { + throw new RuntimeException("Multiple topic definitions found for topic " + name); + } + names.add(name); + try (ClassCreator cc = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedTopicProducer, true), + iface.name().toString() + "_fit_topic", null, Object.class.getName(), iface.name().toString())) { + var verb = cc.getFieldCreator("verb", String.class); + var constructor = cc.getConstructorCreator(String.class); + constructor.invokeSpecialMethod(MethodDescriptor.ofMethod(Object.class, "", void.class), + constructor.getThis()); + constructor.writeInstanceField(verb.getFieldDescriptor(), constructor.getThis(), constructor.getMethodParam(0)); + constructor.returnVoid(); + var publish = cc.getMethodCreator("publish", void.class, Object.class); + var helper = publish + .invokeStaticMethod(MethodDescriptor.ofMethod(TopicHelper.class, "instance", TopicHelper.class)); + publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(TopicHelper.class, "publish", void.class, String.class, String.class, + Object.class), + helper, publish.load(name), publish.readInstanceField(verb.getFieldDescriptor(), publish.getThis()), + publish.getMethodParam(0)); + publish.returnVoid(); + topics.put(iface.name(), new TopicsBuildItem.DiscoveredTopic(name, cc.getClassName(), paramType, + iface.hasAnnotation(Export.class))); + } + } + return new TopicsBuildItem(topics); + } + + @BuildStep + SubscriptionMetaAnnotationsBuildItem subscriptionAnnotations(CombinedIndexBuildItem combinedIndexBuildItem, + ModuleNameBuildItem moduleNameBuildItem) { + + Map annotations = new HashMap<>(); + for (var subscriptions : combinedIndexBuildItem.getComputingIndex().getAnnotations(Subscription.class)) { + if (subscriptions.target().kind() != AnnotationTarget.Kind.CLASS) { + continue; + } + annotations.put(subscriptions.target().asClass().name(), + SubscriptionMetaAnnotationsBuildItem.fromJandex(subscriptions, moduleNameBuildItem.getModuleName())); + } + return new SubscriptionMetaAnnotationsBuildItem(annotations); + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/VerbClientBuildItem.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/VerbClientBuildItem.java new file mode 100644 index 0000000000..669e005bf5 --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/VerbClientBuildItem.java @@ -0,0 +1,25 @@ +package xyz.block.ftl.deployment; + +import java.util.HashMap; +import java.util.Map; + +import org.jboss.jandex.DotName; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class VerbClientBuildItem extends SimpleBuildItem { + + final Map verbClients; + + public VerbClientBuildItem(Map verbClients) { + this.verbClients = new HashMap<>(verbClients); + } + + public Map getVerbClients() { + return verbClients; + } + + public record DiscoveredClients(String name, String module, String generatedClient) { + + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/VerbClientsProcessor.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/VerbClientsProcessor.java new file mode 100644 index 0000000000..7d590a9a0f --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/VerbClientsProcessor.java @@ -0,0 +1,212 @@ +package xyz.block.ftl.deployment; + +import java.util.HashMap; +import java.util.Map; + +import jakarta.inject.Singleton; + +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodDescriptor; +import xyz.block.ftl.VerbClient; +import xyz.block.ftl.VerbClientDefinition; +import xyz.block.ftl.VerbClientEmpty; +import xyz.block.ftl.VerbClientSink; +import xyz.block.ftl.VerbClientSource; +import xyz.block.ftl.runtime.VerbClientHelper; + +public class VerbClientsProcessor { + + public static final DotName VERB_CLIENT = DotName.createSimple(VerbClient.class); + public static final DotName VERB_CLIENT_SINK = DotName.createSimple(VerbClientSink.class); + public static final DotName VERB_CLIENT_SOURCE = DotName.createSimple(VerbClientSource.class); + public static final DotName VERB_CLIENT_EMPTY = DotName.createSimple(VerbClientEmpty.class); + public static final String TEST_ANNOTATION = "xyz.block.ftl.java.test.FTLManaged"; + + @BuildStep + VerbClientBuildItem handleTopics(CombinedIndexBuildItem index, BuildProducer generatedClients, + BuildProducer generatedBeanBuildItemBuildProducer, + ModuleNameBuildItem moduleNameBuildItem, + LaunchModeBuildItem launchModeBuildItem) { + var clientDefinitions = index.getComputingIndex().getAnnotations(VerbClientDefinition.class); + Map clients = new HashMap<>(); + for (var clientDefinition : clientDefinitions) { + var iface = clientDefinition.target().asClass(); + if (!iface.isInterface()) { + throw new RuntimeException( + "@VerbClientDefinition can only be applied to interfaces and " + iface.name() + " is not an interface"); + } + String name = clientDefinition.value("name").asString(); + AnnotationValue moduleValue = clientDefinition.value("module"); + String module = moduleValue == null || moduleValue.asString().isEmpty() ? moduleNameBuildItem.getModuleName() + : moduleValue.asString(); + boolean found = false; + ClassOutput classOutput; + if (launchModeBuildItem.isTest()) { + //when running in tests we actually make these beans, so they can be injected into the tests + //the @TestResource qualifier is used so they can only be injected into test code + //TODO: is this the best way of handling this? revisit later + + classOutput = new GeneratedBeanGizmoAdaptor(generatedBeanBuildItemBuildProducer); + } else { + classOutput = new GeneratedClassGizmoAdaptor(generatedClients, true); + } + //TODO: map and list return types + for (var i : iface.interfaceTypes()) { + if (i.name().equals(VERB_CLIENT)) { + if (i.kind() == Type.Kind.PARAMETERIZED_TYPE) { + var returnType = i.asParameterizedType().arguments().get(1); + var paramType = i.asParameterizedType().arguments().get(0); + try (ClassCreator cc = new ClassCreator(classOutput, iface.name().toString() + "_fit_verbclient", null, + Object.class.getName(), iface.name().toString())) { + if (launchModeBuildItem.isTest()) { + cc.addAnnotation(TEST_ANNOTATION); + cc.addAnnotation(Singleton.class); + } + var publish = cc.getMethodCreator("call", returnType.name().toString(), + paramType.name().toString()); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + var results = publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.getMethodParam(0), + publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); + publish.returnValue(results); + publish = cc.getMethodCreator("call", Object.class, Object.class); + helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + results = publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.getMethodParam(0), + publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); + publish.returnValue(results); + clients.put(iface.name(), + new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); + } + found = true; + break; + } else { + throw new RuntimeException( + "@VerbClientDefinition can only be applied to interfaces that directly extend a verb client type with concrete type parameters and " + + iface.name() + " does not have concrete type parameters"); + } + } else if (i.name().equals(VERB_CLIENT_SINK)) { + if (i.kind() == Type.Kind.PARAMETERIZED_TYPE) { + var paramType = i.asParameterizedType().arguments().get(0); + try (ClassCreator cc = new ClassCreator(classOutput, iface.name().toString() + "_fit_verbclient", null, + Object.class.getName(), iface.name().toString())) { + if (launchModeBuildItem.isTest()) { + cc.addAnnotation(TEST_ANNOTATION); + cc.addAnnotation(Singleton.class); + } + var publish = cc.getMethodCreator("call", void.class, paramType.name().toString()); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.getMethodParam(0), + publish.loadClass(Void.class), publish.load(false), publish.load(false)); + publish.returnVoid(); + publish = cc.getMethodCreator("call", void.class, Object.class); + helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.getMethodParam(0), + publish.loadClass(Void.class), publish.load(false), publish.load(false)); + publish.returnVoid(); + clients.put(iface.name(), + new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); + } + found = true; + break; + } else { + throw new RuntimeException( + "@VerbClientDefinition can only be applied to interfaces that directly extend a verb client type with concrete type parameters and " + + iface.name() + " does not have concrete type parameters"); + } + } else if (i.name().equals(VERB_CLIENT_SOURCE)) { + if (i.kind() == Type.Kind.PARAMETERIZED_TYPE) { + var returnType = i.asParameterizedType().arguments().get(0); + try (ClassCreator cc = new ClassCreator(classOutput, iface.name().toString() + "_fit_verbclient", null, + Object.class.getName(), iface.name().toString())) { + if (launchModeBuildItem.isTest()) { + cc.addAnnotation(TEST_ANNOTATION); + cc.addAnnotation(Singleton.class); + } + var publish = cc.getMethodCreator("call", returnType.name().toString()); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + var results = publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.loadNull(), + publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); + publish.returnValue(results); + + publish = cc.getMethodCreator("call", Object.class); + helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + results = publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.loadNull(), + publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); + publish.returnValue(results); + clients.put(iface.name(), + new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); + } + found = true; + break; + } else { + throw new RuntimeException( + "@VerbClientDefinition can only be applied to interfaces that directly extend a verb client type with concrete type parameters and " + + iface.name() + " does not have concrete type parameters"); + } + } else if (i.name().equals(VERB_CLIENT_EMPTY)) { + try (ClassCreator cc = new ClassCreator(classOutput, iface.name().toString() + "_fit_verbclient", null, + Object.class.getName(), iface.name().toString())) { + if (launchModeBuildItem.isTest()) { + cc.addAnnotation(TEST_ANNOTATION); + cc.addAnnotation(Singleton.class); + } + var publish = cc.getMethodCreator("call", void.class); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.loadNull(), + publish.loadClass(Void.class), publish.load(false), publish.load(false)); + publish.returnVoid(); + clients.put(iface.name(), new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); + } + found = true; + break; + } + } + if (!found) { + throw new RuntimeException( + "@VerbClientDefinition can only be applied to interfaces that directly extend a verb client type with concrete type parameters and " + + iface.name() + " does not extend a verb client type"); + } + } + return new VerbClientBuildItem(clients); + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider b/java-runtime/ftl-runtime/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider new file mode 100644 index 0000000000..98be2f6c6f --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider @@ -0,0 +1 @@ +xyz.block.ftl.deployment.FTLCodeGenerator \ No newline at end of file diff --git a/java-runtime/ftl-runtime/deployment/src/test/java/xyz/block/ftl/java/runtime/test/FtlJavaRuntimeDevModeTest.java b/java-runtime/ftl-runtime/deployment/src/test/java/xyz/block/ftl/java/runtime/test/FtlJavaRuntimeDevModeTest.java new file mode 100644 index 0000000000..b2f045f4aa --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/test/java/xyz/block/ftl/java/runtime/test/FtlJavaRuntimeDevModeTest.java @@ -0,0 +1,25 @@ +package xyz.block.ftl.java.runtime.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +@Disabled +public class FtlJavaRuntimeDevModeTest { + + // Start hot reload (DevMode) test with your extension loaded + @RegisterExtension + static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void writeYourOwnDevModeTest() { + // Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information + Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName()); + } +} diff --git a/java-runtime/ftl-runtime/deployment/src/test/java/xyz/block/ftl/java/runtime/test/FtlJavaRuntimeTest.java b/java-runtime/ftl-runtime/deployment/src/test/java/xyz/block/ftl/java/runtime/test/FtlJavaRuntimeTest.java new file mode 100644 index 0000000000..45c2c2eef7 --- /dev/null +++ b/java-runtime/ftl-runtime/deployment/src/test/java/xyz/block/ftl/java/runtime/test/FtlJavaRuntimeTest.java @@ -0,0 +1,25 @@ +package xyz.block.ftl.java.runtime.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +@Disabled +public class FtlJavaRuntimeTest { + + // Start unit test with your extension loaded + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void writeYourOwnUnitTest() { + // Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information + Assertions.assertTrue(true, "Add some assertions to " + getClass().getName()); + } +} diff --git a/java-runtime/ftl-runtime/integration-tests/pom.xml b/java-runtime/ftl-runtime/integration-tests/pom.xml new file mode 100644 index 0000000000..11c116b7c7 --- /dev/null +++ b/java-runtime/ftl-runtime/integration-tests/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + + xyz.block + ftl-java-runtime-parent + 1.0.0-SNAPSHOT + + ftl-java-runtime-integration-tests + Ftl Java Runtime - Integration Tests + + + true + + + + + xyz.block + ftl-java-runtime + + + xyz.block + ftl-java-test-framework + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-junit5-mockito + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + generate-code + generate-code-tests + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native-image + + + native + + + + + + maven-surefire-plugin + + ${native.surefire.skip} + + + + + + false + true + + + + diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/builtin.pb b/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/builtin.pb new file mode 100644 index 0000000000..83a40d59ed Binary files /dev/null and b/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/builtin.pb differ diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/echo.pb b/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/echo.pb new file mode 100644 index 0000000000..6f55e044cd Binary files /dev/null and b/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/echo.pb differ diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/time.pb b/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/time.pb new file mode 100644 index 0000000000..5388d67667 Binary files /dev/null and b/java-runtime/ftl-runtime/integration-tests/src/main/ftl-module-schema/time.pb differ diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java new file mode 100644 index 0000000000..cf91946a90 --- /dev/null +++ b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java @@ -0,0 +1,46 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You 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 xyz.block.ftl.java.runtime.it; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.core.MediaType; + +import ftl.echo.EchoClient; +import ftl.echo.EchoRequest; +import xyz.block.ftl.Verb; + +@ApplicationScoped +public class FtlJavaRuntimeResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + public String post(Person person) { + return "Hello " + person.first() + " " + person.last(); + } + + @Verb + public String hello(String name, EchoClient echoClient) { + return "Hello " + echoClient.call(new EchoRequest().setName(name)).getMessage(); + } + + @Verb + public void publish(Person person, MyTopic topic) { + topic.publish(person); + } +} diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java new file mode 100644 index 0000000000..f5b381f0da --- /dev/null +++ b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java @@ -0,0 +1,10 @@ +package xyz.block.ftl.java.runtime.it; + +import xyz.block.ftl.Export; +import xyz.block.ftl.Topic; +import xyz.block.ftl.TopicDefinition; + +@Export +@TopicDefinition(name = "testTopic") +public interface MyTopic extends Topic { +} diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/Person.java b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/Person.java new file mode 100644 index 0000000000..d7233db37a --- /dev/null +++ b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/Person.java @@ -0,0 +1,5 @@ +package xyz.block.ftl.java.runtime.it; + +public record Person(String first, String last) { + +} diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/resources/application.properties b/java-runtime/ftl-runtime/integration-tests/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java b/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java new file mode 100644 index 0000000000..ef47c2b6a1 --- /dev/null +++ b/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java @@ -0,0 +1,62 @@ +package xyz.block.ftl.java.runtime.it; + +import java.util.function.Function; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import ftl.echo.EchoClient; +import ftl.echo.EchoRequest; +import ftl.echo.EchoResponse; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import xyz.block.ftl.VerbClient; +import xyz.block.ftl.VerbClientDefinition; +import xyz.block.ftl.VerbClientSink; +import xyz.block.ftl.java.test.FTLManaged; +import xyz.block.ftl.java.test.internal.FTLTestResource; +import xyz.block.ftl.java.test.internal.TestVerbServer; + +@QuarkusTest +@QuarkusTestResource(FTLTestResource.class) +public class FtlJavaRuntimeResourceTest { + + @FTLManaged + @Inject + PublishVerbClient myVerbClient; + + @FTLManaged + @Inject + HelloClient helloClient; + + @Test + public void testHelloEndpoint() { + TestVerbServer.registerFakeVerb("echo", "echo", new Function() { + @Override + public EchoResponse apply(EchoRequest s) { + return new EchoResponse(s.getName()); + } + }); + EchoClient echoClient = Mockito.mock(EchoClient.class); + Mockito.when(echoClient.call(Mockito.any())).thenReturn(new EchoResponse().setMessage("Stuart")); + Assertions.assertEquals("Hello Stuart", helloClient.call("Stuart")); + } + + @Test + @Disabled + public void testTopic() { + myVerbClient.call(new Person("Stuart", "Douglas")); + } + + @VerbClientDefinition(name = "publish") + interface PublishVerbClient extends VerbClientSink { + } + + @VerbClientDefinition(name = "hello") + interface HelloClient extends VerbClient { + } +} diff --git a/java-runtime/ftl-runtime/pom.xml b/java-runtime/ftl-runtime/pom.xml new file mode 100644 index 0000000000..4ff40b3f4f --- /dev/null +++ b/java-runtime/ftl-runtime/pom.xml @@ -0,0 +1,264 @@ + + + 4.0.0 + xyz.block + ftl-java-runtime-parent + 1.0.0-SNAPSHOT + pom + Ftl Java Runtime - Parent + + + deployment + runtime + integration-tests + test-framework + + + + 3.13.0 + ${surefire-plugin.version} + 17 + UTF-8 + UTF-8 + 3.12.3 + 3.2.5 + ${basedir}/../../.. + 1.65.1 + 1.13.0 + 2.24.1 + 1.11.0 + + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + xyz.block + ftl-java-runtime + ${project.version} + + + xyz.block + ftl-java-runtime-deployment + ${project.version} + + + xyz.block + ftl-java-test-framework + ${project.version} + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + + + com.squareup + javapoet + ${javapoet.version} + + + + com.squareup.wire + wire-runtime-jvm + ${wire.version} + + + com.squareup.wire + wire-grpc-server + ${wire.version} + + + com.squareup.wire + wire-grpc-client-jvm + ${wire.version} + + + + + io.grpc + grpc-netty + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + ${settings.localRepository} + + + + + maven-failsafe-plugin + ${failsafe-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + ${settings.localRepository} + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + net.revelc.code.formatter + formatter-maven-plugin + ${version.formatter.plugin} + + + quarkus-ide-config + io.quarkus + ${quarkus.version} + + + + + .cache/formatter-maven-plugin-${version.formatter.plugin} + eclipse-format.xml + LF + ${format.skip} + + + + net.revelc.code + impsort-maven-plugin + ${version.impsort.plugin} + + + .cache/impsort-maven-plugin-${version.impsort.plugin} + java.,javax.,jakarta.,org.,com. + * + ${format.skip} + true + + + + + + + + + + format + + true + + !no-format + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + process-sources + + format + + + + + + net.revelc.code + impsort-maven-plugin + + true + + + + sort-imports + + sort + + + + + + + + + validate + + true + + no-format + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + process-sources + + validate + + + + + + net.revelc.code + impsort-maven-plugin + + true + + + + check-imports + + check + + + + + + + + + diff --git a/java-runtime/ftl-runtime/runtime/pom.xml b/java-runtime/ftl-runtime/runtime/pom.xml new file mode 100644 index 0000000000..9ea5ec0c64 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + + xyz.block + ftl-java-runtime-parent + 1.0.0-SNAPSHOT + + ftl-java-runtime + Ftl Java Runtime - Runtime + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-grpc + + + io.quarkus + quarkus-rest-jackson + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + io.grpc + grpc-stub + + + io.grpc + grpc-services + + + + io.grpc + grpc-netty + + + io.grpc + grpc-protobuf + + + javax.annotation + javax.annotation-api + + + org.jetbrains + annotations + + + + + + + kr.motd.maven + os-maven-plugin + 1.6.0 + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + generate-sources + + add-source + + + + ${project.basedir}/target/generated-sources/protoc + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + + compile + compile-custom + test-compile + test-compile-custom + + + + + com.google.protobuf:protoc:3.25.4:exe:${os.detected.classifier} + ${rootDir}/backend/protos + grpc-java + io.grpc:protoc-gen-grpc-java:1.65.1:exe:${os.detected.classifier} + + + + + diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Config.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Config.java new file mode 100644 index 0000000000..f757472983 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Config.java @@ -0,0 +1,12 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Config { + String value(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Cron.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Cron.java new file mode 100644 index 0000000000..31ffc34218 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Cron.java @@ -0,0 +1,14 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Cron { + + String value(); + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Export.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Export.java new file mode 100644 index 0000000000..63354ad057 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Export.java @@ -0,0 +1,14 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks the given item as exported in the FTL schema. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER }) +public @interface Export { +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/GeneratedRef.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/GeneratedRef.java new file mode 100644 index 0000000000..2b24f59af4 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/GeneratedRef.java @@ -0,0 +1,11 @@ +package xyz.block.ftl; + +/** + * Indicates that the class was generated from an external module. + */ +public @interface GeneratedRef { + + String name(); + + String module(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java new file mode 100644 index 0000000000..4f5cc51242 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java @@ -0,0 +1,11 @@ +package xyz.block.ftl; + +import java.time.Duration; + +/** + * Client that can be used to acquire a FTL lease. If the lease cannot be acquired a {@link LeaseFailedException} is thrown. + */ +public interface LeaseClient { + + void acquireLease(Duration duration, String... keys) throws LeaseFailedException; +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseFailedException.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseFailedException.java new file mode 100644 index 0000000000..0a9bb45ef5 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseFailedException.java @@ -0,0 +1,26 @@ +package xyz.block.ftl; + +/** + * Checked exception that is thrown when a lease cannot be acquired + */ +public class LeaseFailedException extends Exception { + + public LeaseFailedException() { + } + + public LeaseFailedException(String message) { + super(message); + } + + public LeaseFailedException(String message, Throwable cause) { + super(message, cause); + } + + public LeaseFailedException(Throwable cause) { + super(cause); + } + + public LeaseFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Retry.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Retry.java new file mode 100644 index 0000000000..19752db1e6 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Retry.java @@ -0,0 +1,20 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface Retry { + int count() default 0; + + String minBackoff() default ""; + + String maxBackoff() default ""; + + String catchModule() default ""; + + String catchVerb() default ""; +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Secret.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Secret.java new file mode 100644 index 0000000000..12d61ce1d2 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Secret.java @@ -0,0 +1,12 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Secret { + String value(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Subscription.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Subscription.java new file mode 100644 index 0000000000..9b2283f5e0 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Subscription.java @@ -0,0 +1,28 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface Subscription { + /** + * @return The module of the topic to subscribe to, if empty then the topic is assumed to be in the current module. + */ + String module() default ""; + + /** + * + * @return The name of the topic to subscribe to. + */ + String topic(); + + /** + * + * @return The subscription name + */ + String name(); + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java new file mode 100644 index 0000000000..16170fe24e --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java @@ -0,0 +1,12 @@ +package xyz.block.ftl; + +/** + * A concrete definition of a topic. Extend this interface and annotate with {@code @TopicDefinition} to define a topic, + * then inject this into verb methods to publish to the topic. + * + * @param + */ +public interface Topic { + + void publish(T object); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java new file mode 100644 index 0000000000..a6482006d6 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java @@ -0,0 +1,17 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TopicDefinition { + /** + * + * @return The name of the topic + */ + String name(); + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Verb.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Verb.java new file mode 100644 index 0000000000..c421a34fd1 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Verb.java @@ -0,0 +1,15 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A FTL verb. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Verb { + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClient.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClient.java new file mode 100644 index 0000000000..b179357c89 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClient.java @@ -0,0 +1,17 @@ +package xyz.block.ftl; + +/** + * A client for a specific verb. + * + * The sink source and empty interfaces allow for different call signatures. + * + * TODO: should these be top level + * + * @param

The verb parameter type + * @param The verb return type + */ +public interface VerbClient { + + R call(P param); + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientDefinition.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientDefinition.java new file mode 100644 index 0000000000..1e258f7fa6 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientDefinition.java @@ -0,0 +1,18 @@ +package xyz.block.ftl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that is used to define a verb client + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface VerbClientDefinition { + + String module() default ""; + + String name(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientEmpty.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientEmpty.java new file mode 100644 index 0000000000..2d68c8d88d --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientEmpty.java @@ -0,0 +1,5 @@ +package xyz.block.ftl; + +public interface VerbClientEmpty { + void call(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientSink.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientSink.java new file mode 100644 index 0000000000..cb05af1038 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientSink.java @@ -0,0 +1,5 @@ +package xyz.block.ftl; + +public interface VerbClientSink

{ + void call(P param); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientSource.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientSource.java new file mode 100644 index 0000000000..95efc04c78 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbClientSource.java @@ -0,0 +1,5 @@ +package xyz.block.ftl; + +public interface VerbClientSource { + R call(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbName.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbName.java new file mode 100644 index 0000000000..1a9a7e2548 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/VerbName.java @@ -0,0 +1,12 @@ +package xyz.block.ftl; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to override the name of a verb. Without this annotation it defaults to the method name. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface VerbName { + String value(); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLConfigSource.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLConfigSource.java new file mode 100644 index 0000000000..15ff77949d --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLConfigSource.java @@ -0,0 +1,65 @@ +package xyz.block.ftl.runtime; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Set; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +public class FTLConfigSource implements ConfigSource { + + final static String SEPARATE_SERVER = "quarkus.grpc.server.use-separate-server"; + final static String PORT = "quarkus.http.port"; + final static String HOST = "quarkus.http.host"; + + final static String FTL_BIND = "FTL_BIND"; + + @Override + public Set getPropertyNames() { + return Set.of(SEPARATE_SERVER, PORT, HOST); + } + + @Override + public int getOrdinal() { + return 1; + } + + @Override + public String getValue(String s) { + switch (s) { + case SEPARATE_SERVER -> { + return "false"; + } + case PORT -> { + String bind = System.getenv(FTL_BIND); + if (bind == null) { + return null; + } + try { + URI uri = new URI(bind); + return Integer.toString(uri.getPort()); + } catch (URISyntaxException e) { + return null; + } + } + case HOST -> { + String bind = System.getenv(FTL_BIND); + if (bind == null) { + return null; + } + try { + URI uri = new URI(bind); + return uri.getHost(); + } catch (URISyntaxException e) { + return null; + } + } + } + return null; + } + + @Override + public String getName() { + return "FTL Config"; + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java new file mode 100644 index 0000000000..25eb62fb65 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java @@ -0,0 +1,232 @@ +package xyz.block.ftl.runtime; + +import java.net.URI; +import java.time.Duration; +import java.util.Arrays; +import java.util.Deque; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.LinkedBlockingDeque; + +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +import com.google.protobuf.ByteString; + +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.StreamObserver; +import io.quarkus.runtime.Startup; +import xyz.block.ftl.LeaseClient; +import xyz.block.ftl.LeaseFailedException; +import xyz.block.ftl.v1.AcquireLeaseRequest; +import xyz.block.ftl.v1.AcquireLeaseResponse; +import xyz.block.ftl.v1.CallRequest; +import xyz.block.ftl.v1.CallResponse; +import xyz.block.ftl.v1.ModuleContextRequest; +import xyz.block.ftl.v1.ModuleContextResponse; +import xyz.block.ftl.v1.PublishEventRequest; +import xyz.block.ftl.v1.PublishEventResponse; +import xyz.block.ftl.v1.VerbServiceGrpc; +import xyz.block.ftl.v1.schema.Ref; + +@Singleton +@Startup +public class FTLController implements LeaseClient { + private static final Logger log = Logger.getLogger(FTLController.class); + final String moduleName; + private StreamObserver leaseClient; + private final Deque> leaseWaiters = new LinkedBlockingDeque<>(); + + private Throwable currentError; + private volatile ModuleContextResponse moduleContextResponse; + private boolean waiters = false; + + final VerbServiceGrpc.VerbServiceStub verbService; + final StreamObserver moduleObserver = new StreamObserver<>() { + @Override + public void onNext(ModuleContextResponse moduleContextResponse) { + synchronized (this) { + currentError = null; + FTLController.this.moduleContextResponse = moduleContextResponse; + if (waiters) { + this.notifyAll(); + waiters = false; + } + } + + } + + @Override + public void onError(Throwable throwable) { + log.error("GRPC connection error", throwable); + synchronized (this) { + currentError = throwable; + if (waiters) { + this.notifyAll(); + waiters = false; + } + } + } + + @Override + public void onCompleted() { + verbService.getModuleContext(ModuleContextRequest.newBuilder().setModule(moduleName).build(), moduleObserver); + } + }; + + public FTLController(@ConfigProperty(name = "ftl.endpoint", defaultValue = "http://localhost:8892") URI uri, + @ConfigProperty(name = "ftl.module.name") String moduleName) { + this.moduleName = moduleName; + var channelBuilder = ManagedChannelBuilder.forAddress(uri.getHost(), uri.getPort()); + if (uri.getScheme().equals("http")) { + channelBuilder.usePlaintext(); + } + var channel = channelBuilder.build(); + verbService = VerbServiceGrpc.newStub(channel); + verbService.getModuleContext(ModuleContextRequest.newBuilder().setModule(moduleName).build(), moduleObserver); + synchronized (this) { + this.leaseClient = verbService.acquireLease(new StreamObserver() { + @Override + public void onNext(AcquireLeaseResponse value) { + leaseWaiters.pop().complete(null); + } + + @Override + public void onError(Throwable t) { + leaseWaiters.pop().completeExceptionally(t); + } + + @Override + public void onCompleted() { + synchronized (FTLController.this) { + while (!leaseWaiters.isEmpty()) { + leaseWaiters.pop().completeExceptionally(new RuntimeException("connection closed")); + } + leaseClient = verbService.acquireLease(this); + } + } + }); + } + } + + public byte[] getSecret(String secretName) { + var context = getModuleContext(); + if (context.containsSecrets(secretName)) { + return context.getSecretsMap().get(secretName).toByteArray(); + } + throw new RuntimeException("Secret not found: " + secretName); + } + + public byte[] getConfig(String secretName) { + var context = getModuleContext(); + if (context.containsConfigs(secretName)) { + return context.getConfigsMap().get(secretName).toByteArray(); + } + throw new RuntimeException("Config not found: " + secretName); + } + + public byte[] callVerb(String name, String module, byte[] payload) { + CompletableFuture cf = new CompletableFuture<>(); + + verbService.call(CallRequest.newBuilder().setVerb(Ref.newBuilder().setModule(module).setName(name)) + .setBody(ByteString.copyFrom(payload)).build(), new StreamObserver<>() { + + @Override + public void onNext(CallResponse callResponse) { + if (callResponse.hasError()) { + cf.completeExceptionally(new RuntimeException(callResponse.getError().getMessage())); + } else { + cf.complete(callResponse.getBody().toByteArray()); + } + } + + @Override + public void onError(Throwable throwable) { + cf.completeExceptionally(throwable); + } + + @Override + public void onCompleted() { + + } + }); + try { + return cf.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void publishEvent(String topic, String callingVerbName, byte[] event) { + CompletableFuture cf = new CompletableFuture<>(); + verbService.publishEvent(PublishEventRequest.newBuilder() + .setCaller(callingVerbName).setBody(ByteString.copyFrom(event)) + .setTopic(Ref.newBuilder().setModule(moduleName).setName(topic).build()).build(), + new StreamObserver() { + @Override + public void onNext(PublishEventResponse publishEventResponse) { + cf.complete(null); + } + + @Override + public void onError(Throwable throwable) { + cf.completeExceptionally(throwable); + } + + @Override + public void onCompleted() { + cf.complete(null); + } + }); + try { + cf.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + public void acquireLease(Duration duration, String... keys) throws LeaseFailedException { + CompletableFuture cf = new CompletableFuture<>(); + synchronized (this) { + leaseWaiters.push(cf); + leaseClient.onNext(AcquireLeaseRequest.newBuilder().setModule(moduleName) + .addAllKey(Arrays.asList(keys)) + .setTtl(com.google.protobuf.Duration.newBuilder() + .setSeconds(duration.toSeconds())) + .build()); + } + try { + cf.get(); + } catch (Exception e) { + throw new LeaseFailedException(e); + } + } + + private ModuleContextResponse getModuleContext() { + var moduleContext = moduleContextResponse; + if (moduleContext != null) { + return moduleContext; + } + synchronized (moduleObserver) { + for (;;) { + moduleContext = moduleContextResponse; + if (moduleContext != null) { + return moduleContext; + } + if (currentError != null) { + throw new RuntimeException(currentError); + } + waiters = true; + try { + moduleObserver.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + } + } + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLHttpHandler.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLHttpHandler.java new file mode 100644 index 0000000000..48596bffe5 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLHttpHandler.java @@ -0,0 +1,245 @@ +package xyz.block.ftl.runtime; + +import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import jakarta.inject.Singleton; +import jakarta.ws.rs.core.MediaType; + +import org.jboss.logging.Logger; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.ByteString; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.FileRegion; +import io.netty.handler.codec.http.*; +import io.netty.util.ReferenceCountUtil; +import io.quarkus.netty.runtime.virtual.VirtualClientConnection; +import io.quarkus.netty.runtime.virtual.VirtualResponseHandler; +import io.quarkus.vertx.http.runtime.QuarkusHttpHeaders; +import io.quarkus.vertx.http.runtime.VertxHttpRecorder; +import xyz.block.ftl.v1.CallRequest; +import xyz.block.ftl.v1.CallResponse; + +@SuppressWarnings("unused") +@Singleton +public class FTLHttpHandler implements VerbInvoker { + + public static final String CONTENT_TYPE = "Content-Type"; + final ObjectMapper mapper; + private static final Logger log = Logger.getLogger("quarkus.amazon.lambda.http"); + + private static final int BUFFER_SIZE = 8096; + + private static final Map> ERROR_HEADERS = Map.of(); + + private static final String COOKIE_HEADER = "Cookie"; + + // comma headers for headers that have comma in value and we don't want to split it up into + // multiple headers + private static final Set COMMA_HEADERS = Set.of("access-control-request-headers"); + + public FTLHttpHandler(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public CallResponse handle(CallRequest in) { + try { + var body = mapper.createParser(in.getBody().newInput()) + .readValueAs(xyz.block.ftl.runtime.builtin.HttpRequest.class); + body.getHeaders().put(FTLRecorder.X_FTL_VERB, List.of(in.getVerb().getName())); + var ret = handleRequest(body); + var mappedResponse = mapper.writer().writeValueAsBytes(ret); + return CallResponse.newBuilder().setBody(ByteString.copyFrom(mappedResponse)).build(); + } catch (Exception e) { + return CallResponse.newBuilder().setError(CallResponse.Error.newBuilder().setMessage(e.getMessage()).build()) + .build(); + } + + } + + public xyz.block.ftl.runtime.builtin.HttpResponse handleRequest(xyz.block.ftl.runtime.builtin.HttpRequest request) { + InetSocketAddress clientAddress = null; + try { + return nettyDispatch(clientAddress, request); + } catch (Exception e) { + log.error("Request Failure", e); + xyz.block.ftl.runtime.builtin.HttpResponse res = new xyz.block.ftl.runtime.builtin.HttpResponse(); + res.setStatus(500); + res.setError(e); + res.setHeaders(ERROR_HEADERS); + return res; + } + + } + + private class NettyResponseHandler implements VirtualResponseHandler { + xyz.block.ftl.runtime.builtin.HttpResponse responseBuilder = new xyz.block.ftl.runtime.builtin.HttpResponse(); + ByteArrayOutputStream baos; + WritableByteChannel byteChannel; + final xyz.block.ftl.runtime.builtin.HttpRequest request; + CompletableFuture future = new CompletableFuture<>(); + + public NettyResponseHandler(xyz.block.ftl.runtime.builtin.HttpRequest request) { + this.request = request; + } + + public CompletableFuture getFuture() { + return future; + } + + @Override + public void handleMessage(Object msg) { + try { + //log.info("Got message: " + msg.getClass().getName()); + + if (msg instanceof HttpResponse) { + HttpResponse res = (HttpResponse) msg; + responseBuilder.setStatus(res.status().code()); + + final Map> headers = new HashMap<>(); + responseBuilder.setHeaders(headers); + for (String name : res.headers().names()) { + final List allForName = res.headers().getAll(name); + if (allForName == null || allForName.isEmpty()) { + continue; + } + headers.put(name, allForName); + } + } + if (msg instanceof HttpContent) { + HttpContent content = (HttpContent) msg; + int readable = content.content().readableBytes(); + if (baos == null && readable > 0) { + baos = createByteStream(); + } + for (int i = 0; i < readable; i++) { + baos.write(content.content().readByte()); + } + } + if (msg instanceof FileRegion) { + FileRegion file = (FileRegion) msg; + if (file.count() > 0 && file.transferred() < file.count()) { + if (baos == null) + baos = createByteStream(); + if (byteChannel == null) + byteChannel = Channels.newChannel(baos); + file.transferTo(byteChannel, file.transferred()); + } + } + if (msg instanceof LastHttpContent) { + if (baos != null) { + List ct = responseBuilder.getHeaders().get(CONTENT_TYPE); + if (ct == null || ct.isEmpty()) { + //TODO: how to handle this + responseBuilder.setBody(baos.toString(StandardCharsets.UTF_8)); + } else if (ct.get(0).contains(MediaType.TEXT_PLAIN)) { + // need to encode as JSON string + responseBuilder.setBody(mapper.writer().writeValueAsString(baos.toString(StandardCharsets.UTF_8))); + } else { + responseBuilder.setBody(baos.toString(StandardCharsets.UTF_8)); + } + } + future.complete(responseBuilder); + } + } catch (Throwable ex) { + future.completeExceptionally(ex); + } finally { + if (msg != null) { + ReferenceCountUtil.release(msg); + } + } + } + + @Override + public void close() { + if (!future.isDone()) + future.completeExceptionally(new RuntimeException("Connection closed")); + } + } + + private xyz.block.ftl.runtime.builtin.HttpResponse nettyDispatch(InetSocketAddress clientAddress, + xyz.block.ftl.runtime.builtin.HttpRequest request) + throws Exception { + QuarkusHttpHeaders quarkusHeaders = new QuarkusHttpHeaders(); + quarkusHeaders.setContextObject(xyz.block.ftl.runtime.builtin.HttpRequest.class, request); + HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod()); + if (httpMethod == null) { + throw new IllegalStateException("Missing HTTP method in request event"); + } + //TODO: encoding schenanigans + StringBuilder path = new StringBuilder(request.getPath()); + if (request.getQuery() != null && !request.getQuery().isEmpty()) { + path.append("?"); + var first = true; + for (var entry : request.getQuery().entrySet()) { + for (var val : entry.getValue()) { + if (first) { + first = false; + } else { + path.append("&"); + } + path.append(entry.getKey()).append("=").append(val); + } + } + } + DefaultHttpRequest nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, + httpMethod, path.toString(), quarkusHeaders); + if (request.getHeaders() != null) { + for (Map.Entry> header : request.getHeaders().entrySet()) { + if (header.getValue() != null) { + for (String val : header.getValue()) { + nettyRequest.headers().add(header.getKey(), val); + } + } + } + } + nettyRequest.headers().add(CONTENT_TYPE, MediaType.APPLICATION_JSON); + + if (!nettyRequest.headers().contains(HttpHeaderNames.HOST)) { + nettyRequest.headers().add(HttpHeaderNames.HOST, "localhost"); + } + + HttpContent requestContent = LastHttpContent.EMPTY_LAST_CONTENT; + if (request.getBody() != null) { + // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 + nettyRequest.headers().add(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); + ByteBuf body = Unpooled.copiedBuffer(request.getBody().toString(), StandardCharsets.UTF_8); //TODO: do we need to look at the request encoding? + requestContent = new DefaultLastHttpContent(body); + } + NettyResponseHandler handler = new NettyResponseHandler(request); + VirtualClientConnection connection = VirtualClientConnection.connect(handler, VertxHttpRecorder.VIRTUAL_HTTP, + clientAddress); + + connection.sendMessage(nettyRequest); + connection.sendMessage(requestContent); + try { + return handler.getFuture().get(); + } finally { + connection.close(); + } + } + + private ByteArrayOutputStream createByteStream() { + ByteArrayOutputStream baos; + baos = new ByteArrayOutputStream(BUFFER_SIZE); + return baos; + } + + private boolean isBinary(String contentType) { + if (contentType != null) { + String ct = contentType.toLowerCase(Locale.ROOT); + return !(ct.startsWith("text") || ct.contains("json") || ct.contains("xml") || ct.contains("yaml")); + } + return false; + } + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java new file mode 100644 index 0000000000..5ee6413076 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java @@ -0,0 +1,145 @@ +package xyz.block.ftl.runtime; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.function.BiFunction; + +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.Arc; +import io.quarkus.runtime.annotations.Recorder; +import xyz.block.ftl.LeaseClient; +import xyz.block.ftl.v1.CallRequest; + +@Recorder +public class FTLRecorder { + + public static final String X_FTL_VERB = "X-ftl-verb"; + + public void registerVerb(String module, String verbName, String methodName, List> parameterTypes, + Class verbHandlerClass, List> paramMappers, + boolean allowNullReturn) { + //TODO: this sucks + try { + var method = verbHandlerClass.getDeclaredMethod(methodName, parameterTypes.toArray(new Class[0])); + var handlerInstance = Arc.container().instance(verbHandlerClass); + Arc.container().instance(VerbRegistry.class).get().register(module, verbName, handlerInstance, method, + paramMappers, allowNullReturn); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void registerHttpIngress(String module, String verbName) { + try { + Arc.container().instance(VerbRegistry.class).get().register(module, verbName, + Arc.container().instance(FTLHttpHandler.class).get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public BiFunction topicSupplier(String className, String callingVerb) { + try { + var cls = Thread.currentThread().getContextClassLoader().loadClass(className.replace("/", ".")); + var topic = cls.getDeclaredConstructor(String.class).newInstance(callingVerb); + return new BiFunction() { + @Override + public Object apply(ObjectMapper mapper, CallRequest callRequest) { + return topic; + } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public BiFunction verbClientSupplier(String className) { + try { + var cls = Thread.currentThread().getContextClassLoader().loadClass(className.replace("/", ".")); + var client = cls.getDeclaredConstructor().newInstance(); + return new BiFunction() { + @Override + public Object apply(ObjectMapper mapper, CallRequest callRequest) { + return client; + } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public BiFunction leaseClientSupplier() { + return new BiFunction() { + volatile LeaseClient leaseClient; + + @Override + public Object apply(ObjectMapper mapper, CallRequest callRequest) { + if (leaseClient == null) { + leaseClient = Arc.container().instance(LeaseClient.class).get(); + } + return leaseClient; + } + }; + } + + public ParameterExtractor topicParamExtractor(String className) { + + try { + var cls = Thread.currentThread().getContextClassLoader().loadClass(className.replace("/", ".")); + Constructor ctor = cls.getDeclaredConstructor(String.class); + return new ParameterExtractor() { + @Override + public Object extractParameter(ResteasyReactiveRequestContext context) { + + try { + Object topic = ctor.newInstance(context.getHeader(X_FTL_VERB, true)); + return topic; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public ParameterExtractor verbParamExtractor(String className) { + try { + var cls = Thread.currentThread().getContextClassLoader().loadClass(className.replace("/", ".")); + var client = cls.getDeclaredConstructor().newInstance(); + return new ParameterExtractor() { + @Override + public Object extractParameter(ResteasyReactiveRequestContext context) { + return client; + } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public ParameterExtractor leaseClientExtractor() { + try { + return new ParameterExtractor() { + + volatile LeaseClient leaseClient; + + @Override + public Object extractParameter(ResteasyReactiveRequestContext context) { + if (leaseClient == null) { + leaseClient = Arc.container().instance(LeaseClient.class).get(); + } + return leaseClient; + } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java new file mode 100644 index 0000000000..b17d9f94fd --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java @@ -0,0 +1,18 @@ +package xyz.block.ftl.runtime; + +import jakarta.enterprise.event.Observes; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import io.quarkus.runtime.StartupEvent; + +/** + * This class configures the FTL serialization + */ +public class JsonSerializationConfig { + + void startup(@Observes StartupEvent event, ObjectMapper mapper) { + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/TopicHelper.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/TopicHelper.java new file mode 100644 index 0000000000..aa1e0fb20b --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/TopicHelper.java @@ -0,0 +1,32 @@ +package xyz.block.ftl.runtime; + +import jakarta.inject.Singleton; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.Arc; + +@Singleton +public class TopicHelper { + + final FTLController controller; + final ObjectMapper mapper; + + public TopicHelper(FTLController controller, ObjectMapper mapper) { + this.controller = controller; + this.mapper = mapper; + } + + public void publish(String topic, String verb, Object message) { + try { + controller.publishEvent(topic, verb, mapper.writeValueAsBytes(message)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public static TopicHelper instance() { + return Arc.container().instance(TopicHelper.class).get(); + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbClientHelper.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbClientHelper.java new file mode 100644 index 0000000000..b28037c76c --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbClientHelper.java @@ -0,0 +1,48 @@ +package xyz.block.ftl.runtime; + +import java.util.Map; + +import jakarta.inject.Singleton; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.Arc; + +@Singleton +public class VerbClientHelper { + + final FTLController controller; + final ObjectMapper mapper; + + public VerbClientHelper(FTLController controller, ObjectMapper mapper) { + this.controller = controller; + this.mapper = mapper; + } + + public Object call(String verb, String module, Object message, Class returnType, boolean listReturnType, + boolean mapReturnType) { + try { + if (message == null) { + //Unit must be an empty map + //TODO: what about optional? + message = Map.of(); + } + var result = controller.callVerb(verb, module, mapper.writeValueAsBytes(message)); + if (listReturnType) { + return mapper.readerForArrayOf(returnType).readValue(result); + } else if (mapReturnType) { + return mapper.readerForMapOf(returnType).readValue(result); + } + if (result == null) { + return null; + } + return mapper.readerFor(returnType).readValue(result); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static VerbClientHelper instance() { + return Arc.container().instance(VerbClientHelper.class).get(); + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java new file mode 100644 index 0000000000..714f197a05 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java @@ -0,0 +1,51 @@ +package xyz.block.ftl.runtime; + +import jakarta.inject.Singleton; + +import io.grpc.stub.StreamObserver; +import io.quarkus.grpc.GrpcService; +import xyz.block.ftl.v1.*; + +@Singleton +@GrpcService +public class VerbHandler extends VerbServiceGrpc.VerbServiceImplBase { + + final VerbRegistry registry; + + public VerbHandler(VerbRegistry registry) { + this.registry = registry; + } + + @Override + public void call(CallRequest request, StreamObserver responseObserver) { + var response = registry.invoke(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void publishEvent(PublishEventRequest request, StreamObserver responseObserver) { + super.publishEvent(request, responseObserver); + } + + @Override + public void sendFSMEvent(SendFSMEventRequest request, StreamObserver responseObserver) { + super.sendFSMEvent(request, responseObserver); + } + + @Override + public StreamObserver acquireLease(StreamObserver responseObserver) { + return super.acquireLease(responseObserver); + } + + @Override + public void getModuleContext(ModuleContextRequest request, StreamObserver responseObserver) { + super.getModuleContext(request, responseObserver); + } + + @Override + public void ping(PingRequest request, StreamObserver responseObserver) { + responseObserver.onNext(PingResponse.newBuilder().build()); + responseObserver.onCompleted(); + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbInvoker.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbInvoker.java new file mode 100644 index 0000000000..d27c233dcd --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbInvoker.java @@ -0,0 +1,9 @@ +package xyz.block.ftl.runtime; + +import xyz.block.ftl.v1.CallRequest; +import xyz.block.ftl.v1.CallResponse; + +public interface VerbInvoker { + + CallResponse handle(CallRequest in); +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java new file mode 100644 index 0000000000..42166c0719 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java @@ -0,0 +1,192 @@ +package xyz.block.ftl.runtime; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; + +import jakarta.inject.Singleton; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.ByteString; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import xyz.block.ftl.v1.CallRequest; +import xyz.block.ftl.v1.CallResponse; + +@Singleton +public class VerbRegistry { + + private static final Logger log = Logger.getLogger(VerbRegistry.class); + + final ObjectMapper mapper; + + private final Map verbs = new ConcurrentHashMap<>(); + + public VerbRegistry(ObjectMapper mapper) { + this.mapper = mapper; + } + + public void register(String module, String name, InstanceHandle verbHandlerClass, Method method, + List> paramMappers, boolean allowNullReturn) { + verbs.put(new Key(module, name), new AnnotatedEndpointHandler(verbHandlerClass, method, paramMappers, allowNullReturn)); + } + + public void register(String module, String name, VerbInvoker verbInvoker) { + verbs.put(new Key(module, name), verbInvoker); + } + + public CallResponse invoke(CallRequest request) { + VerbInvoker handler = verbs.get(new Key(request.getVerb().getModule(), request.getVerb().getName())); + if (handler == null) { + return CallResponse.newBuilder().setError(CallResponse.Error.newBuilder().setMessage("Verb not found").build()) + .build(); + } + return handler.handle(request); + } + + private record Key(String module, String name) { + + } + + private class AnnotatedEndpointHandler implements VerbInvoker { + final InstanceHandle verbHandlerClass; + final Method method; + final List> parameterSuppliers; + final boolean allowNull; + + private AnnotatedEndpointHandler(InstanceHandle verbHandlerClass, Method method, + List> parameterSuppliers, boolean allowNull) { + this.verbHandlerClass = verbHandlerClass; + this.method = method; + this.parameterSuppliers = parameterSuppliers; + this.allowNull = allowNull; + } + + public CallResponse handle(CallRequest in) { + try { + Object[] params = new Object[parameterSuppliers.size()]; + for (int i = 0; i < parameterSuppliers.size(); i++) { + params[i] = parameterSuppliers.get(i).apply(mapper, in); + } + Object ret; + ret = method.invoke(verbHandlerClass.get(), params); + if (ret == null) { + if (allowNull) { + return CallResponse.newBuilder().setBody(ByteString.copyFrom("{}", StandardCharsets.UTF_8)).build(); + } else { + return CallResponse.newBuilder().setError( + CallResponse.Error.newBuilder().setMessage("Verb returned an unexpected null response").build()) + .build(); + } + } else { + var mappedResponse = mapper.writer().writeValueAsBytes(ret); + return CallResponse.newBuilder().setBody(ByteString.copyFrom(mappedResponse)).build(); + } + } catch (Exception e) { + log.errorf(e, "Failed to invoke verb %s.%s", in.getVerb().getModule(), in.getVerb().getName()); + return CallResponse.newBuilder().setError(CallResponse.Error.newBuilder().setMessage(e.getMessage()).build()) + .build(); + } + } + } + + public record BodySupplier(Class inputClass) implements BiFunction { + + @Override + public Object apply(ObjectMapper mapper, CallRequest in) { + try { + return mapper.createParser(in.getBody().newInput()).readValueAs(inputClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public static class SecretSupplier implements BiFunction, ParameterExtractor { + + final String name; + final Class inputClass; + + volatile FTLController ftlController; + + public SecretSupplier(String name, Class inputClass) { + this.name = name; + this.inputClass = inputClass; + } + + @Override + public Object apply(ObjectMapper mapper, CallRequest in) { + if (ftlController == null) { + ftlController = Arc.container().instance(FTLController.class).get(); + } + var secret = ftlController.getSecret(name); + try { + return mapper.createParser(secret).readValueAs(inputClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getName() { + return name; + } + + public Class getInputClass() { + return inputClass; + } + + @Override + public Object extractParameter(ResteasyReactiveRequestContext context) { + return apply(Arc.container().instance(ObjectMapper.class).get(), null); + } + } + + public static class ConfigSupplier implements BiFunction, ParameterExtractor { + + final String name; + final Class inputClass; + + volatile FTLController ftlController; + + public ConfigSupplier(String name, Class inputClass) { + this.name = name; + this.inputClass = inputClass; + } + + @Override + public Object apply(ObjectMapper mapper, CallRequest in) { + if (ftlController == null) { + ftlController = Arc.container().instance(FTLController.class).get(); + } + var secret = ftlController.getConfig(name); + try { + return mapper.createParser(secret).readValueAs(inputClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Object extractParameter(ResteasyReactiveRequestContext context) { + return apply(Arc.container().instance(ObjectMapper.class).get(), null); + } + + public Class getInputClass() { + return inputClass; + } + + public String getName() { + return name; + } + } + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/builtin/HttpRequest.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/builtin/HttpRequest.java new file mode 100644 index 0000000000..59b5d97c60 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/builtin/HttpRequest.java @@ -0,0 +1,66 @@ +package xyz.block.ftl.runtime.builtin; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * TODO: should this be generated? + */ +public class HttpRequest { + private String method; + private String path; + private Map pathParameters; + private Map> query; + private Map> headers; + private JsonNode body; + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Map getPathParameters() { + return pathParameters; + } + + public void setPathParameters(Map pathParameters) { + this.pathParameters = pathParameters; + } + + public Map> getQuery() { + return query; + } + + public void setQuery(Map> query) { + this.query = query; + } + + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public JsonNode getBody() { + return body; + } + + public void setBody(JsonNode body) { + this.body = body; + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/builtin/HttpResponse.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/builtin/HttpResponse.java new file mode 100644 index 0000000000..6db4d8aaf9 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/builtin/HttpResponse.java @@ -0,0 +1,49 @@ +package xyz.block.ftl.runtime.builtin; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonRawValue; + +/** + * TODO: should this be generated + */ +public class HttpResponse { + private long status; + private Map> headers; + @JsonRawValue + private String body; + private Throwable error; + + public long getStatus() { + return status; + } + + public void setStatus(long status) { + this.status = status; + } + + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Throwable getError() { + return error; + } + + public void setError(Throwable error) { + this.error = error; + } +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/java-runtime/ftl-runtime/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000..6f5b0bb2b5 --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +name: Ftl Java Runtime +#description: Do something useful. +metadata: +# keywords: +# - ftl-java-runtime +# guide: ... # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension +# categories: +# - "miscellaneous" +# status: "preview" diff --git a/java-runtime/ftl-runtime/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/java-runtime/ftl-runtime/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 0000000000..28ef804d0b --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1 @@ +xyz.block.ftl.runtime.FTLConfigSource \ No newline at end of file diff --git a/java-runtime/ftl-runtime/test-framework/pom.xml b/java-runtime/ftl-runtime/test-framework/pom.xml new file mode 100644 index 0000000000..c66028219f --- /dev/null +++ b/java-runtime/ftl-runtime/test-framework/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + + xyz.block + ftl-java-runtime-parent + 1.0.0-SNAPSHOT + + ftl-java-test-framework + Ftl Java Runtime - Test Framework + + + + + xyz.block + ftl-java-runtime + + + io.quarkus + quarkus-junit5 + compile + + + io.rest-assured + rest-assured + test + + + + + diff --git a/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/FTLManaged.java b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/FTLManaged.java new file mode 100644 index 0000000000..bfcf6f7b98 --- /dev/null +++ b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/FTLManaged.java @@ -0,0 +1,8 @@ +package xyz.block.ftl.java.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface FTLManaged { +} diff --git a/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/TestFTL.java b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/TestFTL.java new file mode 100644 index 0000000000..0259f26c9a --- /dev/null +++ b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/TestFTL.java @@ -0,0 +1,16 @@ +package xyz.block.ftl.java.test; + +public class TestFTL { + + public static TestFTL FTL = new TestFTL(); + + public static TestFTL ftl() { + return FTL; + } + + public TestFTL setSecret(String secret, byte[] value) { + + return this; + } + +} diff --git a/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/FTLTestResource.java b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/FTLTestResource.java new file mode 100644 index 0000000000..f7fb23accf --- /dev/null +++ b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/FTLTestResource.java @@ -0,0 +1,27 @@ +package xyz.block.ftl.java.test.internal; + +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class FTLTestResource implements QuarkusTestResourceLifecycleManager { + + FTLTestServer server; + + @Override + public Map start() { + server = new FTLTestServer(); + server.start(); + return Map.of("ftl.endpoint", "http://127.0.0.1:" + server.getPort()); + } + + @Override + public void stop() { + server.stop(); + } + + @Override + public void inject(TestInjector testInjector) { + + } +} diff --git a/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/FTLTestServer.java b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/FTLTestServer.java new file mode 100644 index 0000000000..163a3ccad6 --- /dev/null +++ b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/FTLTestServer.java @@ -0,0 +1,33 @@ +package xyz.block.ftl.java.test.internal; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import io.grpc.Server; +import io.grpc.netty.NettyServerBuilder; + +public class FTLTestServer { + + Server grpcServer; + + public void start() { + + var addr = new InetSocketAddress("127.0.0.1", 0); + grpcServer = NettyServerBuilder.forAddress(addr) + .addService(new TestVerbServer()) + .build(); + try { + grpcServer.start(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public int getPort() { + return grpcServer.getPort(); + } + + public void stop() { + grpcServer.shutdown(); + } +} diff --git a/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/TestVerbServer.java b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/TestVerbServer.java new file mode 100644 index 0000000000..bd46c97b0c --- /dev/null +++ b/java-runtime/ftl-runtime/test-framework/src/main/java/xyz/block/ftl/java/test/internal/TestVerbServer.java @@ -0,0 +1,109 @@ +package xyz.block.ftl.java.test.internal; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.ByteString; + +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.StreamObserver; +import io.quarkus.arc.Arc; +import xyz.block.ftl.v1.AcquireLeaseRequest; +import xyz.block.ftl.v1.AcquireLeaseResponse; +import xyz.block.ftl.v1.CallRequest; +import xyz.block.ftl.v1.CallResponse; +import xyz.block.ftl.v1.ModuleContextRequest; +import xyz.block.ftl.v1.ModuleContextResponse; +import xyz.block.ftl.v1.PingRequest; +import xyz.block.ftl.v1.PingResponse; +import xyz.block.ftl.v1.PublishEventRequest; +import xyz.block.ftl.v1.PublishEventResponse; +import xyz.block.ftl.v1.SendFSMEventRequest; +import xyz.block.ftl.v1.SendFSMEventResponse; +import xyz.block.ftl.v1.VerbServiceGrpc; + +public class TestVerbServer extends VerbServiceGrpc.VerbServiceImplBase { + + final VerbServiceGrpc.VerbServiceStub verbService; + + /** + * TODO: this is so hacked up + */ + static final Map> fakeVerbs = new HashMap<>(); + + public TestVerbServer() { + var channelBuilder = ManagedChannelBuilder.forAddress("127.0.0.1", 8081); + channelBuilder.usePlaintext(); + var channel = channelBuilder.build(); + verbService = VerbServiceGrpc.newStub(channel); + } + + @Override + public void call(CallRequest request, StreamObserver responseObserver) { + Key key = new Key(request.getVerb().getModule(), request.getVerb().getName()); + if (fakeVerbs.containsKey(key)) { + //TODO: YUCK YUCK YUCK + //This all needs a refactor + ObjectMapper mapper = Arc.container().instance(ObjectMapper.class).get(); + + Function function = fakeVerbs.get(key); + Class type = null; + for (var m : function.getClass().getMethods()) { + if (m.getName().equals("apply") && m.getParameterCount() == 1) { + type = m.getParameterTypes()[0]; + if (type != Object.class) { + break; + } + } + } + try { + var result = function.apply(mapper.readerFor(type).readValue(request.getBody().newInput())); + responseObserver.onNext( + CallResponse.newBuilder().setBody(ByteString.copyFrom(mapper.writeValueAsBytes(result))).build()); + responseObserver.onCompleted(); + } catch (IOException e) { + responseObserver.onError(e); + } + return; + } + verbService.call(request, responseObserver); + } + + @Override + public void publishEvent(PublishEventRequest request, StreamObserver responseObserver) { + super.publishEvent(request, responseObserver); + } + + @Override + public void sendFSMEvent(SendFSMEventRequest request, StreamObserver responseObserver) { + super.sendFSMEvent(request, responseObserver); + } + + @Override + public StreamObserver acquireLease(StreamObserver responseObserver) { + return super.acquireLease(responseObserver); + } + + @Override + public void getModuleContext(ModuleContextRequest request, StreamObserver responseObserver) { + responseObserver.onNext(ModuleContextResponse.newBuilder().setModule("test") + .putConfigs("test", ByteString.copyFrom("test", StandardCharsets.UTF_8)).build()); + } + + @Override + public void ping(PingRequest request, StreamObserver responseObserver) { + responseObserver.onNext(PingResponse.newBuilder().build()); + responseObserver.onCompleted(); + } + + public static void registerFakeVerb(String module, String verb, Function verbFunction) { + fakeVerbs.put(new Key(module, verb), verbFunction); + } + + record Key(String module, String verb) { + } +} diff --git a/java-runtime/ftl-runtime/test-framework/src/main/resources/application.properties b/java-runtime/ftl-runtime/test-framework/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/java-runtime/java_integration_test.go b/java-runtime/java_integration_test.go new file mode 100644 index 0000000000..0680b4989f --- /dev/null +++ b/java-runtime/java_integration_test.go @@ -0,0 +1,59 @@ +//go:build integration + +package ftl_test + +import ( + "testing" + "time" + + "github.com/alecthomas/assert/v2" + + in "github.com/TBD54566975/ftl/integration" + + "github.com/alecthomas/repr" +) + +func TestJavaToGoCall(t *testing.T) { + in.Run(t, + in.WithJava(), + in.CopyModule("gomodule"), + in.CopyDir("javamodule", "javamodule"), + in.Deploy("gomodule"), + in.Deploy("javamodule"), + in.Call("javamodule", "timeVerb", in.Obj{}, func(t testing.TB, response in.Obj) { + message, ok := response["time"].(string) + assert.True(t, ok, "time is not a string: %s", repr.String(response)) + result, err := time.Parse(time.RFC3339, message) + assert.NoError(t, err, "time is not a valid RFC3339 time: %s", message) + assert.True(t, result.After(time.Now().Add(-time.Minute)), "time is not recent: %s", message) + }), + // We call both the go and pass through Java versions + // To make sure the response is the same + in.Call("gomodule", "emptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { + assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) + }), + in.Call("javamodule", "emptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { + assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) + }), + in.Call("gomodule", "sinkVerb", "ignored", func(t testing.TB, response in.Obj) { + assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) + }), + in.Call("javamodule", "sinkVerb", "ignored", func(t testing.TB, response in.Obj) { + assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) + }), + in.Call("gomodule", "sourceVerb", in.Obj{}, func(t testing.TB, response string) { + assert.Equal(t, "Source Verb", response, "expecting empty response, got %s", response) + }), + in.Call("javamodule", "sourceVerb", in.Obj{}, func(t testing.TB, response string) { + assert.Equal(t, "Source Verb", response, "expecting empty response, got %s", response) + }), + in.Fail( + in.Call("gomodule", "errorEmptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { + assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) + }), "verb failed"), + in.Fail( + in.Call("gomodule", "errorEmptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { + assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) + }), "verb failed"), + ) +} diff --git a/java-runtime/testdata/go/gomodule/ftl.toml b/java-runtime/testdata/go/gomodule/ftl.toml new file mode 100644 index 0000000000..5becff80a9 --- /dev/null +++ b/java-runtime/testdata/go/gomodule/ftl.toml @@ -0,0 +1,5 @@ +module = "gomodule" +language = "go" + +[go.replace] +"github.com/TBD54566975/ftl" = "../.." diff --git a/java-runtime/testdata/go/gomodule/go.mod b/java-runtime/testdata/go/gomodule/go.mod new file mode 100644 index 0000000000..3773c01c5d --- /dev/null +++ b/java-runtime/testdata/go/gomodule/go.mod @@ -0,0 +1,5 @@ +module ftl/gomodule + +go 1.22.2 + +replace github.com/TBD54566975/ftl => ./../../../.. diff --git a/java-runtime/testdata/go/gomodule/go.sum b/java-runtime/testdata/go/gomodule/go.sum new file mode 100644 index 0000000000..e69de29bb2 diff --git a/java-runtime/testdata/go/gomodule/server.go b/java-runtime/testdata/go/gomodule/server.go new file mode 100644 index 0000000000..65d6d4b0df --- /dev/null +++ b/java-runtime/testdata/go/gomodule/server.go @@ -0,0 +1,38 @@ +package gomodule + +import ( + "context" + "fmt" + "time" +) + +type TimeRequest struct { +} +type TimeResponse struct { + Time time.Time +} + +//ftl:verb export +func SourceVerb(ctx context.Context) (string, error) { + return "Source Verb", nil +} + +//ftl:verb export +func SinkVerb(ctx context.Context, req string) error { + return nil +} + +//ftl:verb export +func EmptyVerb(ctx context.Context) error { + return nil +} + +//ftl:verb export +func ErrorEmptyVerb(ctx context.Context) error { + return fmt.Errorf("verb failed") +} + +//ftl:verb export +func Time(ctx context.Context, req TimeRequest) (TimeResponse, error) { + return TimeResponse{Time: time.Now()}, nil +} diff --git a/java-runtime/testdata/go/javamodule/ftl.toml b/java-runtime/testdata/go/javamodule/ftl.toml new file mode 100644 index 0000000000..864288fa76 --- /dev/null +++ b/java-runtime/testdata/go/javamodule/ftl.toml @@ -0,0 +1,2 @@ +module = "echoclient" +language = "java" diff --git a/java-runtime/testdata/go/javamodule/pom.xml b/java-runtime/testdata/go/javamodule/pom.xml new file mode 100644 index 0000000000..43d234296e --- /dev/null +++ b/java-runtime/testdata/go/javamodule/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + xyz.block.ftl.examples + javamodule + 1.0.0-SNAPSHOT + + + 1.0-SNAPSHOT + 3.13.0 + 2.0.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.12.3 + true + 3.2.5 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + xyz.block + ftl-java-runtime + 1.0.0-SNAPSHOT + + + io.quarkus + quarkus-kotlin + + + io.quarkus + quarkus-jackson + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-junit5 + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + io.rest-assured + kotlin-extensions + test + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + false + true + + + + diff --git a/java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java b/java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java new file mode 100644 index 0000000000..da50c7035f --- /dev/null +++ b/java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java @@ -0,0 +1,45 @@ +package xyz.block.ftl.java.test; + +import ftl.gomodule.EmptyVerbClient; +import ftl.gomodule.ErrorEmptyVerbClient; +import ftl.gomodule.SinkVerbClient; +import ftl.gomodule.SourceVerbClient; +import ftl.gomodule.TimeClient; +import ftl.gomodule.TimeRequest; +import ftl.gomodule.TimeResponse; +import org.jetbrains.annotations.NotNull; +import xyz.block.ftl.Export; +import xyz.block.ftl.Verb; + +public class TestInvokeGo { + + @Export + @Verb + public void emptyVerb(EmptyVerbClient emptyVerbClient) { + emptyVerbClient.call(); + } + + @Export + @Verb + public void sinkVerb(String input, SinkVerbClient sinkVerbClient) { + sinkVerbClient.call(input); + } + + @Export + @Verb + public String sourceVerb(SourceVerbClient sourceVerbClient) { + return sourceVerbClient.call(); + } + @Export + @Verb + public void errorEmptyVerb(ErrorEmptyVerbClient client) { + client.call(); + } + + @Export + @Verb + public @NotNull TimeResponse timeVerb(TimeClient client) { + return client.call(new TimeRequest()); + } + +} diff --git a/kotlin-runtime/ftl-runtime/pom.xml b/kotlin-runtime/ftl-runtime/pom.xml index ad9bdeef00..4a2e9c72d1 100644 --- a/kotlin-runtime/ftl-runtime/pom.xml +++ b/kotlin-runtime/ftl-runtime/pom.xml @@ -53,10 +53,10 @@ false 1.23.6 17 - 2.0.0 + 2.0.10 false 4.9.9 - 1.65.1 + 1.66.0 1.5.6 5.10.3 8.0 @@ -77,7 +77,7 @@ org.jetbrains.kotlin kotlin-reflect - 2.0.0 + 2.0.10 @@ -293,7 +293,7 @@ org.codehaus.mojo exec-maven-plugin - 3.3.0 + 3.4.0 wire-client diff --git a/kotlin-runtime/scaffolding/{{ .Name | lower }}/pom.xml b/kotlin-runtime/scaffolding/{{ .Name | lower }}/pom.xml index 92047d36e6..789035c48d 100644 --- a/kotlin-runtime/scaffolding/{{ .Name | lower }}/pom.xml +++ b/kotlin-runtime/scaffolding/{{ .Name | lower }}/pom.xml @@ -11,7 +11,7 @@ 1.0-SNAPSHOT 1.8 - 2.0.0 + 2.0.10 true ${java.version} ${java.version} diff --git a/lsp/hoveritems.go b/lsp/hoveritems.go index ddd63c82ec..db9ad3c6ac 100644 --- a/lsp/hoveritems.go +++ b/lsp/hoveritems.go @@ -5,7 +5,7 @@ var hoverMap = map[string]string{ "//ftl:cron": "## Cron\n\nA cron job is an Empty verb that will be called on a schedule. The syntax is described [here](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/crontab.html).\n\nYou can also use a shorthand syntax for the cron job, supporting seconds (`s`), minutes (`m`), hours (`h`), and specific days of the week (e.g. `Mon`).\n\n### Examples\n\nThe following function will be called hourly:\n\n```go\n//ftl:cron 0 * * * *\nfunc Hourly(ctx context.Context) error {\n // ...\n}\n```\n\nEvery 12 hours, starting at UTC midnight:\n\n```go\n//ftl:cron 12h\nfunc TwiceADay(ctx context.Context) error {\n // ...\n}\n```\n\nEvery Monday at UTC midnight:\n\n```go\n//ftl:cron Mon\nfunc Mondays(ctx context.Context) error {\n // ...\n}\n```\n\n", "//ftl:enum": "## Type enums (sum types)\n\n[Sum types](https://en.wikipedia.org/wiki/Tagged_union) are supported by FTL's type system, but aren't directly supported by Go. However they can be approximated with the use of [sealed interfaces](https://blog.chewxy.com/2018/03/18/golang-interfaces/). To declare a sum type in FTL use the comment directive `//ftl:enum`:\n\n```go\n//ftl:enum\ntype Animal interface { animal() }\n\ntype Cat struct {}\nfunc (Cat) animal() {}\n\ntype Dog struct {}\nfunc (Dog) animal() {}\n```\n## Value enums\n\nA value enum is an enumerated set of string or integer values.\n\n```go\n//ftl:enum\ntype Colour string\n\nconst (\n Red Colour = \"red\"\n Green Colour = \"green\"\n Blue Colour = \"blue\"\n)\n```\n", "//ftl:ingress": "## HTTP Ingress\n\nVerbs annotated with `ftl:ingress` will be exposed via HTTP (`http` is the default ingress type). These endpoints will then be available on one of our default `ingress` ports (local development defaults to `http://localhost:8891`).\n\nThe following will be available at `http://localhost:8891/http/users/123/posts?postId=456`.\n\n```go\ntype GetRequest struct {\n\tUserID string `json:\"userId\"`\n\tPostID string `json:\"postId\"`\n}\n\ntype GetResponse struct {\n\tMessage string `json:\"msg\"`\n}\n\n//ftl:ingress GET /http/users/{userId}/posts\nfunc Get(ctx context.Context, req builtin.HttpRequest[GetRequest]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) {\n // ...\n}\n```\n\n> **NOTE!**\n> The `req` and `resp` types of HTTP `ingress` [verbs](../verbs) must be `builtin.HttpRequest` and `builtin.HttpResponse` respectively. These types provide the necessary fields for HTTP `ingress` (`headers`, `statusCode`, etc.)\n>\n> You will need to import `ftl/builtin`.\n\nKey points:\n\n- `ingress` verbs will be automatically exported by default.\n\n## Field mapping\n\nGiven the following request verb:\n\n```go\ntype GetRequest struct {\n\tUserID string `json:\"userId\"`\n\tTag ftl.Option[string] `json:\"tag\"`\n\tPostID string `json:\"postId\"`\n}\n\ntype GetResponse struct {\n\tMessage string `json:\"msg\"`\n}\n\n//ftl:ingress http GET /users/{userId}/posts/{postId}\nfunc Get(ctx context.Context, req builtin.HttpRequest[GetRequest]) (builtin.HttpResponse[GetResponse, string], error) {\n\treturn builtin.HttpResponse[GetResponse, string]{\n\t\tHeaders: map[string][]string{\"Get\": {\"Header from FTL\"}},\n\t\tBody: ftl.Some(GetResponse{\n\t\t\tMessage: fmt.Sprintf(\"UserID: %s, PostID: %s, Tag: %s\", req.Body.UserID, req.Body.PostID, req.Body.Tag.Default(\"none\")),\n\t\t}),\n\t}, nil\n}\n```\n\n`path`, `query`, and `body` parameters are automatically mapped to the `req` structure.\n\nFor example, this curl request will map `userId` to `req.Body.UserID` and `postId` to `req.Body.PostID`, and `tag` to `req.Body.Tag`:\n\n```sh\ncurl -i http://localhost:8891/users/123/posts/456?tag=ftl\n```\n\nThe response here will be:\n\n```json\n{\n \"msg\": \"UserID: 123, PostID: 456, Tag: ftl\"\n}\n```\n\n#### Optional fields\n\nOptional fields are represented by the `ftl.Option` type. The `Option` type is a wrapper around the actual type and can be `Some` or `None`. In the example above, the `Tag` field is optional.\n\n```sh\ncurl -i http://localhost:8891/users/123/posts/456\n```\n\nBecause the `tag` query parameter is not provided, the response will be:\n\n```json\n{\n \"msg\": \"UserID: 123, PostID: 456, Tag: none\"\n}\n```\n\n#### Casing\n\nField names use lowerCamelCase by default. You can override this by using the `json` tag.\n\n## SumTypes\n\nGiven the following request verb:\n\n```go\n//ftl:enum export\ntype SumType interface {\n\ttag()\n}\n\ntype A string\n\nfunc (A) tag() {}\n\ntype B []string\n\nfunc (B) tag() {}\n\n//ftl:ingress http GET /typeenum\nfunc TypeEnum(ctx context.Context, req builtin.HttpRequest[SumType]) (builtin.HttpResponse[SumType, string], error) {\n\treturn builtin.HttpResponse[SumType, string]{Body: ftl.Some(req.Body)}, nil\n}\n```\n\nThe following curl request will map the `SumType` name and value to the `req.Body`:\n\n```sh\ncurl -X GET \"http://localhost:8891/typeenum\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"name\": \"A\", \"value\": \"sample\"}'\n```\n\nThe response will be:\n\n```json\n{\n \"name\": \"A\",\n \"value\": \"sample\"\n}\n```\n\n## Encoding query params as JSON\n\nComplex query params can also be encoded as JSON using the `@json` query parameter. For example:\n\n> `{\"tag\":\"ftl\"}` url-encoded is `%7B%22tag%22%3A%22ftl%22%7D`\n\n```bash\ncurl -i http://localhost:8891/users/123/posts/456?@json=%7B%22tag%22%3A%22ftl%22%7D\n```\n", - "//ftl:retry": "## Retries\n\nAny verb called asynchronously (specifically, PubSub subscribers and FSM states), may optionally specify a basic exponential backoff retry policy via a Go comment directive. The directive has the following syntax:\n\n```go\n//ftl:retry [] []\n```\n\n`attempts` and `max-backoff` default to unlimited if not specified.\n\nFor example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc.\n\n```go\n//ftl:retry 10 5s 1m\nfunc Invoiced(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n", + "//ftl:retry": "## Retries\n\nAny verb called asynchronously (specifically, PubSub subscribers and FSM states), may optionally specify a basic exponential backoff retry policy via a Go comment directive. The directive has the following syntax:\n\n```go\n//ftl:retry [] [] [catch ]\n```\n\nFor example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc.\n\n```go\n//ftl:retry 10 5s 1m\nfunc Invoiced(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\n## Catching\nAfter all retries have failed, a catch verb can be used to safely recover.\n\nThese catch verbs have a request type of `builtin.CatchRequest` and no response type. If a catch verb returns an error, it will be retried until it succeeds so it is important to handle errors carefully.\n\n```go\n//ftl retry 5 1s catch recoverPaymentProcessing\nfunc ProcessPayment(ctx context.Context, payment Payment) error {\n ...\n}\n\n//ftl:verb\nfunc RecoverPaymentProcessing(ctx context.Context, request builtin.CatchRequest[Payment]) error {\n // safely handle final failure of the payment\n}\n```\n\nFor FSMs, after a catch verb has been successfully called the FSM will moved to the failed state.", "//ftl:subscribe": "## PubSub\n\nFTL has first-class support for PubSub, modelled on the concepts of topics (where events are sent), subscriptions (a cursor over the topic), and subscribers (functions events are delivered to). Subscribers are, as you would expect, sinks. Each subscription is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each subscription may have multiple subscribers, in which case events will be distributed among them.\n\nFirst, declare a new topic:\n\n```go\nvar Invoices = ftl.Topic[Invoice](\"invoices\")\n```\n\nThen declare each subscription on the topic:\n\n```go\nvar _ = ftl.Subscription(Invoices, \"emailInvoices\")\n```\n\nAnd finally define a Sink to consume from the subscription:\n\n```go\n//ftl:subscribe emailInvoices\nfunc SendInvoiceEmail(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\nEvents can be published to a topic like so:\n\n```go\nInvoices.Publish(ctx, Invoice{...})\n```\n\n> **NOTE!**\n> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it.\n", "//ftl:typealias": "## Type aliases\n\nA type alias is an alternate name for an existing type. It can be declared like so:\n\n```go\n//ftl:typealias\ntype Alias Target\n```\nor\n```go\n//ftl:typealias\ntype Alias = Target\n```\n\neg.\n\n```go\n//ftl:typealias\ntype UserID string\n\n//ftl:typealias\ntype UserToken = string\n```\n", "//ftl:verb": "## Verbs\n\n## Defining Verbs\n\nTo declare a Verb, write a normal Go function with the following signature, annotated with the Go [comment directive](https://tip.golang.org/doc/comment#syntax) `//ftl:verb`:\n\n```go\n//ftl:verb\nfunc F(context.Context, In) (Out, error) { }\n```\n\neg.\n\n```go\ntype EchoRequest struct {}\n\ntype EchoResponse struct {}\n\n//ftl:verb\nfunc Echo(ctx context.Context, in EchoRequest) (EchoResponse, error) {\n // ...\n}\n```\n\nBy default verbs are only [visible](../visibility) to other verbs in the same module.\n\n## Calling Verbs\n\nTo call a verb use `ftl.Call()`. eg.\n\n```go\nout, err := ftl.Call(ctx, echo.Echo, echo.EchoRequest{})\n```\n", diff --git a/renovate.json5 b/renovate.json5 index e5b258894b..2da4c874b5 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -24,6 +24,14 @@ matchManagers: ["hermit"], enabled: false, }, + { + matchPackageNames: [ + "openjdk", // We don't want automatic major version updates, just minors + ], + matchManagers: ["hermit"], + matchUpdateTypes: ["major"], + enabled: false + }, { matchFileNames: ["**/testdata/**/go.mod"], enabled: false, diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index eef266182d..a51291798b 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.11" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" dependencies = [ "clap_builder", "clap_derive", @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -1016,18 +1016,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", @@ -1036,9 +1036,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", "memchr", @@ -1088,9 +1088,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.72" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", diff --git a/scripts/ftl b/scripts/ftl index 637597aaee..7ee8566d7a 100755 --- a/scripts/ftl +++ b/scripts/ftl @@ -4,4 +4,4 @@ ftldir="$(dirname "$0")/.." name="$(basename "$0")" dest="${ftldir}/build/devel" mkdir -p "$dest" -(cd "${ftldir}" && ./bin/go build -ldflags="-s -w -buildid=" -o "$dest/${name}" "./cmd/${name}") && exec "$dest/${name}" "$@" +(cd "${ftldir}/cmd/${name}" && "${ftldir}/bin/go" build -ldflags="-s -w -buildid=" -o "$dest/${name}" ./) && exec "$dest/${name}" "$@" diff --git a/scripts/lint-commit-or-rollback b/scripts/lint-commit-or-rollback new file mode 120000 index 0000000000..1db38bf169 --- /dev/null +++ b/scripts/lint-commit-or-rollback @@ -0,0 +1 @@ +ftl \ No newline at end of file diff --git a/sqlc.yaml b/sqlc.yaml index e234043ab6..d1794ddf46 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -12,7 +12,6 @@ sql: gen: go: &gengo package: "sql" - sql_package: "pgx/v5" out: "backend/controller/sql" emit_interface: true query_parameter_limit: 3 @@ -26,11 +25,11 @@ sql: - db_type: "timestamptz" go_type: "time.Time" - db_type: "pg_catalog.interval" - go_type: "time.Duration" + go_type: "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes.Duration" - db_type: "pg_catalog.interval" nullable: true go_type: - type: "optional.Option[time.Duration]" + type: "optional.Option[sqltypes.Duration]" - db_type: "module_schema_pb" go_type: "*github.com/TBD54566975/ftl/backend/schema.Module" - db_type: "timestamptz" diff --git a/testutils/localstack.go b/testutils/localstack.go new file mode 100644 index 0000000000..e1e4d40390 --- /dev/null +++ b/testutils/localstack.go @@ -0,0 +1,19 @@ +package testutils + +import ( + "context" + "testing" + + "github.com/alecthomas/assert/v2" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" +) + +func NewLocalstackConfig(t *testing.T, ctx context.Context) aws.Config { // nolint: revive + t.Helper() + cc := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider("test", "test", "")) + cfg, err := config.LoadDefaultConfig(ctx, config.WithCredentialsProvider(cc), config.WithRegion("us-west-2")) + assert.NoError(t, err) + return cfg +}