├── .github └── workflows │ ├── release-rc.yaml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── constants ├── env.go └── grpc.go ├── doc.go ├── docs └── assets │ └── nitric-logo.svg ├── go.mod ├── go.sum ├── internal ├── grpc │ └── grpcx.go └── handlers │ ├── handlers.go │ └── handlers_test.go ├── licenses.go ├── lichen.yaml ├── mocks ├── batch.go ├── grpc_clientconn.go ├── keyvalue.go ├── queues.go ├── secrets.go ├── storage.go └── topics.go ├── nitric ├── apis │ ├── api.go │ ├── context.go │ ├── oidc.go │ ├── options.go │ ├── request.go │ ├── response.go │ └── worker.go ├── batch │ ├── batch.go │ ├── batch_suite_test.go │ ├── client.go │ ├── client_test.go │ ├── context.go │ ├── options.go │ ├── request.go │ ├── response.go │ └── worker.go ├── errors │ ├── api_error.go │ └── codes │ │ └── codes.go ├── keyvalue │ ├── client.go │ ├── client_test.go │ ├── keyvalue.go │ └── keyvalue_suite_test.go ├── nitric.go ├── queues │ ├── client.go │ ├── client_test.go │ ├── message.go │ ├── message_test.go │ ├── queue.go │ └── queues_suite_test.go ├── resource │ └── resource.go ├── resources_suite_test.go ├── schedules │ ├── context.go │ ├── request.go │ ├── response.go │ ├── schedule.go │ └── worker.go ├── secrets │ ├── client.go │ ├── client_test.go │ ├── secret.go │ └── secrets_suite_test.go ├── sql │ ├── client.go │ └── sql.go ├── storage │ ├── bucket.go │ ├── client.go │ ├── client_test.go │ ├── context.go │ ├── request.go │ ├── response.go │ ├── storage_suite_test.go │ └── worker.go ├── topics │ ├── client.go │ ├── client_test.go │ ├── context.go │ ├── request.go │ ├── response.go │ ├── topic.go │ ├── topics_suite_test.go │ └── worker.go ├── websockets │ ├── context.go │ ├── request.go │ ├── response.go │ ├── websocket.go │ └── worker.go └── workers │ ├── manager.go │ └── workers.go └── tools ├── readme.txt ├── tools.go └── tools.mk /.github/workflows/release-rc.yaml: -------------------------------------------------------------------------------- 1 | name: Release Candidate 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | jobs: 7 | # Bump the membrane version 8 | version_bump: 9 | name: Bump Version and Create Release 10 | runs-on: ubuntu-latest 11 | outputs: 12 | version_id: ${{ steps.tag_version.outputs.new_tag }} 13 | upload_url: ${{ steps.create_release.outputs.upload_url }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | - name: Bump version and push tag 19 | id: tag_version 20 | uses: mathieudutour/github-tag-action@v5.5 21 | with: 22 | # Don't commit tag 23 | # this will be done as part of the release 24 | dry_run: true 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | release_branches: main,develop 27 | 28 | - name: Calculate RC number 29 | id: vars 30 | run: echo "::set-output name=rc_num::$(git rev-list --merges --count origin/develop...origin/main)" 31 | 32 | - name: Create a GitHub release 33 | id: create_release 34 | uses: actions/create-release@v1 35 | env: 36 | # Use NITRIC_BOT_TOKEN here to 37 | # trigger release 'published' workflows 38 | GITHUB_TOKEN: ${{ secrets.NITRIC_BOT_TOKEN }} 39 | with: 40 | prerelease: true 41 | tag_name: ${{ steps.tag_version.outputs.new_tag }}-rc.${{ steps.vars.outputs.rc_num }} 42 | release_name: Release ${{ steps.tag_version.outputs.new_tag }}-rc.${{ steps.vars.outputs.rc_num }} 43 | body: ${{ steps.tag_version.outputs.changelog }} -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | version: 8 | runs-on: ubuntu-latest 9 | env: 10 | GOPATH: /home/runner/go 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Bump version and push tag 14 | id: tag_version 15 | uses: mathieudutour/github-tag-action@v5.5 16 | with: 17 | github_token: ${{ secrets.GITHUB_TOKEN }} 18 | - name: Create a GitHub release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ steps.tag_version.outputs.new_tag }} 24 | release_name: Release ${{ steps.tag_version.outputs.new_tag }} 25 | body: ${{ steps.tag_version.outputs.changelog }} -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | env: 13 | GOPATH: /home/runner/go 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Setup Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.23 21 | - name: Check License Headers 22 | run: make license-header-check 23 | - name: Run Tests 24 | run: make test-ci 25 | - name: Check Licenses 26 | run: make license-check 27 | # Upload coverage report if main is set 28 | - name: Upload Coverage Report 29 | uses: codecov/codecov-action@v2 30 | with: 31 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 32 | files: all.coverprofile # optional 33 | flags: unittests # optional 34 | fail_ci_if_error: true # optional (default = false) 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # GoLand / IDEA files 9 | .idea/ 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | bin/ 20 | 21 | *.coverprofile 22 | 23 | .vscode/ 24 | 25 | contracts/ 26 | *-contracts.tgz 27 | tools/include/ 28 | tools/protoc-*.zip 29 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: "5m" 3 | 4 | linters: 5 | disable-all: true 6 | enable: 7 | - goimports 8 | - gofmt 9 | - govet 10 | - gofumpt 11 | - whitespace 12 | - staticcheck 13 | - ineffassign 14 | - unused 15 | - misspell 16 | - unconvert 17 | - errcheck 18 | - errorlint 19 | 20 | issues: 21 | max-issues-per-linter: 0 22 | max-same-issues: 0 23 | exclude-files: 24 | - tools/tools.go 25 | - doc.go 26 | exclude-rules: 27 | - path: _test\.go 28 | linters: 29 | - typecheck 30 | linters-settings: 31 | goimports: 32 | local-prefixes: github.com/nitrictech 33 | govet: 34 | check-shadowing: false 35 | stylecheck: 36 | dot-import-whitelist: 37 | [ 38 | "github.com/onsi/gomega", 39 | "github.com/onsi/ginkgo/v2", 40 | "github.com/onsi/gomega/gstruct", 41 | ] 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq (/,${HOME}) 2 | GOLANGCI_LINT_CACHE=/tmp/golangci-lint-cache/ 3 | else 4 | GOLANGCI_LINT_CACHE=${HOME}/.cache/golangci-lint 5 | endif 6 | GOLANGCI_LINT ?= GOLANGCI_LINT_CACHE=$(GOLANGCI_LINT_CACHE) go run github.com/golangci/golangci-lint/cmd/golangci-lint 7 | 8 | include tools/tools.mk 9 | 10 | .PHONY: check 11 | check: lint test 12 | 13 | .PHONY: fmt 14 | fmt: license-header-add 15 | $(GOLANGCI_LINT) run --fix 16 | 17 | .PHONY: lint 18 | lint: license-header-check 19 | $(GOLANGCI_LINT) run 20 | 21 | sourcefiles := $(shell find . -type f -name "*.go") 22 | 23 | license-header-add: 24 | @echo "Add License Headers to source files" 25 | @go run github.com/google/addlicense -c "Nitric Technologies Pty Ltd." -y "2023" $(sourcefiles) 26 | 27 | license-header-check: 28 | @echo "Checking License Headers for source files" 29 | @go run github.com/google/addlicense -check -c "Nitric Technologies Pty Ltd." -y "2023" $(sourcefiles) 30 | 31 | license-check: 32 | @echo Checking OSS Licenses 33 | @go build -o ./bin/licenses ./licenses.go 34 | @go run github.com/uw-labs/lichen --config=./lichen.yaml ./bin/licenses 35 | 36 | .PHONY: clean 37 | clean: 38 | @rm -rf ./interfaces 39 | 40 | check-gopath: 41 | ifndef GOPATH 42 | $(error GOPATH is undefined) 43 | endif 44 | 45 | generate: 46 | go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/core/pkg/proto/kvstore/v1 KvStoreClient,KvStore_ScanKeysClient > mocks/keyvalue.go 47 | go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/core/pkg/proto/queues/v1 QueuesClient > mocks/queues.go 48 | go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/core/pkg/proto/storage/v1 StorageClient > mocks/storage.go 49 | go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/core/pkg/proto/secrets/v1 SecretManagerClient > mocks/secrets.go 50 | go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/core/pkg/proto/topics/v1 TopicsClient > mocks/topics.go 51 | go run github.com/golang/mock/mockgen github.com/nitrictech/nitric/core/pkg/proto/batch/v1 BatchClient > mocks/batch.go 52 | go run github.com/golang/mock/mockgen -package mock_v1 google.golang.org/grpc ClientConnInterface > mocks/grpc_clientconn.go 53 | 54 | # Runs tests for coverage upload to codecov.io 55 | test-ci: generate 56 | @echo Testing Nitric Go SDK 57 | @go run github.com/onsi/ginkgo/ginkgo -cover -outputdir=./ -coverprofile=all.coverprofile ./... 58 | 59 | .PHONY: test 60 | test: generate 61 | @echo Testing Nitric Go SDK 62 | @go run github.com/onsi/ginkgo/ginkgo -cover ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Nitric Logo 4 | 5 |

6 | 7 |

8 | Build nitric applications with Go 9 |

10 | 11 |

12 | 13 | codecov 14 | 15 | 16 | Version 17 | 18 | 19 | Go report card 20 | 21 |

22 | 23 | # Go SDK for Nitric 24 | 25 | The Go SDK supports the use of the Nitric framework with GoLang 1.21. For more information, check out the main [Nitric repo](https://github.com/nitrictech/nitric). 26 | 27 | Nitric SDKs provide an infrastructure-as-code style that lets you define resources in code. You can also write the functions that support the logic behind APIs, subscribers and schedules. 28 | 29 | You can request the type of access you need to resources such as publishing for topics, without dealing directly with IAM or policy documents. 30 | 31 | ## Status 32 | 33 | The SDK is in early stage development and APIs and interfaces are still subject to breaking changes. We’d love your feedback as we build additional functionality! 34 | 35 | ## Get in touch 36 | 37 | - Ask questions in [GitHub discussions](https://github.com/nitrictech/nitric/discussions) 38 | 39 | - Join us on [Discord](https://discord.gg/Webemece5C) 40 | 41 | - Find us on [Twitter](https://twitter.com/nitric_io) 42 | 43 | - Send us an [email](mailto:maintainers@nitric.io) 44 | 45 | ## Get Started 46 | 47 | Check out the [Nitric docs](https://nitric.io/docs) to see how to get started using Nitric. 48 | -------------------------------------------------------------------------------- /constants/env.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package constants 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strconv" 21 | "time" 22 | ) 23 | 24 | const ( 25 | nitricServiceHostDefault = "127.0.0.1" 26 | nitricServicePortDefault = "50051" 27 | nitricServiceDialTimeoutDefault = "5000" 28 | nitricServiceAddress = "SERVICE_ADDRESS" 29 | ) 30 | 31 | // getEnvWithFallback - Returns an envirable variable's value from its name or a default value if the variable isn't set 32 | func GetEnvWithFallback(varName string, fallback string) string { 33 | if v := os.Getenv(varName); v == "" { 34 | return fallback 35 | } else { 36 | return v 37 | } 38 | } 39 | 40 | // NitricDialTimeout - Retrieves default service dial timeout in milliseconds 41 | func NitricDialTimeout() time.Duration { 42 | tInt, _ := strconv.ParseInt(nitricServiceDialTimeoutDefault, 10, 64) 43 | 44 | return time.Duration(tInt) * time.Millisecond 45 | } 46 | 47 | // nitricAddress - constructs the full address i.e. host:port, of the nitric service based on config or defaults 48 | func NitricAddress() string { 49 | return GetEnvWithFallback(nitricServiceAddress, fmt.Sprintf("%s:%s", nitricServiceHostDefault, nitricServicePortDefault)) 50 | } 51 | -------------------------------------------------------------------------------- /constants/grpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package constants 16 | 17 | import ( 18 | "google.golang.org/grpc" 19 | "google.golang.org/grpc/credentials/insecure" 20 | ) 21 | 22 | // DefaultOptions - Provides option defaults for creating a gRPC service connection with the Nitric Membrane 23 | func DefaultOptions() []grpc.DialOption { 24 | return []grpc.DialOption{ 25 | grpc.WithTransportCredentials(insecure.NewCredentials()), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package go-sdk is the Go SDK for the Nitric framework. 16 | // 17 | // # Introduction 18 | // 19 | // The Go SDK supports the use of the Nitric framework with Go 1.17+. For more information, check out the main Nitric repo. 20 | // 21 | // Nitric SDKs provide an infrastructure-as-code style that lets you define resources in code. You can also write the functions that support the logic behind APIs, subscribers and schedules. 22 | // You can request the type of access you need to resources such as publishing for topics, without dealing directly with IAM or policy documents. 23 | // 24 | // A good starting point is to check out [resources.NewApi] function. 25 | // 26 | // exampleApi, err := resources.NewApi("example") 27 | // if err != nil { 28 | // fmt.Println(err) 29 | // os.Exit(1) 30 | // } 31 | // 32 | // exampleApi.Get("/hello/:name", func(ctx *faas.HttpContext, next faas.HttpHandler) (*faas.HttpContext, error) { 33 | // params := ctx.Request.PathParams() 34 | // 35 | // if params == nil || len(params["name"]) == 0 { 36 | // ctx.Response.Body = []byte("error retrieving path params") 37 | // ctx.Response.Status = http.StatusBadRequest 38 | // } else { 39 | // ctx.Response.Body = []byte("Hello " + params["name"]) 40 | // ctx.Response.Status = http.StatusOK 41 | // } 42 | // 43 | // return next(ctx) 44 | // }) 45 | // 46 | // fmt.Println("running example API") 47 | // if err := resources.Run(); err != nil { 48 | // fmt.Println(err) 49 | // os.Exit(1) 50 | // } 51 | package main 52 | 53 | import _ "github.com/nitrictech/go-sdk/nitric" 54 | -------------------------------------------------------------------------------- /docs/assets/nitric-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /internal/grpc/grpcx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package grpcx 16 | 17 | import ( 18 | "sync" 19 | 20 | "google.golang.org/grpc" 21 | 22 | "github.com/nitrictech/go-sdk/constants" 23 | ) 24 | 25 | type grpcManager struct { 26 | conn grpc.ClientConnInterface 27 | connMutex sync.Mutex 28 | } 29 | 30 | var m = grpcManager{ 31 | conn: nil, 32 | connMutex: sync.Mutex{}, 33 | } 34 | 35 | func GetConnection() (grpc.ClientConnInterface, error) { 36 | m.connMutex.Lock() 37 | defer m.connMutex.Unlock() 38 | 39 | if m.conn == nil { 40 | conn, err := grpc.NewClient(constants.NitricAddress(), constants.DefaultOptions()...) 41 | if err != nil { 42 | return nil, err 43 | } 44 | m.conn = conn 45 | } 46 | 47 | return m.conn, nil 48 | } 49 | -------------------------------------------------------------------------------- /internal/handlers/handlers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handlers 16 | 17 | import "fmt" 18 | 19 | type ( 20 | Handler[T any] func(context *T) error 21 | Middleware[T any] func(Handler[T]) Handler[T] 22 | ) 23 | 24 | // fromInterface - Converts a function to a Handler 25 | // Valid function types are: 26 | // func() 27 | // func() error 28 | // func(*T) 29 | // func(*T) error 30 | // Handler[T] 31 | // If the function is not a valid type, an error is returned 32 | func HandlerFromInterface[T any](handler interface{}) (Handler[T], error) { 33 | var typedHandler Handler[T] 34 | switch handlerType := handler.(type) { 35 | case func(): 36 | typedHandler = func(ctx *T) error { 37 | handlerType() 38 | return nil 39 | } 40 | case func() error: 41 | typedHandler = func(ctx *T) error { 42 | return handlerType() 43 | } 44 | case func(*T): 45 | typedHandler = func(ctx *T) error { 46 | handlerType(ctx) 47 | return nil 48 | } 49 | case func(*T) error: 50 | typedHandler = Handler[T](handlerType) 51 | case Handler[T]: 52 | typedHandler = handlerType 53 | default: 54 | return nil, fmt.Errorf("invalid handler type: %T", handler) 55 | } 56 | 57 | return typedHandler, nil 58 | } 59 | -------------------------------------------------------------------------------- /internal/handlers/handlers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handlers 16 | 17 | import ( 18 | . "github.com/onsi/ginkgo" 19 | . "github.com/onsi/gomega" 20 | ) 21 | 22 | var _ = Describe("handler", func() { 23 | // func() 24 | // func() error 25 | // func(*T) 26 | // func(*T) error 27 | // func(*T, Handler[T]) error 28 | // Handler[T] 29 | Context("HandlerFromInterface", func() { 30 | When("interface{} is func()", func() { 31 | It("should return a valid handler", func() { 32 | handler, err := HandlerFromInterface[any](func() {}) 33 | 34 | Expect(err).To(BeNil()) 35 | Expect(handler).ToNot(BeNil()) 36 | }) 37 | }) 38 | 39 | When("interface{} is func() error", func() { 40 | It("should return a valid handler", func() { 41 | handler, err := HandlerFromInterface[any](func() error { return nil }) 42 | 43 | Expect(err).To(BeNil()) 44 | Expect(handler).ToNot(BeNil()) 45 | }) 46 | }) 47 | 48 | When("interface{} is func(*T)", func() { 49 | It("should return a valid handler", func() { 50 | handler, err := HandlerFromInterface[string](func(*string) {}) 51 | 52 | Expect(err).To(BeNil()) 53 | Expect(handler).ToNot(BeNil()) 54 | }) 55 | }) 56 | 57 | When("interface{} is func(*T) error", func() { 58 | It("should return a valid handler", func() { 59 | handler, err := HandlerFromInterface[string](func(*string) error { return nil }) 60 | 61 | Expect(err).To(BeNil()) 62 | Expect(handler).ToNot(BeNil()) 63 | }) 64 | }) 65 | 66 | When("interface{} is not a valid type", func() { 67 | It("should return an error", func() { 68 | handler, err := HandlerFromInterface[string](func() (error, error) { return nil, nil }) 69 | 70 | Expect(err).ToNot(BeNil()) 71 | Expect(handler).To(BeNil()) 72 | }) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /licenses.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | // NOTE: 18 | // This main package is a workaround for binary license scanning that forces transitive dependencies in 19 | // Code we're distributing to be analyzed 20 | import ( 21 | _ "github.com/nitrictech/go-sdk/nitric" 22 | _ "github.com/nitrictech/go-sdk/nitric/apis" 23 | _ "github.com/nitrictech/go-sdk/nitric/batch" 24 | _ "github.com/nitrictech/go-sdk/nitric/errors" 25 | _ "github.com/nitrictech/go-sdk/nitric/keyvalue" 26 | _ "github.com/nitrictech/go-sdk/nitric/queues" 27 | _ "github.com/nitrictech/go-sdk/nitric/schedules" 28 | _ "github.com/nitrictech/go-sdk/nitric/secrets" 29 | _ "github.com/nitrictech/go-sdk/nitric/storage" 30 | _ "github.com/nitrictech/go-sdk/nitric/topics" 31 | _ "github.com/nitrictech/go-sdk/nitric/websockets" 32 | ) 33 | 34 | func main() {} 35 | -------------------------------------------------------------------------------- /lichen.yaml: -------------------------------------------------------------------------------- 1 | threshold: .90 2 | 3 | allow: 4 | - "MIT" 5 | - "Apache-2.0" 6 | - "BSD-3-Clause" 7 | - "BSD-2-Clause" 8 | -------------------------------------------------------------------------------- /mocks/batch.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/nitric/core/pkg/proto/batch/v1 (interfaces: BatchClient) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | batchpb "github.com/nitrictech/nitric/core/pkg/proto/batch/v1" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // MockBatchClient is a mock of BatchClient interface. 17 | type MockBatchClient struct { 18 | ctrl *gomock.Controller 19 | recorder *MockBatchClientMockRecorder 20 | } 21 | 22 | // MockBatchClientMockRecorder is the mock recorder for MockBatchClient. 23 | type MockBatchClientMockRecorder struct { 24 | mock *MockBatchClient 25 | } 26 | 27 | // NewMockBatchClient creates a new mock instance. 28 | func NewMockBatchClient(ctrl *gomock.Controller) *MockBatchClient { 29 | mock := &MockBatchClient{ctrl: ctrl} 30 | mock.recorder = &MockBatchClientMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockBatchClient) EXPECT() *MockBatchClientMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // SubmitJob mocks base method. 40 | func (m *MockBatchClient) SubmitJob(arg0 context.Context, arg1 *batchpb.JobSubmitRequest, arg2 ...grpc.CallOption) (*batchpb.JobSubmitResponse, error) { 41 | m.ctrl.T.Helper() 42 | varargs := []interface{}{arg0, arg1} 43 | for _, a := range arg2 { 44 | varargs = append(varargs, a) 45 | } 46 | ret := m.ctrl.Call(m, "SubmitJob", varargs...) 47 | ret0, _ := ret[0].(*batchpb.JobSubmitResponse) 48 | ret1, _ := ret[1].(error) 49 | return ret0, ret1 50 | } 51 | 52 | // SubmitJob indicates an expected call of SubmitJob. 53 | func (mr *MockBatchClientMockRecorder) SubmitJob(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 54 | mr.mock.ctrl.T.Helper() 55 | varargs := append([]interface{}{arg0, arg1}, arg2...) 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitJob", reflect.TypeOf((*MockBatchClient)(nil).SubmitJob), varargs...) 57 | } 58 | -------------------------------------------------------------------------------- /mocks/grpc_clientconn.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: google.golang.org/grpc (interfaces: ClientConnInterface) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | grpc "google.golang.org/grpc" 13 | ) 14 | 15 | // MockClientConnInterface is a mock of ClientConnInterface interface. 16 | type MockClientConnInterface struct { 17 | ctrl *gomock.Controller 18 | recorder *MockClientConnInterfaceMockRecorder 19 | } 20 | 21 | // MockClientConnInterfaceMockRecorder is the mock recorder for MockClientConnInterface. 22 | type MockClientConnInterfaceMockRecorder struct { 23 | mock *MockClientConnInterface 24 | } 25 | 26 | // NewMockClientConnInterface creates a new mock instance. 27 | func NewMockClientConnInterface(ctrl *gomock.Controller) *MockClientConnInterface { 28 | mock := &MockClientConnInterface{ctrl: ctrl} 29 | mock.recorder = &MockClientConnInterfaceMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockClientConnInterface) EXPECT() *MockClientConnInterfaceMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Invoke mocks base method. 39 | func (m *MockClientConnInterface) Invoke(arg0 context.Context, arg1 string, arg2, arg3 interface{}, arg4 ...grpc.CallOption) error { 40 | m.ctrl.T.Helper() 41 | varargs := []interface{}{arg0, arg1, arg2, arg3} 42 | for _, a := range arg4 { 43 | varargs = append(varargs, a) 44 | } 45 | ret := m.ctrl.Call(m, "Invoke", varargs...) 46 | ret0, _ := ret[0].(error) 47 | return ret0 48 | } 49 | 50 | // Invoke indicates an expected call of Invoke. 51 | func (mr *MockClientConnInterfaceMockRecorder) Invoke(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call { 52 | mr.mock.ctrl.T.Helper() 53 | varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...) 54 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Invoke", reflect.TypeOf((*MockClientConnInterface)(nil).Invoke), varargs...) 55 | } 56 | 57 | // NewStream mocks base method. 58 | func (m *MockClientConnInterface) NewStream(arg0 context.Context, arg1 *grpc.StreamDesc, arg2 string, arg3 ...grpc.CallOption) (grpc.ClientStream, error) { 59 | m.ctrl.T.Helper() 60 | varargs := []interface{}{arg0, arg1, arg2} 61 | for _, a := range arg3 { 62 | varargs = append(varargs, a) 63 | } 64 | ret := m.ctrl.Call(m, "NewStream", varargs...) 65 | ret0, _ := ret[0].(grpc.ClientStream) 66 | ret1, _ := ret[1].(error) 67 | return ret0, ret1 68 | } 69 | 70 | // NewStream indicates an expected call of NewStream. 71 | func (mr *MockClientConnInterfaceMockRecorder) NewStream(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { 72 | mr.mock.ctrl.T.Helper() 73 | varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) 74 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewStream", reflect.TypeOf((*MockClientConnInterface)(nil).NewStream), varargs...) 75 | } 76 | -------------------------------------------------------------------------------- /mocks/keyvalue.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/nitric/core/pkg/proto/kvstore/v1 (interfaces: KvStoreClient,KvStore_ScanKeysClient) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | kvstorepb "github.com/nitrictech/nitric/core/pkg/proto/kvstore/v1" 13 | grpc "google.golang.org/grpc" 14 | metadata "google.golang.org/grpc/metadata" 15 | ) 16 | 17 | // MockKvStoreClient is a mock of KvStoreClient interface. 18 | type MockKvStoreClient struct { 19 | ctrl *gomock.Controller 20 | recorder *MockKvStoreClientMockRecorder 21 | } 22 | 23 | // MockKvStoreClientMockRecorder is the mock recorder for MockKvStoreClient. 24 | type MockKvStoreClientMockRecorder struct { 25 | mock *MockKvStoreClient 26 | } 27 | 28 | // NewMockKvStoreClient creates a new mock instance. 29 | func NewMockKvStoreClient(ctrl *gomock.Controller) *MockKvStoreClient { 30 | mock := &MockKvStoreClient{ctrl: ctrl} 31 | mock.recorder = &MockKvStoreClientMockRecorder{mock} 32 | return mock 33 | } 34 | 35 | // EXPECT returns an object that allows the caller to indicate expected use. 36 | func (m *MockKvStoreClient) EXPECT() *MockKvStoreClientMockRecorder { 37 | return m.recorder 38 | } 39 | 40 | // DeleteKey mocks base method. 41 | func (m *MockKvStoreClient) DeleteKey(arg0 context.Context, arg1 *kvstorepb.KvStoreDeleteKeyRequest, arg2 ...grpc.CallOption) (*kvstorepb.KvStoreDeleteKeyResponse, error) { 42 | m.ctrl.T.Helper() 43 | varargs := []interface{}{arg0, arg1} 44 | for _, a := range arg2 { 45 | varargs = append(varargs, a) 46 | } 47 | ret := m.ctrl.Call(m, "DeleteKey", varargs...) 48 | ret0, _ := ret[0].(*kvstorepb.KvStoreDeleteKeyResponse) 49 | ret1, _ := ret[1].(error) 50 | return ret0, ret1 51 | } 52 | 53 | // DeleteKey indicates an expected call of DeleteKey. 54 | func (mr *MockKvStoreClientMockRecorder) DeleteKey(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 55 | mr.mock.ctrl.T.Helper() 56 | varargs := append([]interface{}{arg0, arg1}, arg2...) 57 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteKey", reflect.TypeOf((*MockKvStoreClient)(nil).DeleteKey), varargs...) 58 | } 59 | 60 | // GetValue mocks base method. 61 | func (m *MockKvStoreClient) GetValue(arg0 context.Context, arg1 *kvstorepb.KvStoreGetValueRequest, arg2 ...grpc.CallOption) (*kvstorepb.KvStoreGetValueResponse, error) { 62 | m.ctrl.T.Helper() 63 | varargs := []interface{}{arg0, arg1} 64 | for _, a := range arg2 { 65 | varargs = append(varargs, a) 66 | } 67 | ret := m.ctrl.Call(m, "GetValue", varargs...) 68 | ret0, _ := ret[0].(*kvstorepb.KvStoreGetValueResponse) 69 | ret1, _ := ret[1].(error) 70 | return ret0, ret1 71 | } 72 | 73 | // GetValue indicates an expected call of GetValue. 74 | func (mr *MockKvStoreClientMockRecorder) GetValue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 75 | mr.mock.ctrl.T.Helper() 76 | varargs := append([]interface{}{arg0, arg1}, arg2...) 77 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockKvStoreClient)(nil).GetValue), varargs...) 78 | } 79 | 80 | // ScanKeys mocks base method. 81 | func (m *MockKvStoreClient) ScanKeys(arg0 context.Context, arg1 *kvstorepb.KvStoreScanKeysRequest, arg2 ...grpc.CallOption) (kvstorepb.KvStore_ScanKeysClient, error) { 82 | m.ctrl.T.Helper() 83 | varargs := []interface{}{arg0, arg1} 84 | for _, a := range arg2 { 85 | varargs = append(varargs, a) 86 | } 87 | ret := m.ctrl.Call(m, "ScanKeys", varargs...) 88 | ret0, _ := ret[0].(kvstorepb.KvStore_ScanKeysClient) 89 | ret1, _ := ret[1].(error) 90 | return ret0, ret1 91 | } 92 | 93 | // ScanKeys indicates an expected call of ScanKeys. 94 | func (mr *MockKvStoreClientMockRecorder) ScanKeys(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 95 | mr.mock.ctrl.T.Helper() 96 | varargs := append([]interface{}{arg0, arg1}, arg2...) 97 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanKeys", reflect.TypeOf((*MockKvStoreClient)(nil).ScanKeys), varargs...) 98 | } 99 | 100 | // SetValue mocks base method. 101 | func (m *MockKvStoreClient) SetValue(arg0 context.Context, arg1 *kvstorepb.KvStoreSetValueRequest, arg2 ...grpc.CallOption) (*kvstorepb.KvStoreSetValueResponse, error) { 102 | m.ctrl.T.Helper() 103 | varargs := []interface{}{arg0, arg1} 104 | for _, a := range arg2 { 105 | varargs = append(varargs, a) 106 | } 107 | ret := m.ctrl.Call(m, "SetValue", varargs...) 108 | ret0, _ := ret[0].(*kvstorepb.KvStoreSetValueResponse) 109 | ret1, _ := ret[1].(error) 110 | return ret0, ret1 111 | } 112 | 113 | // SetValue indicates an expected call of SetValue. 114 | func (mr *MockKvStoreClientMockRecorder) SetValue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 115 | mr.mock.ctrl.T.Helper() 116 | varargs := append([]interface{}{arg0, arg1}, arg2...) 117 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValue", reflect.TypeOf((*MockKvStoreClient)(nil).SetValue), varargs...) 118 | } 119 | 120 | // MockKvStore_ScanKeysClient is a mock of KvStore_ScanKeysClient interface. 121 | type MockKvStore_ScanKeysClient struct { 122 | ctrl *gomock.Controller 123 | recorder *MockKvStore_ScanKeysClientMockRecorder 124 | } 125 | 126 | // MockKvStore_ScanKeysClientMockRecorder is the mock recorder for MockKvStore_ScanKeysClient. 127 | type MockKvStore_ScanKeysClientMockRecorder struct { 128 | mock *MockKvStore_ScanKeysClient 129 | } 130 | 131 | // NewMockKvStore_ScanKeysClient creates a new mock instance. 132 | func NewMockKvStore_ScanKeysClient(ctrl *gomock.Controller) *MockKvStore_ScanKeysClient { 133 | mock := &MockKvStore_ScanKeysClient{ctrl: ctrl} 134 | mock.recorder = &MockKvStore_ScanKeysClientMockRecorder{mock} 135 | return mock 136 | } 137 | 138 | // EXPECT returns an object that allows the caller to indicate expected use. 139 | func (m *MockKvStore_ScanKeysClient) EXPECT() *MockKvStore_ScanKeysClientMockRecorder { 140 | return m.recorder 141 | } 142 | 143 | // CloseSend mocks base method. 144 | func (m *MockKvStore_ScanKeysClient) CloseSend() error { 145 | m.ctrl.T.Helper() 146 | ret := m.ctrl.Call(m, "CloseSend") 147 | ret0, _ := ret[0].(error) 148 | return ret0 149 | } 150 | 151 | // CloseSend indicates an expected call of CloseSend. 152 | func (mr *MockKvStore_ScanKeysClientMockRecorder) CloseSend() *gomock.Call { 153 | mr.mock.ctrl.T.Helper() 154 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).CloseSend)) 155 | } 156 | 157 | // Context mocks base method. 158 | func (m *MockKvStore_ScanKeysClient) Context() context.Context { 159 | m.ctrl.T.Helper() 160 | ret := m.ctrl.Call(m, "Context") 161 | ret0, _ := ret[0].(context.Context) 162 | return ret0 163 | } 164 | 165 | // Context indicates an expected call of Context. 166 | func (mr *MockKvStore_ScanKeysClientMockRecorder) Context() *gomock.Call { 167 | mr.mock.ctrl.T.Helper() 168 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).Context)) 169 | } 170 | 171 | // Header mocks base method. 172 | func (m *MockKvStore_ScanKeysClient) Header() (metadata.MD, error) { 173 | m.ctrl.T.Helper() 174 | ret := m.ctrl.Call(m, "Header") 175 | ret0, _ := ret[0].(metadata.MD) 176 | ret1, _ := ret[1].(error) 177 | return ret0, ret1 178 | } 179 | 180 | // Header indicates an expected call of Header. 181 | func (mr *MockKvStore_ScanKeysClientMockRecorder) Header() *gomock.Call { 182 | mr.mock.ctrl.T.Helper() 183 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).Header)) 184 | } 185 | 186 | // Recv mocks base method. 187 | func (m *MockKvStore_ScanKeysClient) Recv() (*kvstorepb.KvStoreScanKeysResponse, error) { 188 | m.ctrl.T.Helper() 189 | ret := m.ctrl.Call(m, "Recv") 190 | ret0, _ := ret[0].(*kvstorepb.KvStoreScanKeysResponse) 191 | ret1, _ := ret[1].(error) 192 | return ret0, ret1 193 | } 194 | 195 | // Recv indicates an expected call of Recv. 196 | func (mr *MockKvStore_ScanKeysClientMockRecorder) Recv() *gomock.Call { 197 | mr.mock.ctrl.T.Helper() 198 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).Recv)) 199 | } 200 | 201 | // RecvMsg mocks base method. 202 | func (m *MockKvStore_ScanKeysClient) RecvMsg(arg0 interface{}) error { 203 | m.ctrl.T.Helper() 204 | ret := m.ctrl.Call(m, "RecvMsg", arg0) 205 | ret0, _ := ret[0].(error) 206 | return ret0 207 | } 208 | 209 | // RecvMsg indicates an expected call of RecvMsg. 210 | func (mr *MockKvStore_ScanKeysClientMockRecorder) RecvMsg(arg0 interface{}) *gomock.Call { 211 | mr.mock.ctrl.T.Helper() 212 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).RecvMsg), arg0) 213 | } 214 | 215 | // SendMsg mocks base method. 216 | func (m *MockKvStore_ScanKeysClient) SendMsg(arg0 interface{}) error { 217 | m.ctrl.T.Helper() 218 | ret := m.ctrl.Call(m, "SendMsg", arg0) 219 | ret0, _ := ret[0].(error) 220 | return ret0 221 | } 222 | 223 | // SendMsg indicates an expected call of SendMsg. 224 | func (mr *MockKvStore_ScanKeysClientMockRecorder) SendMsg(arg0 interface{}) *gomock.Call { 225 | mr.mock.ctrl.T.Helper() 226 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).SendMsg), arg0) 227 | } 228 | 229 | // Trailer mocks base method. 230 | func (m *MockKvStore_ScanKeysClient) Trailer() metadata.MD { 231 | m.ctrl.T.Helper() 232 | ret := m.ctrl.Call(m, "Trailer") 233 | ret0, _ := ret[0].(metadata.MD) 234 | return ret0 235 | } 236 | 237 | // Trailer indicates an expected call of Trailer. 238 | func (mr *MockKvStore_ScanKeysClientMockRecorder) Trailer() *gomock.Call { 239 | mr.mock.ctrl.T.Helper() 240 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockKvStore_ScanKeysClient)(nil).Trailer)) 241 | } 242 | -------------------------------------------------------------------------------- /mocks/queues.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/nitric/core/pkg/proto/queues/v1 (interfaces: QueuesClient) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | queuespb "github.com/nitrictech/nitric/core/pkg/proto/queues/v1" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // MockQueuesClient is a mock of QueuesClient interface. 17 | type MockQueuesClient struct { 18 | ctrl *gomock.Controller 19 | recorder *MockQueuesClientMockRecorder 20 | } 21 | 22 | // MockQueuesClientMockRecorder is the mock recorder for MockQueuesClient. 23 | type MockQueuesClientMockRecorder struct { 24 | mock *MockQueuesClient 25 | } 26 | 27 | // NewMockQueuesClient creates a new mock instance. 28 | func NewMockQueuesClient(ctrl *gomock.Controller) *MockQueuesClient { 29 | mock := &MockQueuesClient{ctrl: ctrl} 30 | mock.recorder = &MockQueuesClientMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockQueuesClient) EXPECT() *MockQueuesClientMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // Complete mocks base method. 40 | func (m *MockQueuesClient) Complete(arg0 context.Context, arg1 *queuespb.QueueCompleteRequest, arg2 ...grpc.CallOption) (*queuespb.QueueCompleteResponse, error) { 41 | m.ctrl.T.Helper() 42 | varargs := []interface{}{arg0, arg1} 43 | for _, a := range arg2 { 44 | varargs = append(varargs, a) 45 | } 46 | ret := m.ctrl.Call(m, "Complete", varargs...) 47 | ret0, _ := ret[0].(*queuespb.QueueCompleteResponse) 48 | ret1, _ := ret[1].(error) 49 | return ret0, ret1 50 | } 51 | 52 | // Complete indicates an expected call of Complete. 53 | func (mr *MockQueuesClientMockRecorder) Complete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 54 | mr.mock.ctrl.T.Helper() 55 | varargs := append([]interface{}{arg0, arg1}, arg2...) 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Complete", reflect.TypeOf((*MockQueuesClient)(nil).Complete), varargs...) 57 | } 58 | 59 | // Dequeue mocks base method. 60 | func (m *MockQueuesClient) Dequeue(arg0 context.Context, arg1 *queuespb.QueueDequeueRequest, arg2 ...grpc.CallOption) (*queuespb.QueueDequeueResponse, error) { 61 | m.ctrl.T.Helper() 62 | varargs := []interface{}{arg0, arg1} 63 | for _, a := range arg2 { 64 | varargs = append(varargs, a) 65 | } 66 | ret := m.ctrl.Call(m, "Dequeue", varargs...) 67 | ret0, _ := ret[0].(*queuespb.QueueDequeueResponse) 68 | ret1, _ := ret[1].(error) 69 | return ret0, ret1 70 | } 71 | 72 | // Dequeue indicates an expected call of Dequeue. 73 | func (mr *MockQueuesClientMockRecorder) Dequeue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 74 | mr.mock.ctrl.T.Helper() 75 | varargs := append([]interface{}{arg0, arg1}, arg2...) 76 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dequeue", reflect.TypeOf((*MockQueuesClient)(nil).Dequeue), varargs...) 77 | } 78 | 79 | // Enqueue mocks base method. 80 | func (m *MockQueuesClient) Enqueue(arg0 context.Context, arg1 *queuespb.QueueEnqueueRequest, arg2 ...grpc.CallOption) (*queuespb.QueueEnqueueResponse, error) { 81 | m.ctrl.T.Helper() 82 | varargs := []interface{}{arg0, arg1} 83 | for _, a := range arg2 { 84 | varargs = append(varargs, a) 85 | } 86 | ret := m.ctrl.Call(m, "Enqueue", varargs...) 87 | ret0, _ := ret[0].(*queuespb.QueueEnqueueResponse) 88 | ret1, _ := ret[1].(error) 89 | return ret0, ret1 90 | } 91 | 92 | // Enqueue indicates an expected call of Enqueue. 93 | func (mr *MockQueuesClientMockRecorder) Enqueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | varargs := append([]interface{}{arg0, arg1}, arg2...) 96 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enqueue", reflect.TypeOf((*MockQueuesClient)(nil).Enqueue), varargs...) 97 | } 98 | -------------------------------------------------------------------------------- /mocks/secrets.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/nitric/core/pkg/proto/secrets/v1 (interfaces: SecretManagerClient) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | secretspb "github.com/nitrictech/nitric/core/pkg/proto/secrets/v1" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // MockSecretManagerClient is a mock of SecretManagerClient interface. 17 | type MockSecretManagerClient struct { 18 | ctrl *gomock.Controller 19 | recorder *MockSecretManagerClientMockRecorder 20 | } 21 | 22 | // MockSecretManagerClientMockRecorder is the mock recorder for MockSecretManagerClient. 23 | type MockSecretManagerClientMockRecorder struct { 24 | mock *MockSecretManagerClient 25 | } 26 | 27 | // NewMockSecretManagerClient creates a new mock instance. 28 | func NewMockSecretManagerClient(ctrl *gomock.Controller) *MockSecretManagerClient { 29 | mock := &MockSecretManagerClient{ctrl: ctrl} 30 | mock.recorder = &MockSecretManagerClientMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockSecretManagerClient) EXPECT() *MockSecretManagerClientMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // Access mocks base method. 40 | func (m *MockSecretManagerClient) Access(arg0 context.Context, arg1 *secretspb.SecretAccessRequest, arg2 ...grpc.CallOption) (*secretspb.SecretAccessResponse, error) { 41 | m.ctrl.T.Helper() 42 | varargs := []interface{}{arg0, arg1} 43 | for _, a := range arg2 { 44 | varargs = append(varargs, a) 45 | } 46 | ret := m.ctrl.Call(m, "Access", varargs...) 47 | ret0, _ := ret[0].(*secretspb.SecretAccessResponse) 48 | ret1, _ := ret[1].(error) 49 | return ret0, ret1 50 | } 51 | 52 | // Access indicates an expected call of Access. 53 | func (mr *MockSecretManagerClientMockRecorder) Access(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 54 | mr.mock.ctrl.T.Helper() 55 | varargs := append([]interface{}{arg0, arg1}, arg2...) 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Access", reflect.TypeOf((*MockSecretManagerClient)(nil).Access), varargs...) 57 | } 58 | 59 | // Put mocks base method. 60 | func (m *MockSecretManagerClient) Put(arg0 context.Context, arg1 *secretspb.SecretPutRequest, arg2 ...grpc.CallOption) (*secretspb.SecretPutResponse, error) { 61 | m.ctrl.T.Helper() 62 | varargs := []interface{}{arg0, arg1} 63 | for _, a := range arg2 { 64 | varargs = append(varargs, a) 65 | } 66 | ret := m.ctrl.Call(m, "Put", varargs...) 67 | ret0, _ := ret[0].(*secretspb.SecretPutResponse) 68 | ret1, _ := ret[1].(error) 69 | return ret0, ret1 70 | } 71 | 72 | // Put indicates an expected call of Put. 73 | func (mr *MockSecretManagerClientMockRecorder) Put(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 74 | mr.mock.ctrl.T.Helper() 75 | varargs := append([]interface{}{arg0, arg1}, arg2...) 76 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockSecretManagerClient)(nil).Put), varargs...) 77 | } 78 | -------------------------------------------------------------------------------- /mocks/storage.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/nitric/core/pkg/proto/storage/v1 (interfaces: StorageClient) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | storagepb "github.com/nitrictech/nitric/core/pkg/proto/storage/v1" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // MockStorageClient is a mock of StorageClient interface. 17 | type MockStorageClient struct { 18 | ctrl *gomock.Controller 19 | recorder *MockStorageClientMockRecorder 20 | } 21 | 22 | // MockStorageClientMockRecorder is the mock recorder for MockStorageClient. 23 | type MockStorageClientMockRecorder struct { 24 | mock *MockStorageClient 25 | } 26 | 27 | // NewMockStorageClient creates a new mock instance. 28 | func NewMockStorageClient(ctrl *gomock.Controller) *MockStorageClient { 29 | mock := &MockStorageClient{ctrl: ctrl} 30 | mock.recorder = &MockStorageClientMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockStorageClient) EXPECT() *MockStorageClientMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // Delete mocks base method. 40 | func (m *MockStorageClient) Delete(arg0 context.Context, arg1 *storagepb.StorageDeleteRequest, arg2 ...grpc.CallOption) (*storagepb.StorageDeleteResponse, error) { 41 | m.ctrl.T.Helper() 42 | varargs := []interface{}{arg0, arg1} 43 | for _, a := range arg2 { 44 | varargs = append(varargs, a) 45 | } 46 | ret := m.ctrl.Call(m, "Delete", varargs...) 47 | ret0, _ := ret[0].(*storagepb.StorageDeleteResponse) 48 | ret1, _ := ret[1].(error) 49 | return ret0, ret1 50 | } 51 | 52 | // Delete indicates an expected call of Delete. 53 | func (mr *MockStorageClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 54 | mr.mock.ctrl.T.Helper() 55 | varargs := append([]interface{}{arg0, arg1}, arg2...) 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStorageClient)(nil).Delete), varargs...) 57 | } 58 | 59 | // Exists mocks base method. 60 | func (m *MockStorageClient) Exists(arg0 context.Context, arg1 *storagepb.StorageExistsRequest, arg2 ...grpc.CallOption) (*storagepb.StorageExistsResponse, error) { 61 | m.ctrl.T.Helper() 62 | varargs := []interface{}{arg0, arg1} 63 | for _, a := range arg2 { 64 | varargs = append(varargs, a) 65 | } 66 | ret := m.ctrl.Call(m, "Exists", varargs...) 67 | ret0, _ := ret[0].(*storagepb.StorageExistsResponse) 68 | ret1, _ := ret[1].(error) 69 | return ret0, ret1 70 | } 71 | 72 | // Exists indicates an expected call of Exists. 73 | func (mr *MockStorageClientMockRecorder) Exists(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 74 | mr.mock.ctrl.T.Helper() 75 | varargs := append([]interface{}{arg0, arg1}, arg2...) 76 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockStorageClient)(nil).Exists), varargs...) 77 | } 78 | 79 | // ListBlobs mocks base method. 80 | func (m *MockStorageClient) ListBlobs(arg0 context.Context, arg1 *storagepb.StorageListBlobsRequest, arg2 ...grpc.CallOption) (*storagepb.StorageListBlobsResponse, error) { 81 | m.ctrl.T.Helper() 82 | varargs := []interface{}{arg0, arg1} 83 | for _, a := range arg2 { 84 | varargs = append(varargs, a) 85 | } 86 | ret := m.ctrl.Call(m, "ListBlobs", varargs...) 87 | ret0, _ := ret[0].(*storagepb.StorageListBlobsResponse) 88 | ret1, _ := ret[1].(error) 89 | return ret0, ret1 90 | } 91 | 92 | // ListBlobs indicates an expected call of ListBlobs. 93 | func (mr *MockStorageClientMockRecorder) ListBlobs(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | varargs := append([]interface{}{arg0, arg1}, arg2...) 96 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBlobs", reflect.TypeOf((*MockStorageClient)(nil).ListBlobs), varargs...) 97 | } 98 | 99 | // PreSignUrl mocks base method. 100 | func (m *MockStorageClient) PreSignUrl(arg0 context.Context, arg1 *storagepb.StoragePreSignUrlRequest, arg2 ...grpc.CallOption) (*storagepb.StoragePreSignUrlResponse, error) { 101 | m.ctrl.T.Helper() 102 | varargs := []interface{}{arg0, arg1} 103 | for _, a := range arg2 { 104 | varargs = append(varargs, a) 105 | } 106 | ret := m.ctrl.Call(m, "PreSignUrl", varargs...) 107 | ret0, _ := ret[0].(*storagepb.StoragePreSignUrlResponse) 108 | ret1, _ := ret[1].(error) 109 | return ret0, ret1 110 | } 111 | 112 | // PreSignUrl indicates an expected call of PreSignUrl. 113 | func (mr *MockStorageClientMockRecorder) PreSignUrl(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 114 | mr.mock.ctrl.T.Helper() 115 | varargs := append([]interface{}{arg0, arg1}, arg2...) 116 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreSignUrl", reflect.TypeOf((*MockStorageClient)(nil).PreSignUrl), varargs...) 117 | } 118 | 119 | // Read mocks base method. 120 | func (m *MockStorageClient) Read(arg0 context.Context, arg1 *storagepb.StorageReadRequest, arg2 ...grpc.CallOption) (*storagepb.StorageReadResponse, error) { 121 | m.ctrl.T.Helper() 122 | varargs := []interface{}{arg0, arg1} 123 | for _, a := range arg2 { 124 | varargs = append(varargs, a) 125 | } 126 | ret := m.ctrl.Call(m, "Read", varargs...) 127 | ret0, _ := ret[0].(*storagepb.StorageReadResponse) 128 | ret1, _ := ret[1].(error) 129 | return ret0, ret1 130 | } 131 | 132 | // Read indicates an expected call of Read. 133 | func (mr *MockStorageClientMockRecorder) Read(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 134 | mr.mock.ctrl.T.Helper() 135 | varargs := append([]interface{}{arg0, arg1}, arg2...) 136 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockStorageClient)(nil).Read), varargs...) 137 | } 138 | 139 | // Write mocks base method. 140 | func (m *MockStorageClient) Write(arg0 context.Context, arg1 *storagepb.StorageWriteRequest, arg2 ...grpc.CallOption) (*storagepb.StorageWriteResponse, error) { 141 | m.ctrl.T.Helper() 142 | varargs := []interface{}{arg0, arg1} 143 | for _, a := range arg2 { 144 | varargs = append(varargs, a) 145 | } 146 | ret := m.ctrl.Call(m, "Write", varargs...) 147 | ret0, _ := ret[0].(*storagepb.StorageWriteResponse) 148 | ret1, _ := ret[1].(error) 149 | return ret0, ret1 150 | } 151 | 152 | // Write indicates an expected call of Write. 153 | func (mr *MockStorageClientMockRecorder) Write(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 154 | mr.mock.ctrl.T.Helper() 155 | varargs := append([]interface{}{arg0, arg1}, arg2...) 156 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStorageClient)(nil).Write), varargs...) 157 | } 158 | -------------------------------------------------------------------------------- /mocks/topics.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/nitric/core/pkg/proto/topics/v1 (interfaces: TopicsClient) 3 | 4 | // Package mock_v1 is a generated GoMock package. 5 | package mock_v1 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | topicspb "github.com/nitrictech/nitric/core/pkg/proto/topics/v1" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // MockTopicsClient is a mock of TopicsClient interface. 17 | type MockTopicsClient struct { 18 | ctrl *gomock.Controller 19 | recorder *MockTopicsClientMockRecorder 20 | } 21 | 22 | // MockTopicsClientMockRecorder is the mock recorder for MockTopicsClient. 23 | type MockTopicsClientMockRecorder struct { 24 | mock *MockTopicsClient 25 | } 26 | 27 | // NewMockTopicsClient creates a new mock instance. 28 | func NewMockTopicsClient(ctrl *gomock.Controller) *MockTopicsClient { 29 | mock := &MockTopicsClient{ctrl: ctrl} 30 | mock.recorder = &MockTopicsClientMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockTopicsClient) EXPECT() *MockTopicsClientMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // Publish mocks base method. 40 | func (m *MockTopicsClient) Publish(arg0 context.Context, arg1 *topicspb.TopicPublishRequest, arg2 ...grpc.CallOption) (*topicspb.TopicPublishResponse, error) { 41 | m.ctrl.T.Helper() 42 | varargs := []interface{}{arg0, arg1} 43 | for _, a := range arg2 { 44 | varargs = append(varargs, a) 45 | } 46 | ret := m.ctrl.Call(m, "Publish", varargs...) 47 | ret0, _ := ret[0].(*topicspb.TopicPublishResponse) 48 | ret1, _ := ret[1].(error) 49 | return ret0, ret1 50 | } 51 | 52 | // Publish indicates an expected call of Publish. 53 | func (mr *MockTopicsClientMockRecorder) Publish(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 54 | mr.mock.ctrl.T.Helper() 55 | varargs := append([]interface{}{arg0, arg1}, arg2...) 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockTopicsClient)(nil).Publish), varargs...) 57 | } 58 | -------------------------------------------------------------------------------- /nitric/apis/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package apis 16 | 17 | import ( 18 | "net/textproto" 19 | 20 | apispb "github.com/nitrictech/nitric/core/pkg/proto/apis/v1" 21 | ) 22 | 23 | type Ctx struct { 24 | id string 25 | Request Request 26 | Response *Response 27 | Extras map[string]interface{} 28 | } 29 | 30 | func (c *Ctx) ToClientMessage() *apispb.ClientMessage { 31 | headers := make(map[string]*apispb.HeaderValue) 32 | for k, v := range c.Response.Headers { 33 | headers[k] = &apispb.HeaderValue{ 34 | Value: v, 35 | } 36 | } 37 | 38 | return &apispb.ClientMessage{ 39 | Id: c.id, 40 | Content: &apispb.ClientMessage_HttpResponse{ 41 | HttpResponse: &apispb.HttpResponse{ 42 | Status: int32(c.Response.Status), 43 | Headers: headers, 44 | Body: c.Response.Body, 45 | }, 46 | }, 47 | } 48 | } 49 | 50 | func NewCtx(msg *apispb.ServerMessage) *Ctx { 51 | req := msg.GetHttpRequest() 52 | 53 | headers := make(textproto.MIMEHeader) 54 | for k, v := range req.Headers { 55 | headers[k] = v.GetValue() 56 | } 57 | 58 | query := make(map[string][]string) 59 | for k, v := range req.QueryParams { 60 | query[k] = v.GetValue() 61 | } 62 | 63 | return &Ctx{ 64 | id: msg.Id, 65 | Request: &HttpRequest{ 66 | method: req.Method, 67 | path: req.Path, 68 | pathParams: req.PathParams, 69 | query: query, 70 | headers: headers, 71 | data: req.Body, 72 | }, 73 | Response: &Response{ 74 | Status: 200, 75 | Headers: map[string][]string{}, 76 | Body: nil, 77 | }, 78 | } 79 | } 80 | 81 | func (c *Ctx) WithError(err error) { 82 | c.Response = &Response{ 83 | Status: 500, 84 | Headers: map[string][]string{ 85 | "Content-Type": {"text/plain"}, 86 | }, 87 | Body: []byte("Internal Server Error"), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /nitric/apis/oidc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package apis 16 | 17 | import ( 18 | "github.com/nitrictech/go-sdk/nitric/workers" 19 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 20 | ) 21 | 22 | type OidcOptions struct { 23 | Name string 24 | Issuer string 25 | Audiences []string 26 | Scopes []string 27 | } 28 | 29 | func attachOidc(apiName string, options OidcOptions, manager *workers.Manager) error { 30 | _, err := newOidcSecurityDefinition(apiName, options, manager) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | type SecurityOption = func(scopes []string) OidcOptions 38 | 39 | type OidcSecurityDefinition interface{} 40 | 41 | type oidcSecurityDefinition struct { 42 | OidcSecurityDefinition 43 | 44 | ApiName string 45 | RuleName string 46 | Issuer string 47 | Audiences []string 48 | 49 | manager *workers.Manager 50 | } 51 | 52 | func newOidcSecurityDefinition(apiName string, options OidcOptions, manager *workers.Manager) (OidcSecurityDefinition, error) { 53 | o := &oidcSecurityDefinition{ 54 | ApiName: apiName, 55 | RuleName: options.Name, 56 | Issuer: options.Issuer, 57 | Audiences: options.Audiences, 58 | manager: manager, 59 | } 60 | 61 | // declare resource 62 | registerResult := <-manager.RegisterResource(&v1.ResourceDeclareRequest{ 63 | Id: &v1.ResourceIdentifier{ 64 | Name: options.Name, 65 | Type: v1.ResourceType_ApiSecurityDefinition, 66 | }, 67 | Config: &v1.ResourceDeclareRequest_ApiSecurityDefinition{ 68 | ApiSecurityDefinition: &v1.ApiSecurityDefinitionResource{ 69 | ApiName: apiName, 70 | Definition: &v1.ApiSecurityDefinitionResource_Oidc{ 71 | Oidc: &v1.ApiOpenIdConnectionDefinition{ 72 | Issuer: o.Issuer, 73 | Audiences: o.Audiences, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }) 79 | if registerResult.Err != nil { 80 | return nil, registerResult.Err 81 | } 82 | 83 | return o, nil 84 | } 85 | -------------------------------------------------------------------------------- /nitric/apis/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package apis 16 | 17 | type ( 18 | ApiOption func(api *api) 19 | RouteOption func(route Route) 20 | MethodOption func(mo *methodOptions) 21 | ) 22 | 23 | type JwtSecurityRule struct { 24 | Issuer string 25 | Audiences []string 26 | } 27 | 28 | type methodOptions struct { 29 | security []OidcOptions 30 | securityDisabled bool 31 | } 32 | 33 | // WithMiddleware - Apply a middleware function to all handlers in the API 34 | func WithMiddleware(middleware Middleware) ApiOption { 35 | return func(api *api) { 36 | api.middleware = middleware 37 | } 38 | } 39 | 40 | func OidcRule(name string, issuer string, audiences []string) SecurityOption { 41 | return func(scopes []string) OidcOptions { 42 | return OidcOptions{ 43 | Name: name, 44 | Issuer: issuer, 45 | Audiences: audiences, 46 | Scopes: scopes, 47 | } 48 | } 49 | } 50 | 51 | // WithSecurityJwtRule - Apply a JWT security rule to the API 52 | func WithSecurityJwtRule(name string, rule JwtSecurityRule) ApiOption { 53 | return func(api *api) { 54 | if api.securityRules == nil { 55 | api.securityRules = make(map[string]interface{}) 56 | } 57 | 58 | api.securityRules[name] = rule 59 | } 60 | } 61 | 62 | // WithSecurity - Apply security settings to the API 63 | func WithSecurity(oidcOptions OidcOptions) ApiOption { 64 | return func(api *api) { 65 | if api.security == nil { 66 | api.security = []OidcOptions{oidcOptions} 67 | } else { 68 | api.security = append(api.security, oidcOptions) 69 | } 70 | } 71 | } 72 | 73 | // WithPath - Set the base path for the API 74 | func WithPath(path string) ApiOption { 75 | return func(api *api) { 76 | api.path = path 77 | } 78 | } 79 | 80 | // WithNoMethodSecurity - Disable security for a method 81 | func WithNoMethodSecurity() MethodOption { 82 | return func(mo *methodOptions) { 83 | mo.securityDisabled = true 84 | } 85 | } 86 | 87 | // WithMethodSecurity - Override/set the security settings for a method 88 | func WithMethodSecurity(oidcOptions OidcOptions) MethodOption { 89 | return func(mo *methodOptions) { 90 | mo.securityDisabled = false 91 | if mo.security == nil { 92 | mo.security = []OidcOptions{oidcOptions} 93 | } else { 94 | mo.security = append(mo.security, oidcOptions) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /nitric/apis/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package apis 16 | 17 | import "net/textproto" 18 | 19 | type Request interface { 20 | Method() string 21 | Path() string 22 | Data() []byte 23 | Query() map[string][]string 24 | Headers() textproto.MIMEHeader 25 | PathParams() map[string]string 26 | } 27 | 28 | type HttpRequest struct { 29 | method string 30 | path string 31 | data []byte 32 | query map[string][]string 33 | headers textproto.MIMEHeader 34 | pathParams map[string]string 35 | } 36 | 37 | func (h *HttpRequest) Method() string { 38 | return h.method 39 | } 40 | 41 | func (h *HttpRequest) Path() string { 42 | return h.path 43 | } 44 | 45 | func (h *HttpRequest) Data() []byte { 46 | return h.data 47 | } 48 | 49 | func (h *HttpRequest) Query() map[string][]string { 50 | return h.query 51 | } 52 | 53 | func (h *HttpRequest) Headers() textproto.MIMEHeader { 54 | return h.headers 55 | } 56 | 57 | func (h *HttpRequest) PathParams() map[string]string { 58 | return h.pathParams 59 | } 60 | -------------------------------------------------------------------------------- /nitric/apis/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package apis 16 | 17 | type Response struct { 18 | Status int 19 | Headers map[string][]string 20 | Body []byte 21 | } 22 | -------------------------------------------------------------------------------- /nitric/apis/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package apis 16 | 17 | import ( 18 | "context" 19 | errorsstd "errors" 20 | 21 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 22 | "github.com/nitrictech/go-sdk/internal/handlers" 23 | "github.com/nitrictech/go-sdk/nitric/errors" 24 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 25 | "github.com/nitrictech/go-sdk/nitric/workers" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/apis/v1" 27 | ) 28 | 29 | type apiWorker struct { 30 | client v1.ApiClient 31 | Handler handlers.Handler[Ctx] 32 | registrationRequest *v1.RegistrationRequest 33 | } 34 | 35 | type apiWorkerOpts struct { 36 | RegistrationRequest *v1.RegistrationRequest 37 | Handler handlers.Handler[Ctx] 38 | } 39 | 40 | var _ workers.StreamWorker = (*apiWorker)(nil) 41 | 42 | // Start runs the API worker, creating a stream to the Nitric server 43 | func (a *apiWorker) Start(ctx context.Context) error { 44 | initReq := &v1.ClientMessage{ 45 | Content: &v1.ClientMessage_RegistrationRequest{ 46 | RegistrationRequest: a.registrationRequest, 47 | }, 48 | } 49 | 50 | createStream := func(ctx context.Context) (workers.Stream[v1.ClientMessage, v1.RegistrationResponse, *v1.ServerMessage], error) { 51 | return a.client.Serve(ctx) 52 | } 53 | 54 | handlerSrvMsg := func(msg *v1.ServerMessage) (*v1.ClientMessage, error) { 55 | if msg.GetRegistrationResponse() != nil { 56 | // No need to respond to the registration response 57 | return nil, nil 58 | } 59 | 60 | if msg.GetHttpRequest() != nil { 61 | handlerCtx := NewCtx(msg) 62 | 63 | err := a.Handler(handlerCtx) 64 | if err != nil { 65 | handlerCtx.WithError(err) 66 | } 67 | 68 | return handlerCtx.ToClientMessage(), nil 69 | } 70 | 71 | return nil, errors.NewWithCause( 72 | codes.Internal, 73 | "ApiWorker: Unhandled server message", 74 | errorsstd.New("unhandled server message"), 75 | ) 76 | } 77 | 78 | return workers.HandleStream( 79 | ctx, 80 | createStream, 81 | initReq, 82 | handlerSrvMsg, 83 | ) 84 | } 85 | 86 | func newApiWorker(opts *apiWorkerOpts) *apiWorker { 87 | conn, err := grpcx.GetConnection() 88 | if err != nil { 89 | panic(errors.NewWithCause( 90 | codes.Unavailable, 91 | "NewApiWorker: Unable to reach ApiClient", 92 | err, 93 | )) 94 | } 95 | 96 | client := v1.NewApiClient(conn) 97 | 98 | return &apiWorker{ 99 | client: client, 100 | registrationRequest: opts.RegistrationRequest, 101 | Handler: opts.Handler, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /nitric/batch/batch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/nitrictech/go-sdk/internal/handlers" 21 | "github.com/nitrictech/go-sdk/nitric/workers" 22 | batchpb "github.com/nitrictech/nitric/core/pkg/proto/batch/v1" 23 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 24 | ) 25 | 26 | // JobPermission defines the available permissions on a job 27 | type JobPermission string 28 | 29 | type Handler = handlers.Handler[Ctx] 30 | 31 | const ( 32 | // JobSubmit is required to call Submit on a job. 33 | JobSubmit JobPermission = "submit" 34 | ) 35 | 36 | type JobReference interface { 37 | // Allow requests the given permissions to the job. 38 | Allow(permission JobPermission, permissions ...JobPermission) *BatchClient 39 | 40 | // Handler will register and start the job task handler that will be called for all task submitted to this job. 41 | // Valid function signatures for middleware are: 42 | // 43 | // func() 44 | // func() error 45 | // func(*batch.Ctx) 46 | // func(*batch.Ctx) error 47 | // Handler[batch.Ctx] 48 | Handler(handler interface{}, options ...HandlerOption) 49 | } 50 | 51 | type jobReference struct { 52 | name string 53 | manager *workers.Manager 54 | registerChan <-chan workers.RegisterResult 55 | } 56 | 57 | // NewJob creates a new job resource with the give name. 58 | func NewJob(name string) JobReference { 59 | job := &jobReference{ 60 | name: name, 61 | manager: workers.GetDefaultManager(), 62 | } 63 | 64 | job.registerChan = job.manager.RegisterResource(&v1.ResourceDeclareRequest{ 65 | Id: &v1.ResourceIdentifier{ 66 | Type: v1.ResourceType_Job, 67 | Name: name, 68 | }, 69 | Config: &v1.ResourceDeclareRequest_Job{ 70 | Job: &v1.JobResource{}, 71 | }, 72 | }) 73 | 74 | return job 75 | } 76 | 77 | func (j *jobReference) Allow(permission JobPermission, permissions ...JobPermission) *BatchClient { 78 | allPerms := append([]JobPermission{permission}, permissions...) 79 | 80 | actions := []v1.Action{} 81 | for _, perm := range allPerms { 82 | switch perm { 83 | case JobSubmit: 84 | actions = append(actions, v1.Action_JobSubmit) 85 | default: 86 | panic(fmt.Errorf("JobPermission %s unknown", perm)) 87 | } 88 | } 89 | 90 | registerResult := <-j.registerChan 91 | if registerResult.Err != nil { 92 | panic(registerResult.Err) 93 | } 94 | 95 | err := j.manager.RegisterPolicy(registerResult.Identifier, actions...) 96 | if err != nil { 97 | panic(err) 98 | } 99 | 100 | client, err := NewBatchClient(j.name) 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | return client 106 | } 107 | 108 | func (j *jobReference) Handler(handler interface{}, opts ...HandlerOption) { 109 | options := &handlerOptions{} 110 | 111 | for _, opt := range opts { 112 | opt(options) 113 | } 114 | 115 | registrationRequest := &batchpb.RegistrationRequest{ 116 | JobName: j.name, 117 | Requirements: &batchpb.JobResourceRequirements{}, 118 | } 119 | 120 | if options.cpus != nil { 121 | registrationRequest.Requirements.Cpus = *options.cpus 122 | } 123 | 124 | if options.memory != nil { 125 | registrationRequest.Requirements.Memory = *options.memory 126 | } 127 | 128 | if options.gpus != nil { 129 | registrationRequest.Requirements.Gpus = *options.gpus 130 | } 131 | 132 | typedHandler, err := handlers.HandlerFromInterface[Ctx](handler) 133 | if err != nil { 134 | panic(err) 135 | } 136 | 137 | jobOpts := &jobWorkerOpts{ 138 | RegistrationRequest: registrationRequest, 139 | Handler: typedHandler, 140 | } 141 | 142 | worker := newJobWorker(jobOpts) 143 | j.manager.AddWorker("JobWorker:"+j.name, worker) 144 | } 145 | -------------------------------------------------------------------------------- /nitric/batch/batch_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch_test 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestBatch(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Batch (Jobs) Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/batch/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/grpc" 21 | 22 | "github.com/nitrictech/go-sdk/constants" 23 | "github.com/nitrictech/go-sdk/nitric/errors" 24 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 25 | v1 "github.com/nitrictech/nitric/core/pkg/proto/batch/v1" 26 | "github.com/nitrictech/protoutils" 27 | ) 28 | 29 | // Batch 30 | type BatchClientIn interface { 31 | // Name returns the Job name. 32 | Name() string 33 | 34 | // Submit will submit the provided request to the job. 35 | Submit(ctx context.Context, data map[string]interface{}) error 36 | } 37 | 38 | type BatchClient struct { 39 | name string 40 | batchClient v1.BatchClient 41 | } 42 | 43 | func (s *BatchClient) Name() string { 44 | return s.name 45 | } 46 | 47 | func (s *BatchClient) Submit(ctx context.Context, data map[string]interface{}) error { 48 | dataStruct, err := protoutils.NewStruct(data) 49 | if err != nil { 50 | return errors.NewWithCause(codes.InvalidArgument, "Batch.Submit", err) 51 | } 52 | 53 | // Create the request 54 | req := &v1.JobSubmitRequest{ 55 | JobName: s.name, 56 | Data: &v1.JobData{ 57 | Data: &v1.JobData_Struct{ 58 | Struct: dataStruct, 59 | }, 60 | }, 61 | } 62 | 63 | // Submit the request 64 | _, err = s.batchClient.SubmitJob(ctx, req) 65 | if err != nil { 66 | return errors.FromGrpcError(err) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func NewBatchClient(name string) (*BatchClient, error) { 73 | conn, err := grpc.NewClient(constants.NitricAddress(), constants.DefaultOptions()...) 74 | if err != nil { 75 | return nil, errors.NewWithCause( 76 | codes.Unavailable, 77 | "NewBatchClient: unable to reach nitric server", 78 | err, 79 | ) 80 | } 81 | 82 | batchClient := v1.NewBatchClient(conn) 83 | 84 | return &BatchClient{ 85 | name: name, 86 | batchClient: batchClient, 87 | }, nil 88 | } 89 | -------------------------------------------------------------------------------- /nitric/batch/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "strings" 21 | 22 | "github.com/golang/mock/gomock" 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 27 | v1 "github.com/nitrictech/nitric/core/pkg/proto/batch/v1" 28 | "github.com/nitrictech/protoutils" 29 | ) 30 | 31 | var _ = Describe("File", func() { 32 | var ( 33 | ctrl *gomock.Controller 34 | mockBatchClient *mock_v1.MockBatchClient 35 | j *BatchClient 36 | jobName string 37 | ctx context.Context 38 | ) 39 | 40 | BeforeEach(func() { 41 | ctrl = gomock.NewController(GinkgoT()) 42 | mockBatchClient = mock_v1.NewMockBatchClient(ctrl) 43 | 44 | jobName = "test-job" 45 | j = &BatchClient{ 46 | name: jobName, 47 | batchClient: mockBatchClient, 48 | } 49 | 50 | ctx = context.Background() 51 | }) 52 | 53 | AfterEach(func() { 54 | ctrl.Finish() 55 | }) 56 | 57 | Describe("Name()", func() { 58 | It("should have the same job name as the one provided", func() { 59 | _jobName := j.Name() 60 | Expect(_jobName).To(Equal(jobName)) 61 | }) 62 | }) 63 | 64 | Describe("Submit()", func() { 65 | var dataToBeSubmitted map[string]interface{} 66 | 67 | BeforeEach(func() { 68 | dataToBeSubmitted = map[string]interface{}{ 69 | "data": "hello world", 70 | } 71 | }) 72 | 73 | When("the gRPC Read operation is successful", func() { 74 | BeforeEach(func() { 75 | payloadStruct, err := protoutils.NewStruct(dataToBeSubmitted) 76 | Expect(err).ToNot(HaveOccurred()) 77 | 78 | mockBatchClient.EXPECT().SubmitJob(gomock.Any(), &v1.JobSubmitRequest{ 79 | JobName: jobName, 80 | Data: &v1.JobData{ 81 | Data: &v1.JobData_Struct{ 82 | Struct: payloadStruct, 83 | }, 84 | }, 85 | }).Return( 86 | &v1.JobSubmitResponse{}, 87 | nil).Times(1) 88 | }) 89 | 90 | It("should not return error", func() { 91 | err := j.Submit(ctx, dataToBeSubmitted) 92 | 93 | Expect(err).ToNot(HaveOccurred()) 94 | }) 95 | }) 96 | 97 | When("the grpc server returns an error", func() { 98 | var errorMsg string 99 | 100 | BeforeEach(func() { 101 | errorMsg = "Internal Error" 102 | 103 | By("the gRPC server returning an error") 104 | mockBatchClient.EXPECT().SubmitJob(gomock.Any(), gomock.Any()).Return( 105 | nil, 106 | errors.New(errorMsg), 107 | ).Times(1) 108 | }) 109 | 110 | It("should return the passed error", func() { 111 | err := j.Submit(ctx, dataToBeSubmitted) 112 | 113 | By("returning error with expected message") 114 | Expect(err).To(HaveOccurred()) 115 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 116 | }) 117 | }) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /nitric/batch/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | import batchpb "github.com/nitrictech/nitric/core/pkg/proto/batch/v1" 18 | 19 | type Ctx struct { 20 | id string 21 | Request Request 22 | Response *Response 23 | Extras map[string]interface{} 24 | } 25 | 26 | func (c *Ctx) ToClientMessage() *batchpb.ClientMessage { 27 | return &batchpb.ClientMessage{ 28 | Id: c.id, 29 | Content: &batchpb.ClientMessage_JobResponse{ 30 | JobResponse: &batchpb.JobResponse{ 31 | Success: true, 32 | }, 33 | }, 34 | } 35 | } 36 | 37 | func NewCtx(msg *batchpb.ServerMessage) *Ctx { 38 | return &Ctx{ 39 | id: msg.Id, 40 | Request: &requestImpl{ 41 | jobName: msg.GetJobRequest().GetJobName(), 42 | data: msg.GetJobRequest().GetData().GetStruct().AsMap(), 43 | }, 44 | Response: &Response{ 45 | Success: true, 46 | }, 47 | } 48 | } 49 | 50 | func (c *Ctx) WithError(err error) { 51 | c.Response = &Response{ 52 | Success: false, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /nitric/batch/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | type HandlerOption func(opts *handlerOptions) 18 | 19 | // HandlerOptions defines the resource requirements for a job 20 | type handlerOptions struct { 21 | // Cpus is the number of CPUs/vCPUs to allocate to the job 22 | cpus *float32 23 | // Memory is the amount of memory in MiB to allocate to the job 24 | memory *int64 25 | // Gpus is the number of GPUs to allocate to the job 26 | gpus *int64 27 | } 28 | 29 | // WithCpus - Set the number of CPUs/vCPUs to allocate to job handler instances 30 | func WithCpus(cpus float32) HandlerOption { 31 | return func(opts *handlerOptions) { 32 | opts.cpus = &cpus 33 | } 34 | } 35 | 36 | // WithMemory - Set the amount of memory in MiB to allocate to job handler instances 37 | func WithMemory(mib int64) HandlerOption { 38 | return func(opts *handlerOptions) { 39 | opts.memory = &mib 40 | } 41 | } 42 | 43 | // WithGpus - Set the number of GPUs to allocate to job handler instances 44 | func WithGpus(gpus int64) HandlerOption { 45 | return func(opts *handlerOptions) { 46 | opts.gpus = &gpus 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /nitric/batch/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | type Request interface { 18 | JobName() string 19 | Data() map[string]interface{} 20 | } 21 | 22 | type requestImpl struct { 23 | jobName string 24 | data map[string]interface{} 25 | } 26 | 27 | func (m *requestImpl) JobName() string { 28 | return m.jobName 29 | } 30 | 31 | func (m *requestImpl) Data() map[string]interface{} { 32 | return m.data 33 | } 34 | -------------------------------------------------------------------------------- /nitric/batch/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | type Response struct { 18 | Success bool 19 | } 20 | -------------------------------------------------------------------------------- /nitric/batch/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package batch 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/grpc" 21 | 22 | errorsstd "errors" 23 | 24 | "github.com/nitrictech/go-sdk/constants" 25 | "github.com/nitrictech/go-sdk/nitric/errors" 26 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 27 | "github.com/nitrictech/go-sdk/nitric/workers" 28 | v1 "github.com/nitrictech/nitric/core/pkg/proto/batch/v1" 29 | ) 30 | 31 | type jobWorker struct { 32 | client v1.JobClient 33 | registrationRequest *v1.RegistrationRequest 34 | handler Handler 35 | } 36 | type jobWorkerOpts struct { 37 | RegistrationRequest *v1.RegistrationRequest 38 | Handler Handler 39 | } 40 | 41 | // Start runs the Job worker, creating a stream to the Nitric server 42 | func (s *jobWorker) Start(ctx context.Context) error { 43 | initReq := &v1.ClientMessage{ 44 | Content: &v1.ClientMessage_RegistrationRequest{ 45 | RegistrationRequest: s.registrationRequest, 46 | }, 47 | } 48 | 49 | createStream := func(ctx context.Context) (workers.Stream[v1.ClientMessage, v1.RegistrationResponse, *v1.ServerMessage], error) { 50 | return s.client.HandleJob(ctx) 51 | } 52 | 53 | handleSrvMsg := func(msg *v1.ServerMessage) (*v1.ClientMessage, error) { 54 | if msg.GetJobRequest() != nil { 55 | handlerCtx := NewCtx(msg) 56 | 57 | err := s.handler(handlerCtx) 58 | if err != nil { 59 | handlerCtx.WithError(err) 60 | } 61 | 62 | return handlerCtx.ToClientMessage(), nil 63 | } 64 | 65 | return nil, errors.NewWithCause( 66 | codes.Internal, 67 | "JobWorker: Unhandled server message", 68 | errorsstd.New("unhandled server message"), 69 | ) 70 | } 71 | 72 | return workers.HandleStream(ctx, createStream, initReq, handleSrvMsg) 73 | } 74 | 75 | func newJobWorker(opts *jobWorkerOpts) *jobWorker { 76 | conn, err := grpc.NewClient(constants.NitricAddress(), constants.DefaultOptions()...) 77 | if err != nil { 78 | panic(errors.NewWithCause( 79 | codes.Unavailable, 80 | "NewJobWorker: Unable to reach JobClient", 81 | err, 82 | )) 83 | } 84 | 85 | client := v1.NewJobClient(conn) 86 | 87 | return &jobWorker{ 88 | client: client, 89 | registrationRequest: opts.RegistrationRequest, 90 | handler: opts.Handler, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /nitric/errors/api_error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | multierror "github.com/missionMeteora/toolkit/errors" 22 | "google.golang.org/grpc/status" 23 | 24 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 25 | ) 26 | 27 | type ApiError struct { 28 | code codes.Code 29 | msg string 30 | cause error 31 | } 32 | 33 | func (a *ApiError) Unwrap() error { 34 | return a.cause 35 | } 36 | 37 | func (a *ApiError) Error() string { 38 | if a.cause != nil { 39 | // If the wrapped error is an ApiError than these should unwrap 40 | return fmt.Sprintf("%s: %s: \n %s", a.code.String(), a.msg, a.cause.Error()) 41 | } 42 | 43 | return fmt.Sprintf("%s: %s", a.code.String(), a.msg) 44 | } 45 | 46 | // FromGrpcError - translates a standard grpc error to a nitric api error 47 | func FromGrpcError(err error) error { 48 | if s, ok := status.FromError(err); ok { 49 | errList := &multierror.ErrorList{} 50 | errList.Push(err) 51 | for _, item := range s.Details() { 52 | errList.Push(fmt.Errorf("%v", item)) 53 | } 54 | 55 | return &ApiError{ 56 | code: codes.Code(s.Code()), 57 | msg: s.Message(), 58 | cause: errList.Err(), 59 | } 60 | } 61 | 62 | return &ApiError{ 63 | code: codes.Unknown, 64 | msg: "error from grpc library", 65 | cause: err, 66 | } 67 | } 68 | 69 | // Code - returns a nitric api error code from an error or Unknown if the error was not a nitric api error 70 | func Code(err error) codes.Code { 71 | var apiErr *ApiError 72 | if ok := errors.As(err, &apiErr); ok { 73 | return apiErr.code 74 | } 75 | 76 | return codes.Unknown 77 | } 78 | 79 | // New - Creates a new nitric API error 80 | func New(c codes.Code, msg string) error { 81 | return &ApiError{ 82 | code: c, 83 | msg: msg, 84 | } 85 | } 86 | 87 | // NewWithCause - Creates a new nitric API error with the given error as it's cause 88 | func NewWithCause(c codes.Code, msg string, cause error) error { 89 | return &ApiError{ 90 | code: c, 91 | msg: msg, 92 | cause: cause, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /nitric/errors/codes/codes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package codes 16 | 17 | type Code int 18 | 19 | const ( 20 | OK Code = 0 21 | Cancelled Code = 1 22 | Unknown Code = 2 23 | InvalidArgument Code = 3 24 | DeadlineExceeded Code = 4 25 | NotFound Code = 5 26 | AlreadyExists Code = 6 27 | PermissionDenied Code = 7 28 | ResourceExhausted Code = 8 29 | FailedPrecondition Code = 9 30 | Aborted Code = 10 31 | OutOfRange Code = 11 32 | Unimplemented Code = 12 33 | Internal Code = 13 34 | Unavailable Code = 14 35 | DataLoss Code = 15 36 | Unauthenticated Code = 16 37 | ) 38 | 39 | func (c Code) String() string { 40 | switch c { 41 | case OK: 42 | return "OK" 43 | case Cancelled: 44 | return "Cancelled" 45 | case Unknown: 46 | return "Unknown" 47 | case InvalidArgument: 48 | return "Invalid Argument" 49 | case DeadlineExceeded: 50 | return "Deadline Exceeded" 51 | case AlreadyExists: 52 | return "Already Exists" 53 | case PermissionDenied: 54 | return "Permission Denied" 55 | case ResourceExhausted: 56 | return "Resource Exhausted" 57 | case FailedPrecondition: 58 | return "Failed Precondition" 59 | case Aborted: 60 | return "Aborted" 61 | case OutOfRange: 62 | return "Out of Range" 63 | case Unimplemented: 64 | return "Unimplemented" 65 | case Internal: 66 | return "Internal" 67 | case Unavailable: 68 | return "Unavailable" 69 | case DataLoss: 70 | return "Data Loss" 71 | case Unauthenticated: 72 | return "Unauthenticated" 73 | default: 74 | return "Unknown" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /nitric/keyvalue/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keyvalue 16 | 17 | import ( 18 | "context" 19 | 20 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 21 | "github.com/nitrictech/go-sdk/nitric/errors" 22 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 23 | "github.com/nitrictech/protoutils" 24 | 25 | v1 "github.com/nitrictech/nitric/core/pkg/proto/kvstore/v1" 26 | ) 27 | 28 | type ScanKeysRequest = v1.KvStoreScanKeysRequest 29 | 30 | type ScanKeysOption = func(*ScanKeysRequest) 31 | 32 | // Apply a prefix to the scan keys request 33 | func WithPrefix(prefix string) ScanKeysOption { 34 | return func(req *ScanKeysRequest) { 35 | req.Prefix = prefix 36 | } 37 | } 38 | 39 | // TODO: maybe move keystream to separate file 40 | type KeyStream struct { 41 | stream v1.KvStore_ScanKeysClient 42 | } 43 | 44 | func (k *KeyStream) Recv() (string, error) { 45 | resp, err := k.stream.Recv() 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | return resp.Key, nil 51 | } 52 | 53 | type KvStoreClientIface interface { 54 | // Name - The name of the store 55 | Name() string 56 | // Get a value from the store 57 | Get(ctx context.Context, key string) (map[string]interface{}, error) 58 | // Set a value in the store 59 | Set(ctx context.Context, key string, value map[string]interface{}) error 60 | // Delete a value from the store 61 | Delete(ctx context.Context, key string) error 62 | // Return an async iterable of keys in the store 63 | Keys(ctx context.Context, options ...ScanKeysOption) (*KeyStream, error) 64 | } 65 | 66 | type KvStoreClient struct { 67 | name string 68 | kvClient v1.KvStoreClient 69 | } 70 | 71 | func (s *KvStoreClient) Name() string { 72 | return s.name 73 | } 74 | 75 | func (s *KvStoreClient) Get(ctx context.Context, key string) (map[string]interface{}, error) { 76 | ref := &v1.ValueRef{ 77 | Store: s.name, 78 | Key: key, 79 | } 80 | 81 | r, err := s.kvClient.GetValue(ctx, &v1.KvStoreGetValueRequest{ 82 | Ref: ref, 83 | }) 84 | if err != nil { 85 | return nil, errors.FromGrpcError(err) 86 | } 87 | 88 | val := r.GetValue() 89 | if val == nil { 90 | return nil, errors.New(codes.NotFound, "Key not found") 91 | } 92 | content := val.GetContent().AsMap() 93 | 94 | return content, nil 95 | } 96 | 97 | func (s *KvStoreClient) Set(ctx context.Context, key string, value map[string]interface{}) error { 98 | ref := &v1.ValueRef{ 99 | Store: s.name, 100 | Key: key, 101 | } 102 | 103 | // Convert payload to Protobuf Struct 104 | contentStruct, err := protoutils.NewStruct(value) 105 | if err != nil { 106 | return errors.NewWithCause(codes.InvalidArgument, "Store.Set", err) 107 | } 108 | 109 | _, err = s.kvClient.SetValue(ctx, &v1.KvStoreSetValueRequest{ 110 | Ref: ref, 111 | Content: contentStruct, 112 | }) 113 | if err != nil { 114 | return errors.FromGrpcError(err) 115 | } 116 | 117 | return nil 118 | } 119 | 120 | func (s *KvStoreClient) Delete(ctx context.Context, key string) error { 121 | ref := &v1.ValueRef{ 122 | Store: s.name, 123 | Key: key, 124 | } 125 | 126 | _, err := s.kvClient.DeleteKey(ctx, &v1.KvStoreDeleteKeyRequest{ 127 | Ref: ref, 128 | }) 129 | if err != nil { 130 | return errors.FromGrpcError(err) 131 | } 132 | 133 | return nil 134 | } 135 | 136 | func (s *KvStoreClient) Keys(ctx context.Context, opts ...ScanKeysOption) (*KeyStream, error) { 137 | store := &v1.Store{ 138 | Name: s.name, 139 | } 140 | 141 | request := &v1.KvStoreScanKeysRequest{ 142 | Store: store, 143 | Prefix: "", 144 | } 145 | 146 | // Apply options to the request payload - Prefix modification 147 | for _, opt := range opts { 148 | opt(request) 149 | } 150 | 151 | streamClient, err := s.kvClient.ScanKeys(ctx, request) 152 | if err != nil { 153 | return nil, errors.FromGrpcError(err) 154 | } 155 | 156 | return &KeyStream{ 157 | stream: streamClient, 158 | }, nil 159 | } 160 | 161 | func NewKvStoreClient(name string) (*KvStoreClient, error) { 162 | conn, err := grpcx.GetConnection() 163 | if err != nil { 164 | return nil, errors.NewWithCause( 165 | codes.Unavailable, 166 | "NewKvStoreClient: unable to reach nitric server", 167 | err, 168 | ) 169 | } 170 | 171 | client := v1.NewKvStoreClient(conn) 172 | 173 | return &KvStoreClient{ 174 | name: name, 175 | kvClient: client, 176 | }, nil 177 | } 178 | -------------------------------------------------------------------------------- /nitric/keyvalue/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keyvalue 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "strings" 21 | 22 | "github.com/golang/mock/gomock" 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 27 | v1 "github.com/nitrictech/nitric/core/pkg/proto/kvstore/v1" 28 | "github.com/nitrictech/protoutils" 29 | ) 30 | 31 | var _ = Describe("KeyValue Store API", func() { 32 | var ( 33 | ctrl *gomock.Controller 34 | mockKV *mock_v1.MockKvStoreClient 35 | kv *KvStoreClient 36 | store KvStoreClientIface 37 | storeName string 38 | ) 39 | 40 | BeforeEach(func() { 41 | ctrl = gomock.NewController(GinkgoT()) 42 | mockKV = mock_v1.NewMockKvStoreClient(ctrl) 43 | storeName = "test-store" 44 | kv = &KvStoreClient{name: storeName, kvClient: mockKV} 45 | store = kv 46 | }) 47 | 48 | AfterEach(func() { 49 | ctrl.Finish() 50 | }) 51 | 52 | Context("Having a valid store", func() { 53 | When("requesting a store's name", func() { 54 | It("should have the provided store name", func() { 55 | Expect(store.Name()).To(Equal(storeName)) 56 | }) 57 | }) 58 | 59 | Describe("Get", func() { 60 | var key string 61 | var expectedValue map[string]interface{} 62 | 63 | BeforeEach(func() { 64 | key = "test-key" 65 | expectedValue = map[string]interface{}{"data": "value"} 66 | }) 67 | 68 | When("the key exists", func() { 69 | BeforeEach(func() { 70 | contentStruct, _ := protoutils.NewStruct(expectedValue) 71 | mockKV.EXPECT().GetValue(gomock.Any(), gomock.Any()).Return(&v1.KvStoreGetValueResponse{ 72 | Value: &v1.Value{ 73 | Ref: &v1.ValueRef{ 74 | Store: storeName, 75 | Key: key, 76 | }, 77 | Content: contentStruct, 78 | }, 79 | }, nil).Times(1) 80 | }) 81 | 82 | It("should return the correct value", func() { 83 | value, err := store.Get(context.Background(), key) 84 | Expect(err).NotTo(HaveOccurred()) 85 | Expect(value).To(Equal(expectedValue)) 86 | }) 87 | }) 88 | 89 | When("the key does not exists", func() { 90 | BeforeEach(func() { 91 | mockKV.EXPECT().GetValue(gomock.Any(), gomock.Any()).Return(&v1.KvStoreGetValueResponse{ 92 | Value: nil, 93 | }, nil).Times(1) 94 | }) 95 | 96 | It("should return an error", func() { 97 | _, err := store.Get(context.Background(), key) 98 | Expect(err).To(HaveOccurred()) 99 | }) 100 | }) 101 | }) 102 | 103 | Describe("Set", func() { 104 | var key string 105 | var valueToSet map[string]interface{} 106 | 107 | BeforeEach(func() { 108 | key = "test-key" 109 | valueToSet = map[string]interface{}{"data": "value"} 110 | }) 111 | 112 | When("the operation is successful", func() { 113 | BeforeEach(func() { 114 | mockKV.EXPECT().SetValue(gomock.Any(), gomock.Any()).Return( 115 | &v1.KvStoreSetValueResponse{}, 116 | nil, 117 | ).Times(1) 118 | }) 119 | 120 | It("should successfully set the value", func() { 121 | err := store.Set(context.Background(), key, valueToSet) 122 | Expect(err).ToNot(HaveOccurred()) 123 | }) 124 | }) 125 | 126 | When("the operation fails", func() { 127 | var errorMsg string 128 | BeforeEach(func() { 129 | errorMsg = "Internal Error" 130 | mockKV.EXPECT().SetValue(gomock.Any(), gomock.Any()).Return( 131 | nil, 132 | errors.New(errorMsg), 133 | ).Times(1) 134 | }) 135 | 136 | It("should return an error", func() { 137 | err := store.Set(context.Background(), key, valueToSet) 138 | Expect(err).To(HaveOccurred()) 139 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 140 | }) 141 | }) 142 | }) 143 | 144 | Describe("Delete", func() { 145 | var key string 146 | 147 | BeforeEach(func() { 148 | key = "test-key" 149 | }) 150 | 151 | When("the operation is successful", func() { 152 | BeforeEach(func() { 153 | mockKV.EXPECT().DeleteKey(gomock.Any(), gomock.Any()).Return( 154 | &v1.KvStoreDeleteKeyResponse{}, 155 | nil, 156 | ).Times(1) 157 | }) 158 | 159 | It("should successfully set the value", func() { 160 | err := store.Delete(context.Background(), key) 161 | Expect(err).ToNot(HaveOccurred()) 162 | }) 163 | }) 164 | 165 | When("the GRPC operation fails", func() { 166 | var errorMsg string 167 | 168 | BeforeEach(func() { 169 | errorMsg = "Internal Error" 170 | mockKV.EXPECT().DeleteKey(gomock.Any(), gomock.Any()).Return( 171 | nil, 172 | errors.New(errorMsg), 173 | ).Times(1) 174 | }) 175 | 176 | It("should return an error", func() { 177 | err := store.Delete(context.Background(), key) 178 | Expect(err).To(HaveOccurred()) 179 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 180 | }) 181 | }) 182 | }) 183 | 184 | Describe("Keys", func() { 185 | When("the operation is successful", func() { 186 | var expectedKey string 187 | 188 | BeforeEach(func() { 189 | expectedKey = "key1" 190 | mockStream := mock_v1.NewMockKvStore_ScanKeysClient(ctrl) 191 | mockKV.EXPECT().ScanKeys(gomock.Any(), gomock.Any()).Return(mockStream, nil).Times(1) 192 | mockStream.EXPECT().Recv().Return(&v1.KvStoreScanKeysResponse{Key: expectedKey}, nil).AnyTimes() 193 | }) 194 | 195 | It("should return a stream of keys", func() { 196 | stream, err := store.Keys(context.Background()) 197 | Expect(err).ToNot(HaveOccurred()) 198 | key, err := stream.Recv() 199 | Expect(err).ToNot(HaveOccurred()) 200 | Expect(key).To(Equal(expectedKey)) 201 | }) 202 | }) 203 | 204 | When("the operation fails", func() { 205 | var errorMsg string 206 | BeforeEach(func() { 207 | errorMsg = "Internal Error" 208 | mockStream := mock_v1.NewMockKvStore_ScanKeysClient(ctrl) 209 | mockKV.EXPECT().ScanKeys(gomock.Any(), gomock.Any()).Return(mockStream, nil).Times(1) 210 | mockStream.EXPECT().Recv().Return(nil, errors.New(errorMsg)).Times(1) 211 | }) 212 | 213 | It("should return an error", func() { 214 | stream, err := store.Keys(context.Background()) 215 | Expect(err).ToNot(HaveOccurred()) 216 | _, err = stream.Recv() 217 | Expect(err).To(HaveOccurred()) 218 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 219 | }) 220 | }) 221 | }) 222 | }) 223 | }) 224 | -------------------------------------------------------------------------------- /nitric/keyvalue/keyvalue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keyvalue 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/nitrictech/go-sdk/nitric/workers" 21 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 22 | ) 23 | 24 | type KvStorePermission string 25 | 26 | const ( 27 | KvStoreSet KvStorePermission = "set" 28 | KvStoreGet KvStorePermission = "get" 29 | KvStoreDelete KvStorePermission = "delete" 30 | ) 31 | 32 | var KvStoreEverything []KvStorePermission = []KvStorePermission{KvStoreSet, KvStoreGet, KvStoreDelete} 33 | 34 | type KvStore interface { 35 | // Allow requests the given permissions to the key/value store. 36 | Allow(permission KvStorePermission, permissions ...KvStorePermission) KvStoreClientIface 37 | } 38 | 39 | type kvstore struct { 40 | name string 41 | manager *workers.Manager 42 | registerChan <-chan workers.RegisterResult 43 | } 44 | 45 | // NewKv - Create a new Key/Value store resource 46 | func NewKv(name string) *kvstore { 47 | kvstore := &kvstore{ 48 | name: name, 49 | manager: workers.GetDefaultManager(), 50 | registerChan: make(chan workers.RegisterResult), 51 | } 52 | 53 | kvstore.registerChan = kvstore.manager.RegisterResource(&v1.ResourceDeclareRequest{ 54 | Id: &v1.ResourceIdentifier{ 55 | Type: v1.ResourceType_KeyValueStore, 56 | Name: name, 57 | }, 58 | Config: &v1.ResourceDeclareRequest_KeyValueStore{ 59 | KeyValueStore: &v1.KeyValueStoreResource{}, 60 | }, 61 | }) 62 | 63 | return kvstore 64 | } 65 | 66 | func (k *kvstore) Allow(permission KvStorePermission, permissions ...KvStorePermission) KvStoreClientIface { 67 | allPerms := append([]KvStorePermission{permission}, permissions...) 68 | 69 | actions := []v1.Action{} 70 | for _, perm := range allPerms { 71 | switch perm { 72 | case KvStoreGet: 73 | actions = append(actions, v1.Action_KeyValueStoreRead) 74 | case KvStoreSet: 75 | actions = append(actions, v1.Action_KeyValueStoreWrite) 76 | case KvStoreDelete: 77 | actions = append(actions, v1.Action_KeyValueStoreDelete) 78 | default: 79 | panic(fmt.Sprintf("KvStorePermission %s unknown", perm)) 80 | } 81 | } 82 | 83 | registerResult := <-k.registerChan 84 | 85 | if registerResult.Err != nil { 86 | panic(registerResult.Err) 87 | } 88 | 89 | err := k.manager.RegisterPolicy(registerResult.Identifier, actions...) 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | client, err := NewKvStoreClient(k.name) 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | return client 100 | } 101 | -------------------------------------------------------------------------------- /nitric/keyvalue/keyvalue_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keyvalue_test 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestKeyvalue(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Keyvalue Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/nitric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nitric 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "os/signal" 22 | "syscall" 23 | 24 | "github.com/nitrictech/go-sdk/nitric/apis" 25 | "github.com/nitrictech/go-sdk/nitric/batch" 26 | "github.com/nitrictech/go-sdk/nitric/keyvalue" 27 | "github.com/nitrictech/go-sdk/nitric/queues" 28 | "github.com/nitrictech/go-sdk/nitric/schedules" 29 | "github.com/nitrictech/go-sdk/nitric/secrets" 30 | "github.com/nitrictech/go-sdk/nitric/sql" 31 | "github.com/nitrictech/go-sdk/nitric/storage" 32 | "github.com/nitrictech/go-sdk/nitric/topics" 33 | "github.com/nitrictech/go-sdk/nitric/websockets" 34 | "github.com/nitrictech/go-sdk/nitric/workers" 35 | ) 36 | 37 | var ( 38 | NewApi = apis.NewApi 39 | NewKv = keyvalue.NewKv 40 | NewQueue = queues.NewQueue 41 | NewSchedule = schedules.NewSchedule 42 | NewSecret = secrets.NewSecret 43 | NewSqlDatabase = sql.NewSqlDatabase 44 | NewBucket = storage.NewBucket 45 | NewTopic = topics.NewTopic 46 | NewWebsocket = websockets.NewWebsocket 47 | NewJob = batch.NewJob 48 | ) 49 | 50 | func Run() { 51 | ctx, cancel := context.WithCancel(context.Background()) 52 | defer cancel() 53 | 54 | sigChan := make(chan os.Signal, 1) 55 | signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) 56 | 57 | go func() { 58 | <-sigChan 59 | fmt.Printf("Received signal, shutting down...\n") 60 | cancel() 61 | }() 62 | 63 | err := workers.GetDefaultManager().Run(ctx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /nitric/queues/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queues 16 | 17 | import ( 18 | "context" 19 | 20 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 21 | "github.com/nitrictech/go-sdk/nitric/errors" 22 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 23 | v1 "github.com/nitrictech/nitric/core/pkg/proto/queues/v1" 24 | ) 25 | 26 | // QueueClientIface is a resource for async enqueueing/dequeueing of messages. 27 | type QueueClientIface interface { 28 | // Name - The name of the queue 29 | Name() string 30 | // Enqueue - Push a number of messages to a queue 31 | Enqueue(ctx context.Context, messages []map[string]interface{}) ([]*FailedMessage, error) 32 | // Dequeue - Retrieve messages from a queue to a maximum of the given depth 33 | Dequeue(ctx context.Context, depth int) ([]ReceivedMessage, error) 34 | } 35 | 36 | type QueueClient struct { 37 | name string 38 | queueClient v1.QueuesClient 39 | } 40 | 41 | func (q *QueueClient) Name() string { 42 | return q.name 43 | } 44 | 45 | func (q *QueueClient) Dequeue(ctx context.Context, depth int) ([]ReceivedMessage, error) { 46 | if depth < 1 { 47 | return nil, errors.New(codes.InvalidArgument, "Queue.Dequeue: depth cannot be less than 1") 48 | } 49 | 50 | r, err := q.queueClient.Dequeue(ctx, &v1.QueueDequeueRequest{ 51 | QueueName: q.name, 52 | Depth: int32(depth), 53 | }) 54 | if err != nil { 55 | return nil, errors.FromGrpcError(err) 56 | } 57 | 58 | rts := make([]ReceivedMessage, len(r.GetMessages())) 59 | 60 | for i, message := range r.GetMessages() { 61 | rts[i] = &leasedMessage{ 62 | queueName: q.name, 63 | queueClient: q.queueClient, 64 | leaseId: message.GetLeaseId(), 65 | message: wireToMessage(message.GetMessage()), 66 | } 67 | } 68 | 69 | return rts, nil 70 | } 71 | 72 | func (q *QueueClient) Enqueue(ctx context.Context, messages []map[string]interface{}) ([]*FailedMessage, error) { 73 | // Convert SDK Message objects to gRPC Message objects 74 | wireMessages := make([]*v1.QueueMessage, len(messages)) 75 | for i, message := range messages { 76 | wireMessage, err := messageToWire(message) 77 | if err != nil { 78 | return nil, errors.NewWithCause( 79 | codes.Internal, 80 | "Queue.Enqueue: Unable to enqueue messages", 81 | err, 82 | ) 83 | } 84 | wireMessages[i] = wireMessage 85 | } 86 | 87 | // Push the messages to the queue 88 | res, err := q.queueClient.Enqueue(ctx, &v1.QueueEnqueueRequest{ 89 | QueueName: q.name, 90 | Messages: wireMessages, 91 | }) 92 | if err != nil { 93 | return nil, errors.FromGrpcError(err) 94 | } 95 | 96 | // Convert the gRPC Failed Messages to SDK Failed Message objects 97 | failedMessages := make([]*FailedMessage, len(res.GetFailedMessages())) 98 | for i, failedMessage := range res.GetFailedMessages() { 99 | failedMessages[i] = &FailedMessage{ 100 | Message: wireToMessage(failedMessage.GetMessage()), 101 | Reason: failedMessage.GetDetails(), 102 | } 103 | } 104 | 105 | return failedMessages, nil 106 | } 107 | 108 | func NewQueueClient(name string) (*QueueClient, error) { 109 | conn, err := grpcx.GetConnection() 110 | if err != nil { 111 | return nil, errors.NewWithCause( 112 | codes.Unavailable, 113 | "NewQueueClient: unable to reach nitric server", 114 | err, 115 | ) 116 | } 117 | 118 | queueClient := v1.NewQueuesClient(conn) 119 | 120 | return &QueueClient{ 121 | name: name, 122 | queueClient: queueClient, 123 | }, nil 124 | } 125 | -------------------------------------------------------------------------------- /nitric/queues/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queues 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "strconv" 21 | "strings" 22 | 23 | "github.com/golang/mock/gomock" 24 | . "github.com/onsi/ginkgo" 25 | . "github.com/onsi/gomega" 26 | 27 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 28 | v1 "github.com/nitrictech/nitric/core/pkg/proto/queues/v1" 29 | ) 30 | 31 | var _ = Describe("Queue interface", func() { 32 | var ( 33 | ctrl *gomock.Controller 34 | mockQ *mock_v1.MockQueuesClient 35 | queueName string 36 | q QueueClientIface 37 | ctx context.Context 38 | ) 39 | 40 | BeforeEach(func() { 41 | ctrl = gomock.NewController(GinkgoT()) 42 | mockQ = mock_v1.NewMockQueuesClient(ctrl) 43 | queueName = "test-queue" 44 | q = &QueueClient{ 45 | queueClient: mockQ, 46 | name: queueName, 47 | } 48 | ctx = context.Background() 49 | }) 50 | 51 | AfterEach(func() { 52 | ctrl.Finish() 53 | }) 54 | 55 | Context("Having a valid queue", func() { 56 | Describe("Name", func() { 57 | It("should return the correct queue name", func() { 58 | Expect(q.Name()).To(Equal(queueName)) 59 | }) 60 | }) 61 | 62 | Describe("Enqueue", func() { 63 | var messages []map[string]interface{} 64 | 65 | BeforeEach(func() { 66 | messages = []map[string]interface{}{ 67 | {"message": "hello"}, 68 | {"message": "world"}, 69 | } 70 | }) 71 | 72 | When("the operation is successful", func() { 73 | BeforeEach(func() { 74 | mockQ.EXPECT().Enqueue(gomock.Any(), gomock.Any()).Return( 75 | &v1.QueueEnqueueResponse{ 76 | FailedMessages: nil, 77 | }, 78 | nil, 79 | ).Times(1) 80 | }) 81 | 82 | It("should successfully enqueue messages", func() { 83 | failedMessages, err := q.Enqueue(ctx, messages) 84 | Expect(err).ToNot(HaveOccurred()) 85 | Expect(failedMessages).To(BeEmpty()) 86 | }) 87 | }) 88 | 89 | When("a message send fails", func() { 90 | var failedMsg map[string]interface{} 91 | var failureReason string 92 | 93 | BeforeEach(func() { 94 | failedMsg = messages[0] 95 | wiredFailedMsg, err := messageToWire(failedMsg) 96 | Expect(err).ToNot(HaveOccurred()) 97 | failureReason = "failed to send task" 98 | 99 | mockQ.EXPECT().Enqueue(gomock.Any(), gomock.Any()).Return( 100 | &v1.QueueEnqueueResponse{ 101 | FailedMessages: []*v1.FailedEnqueueMessage{ 102 | { 103 | Message: wiredFailedMsg, 104 | Details: failureReason, 105 | }, 106 | }, 107 | }, 108 | nil, 109 | ).Times(1) 110 | }) 111 | 112 | It("should receive a message from []*FailedMessage", func() { 113 | failedMessages, err := q.Enqueue(ctx, messages) 114 | Expect(err).ToNot(HaveOccurred()) 115 | Expect(failedMessages).To(HaveLen(1)) 116 | Expect(failedMessages[0].Message).To(Equal(failedMsg)) 117 | Expect(failedMessages[0].Reason).To(Equal(failureReason)) 118 | }) 119 | }) 120 | 121 | When("the operation fails", func() { 122 | var errorMsg string 123 | 124 | BeforeEach(func() { 125 | errorMsg = "internal error" 126 | mockQ.EXPECT().Enqueue(gomock.Any(), gomock.Any()).Return( 127 | nil, 128 | errors.New(errorMsg), 129 | ).AnyTimes() 130 | }) 131 | 132 | It("should return an error", func() { 133 | _, err := q.Enqueue(ctx, messages) 134 | Expect(err).To(HaveOccurred()) 135 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 136 | }) 137 | }) 138 | }) 139 | 140 | Describe("Dequeue", func() { 141 | var depth int 142 | 143 | When("the depth is less than 1", func() { 144 | BeforeEach(func() { 145 | depth = 0 146 | }) 147 | 148 | It("should return an error", func() { 149 | messages, err := q.Dequeue(ctx, depth) 150 | Expect(err).To(HaveOccurred()) 151 | Expect(messages).To(BeNil()) 152 | }) 153 | }) 154 | 155 | When("the operation is successful", func() { 156 | var messagesQueue []map[string]interface{} 157 | 158 | BeforeEach(func() { 159 | messagesQueue = []map[string]interface{}{ 160 | {"message": "hello"}, 161 | {"message": "world"}, 162 | } 163 | depth = len(messagesQueue) 164 | 165 | dequeuedMessages := make([]*v1.DequeuedMessage, 0, depth) 166 | for i := 0; i < depth; i++ { 167 | msg, err := messageToWire(messagesQueue[i]) 168 | Expect(err).ToNot(HaveOccurred()) 169 | 170 | dequeuedMessages = append(dequeuedMessages, &v1.DequeuedMessage{ 171 | LeaseId: strconv.Itoa(i), 172 | Message: msg, 173 | }) 174 | } 175 | 176 | mockQ.EXPECT().Dequeue(ctx, &v1.QueueDequeueRequest{ 177 | QueueName: queueName, 178 | Depth: int32(depth), 179 | }).Return(&v1.QueueDequeueResponse{ 180 | Messages: dequeuedMessages, 181 | }, nil).AnyTimes() 182 | }) 183 | 184 | It("should receive tasks equal to depth", func() { 185 | messages, err := q.Dequeue(ctx, depth) 186 | Expect(err).ToNot(HaveOccurred()) 187 | Expect(messages).To(HaveLen(depth)) 188 | }) 189 | 190 | It("should have message of type receivedMessageImpl", func() { 191 | messages, err := q.Dequeue(ctx, depth) 192 | Expect(err).ToNot(HaveOccurred()) 193 | 194 | _, ok := messages[0].(*leasedMessage) 195 | Expect(ok).To(BeTrue()) 196 | }) 197 | 198 | It("should contain the returned messages", func() { 199 | dequeuedMessages, err := q.Dequeue(ctx, depth) 200 | Expect(err).ToNot(HaveOccurred()) 201 | 202 | for i := 0; i < depth; i++ { 203 | msg := dequeuedMessages[i] 204 | Expect(msg.Message()).To(Equal(messagesQueue[i])) 205 | } 206 | }) 207 | }) 208 | 209 | When("the operation fails", func() { 210 | var errorMsg string 211 | 212 | BeforeEach(func() { 213 | depth = 1 214 | errorMsg = "internal error" 215 | mockQ.EXPECT().Dequeue(gomock.Any(), gomock.Any()).Return( 216 | nil, 217 | errors.New(errorMsg), 218 | ).Times(1) 219 | }) 220 | 221 | It("should return an error", func() { 222 | _, err := q.Dequeue(ctx, depth) 223 | Expect(err).To(HaveOccurred()) 224 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 225 | }) 226 | }) 227 | }) 228 | }) 229 | }) 230 | -------------------------------------------------------------------------------- /nitric/queues/message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queues 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/nitrictech/go-sdk/nitric/errors" 21 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 22 | v1 "github.com/nitrictech/nitric/core/pkg/proto/queues/v1" 23 | "github.com/nitrictech/protoutils" 24 | ) 25 | 26 | type ReceivedMessage interface { 27 | // Queue - Returns the name of the queue this message was retrieved from 28 | Queue() string 29 | // Message - Returns the Message data contained in this Received Message instance 30 | Message() map[string]interface{} 31 | // Complete - Completes the message removing it from the queue 32 | Complete(context.Context) error 33 | } 34 | 35 | type leasedMessage struct { 36 | queueName string 37 | queueClient v1.QueuesClient 38 | leaseId string 39 | message map[string]interface{} 40 | } 41 | 42 | func (r *leasedMessage) Message() map[string]interface{} { 43 | return r.message 44 | } 45 | 46 | func (r *leasedMessage) Queue() string { 47 | return r.queueName 48 | } 49 | 50 | func (r *leasedMessage) Complete(ctx context.Context) error { 51 | _, err := r.queueClient.Complete(ctx, &v1.QueueCompleteRequest{ 52 | QueueName: r.queueName, 53 | LeaseId: r.leaseId, 54 | }) 55 | 56 | return err 57 | } 58 | 59 | type FailedMessage struct { 60 | // Message - The message that failed to queue 61 | Message map[string]interface{} 62 | // Reason - Reason for the failure 63 | Reason string 64 | } 65 | 66 | func messageToWire(message map[string]interface{}) (*v1.QueueMessage, error) { 67 | // Convert payload to Protobuf Struct 68 | payloadStruct, err := protoutils.NewStruct(message) 69 | if err != nil { 70 | return nil, errors.NewWithCause( 71 | codes.Internal, 72 | "messageToWire: failed to serialize message: %s", 73 | err, 74 | ) 75 | } 76 | 77 | return &v1.QueueMessage{ 78 | Content: &v1.QueueMessage_StructPayload{ 79 | StructPayload: payloadStruct, 80 | }, 81 | }, nil 82 | } 83 | 84 | func wireToMessage(message *v1.QueueMessage) map[string]interface{} { 85 | // TODO: verify that AsMap() ignores the proto field values 86 | return message.GetStructPayload().AsMap() 87 | } 88 | -------------------------------------------------------------------------------- /nitric/queues/message_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queues 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/golang/mock/gomock" 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/queues/v1" 27 | ) 28 | 29 | var _ = Describe("ReceivedMessage", func() { 30 | var ( 31 | ctrl *gomock.Controller 32 | mockQ *mock_v1.MockQueuesClient 33 | queueName string 34 | leaseID string 35 | message map[string]interface{} 36 | receivedMsg ReceivedMessage 37 | ctx context.Context 38 | ) 39 | 40 | BeforeEach(func() { 41 | ctrl = gomock.NewController(GinkgoT()) 42 | mockQ = mock_v1.NewMockQueuesClient(ctrl) 43 | queueName = "test-queue" 44 | leaseID = "1" 45 | message = map[string]interface{}{ 46 | "message": "hello", 47 | } 48 | receivedMsg = &leasedMessage{ 49 | queueName: queueName, 50 | queueClient: mockQ, 51 | leaseId: leaseID, 52 | message: message, 53 | } 54 | ctx = context.Background() 55 | }) 56 | 57 | AfterEach(func() { 58 | ctrl.Finish() 59 | }) 60 | 61 | Describe("Message", func() { 62 | It("should return the correct message", func() { 63 | Expect(receivedMsg.Message()).To(Equal(message)) 64 | }) 65 | }) 66 | 67 | Describe("Queue", func() { 68 | It("should return the correct queue name", func() { 69 | Expect(receivedMsg.Queue()).To(Equal(queueName)) 70 | }) 71 | }) 72 | 73 | Describe("Complete", func() { 74 | It("should complete the message successfully", func() { 75 | mockQ.EXPECT().Complete(ctx, &v1.QueueCompleteRequest{ 76 | QueueName: queueName, 77 | LeaseId: leaseID, 78 | }).Return(&v1.QueueCompleteResponse{}, nil) 79 | 80 | err := receivedMsg.Complete(ctx) 81 | Expect(err).NotTo(HaveOccurred()) 82 | }) 83 | 84 | It("should handle errors when completing the message", func() { 85 | mockQ.EXPECT().Complete(ctx, gomock.Any()).Return(nil, fmt.Errorf("some error")) 86 | 87 | err := receivedMsg.Complete(ctx) 88 | Expect(err).To(HaveOccurred()) 89 | }) 90 | }) 91 | }) 92 | 93 | var _ = Describe("Helper functions", func() { 94 | Describe("messageToWire", func() { 95 | It("should convert a map to a protobuf message", func() { 96 | message := map[string]interface{}{ 97 | "message": "hello", 98 | } 99 | 100 | wireMsg, err := messageToWire(message) 101 | Expect(err).NotTo(HaveOccurred()) 102 | Expect(wireMsg.GetStructPayload().AsMap()).To(Equal(message)) 103 | }) 104 | 105 | It("should handle errors when converting a map to a protobuf message", func() { 106 | message := map[string]interface{}{ 107 | "message": make(chan int), // channels cannot be converted to protobuf 108 | } 109 | 110 | _, err := messageToWire(message) 111 | Expect(err).To(HaveOccurred()) 112 | }) 113 | }) 114 | 115 | Describe("wireToMessage", func() { 116 | It("should convert a protobuf message to a map", func() { 117 | message := map[string]interface{}{ 118 | "message": "hello", 119 | } 120 | wireMsg, err := messageToWire(message) 121 | Expect(err).NotTo(HaveOccurred()) 122 | 123 | convertedMessage := wireToMessage(wireMsg) 124 | Expect(convertedMessage).To(Equal(message)) 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /nitric/queues/queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queues 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/nitrictech/go-sdk/nitric/workers" 21 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 22 | ) 23 | 24 | type QueuePermission string 25 | 26 | const ( 27 | QueueEnqueue QueuePermission = "enqueue" 28 | QueueDequeue QueuePermission = "dequeue" 29 | ) 30 | 31 | var QueueEverything []QueuePermission = []QueuePermission{QueueEnqueue, QueueDequeue} 32 | 33 | type Queue interface { 34 | // Allow requests the given permissions to the queue. 35 | Allow(permission QueuePermission, permissions ...QueuePermission) *QueueClient 36 | } 37 | 38 | type queue struct { 39 | name string 40 | manager *workers.Manager 41 | registerChan <-chan workers.RegisterResult 42 | } 43 | 44 | // NewQueue - Create a new Queue resource 45 | func NewQueue(name string) *queue { 46 | queue := &queue{ 47 | name: name, 48 | manager: workers.GetDefaultManager(), 49 | registerChan: make(chan workers.RegisterResult), 50 | } 51 | 52 | queue.registerChan = queue.manager.RegisterResource(&v1.ResourceDeclareRequest{ 53 | Id: &v1.ResourceIdentifier{ 54 | Type: v1.ResourceType_Queue, 55 | Name: name, 56 | }, 57 | Config: &v1.ResourceDeclareRequest_Queue{ 58 | Queue: &v1.QueueResource{}, 59 | }, 60 | }) 61 | 62 | return queue 63 | } 64 | 65 | func (q *queue) Allow(permission QueuePermission, permissions ...QueuePermission) *QueueClient { 66 | allPerms := append([]QueuePermission{permission}, permissions...) 67 | 68 | actions := []v1.Action{} 69 | for _, perm := range allPerms { 70 | switch perm { 71 | case QueueDequeue: 72 | actions = append(actions, v1.Action_QueueDequeue) 73 | case QueueEnqueue: 74 | actions = append(actions, v1.Action_QueueEnqueue) 75 | default: 76 | panic(fmt.Sprintf("QueuePermission %s unknown", perm)) 77 | } 78 | } 79 | 80 | registerResult := <-q.registerChan 81 | if registerResult.Err != nil { 82 | panic(registerResult.Err) 83 | } 84 | 85 | err := q.manager.RegisterPolicy(registerResult.Identifier, actions...) 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | client, err := NewQueueClient(q.name) 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | return client 96 | } 97 | -------------------------------------------------------------------------------- /nitric/queues/queues_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queues 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestQueuing(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Queues Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/resource/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package resources 16 | 17 | type Details struct { 18 | // The identifier of the resource 19 | ID string 20 | // The provider this resource is deployed with (e.g. aws) 21 | Provider string 22 | // The service this resource is deployed on (e.g. ApiGateway) 23 | Service string 24 | } 25 | -------------------------------------------------------------------------------- /nitric/resources_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nitric 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestNitric(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Nitric Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/schedules/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schedules 16 | 17 | import schedulespb "github.com/nitrictech/nitric/core/pkg/proto/schedules/v1" 18 | 19 | type Ctx struct { 20 | id string 21 | Request Request 22 | Response *Response 23 | Extras map[string]interface{} 24 | } 25 | 26 | func (c *Ctx) ToClientMessage() *schedulespb.ClientMessage { 27 | return &schedulespb.ClientMessage{ 28 | Id: c.id, 29 | Content: &schedulespb.ClientMessage_IntervalResponse{ 30 | IntervalResponse: &schedulespb.IntervalResponse{}, 31 | }, 32 | } 33 | } 34 | 35 | func NewCtx(msg *schedulespb.ServerMessage) *Ctx { 36 | return &Ctx{ 37 | id: msg.Id, 38 | Request: &scheduleRequest{ 39 | scheduleName: msg.GetIntervalRequest().ScheduleName, 40 | }, 41 | Response: &Response{ 42 | Success: true, 43 | }, 44 | } 45 | } 46 | 47 | func (c *Ctx) WithError(err error) { 48 | c.Response = &Response{ 49 | Success: false, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /nitric/schedules/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schedules 16 | 17 | type Request interface { 18 | ScheduleName() string 19 | } 20 | 21 | type scheduleRequest struct { 22 | scheduleName string 23 | } 24 | 25 | func (i *scheduleRequest) ScheduleName() string { 26 | return i.scheduleName 27 | } 28 | -------------------------------------------------------------------------------- /nitric/schedules/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schedules 16 | 17 | type Response struct { 18 | Success bool 19 | } 20 | -------------------------------------------------------------------------------- /nitric/schedules/schedule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schedules 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/nitrictech/go-sdk/internal/handlers" 21 | "github.com/nitrictech/go-sdk/nitric/workers" 22 | schedulespb "github.com/nitrictech/nitric/core/pkg/proto/schedules/v1" 23 | ) 24 | 25 | type Schedule interface { 26 | // Run a function at a certain interval defined by the cronExpression. 27 | // Valid function signatures for handler are: 28 | // 29 | // func() 30 | // func() error 31 | // func(*schedules.Ctx) 32 | // func(*schedules.Ctx) error 33 | // Handler[schedules.Ctx] 34 | Cron(cron string, handler interface{}) 35 | 36 | // Run a function at a certain interval defined by the rate. The rate is e.g. '7 days'. All rates accept a number and a frequency. Valid frequencies are 'days', 'hours' or 'minutes'. 37 | // Valid function signatures for handler are: 38 | // 39 | // func() 40 | // func() error 41 | // func(*schedules.Ctx) 42 | // func(*schedules.Ctx) error 43 | // Handler[schedules.Ctx] 44 | Every(rate string, handler interface{}) 45 | } 46 | 47 | type schedule struct { 48 | name string 49 | manager *workers.Manager 50 | } 51 | 52 | var _ Schedule = (*schedule)(nil) 53 | 54 | // NewSchedule - Create a new Schedule resource 55 | func NewSchedule(name string) Schedule { 56 | return &schedule{ 57 | name: name, 58 | manager: workers.GetDefaultManager(), 59 | } 60 | } 61 | 62 | func (s *schedule) Cron(cron string, handler interface{}) { 63 | scheduleCron := &schedulespb.ScheduleCron{ 64 | Expression: cron, 65 | } 66 | 67 | registrationRequest := &schedulespb.RegistrationRequest{ 68 | ScheduleName: s.name, 69 | Cadence: &schedulespb.RegistrationRequest_Cron{ 70 | Cron: scheduleCron, 71 | }, 72 | } 73 | 74 | typedHandler, err := handlers.HandlerFromInterface[Ctx](handler) 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | opts := &scheduleWorkerOpts{ 80 | RegistrationRequest: registrationRequest, 81 | Handler: typedHandler, 82 | } 83 | 84 | worker := newScheduleWorker(opts) 85 | s.manager.AddWorker("IntervalWorkerCron:"+strings.Join([]string{ 86 | s.name, 87 | cron, 88 | }, "-"), worker) 89 | } 90 | 91 | func (s *schedule) Every(rate string, handler interface{}) { 92 | scheduleEvery := &schedulespb.ScheduleEvery{ 93 | Rate: rate, 94 | } 95 | 96 | registrationRequest := &schedulespb.RegistrationRequest{ 97 | ScheduleName: s.name, 98 | Cadence: &schedulespb.RegistrationRequest_Every{ 99 | Every: scheduleEvery, 100 | }, 101 | } 102 | 103 | typedHandler, err := handlers.HandlerFromInterface[Ctx](handler) 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | opts := &scheduleWorkerOpts{ 109 | RegistrationRequest: registrationRequest, 110 | Handler: typedHandler, 111 | } 112 | 113 | worker := newScheduleWorker(opts) 114 | s.manager.AddWorker("IntervalWorkerEvery:"+strings.Join([]string{ 115 | s.name, 116 | rate, 117 | }, "-"), worker) 118 | } 119 | -------------------------------------------------------------------------------- /nitric/schedules/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schedules 16 | 17 | import ( 18 | "context" 19 | errorsstd "errors" 20 | 21 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 22 | "github.com/nitrictech/go-sdk/internal/handlers" 23 | "github.com/nitrictech/go-sdk/nitric/errors" 24 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 25 | "github.com/nitrictech/go-sdk/nitric/workers" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/schedules/v1" 27 | ) 28 | 29 | type scheduleWorker struct { 30 | client v1.SchedulesClient 31 | registrationRequest *v1.RegistrationRequest 32 | handler handlers.Handler[Ctx] 33 | } 34 | type scheduleWorkerOpts struct { 35 | RegistrationRequest *v1.RegistrationRequest 36 | Handler handlers.Handler[Ctx] 37 | } 38 | 39 | // Start runs the Schedule worker, creating a stream to the Nitric server 40 | func (i *scheduleWorker) Start(ctx context.Context) error { 41 | initReq := &v1.ClientMessage{ 42 | Content: &v1.ClientMessage_RegistrationRequest{ 43 | RegistrationRequest: i.registrationRequest, 44 | }, 45 | } 46 | 47 | createStream := func(ctx context.Context) (workers.Stream[v1.ClientMessage, v1.RegistrationResponse, *v1.ServerMessage], error) { 48 | return i.client.Schedule(ctx) 49 | } 50 | 51 | handlerSrvMsg := func(msg *v1.ServerMessage) (*v1.ClientMessage, error) { 52 | if msg.GetIntervalRequest() != nil { 53 | handlerCtx := NewCtx(msg) 54 | 55 | err := i.handler(handlerCtx) 56 | if err != nil { 57 | handlerCtx.WithError(err) 58 | } 59 | 60 | return handlerCtx.ToClientMessage(), nil 61 | } 62 | return nil, errors.NewWithCause( 63 | codes.Internal, 64 | "ScheduleWorker: Unhandled server message", 65 | errorsstd.New("unhandled server message"), 66 | ) 67 | } 68 | 69 | return workers.HandleStream( 70 | ctx, 71 | createStream, 72 | initReq, 73 | handlerSrvMsg, 74 | ) 75 | } 76 | 77 | func newScheduleWorker(opts *scheduleWorkerOpts) *scheduleWorker { 78 | conn, err := grpcx.GetConnection() 79 | if err != nil { 80 | panic(errors.NewWithCause( 81 | codes.Unavailable, 82 | "NewScheduleWorker: Unable to reach SchedulesClient", 83 | err, 84 | )) 85 | } 86 | 87 | client := v1.NewSchedulesClient(conn) 88 | 89 | return &scheduleWorker{ 90 | client: client, 91 | registrationRequest: opts.RegistrationRequest, 92 | handler: opts.Handler, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /nitric/secrets/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package secrets 16 | 17 | import ( 18 | "context" 19 | 20 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 21 | "github.com/nitrictech/go-sdk/nitric/errors" 22 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 23 | v1 "github.com/nitrictech/nitric/core/pkg/proto/secrets/v1" 24 | ) 25 | 26 | type SecretValue []byte 27 | 28 | func (s SecretValue) AsString() string { 29 | return string(s) 30 | } 31 | 32 | type SecretClientIface interface { 33 | // Name - Return the name of this secret 34 | Name() string 35 | // Put - Store a new value in this secret, returning a reference to the new version created 36 | Put(ctx context.Context, value []byte) (string, error) 37 | // Access - Access the latest version of this secret 38 | Access(ctx context.Context) (SecretValue, error) 39 | // AccessVersion - Access a specific version of the secret 40 | AccessVersion(ctx context.Context, version string) (SecretValue, error) 41 | } 42 | 43 | var _ SecretClientIface = (*SecretClient)(nil) 44 | 45 | // SecretClient - Reference to a cloud secret 46 | type SecretClient struct { 47 | name string 48 | secretClient v1.SecretManagerClient 49 | } 50 | 51 | // Name - Return the name of this secret 52 | func (s *SecretClient) Name() string { 53 | return s.name 54 | } 55 | 56 | // Put - Store a new value in this secret, returning a reference to the new version created 57 | func (s *SecretClient) Put(ctx context.Context, value []byte) (string, error) { 58 | resp, err := s.secretClient.Put(ctx, &v1.SecretPutRequest{ 59 | Secret: &v1.Secret{ 60 | Name: s.name, 61 | }, 62 | Value: value, 63 | }) 64 | if err != nil { 65 | return "", errors.FromGrpcError(err) 66 | } 67 | 68 | return resp.GetSecretVersion().Version, nil 69 | } 70 | 71 | const latestVersionId = "latest" 72 | 73 | // Access - Access the latest version of this secret 74 | func (s *SecretClient) Access(ctx context.Context) (SecretValue, error) { 75 | return s.AccessVersion(ctx, latestVersionId) 76 | } 77 | 78 | // AccessVersion - Access a specific version of the secret 79 | func (s *SecretClient) AccessVersion(ctx context.Context, version string) (SecretValue, error) { 80 | r, err := s.secretClient.Access(ctx, &v1.SecretAccessRequest{ 81 | SecretVersion: &v1.SecretVersion{ 82 | Secret: &v1.Secret{ 83 | Name: s.name, 84 | }, 85 | Version: version, 86 | }, 87 | }) 88 | if err != nil { 89 | return nil, errors.FromGrpcError(err) 90 | } 91 | 92 | return SecretValue(r.GetValue()), nil 93 | } 94 | 95 | func NewSecretClient(name string) (*SecretClient, error) { 96 | conn, err := grpcx.GetConnection() 97 | if err != nil { 98 | return nil, errors.NewWithCause( 99 | codes.Unavailable, 100 | "NewSecretClient: unable to reach nitric server", 101 | err, 102 | ) 103 | } 104 | 105 | sClient := v1.NewSecretManagerClient(conn) 106 | 107 | return &SecretClient{ 108 | secretClient: sClient, 109 | name: name, 110 | }, nil 111 | } 112 | -------------------------------------------------------------------------------- /nitric/secrets/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package secrets 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "strings" 21 | 22 | "github.com/golang/mock/gomock" 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 27 | v1 "github.com/nitrictech/nitric/core/pkg/proto/secrets/v1" 28 | ) 29 | 30 | var _ = Describe("Secret", func() { 31 | var ( 32 | ctrl *gomock.Controller 33 | mockSC *mock_v1.MockSecretManagerClient 34 | secret *SecretClient 35 | ctx context.Context 36 | ) 37 | 38 | BeforeEach(func() { 39 | ctrl = gomock.NewController(GinkgoT()) 40 | mockSC = mock_v1.NewMockSecretManagerClient(ctrl) 41 | secret = &SecretClient{ 42 | name: "test-secret", 43 | secretClient: mockSC, 44 | } 45 | ctx = context.Background() 46 | }) 47 | 48 | AfterEach(func() { 49 | ctrl.Finish() 50 | }) 51 | 52 | Context("Having a valid secretRef", func() { 53 | Describe("Name", func() { 54 | It("should return the correct secrets name", func() { 55 | Expect(secret.Name()).To(Equal("test-secret")) 56 | }) 57 | }) 58 | 59 | Describe("Put", func() { 60 | var ( 61 | _sr *SecretClient 62 | secretValue []byte 63 | ) 64 | 65 | BeforeEach(func() { 66 | _sr = &SecretClient{ 67 | name: "test-secret", 68 | secretClient: mockSC, 69 | } 70 | secretValue = []byte("ssssshhhh... it's a secret") 71 | }) 72 | 73 | When("the RPC operation is successful", func() { 74 | var ( 75 | secret *v1.Secret 76 | versionName string 77 | ) 78 | 79 | BeforeEach(func() { 80 | secret = &v1.Secret{ 81 | Name: _sr.Name(), 82 | } 83 | versionName = "1" 84 | 85 | mockSC.EXPECT().Put(gomock.Any(), &v1.SecretPutRequest{ 86 | Secret: secret, 87 | Value: secretValue, 88 | }).Return( 89 | &v1.SecretPutResponse{ 90 | SecretVersion: &v1.SecretVersion{ 91 | Secret: secret, 92 | Version: versionName, 93 | }, 94 | }, 95 | nil, 96 | ).Times(1) 97 | }) 98 | 99 | It("should return a reference to the created secret version", func() { 100 | sv, err := _sr.Put(ctx, secretValue) 101 | 102 | By("not returning an error") 103 | Expect(err).ToNot(HaveOccurred()) 104 | 105 | By("returning a string secret version") 106 | Expect(sv).To(Equal(versionName)) 107 | }) 108 | }) 109 | 110 | When("the RPC operation fails", func() { 111 | var errorMsg string 112 | 113 | BeforeEach(func() { 114 | errorMsg = "Internal Error" 115 | 116 | mockSC.EXPECT().Put(gomock.Any(), gomock.Any()).Return( 117 | nil, 118 | errors.New(errorMsg), 119 | ).Times(1) 120 | }) 121 | 122 | It("should pass through the error", func() { 123 | sv, err := _sr.Put(ctx, secretValue) 124 | 125 | By("returning the error") 126 | Expect(err).To(HaveOccurred()) 127 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 128 | 129 | By("returning a blank secret version reference") 130 | Expect(sv).To(BeEmpty()) 131 | }) 132 | }) 133 | }) 134 | }) 135 | }) 136 | -------------------------------------------------------------------------------- /nitric/secrets/secret.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package secrets 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/nitrictech/go-sdk/nitric/workers" 21 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 22 | ) 23 | 24 | type SecretPermission string 25 | 26 | const ( 27 | SecretAccess SecretPermission = "access" 28 | SecretPut SecretPermission = "put" 29 | ) 30 | 31 | var SecretEverything []SecretPermission = []SecretPermission{SecretAccess, SecretPut} 32 | 33 | type Secret interface { 34 | // Allow requests the given permissions to the secret. 35 | Allow(permission SecretPermission, permissions ...SecretPermission) *SecretClient 36 | } 37 | 38 | type secret struct { 39 | name string 40 | manager *workers.Manager 41 | registerChan <-chan workers.RegisterResult 42 | } 43 | 44 | // NewSecret - Create a new Secret resource 45 | func NewSecret(name string) *secret { 46 | secret := &secret{ 47 | name: name, 48 | manager: workers.GetDefaultManager(), 49 | } 50 | 51 | secret.registerChan = secret.manager.RegisterResource(&v1.ResourceDeclareRequest{ 52 | Id: &v1.ResourceIdentifier{ 53 | Type: v1.ResourceType_Secret, 54 | Name: name, 55 | }, 56 | Config: &v1.ResourceDeclareRequest_Secret{ 57 | Secret: &v1.SecretResource{}, 58 | }, 59 | }) 60 | 61 | return secret 62 | } 63 | 64 | func (s *secret) Allow(permission SecretPermission, permissions ...SecretPermission) *SecretClient { 65 | allPerms := append([]SecretPermission{permission}, permissions...) 66 | 67 | actions := []v1.Action{} 68 | for _, perm := range allPerms { 69 | switch perm { 70 | case SecretAccess: 71 | actions = append(actions, v1.Action_SecretAccess) 72 | case SecretPut: 73 | actions = append(actions, v1.Action_SecretPut) 74 | default: 75 | panic(fmt.Sprintf("secretPermission %s unknown", perm)) 76 | } 77 | } 78 | 79 | registerResult := <-s.registerChan 80 | if registerResult.Err != nil { 81 | panic(registerResult.Err) 82 | } 83 | 84 | err := s.manager.RegisterPolicy(registerResult.Identifier, actions...) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | client, err := NewSecretClient(s.name) 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | return client 95 | } 96 | -------------------------------------------------------------------------------- /nitric/secrets/secrets_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package secrets_test 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestSecrets(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Secrets Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/sql/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sql 16 | 17 | import ( 18 | "context" 19 | 20 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 21 | "github.com/nitrictech/go-sdk/nitric/errors" 22 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 23 | 24 | v1 "github.com/nitrictech/nitric/core/pkg/proto/sql/v1" 25 | ) 26 | 27 | type SqlClientIface interface { 28 | // Name - The name of the store 29 | Name() string 30 | // Get a value from the store 31 | ConnectionString(ctx context.Context) (string, error) 32 | } 33 | 34 | type SqlClient struct { 35 | name string 36 | sqlClient v1.SqlClient 37 | } 38 | 39 | func (s *SqlClient) Name() string { 40 | return s.name 41 | } 42 | 43 | func (s *SqlClient) ConnectionString(ctx context.Context) (string, error) { 44 | resp, err := s.sqlClient.ConnectionString(ctx, &v1.SqlConnectionStringRequest{ 45 | DatabaseName: s.name, 46 | }) 47 | if err != nil { 48 | return "", err 49 | } 50 | 51 | return resp.ConnectionString, nil 52 | } 53 | 54 | func NewSqlClient(name string) (*SqlClient, error) { 55 | conn, err := grpcx.GetConnection() 56 | if err != nil { 57 | return nil, errors.NewWithCause( 58 | codes.Unavailable, 59 | "unable to reach nitric server", 60 | err, 61 | ) 62 | } 63 | 64 | client := v1.NewSqlClient(conn) 65 | 66 | return &SqlClient{ 67 | name: name, 68 | sqlClient: client, 69 | }, nil 70 | } 71 | -------------------------------------------------------------------------------- /nitric/sql/sql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sql 16 | 17 | import ( 18 | "github.com/nitrictech/go-sdk/nitric/workers" 19 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 20 | ) 21 | 22 | type sqlDatabaseOption func(*v1.SqlDatabaseResource) 23 | 24 | func WithMigrationsPath(path string) sqlDatabaseOption { 25 | return func(r *v1.SqlDatabaseResource) { 26 | r.Migrations = &v1.SqlDatabaseMigrations{ 27 | Migrations: &v1.SqlDatabaseMigrations_MigrationsPath{ 28 | MigrationsPath: path, 29 | }, 30 | } 31 | } 32 | } 33 | 34 | // NewSqlDatabase - Create a new Sql Database resource 35 | func NewSqlDatabase(name string, opts ...sqlDatabaseOption) *SqlClient { 36 | resourceConfig := &v1.ResourceDeclareRequest_SqlDatabase{ 37 | SqlDatabase: &v1.SqlDatabaseResource{}, 38 | } 39 | 40 | for _, opt := range opts { 41 | opt(resourceConfig.SqlDatabase) 42 | } 43 | 44 | registerChan := workers.GetDefaultManager().RegisterResource(&v1.ResourceDeclareRequest{ 45 | Id: &v1.ResourceIdentifier{ 46 | Type: v1.ResourceType_SqlDatabase, 47 | Name: name, 48 | }, 49 | Config: resourceConfig, 50 | }) 51 | 52 | // Make sure that registerChan is read 53 | // Currently sql databases do not have allow methods so there is no reason to block on this 54 | go func() { 55 | <-registerChan 56 | }() 57 | 58 | client, err := NewSqlClient(name) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | return client 64 | } 65 | -------------------------------------------------------------------------------- /nitric/storage/bucket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/nitrictech/go-sdk/internal/handlers" 22 | "github.com/nitrictech/go-sdk/nitric/workers" 23 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 24 | storagepb "github.com/nitrictech/nitric/core/pkg/proto/storage/v1" 25 | ) 26 | 27 | type BucketPermission string 28 | 29 | type bucket struct { 30 | name string 31 | manager *workers.Manager 32 | registerChan <-chan workers.RegisterResult 33 | } 34 | 35 | type Bucket interface { 36 | // Allow requests the given permissions to the bucket. 37 | Allow(permission BucketPermission, permissions ...BucketPermission) *BucketClient 38 | 39 | // On registers a handler for a specific event type on the bucket. 40 | // Valid function signatures for handler are: 41 | // 42 | // func() 43 | // func() error 44 | // func(*storage.Ctx) 45 | // func(*storage.Ctx) error 46 | // Handler[storage.Ctx] 47 | On(eventType EventType, notificationPrefixFilter string, handler interface{}) 48 | } 49 | 50 | const ( 51 | BucketRead BucketPermission = "read" 52 | BucketWrite BucketPermission = "write" 53 | BucketDelete BucketPermission = "delete" 54 | ) 55 | 56 | var BucketEverything []BucketPermission = []BucketPermission{BucketRead, BucketWrite, BucketDelete} 57 | 58 | // NewBucket - Create a new Bucket resource 59 | func NewBucket(name string) Bucket { 60 | bucket := &bucket{ 61 | name: name, 62 | manager: workers.GetDefaultManager(), 63 | } 64 | 65 | bucket.registerChan = bucket.manager.RegisterResource(&v1.ResourceDeclareRequest{ 66 | Id: &v1.ResourceIdentifier{ 67 | Type: v1.ResourceType_Bucket, 68 | Name: name, 69 | }, 70 | Config: &v1.ResourceDeclareRequest_Bucket{ 71 | Bucket: &v1.BucketResource{}, 72 | }, 73 | }) 74 | 75 | return bucket 76 | } 77 | 78 | func (b *bucket) Allow(permission BucketPermission, permissions ...BucketPermission) *BucketClient { 79 | allPerms := append([]BucketPermission{permission}, permissions...) 80 | 81 | actions := []v1.Action{} 82 | for _, perm := range allPerms { 83 | switch perm { 84 | case BucketRead: 85 | actions = append(actions, v1.Action_BucketFileGet, v1.Action_BucketFileList) 86 | case BucketWrite: 87 | actions = append(actions, v1.Action_BucketFilePut) 88 | case BucketDelete: 89 | actions = append(actions, v1.Action_BucketFileDelete) 90 | default: 91 | panic(fmt.Sprintf("bucketPermission %s unknown", perm)) 92 | } 93 | } 94 | 95 | registerResult := <-b.registerChan 96 | if registerResult.Err != nil { 97 | panic(registerResult.Err) 98 | } 99 | 100 | err := b.manager.RegisterPolicy(registerResult.Identifier, actions...) 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | client, err := NewBucketClient(b.name) 106 | if err != nil { 107 | panic(err) 108 | } 109 | 110 | return client 111 | } 112 | 113 | func (b *bucket) On(eventType EventType, notificationPrefixFilter string, handler interface{}) { 114 | var blobEventType storagepb.BlobEventType 115 | switch eventType { 116 | case WriteNotification: 117 | blobEventType = storagepb.BlobEventType_Created 118 | case DeleteNotification: 119 | blobEventType = storagepb.BlobEventType_Deleted 120 | } 121 | 122 | registrationRequest := &storagepb.RegistrationRequest{ 123 | BucketName: b.name, 124 | BlobEventType: blobEventType, 125 | KeyPrefixFilter: notificationPrefixFilter, 126 | } 127 | 128 | typedHandler, err := handlers.HandlerFromInterface[Ctx](handler) 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | opts := &bucketEventWorkerOpts{ 134 | RegistrationRequest: registrationRequest, 135 | Handler: typedHandler, 136 | } 137 | 138 | worker := newBucketEventWorker(opts) 139 | 140 | b.manager.AddWorker("bucketNotification:"+strings.Join([]string{ 141 | b.name, notificationPrefixFilter, string(eventType), 142 | }, "-"), worker) 143 | } 144 | -------------------------------------------------------------------------------- /nitric/storage/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "google.golang.org/protobuf/types/known/durationpb" 22 | 23 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 24 | "github.com/nitrictech/go-sdk/nitric/errors" 25 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/storage/v1" 27 | ) 28 | 29 | // Cloud storage bucket resource for large file storage. 30 | type BucketClientIface interface { 31 | // Name - Get the name of the bucket 32 | Name() string 33 | // ListFiles - List the files in the bucket 34 | ListFiles(ctx context.Context) ([]string, error) 35 | // Read - Read this object 36 | Read(ctx context.Context, key string) ([]byte, error) 37 | // Write - Write this object 38 | Write(ctx context.Context, key string, data []byte) error 39 | // Delete - Delete this object 40 | Delete(ctx context.Context, key string) error 41 | // UploadUrl - Creates a signed Url for uploading this file reference 42 | UploadUrl(ctx context.Context, key string, opts ...PresignUrlOption) (string, error) 43 | // DownloadUrl - Creates a signed Url for downloading this file reference 44 | DownloadUrl(ctx context.Context, key string, opts ...PresignUrlOption) (string, error) 45 | } 46 | 47 | var _ BucketClientIface = (*BucketClient)(nil) 48 | 49 | type BucketClient struct { 50 | storageClient v1.StorageClient 51 | name string 52 | } 53 | 54 | func (o *BucketClient) Read(ctx context.Context, key string) ([]byte, error) { 55 | r, err := o.storageClient.Read(ctx, &v1.StorageReadRequest{ 56 | BucketName: o.name, 57 | Key: key, 58 | }) 59 | if err != nil { 60 | return nil, errors.FromGrpcError(err) 61 | } 62 | 63 | return r.GetBody(), nil 64 | } 65 | 66 | func (o *BucketClient) Write(ctx context.Context, key string, content []byte) error { 67 | if _, err := o.storageClient.Write(ctx, &v1.StorageWriteRequest{ 68 | BucketName: o.name, 69 | Key: key, 70 | Body: content, 71 | }); err != nil { 72 | return errors.FromGrpcError(err) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (o *BucketClient) Delete(ctx context.Context, key string) error { 79 | if _, err := o.storageClient.Delete(ctx, &v1.StorageDeleteRequest{ 80 | BucketName: o.name, 81 | Key: key, 82 | }); err != nil { 83 | return errors.FromGrpcError(err) 84 | } 85 | 86 | return nil 87 | } 88 | 89 | func (b *BucketClient) ListFiles(ctx context.Context) ([]string, error) { 90 | resp, err := b.storageClient.ListBlobs(ctx, &v1.StorageListBlobsRequest{ 91 | BucketName: b.name, 92 | }) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | fileRefs := make([]string, 0) 98 | 99 | for _, f := range resp.Blobs { 100 | fileRefs = append(fileRefs, f.Key) 101 | } 102 | 103 | return fileRefs, nil 104 | } 105 | 106 | type Mode int 107 | 108 | const ( 109 | ModeRead Mode = iota 110 | ModeWrite 111 | ) 112 | 113 | type presignUrlOptions struct { 114 | mode Mode 115 | expiry time.Duration 116 | } 117 | 118 | type PresignUrlOption func(opts *presignUrlOptions) 119 | 120 | func WithPresignUrlExpiry(expiry time.Duration) PresignUrlOption { 121 | return func(opts *presignUrlOptions) { 122 | opts.expiry = expiry 123 | } 124 | } 125 | 126 | func getPresignUrlOpts(mode Mode, opts ...PresignUrlOption) *presignUrlOptions { 127 | defaultOpts := &presignUrlOptions{ 128 | mode: mode, 129 | expiry: time.Minute * 5, 130 | } 131 | 132 | for _, opt := range opts { 133 | opt(defaultOpts) 134 | } 135 | 136 | return defaultOpts 137 | } 138 | 139 | func (o *BucketClient) UploadUrl(ctx context.Context, key string, opts ...PresignUrlOption) (string, error) { 140 | optsWithDefaults := getPresignUrlOpts(ModeWrite, opts...) 141 | 142 | return o.signUrl(ctx, key, optsWithDefaults) 143 | } 144 | 145 | func (o *BucketClient) DownloadUrl(ctx context.Context, key string, opts ...PresignUrlOption) (string, error) { 146 | optsWithDefaults := getPresignUrlOpts(ModeRead, opts...) 147 | 148 | return o.signUrl(ctx, key, optsWithDefaults) 149 | } 150 | 151 | func (o *BucketClient) signUrl(ctx context.Context, key string, opts *presignUrlOptions) (string, error) { 152 | op := v1.StoragePreSignUrlRequest_READ 153 | 154 | if opts.mode == ModeWrite { 155 | op = v1.StoragePreSignUrlRequest_WRITE 156 | } 157 | 158 | r, err := o.storageClient.PreSignUrl(ctx, &v1.StoragePreSignUrlRequest{ 159 | BucketName: o.name, 160 | Key: key, 161 | Operation: op, 162 | Expiry: durationpb.New(opts.expiry), 163 | }) 164 | if err != nil { 165 | return "", errors.FromGrpcError(err) 166 | } 167 | 168 | return r.Url, nil 169 | } 170 | 171 | func (b *BucketClient) Name() string { 172 | return b.name 173 | } 174 | 175 | func NewBucketClient(name string) (*BucketClient, error) { 176 | conn, err := grpcx.GetConnection() 177 | if err != nil { 178 | return nil, errors.NewWithCause( 179 | codes.Unavailable, 180 | "NewBucketClient: unable to reach nitric server", 181 | err, 182 | ) 183 | } 184 | 185 | storageClient := v1.NewStorageClient(conn) 186 | 187 | return &BucketClient{ 188 | name: name, 189 | storageClient: storageClient, 190 | }, nil 191 | } 192 | -------------------------------------------------------------------------------- /nitric/storage/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "strings" 21 | 22 | "github.com/golang/mock/gomock" 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 27 | v1 "github.com/nitrictech/nitric/core/pkg/proto/storage/v1" 28 | ) 29 | 30 | var _ = Describe("Bucket", func() { 31 | var ( 32 | ctrl *gomock.Controller 33 | mockStorage *mock_v1.MockStorageClient 34 | bucket *BucketClient 35 | bucketName string 36 | ctx context.Context 37 | ) 38 | 39 | BeforeEach(func() { 40 | ctrl = gomock.NewController(GinkgoT()) 41 | mockStorage = mock_v1.NewMockStorageClient(ctrl) 42 | 43 | bucketName = "test-bucket" 44 | 45 | bucket = &BucketClient{ 46 | name: bucketName, 47 | storageClient: mockStorage, 48 | } 49 | 50 | ctx = context.Background() 51 | }) 52 | 53 | AfterEach(func() { 54 | ctrl.Finish() 55 | }) 56 | 57 | Describe("ListFiles()", func() { 58 | When("the gRPC operation of ListBlobs fails", func() { 59 | var errorMsg string 60 | 61 | BeforeEach(func() { 62 | errorMsg = "Internal Error" 63 | 64 | By("the nitric membrane returning an error") 65 | mockStorage.EXPECT().ListBlobs(gomock.Any(), &v1.StorageListBlobsRequest{ 66 | BucketName: bucketName, 67 | }).Times(1).Return(nil, errors.New(errorMsg)) 68 | }) 69 | 70 | It("should return an error", func() { 71 | By("calling Files() on the bucket reference") 72 | files, err := bucket.ListFiles(ctx) 73 | 74 | By("receiving an error with same error message") 75 | Expect(err).Should(HaveOccurred()) 76 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 77 | 78 | By("receiving nil files") 79 | Expect(files).To(BeNil()) 80 | }) 81 | }) 82 | 83 | When("the gRPC operation of ListBlobs succeeds", func() { 84 | var files []string 85 | 86 | BeforeEach(func() { 87 | files = []string{ 88 | "file-1.txt", 89 | "file-2.txt", 90 | } 91 | 92 | blobs := make([]*v1.Blob, 0, len(files)) 93 | for _, file := range files { 94 | blobs = append(blobs, &v1.Blob{ 95 | Key: file, 96 | }) 97 | } 98 | 99 | By("the bucket not being empty") 100 | mockStorage.EXPECT().ListBlobs(gomock.Any(), &v1.StorageListBlobsRequest{ 101 | BucketName: bucketName, 102 | }).Return(&v1.StorageListBlobsResponse{ 103 | Blobs: blobs, 104 | }, nil).Times(1) 105 | }) 106 | 107 | It("should list the files in the bucket", func() { 108 | By("bucket.Files() being called") 109 | _files, err := bucket.ListFiles(ctx) 110 | 111 | By("not returning an error") 112 | Expect(err).ToNot(HaveOccurred()) 113 | 114 | By("returning the files") 115 | Expect(_files).To(HaveExactElements(files)) 116 | }) 117 | }) 118 | }) 119 | 120 | Describe("Name()", func() { 121 | It("should have the same name as the one provided", func() { 122 | _bucketName := bucket.Name() 123 | Expect(_bucketName).To(Equal(bucketName)) 124 | }) 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /nitric/storage/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import storagepb "github.com/nitrictech/nitric/core/pkg/proto/storage/v1" 18 | 19 | type Ctx struct { 20 | id string 21 | Request Request 22 | Response *Response 23 | Extras map[string]interface{} 24 | } 25 | 26 | func (c *Ctx) ToClientMessage() *storagepb.ClientMessage { 27 | return &storagepb.ClientMessage{ 28 | Id: c.id, 29 | Content: &storagepb.ClientMessage_BlobEventResponse{ 30 | BlobEventResponse: &storagepb.BlobEventResponse{ 31 | Success: c.Response.Success, 32 | }, 33 | }, 34 | } 35 | } 36 | 37 | func NewCtx(msg *storagepb.ServerMessage) *Ctx { 38 | req := msg.GetBlobEventRequest() 39 | 40 | return &Ctx{ 41 | id: msg.Id, 42 | Request: &requestImpl{ 43 | key: req.GetBlobEvent().Key, 44 | }, 45 | Response: &Response{ 46 | Success: true, 47 | }, 48 | } 49 | } 50 | 51 | func (c *Ctx) WithError(err error) { 52 | c.Response = &Response{ 53 | Success: false, 54 | } 55 | } 56 | 57 | // type FileCtx struct { 58 | // Request FileRequest 59 | // Response *FileResponse 60 | // Extras map[string]interface{} 61 | // } 62 | -------------------------------------------------------------------------------- /nitric/storage/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | type EventType string 18 | 19 | var EventTypes = []EventType{WriteNotification, DeleteNotification} 20 | 21 | const ( 22 | WriteNotification EventType = "write" 23 | DeleteNotification EventType = "delete" 24 | ) 25 | 26 | type Request interface { 27 | Key() string 28 | NotificationType() EventType 29 | } 30 | 31 | type requestImpl struct { 32 | key string 33 | notificationType EventType 34 | } 35 | 36 | func (b *requestImpl) Key() string { 37 | return b.key 38 | } 39 | 40 | func (b *requestImpl) NotificationType() EventType { 41 | return b.notificationType 42 | } 43 | 44 | // File Event 45 | 46 | // XXX: File requests currently not implemented 47 | // type FileRequest interface { 48 | // Bucket() *Bucket 49 | // NotificationType() EventType 50 | // } 51 | 52 | // type fileRequestImpl struct { 53 | // bucket Bucket 54 | // notificationType EventType 55 | // } 56 | 57 | // func (f *fileRequestImpl) Bucket() Bucket { 58 | // return f.bucket 59 | // } 60 | 61 | // func (f *fileRequestImpl) NotificationType() EventType { 62 | // return f.notificationType 63 | // } 64 | -------------------------------------------------------------------------------- /nitric/storage/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | type Response struct { 18 | Success bool 19 | } 20 | 21 | type FileResponse struct { 22 | Success bool 23 | } 24 | -------------------------------------------------------------------------------- /nitric/storage/storage_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestStorage(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Storage Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/storage/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import ( 18 | "context" 19 | errorsstd "errors" 20 | 21 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 22 | "github.com/nitrictech/go-sdk/internal/handlers" 23 | "github.com/nitrictech/go-sdk/nitric/errors" 24 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 25 | "github.com/nitrictech/go-sdk/nitric/workers" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/storage/v1" 27 | ) 28 | 29 | type bucketEventWorker struct { 30 | client v1.StorageListenerClient 31 | registrationRequest *v1.RegistrationRequest 32 | handler handlers.Handler[Ctx] 33 | } 34 | type bucketEventWorkerOpts struct { 35 | RegistrationRequest *v1.RegistrationRequest 36 | Handler handlers.Handler[Ctx] 37 | } 38 | 39 | // Start runs the BucketEvent worker, creating a stream to the Nitric server 40 | func (b *bucketEventWorker) Start(ctx context.Context) error { 41 | initReq := &v1.ClientMessage{ 42 | Content: &v1.ClientMessage_RegistrationRequest{ 43 | RegistrationRequest: b.registrationRequest, 44 | }, 45 | } 46 | 47 | createStream := func(ctx context.Context) (workers.Stream[v1.ClientMessage, v1.RegistrationResponse, *v1.ServerMessage], error) { 48 | return b.client.Listen(ctx) 49 | } 50 | 51 | handlerSrvMsg := func(msg *v1.ServerMessage) (*v1.ClientMessage, error) { 52 | if msg.GetBlobEventRequest() != nil { 53 | handlerCtx := NewCtx(msg) 54 | 55 | err := b.handler(handlerCtx) 56 | if err != nil { 57 | handlerCtx.WithError(err) 58 | } 59 | 60 | return handlerCtx.ToClientMessage(), nil 61 | } 62 | 63 | return nil, errors.NewWithCause( 64 | codes.Internal, 65 | "BucketEventWorker: Unhandled server message", 66 | errorsstd.New("unhandled server message"), 67 | ) 68 | } 69 | 70 | return workers.HandleStream(ctx, createStream, initReq, handlerSrvMsg) 71 | } 72 | 73 | func newBucketEventWorker(opts *bucketEventWorkerOpts) *bucketEventWorker { 74 | conn, err := grpcx.GetConnection() 75 | if err != nil { 76 | panic(errors.NewWithCause( 77 | codes.Unavailable, 78 | "NewBlobEventWorker: Unable to reach StorageListenerClient", 79 | err, 80 | )) 81 | } 82 | 83 | client := v1.NewStorageListenerClient(conn) 84 | 85 | return &bucketEventWorker{ 86 | client: client, 87 | registrationRequest: opts.RegistrationRequest, 88 | handler: opts.Handler, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /nitric/topics/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "google.golang.org/protobuf/types/known/durationpb" 22 | 23 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 24 | "github.com/nitrictech/go-sdk/nitric/errors" 25 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/topics/v1" 27 | "github.com/nitrictech/protoutils" 28 | ) 29 | 30 | type PublishOption func(*v1.TopicPublishRequest) 31 | 32 | // TopicClientIface for pub/sub async messaging. 33 | type TopicClientIface interface { 34 | // Name returns the Topic name. 35 | Name() string 36 | 37 | // Publish will publish the provided events on the topic. 38 | Publish(ctx context.Context, message map[string]interface{}, options ...PublishOption) error 39 | } 40 | 41 | type TopicClient struct { 42 | name string 43 | topicClient v1.TopicsClient 44 | } 45 | 46 | func (s *TopicClient) Name() string { 47 | return s.name 48 | } 49 | 50 | // WithDelay - Delay event publishing by the given duration 51 | func WithDelay(duration time.Duration) func(*v1.TopicPublishRequest) { 52 | return func(epr *v1.TopicPublishRequest) { 53 | epr.Delay = durationpb.New(duration) 54 | } 55 | } 56 | 57 | func (s *TopicClient) Publish(ctx context.Context, message map[string]interface{}, opts ...PublishOption) error { 58 | payloadStruct, err := protoutils.NewStruct(message) 59 | if err != nil { 60 | return errors.NewWithCause(codes.InvalidArgument, "Topic.Publish", err) 61 | } 62 | 63 | event := &v1.TopicPublishRequest{ 64 | TopicName: s.name, 65 | Message: &v1.TopicMessage{ 66 | Content: &v1.TopicMessage_StructPayload{ 67 | StructPayload: payloadStruct, 68 | }, 69 | }, 70 | } 71 | 72 | // Apply options to the event payload 73 | for _, opt := range opts { 74 | opt(event) 75 | } 76 | 77 | _, err = s.topicClient.Publish(ctx, event) 78 | if err != nil { 79 | return errors.FromGrpcError(err) 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func NewTopicClient(name string) (*TopicClient, error) { 86 | conn, err := grpcx.GetConnection() 87 | if err != nil { 88 | return nil, errors.NewWithCause( 89 | codes.Unavailable, 90 | "NewTopicClient: unable to reach nitric server", 91 | err, 92 | ) 93 | } 94 | 95 | topicClient := v1.NewTopicsClient(conn) 96 | 97 | return &TopicClient{ 98 | name: name, 99 | topicClient: topicClient, 100 | }, nil 101 | } 102 | -------------------------------------------------------------------------------- /nitric/topics/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "strings" 21 | 22 | "github.com/golang/mock/gomock" 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | mock_v1 "github.com/nitrictech/go-sdk/mocks" 27 | v1 "github.com/nitrictech/nitric/core/pkg/proto/topics/v1" 28 | "github.com/nitrictech/protoutils" 29 | ) 30 | 31 | var _ = Describe("Topic", func() { 32 | var ( 33 | ctrl *gomock.Controller 34 | mockTopic *mock_v1.MockTopicsClient 35 | t *TopicClient 36 | topicName string 37 | ctx context.Context 38 | ) 39 | 40 | BeforeEach(func() { 41 | ctrl = gomock.NewController(GinkgoT()) 42 | mockTopic = mock_v1.NewMockTopicsClient(ctrl) 43 | 44 | topicName = "test-topic" 45 | t = &TopicClient{ 46 | name: topicName, 47 | topicClient: mockTopic, 48 | } 49 | 50 | ctx = context.Background() 51 | }) 52 | 53 | AfterEach(func() { 54 | ctrl.Finish() 55 | }) 56 | 57 | Describe("Name()", func() { 58 | It("should have the same topic name as the one provided", func() { 59 | _topicName := t.Name() 60 | Expect(_topicName).To(Equal(topicName)) 61 | }) 62 | }) 63 | 64 | Describe("Publish()", func() { 65 | var messageToBePublished map[string]interface{} 66 | 67 | BeforeEach(func() { 68 | messageToBePublished = map[string]interface{}{ 69 | "data": "hello world", 70 | } 71 | }) 72 | 73 | When("the gRPC Read operation is successful", func() { 74 | BeforeEach(func() { 75 | payloadStruct, err := protoutils.NewStruct(messageToBePublished) 76 | Expect(err).ToNot(HaveOccurred()) 77 | 78 | mockTopic.EXPECT().Publish(gomock.Any(), &v1.TopicPublishRequest{ 79 | TopicName: topicName, 80 | Message: &v1.TopicMessage{ 81 | Content: &v1.TopicMessage_StructPayload{ 82 | StructPayload: payloadStruct, 83 | }, 84 | }, 85 | }).Return( 86 | &v1.TopicPublishResponse{}, 87 | nil).Times(1) 88 | }) 89 | 90 | It("should not return error", func() { 91 | err := t.Publish(ctx, messageToBePublished) 92 | 93 | Expect(err).ToNot(HaveOccurred()) 94 | }) 95 | }) 96 | 97 | When("the grpc server returns an error", func() { 98 | var errorMsg string 99 | 100 | BeforeEach(func() { 101 | errorMsg = "Internal Error" 102 | 103 | By("the gRPC server returning an error") 104 | mockTopic.EXPECT().Publish(gomock.Any(), gomock.Any()).Return( 105 | nil, 106 | errors.New(errorMsg), 107 | ).Times(1) 108 | }) 109 | 110 | It("should return the passed error", func() { 111 | err := t.Publish(ctx, messageToBePublished) 112 | 113 | By("returning error with expected message") 114 | Expect(err).To(HaveOccurred()) 115 | Expect(strings.Contains(err.Error(), errorMsg)).To(BeTrue()) 116 | }) 117 | }) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /nitric/topics/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import topicspb "github.com/nitrictech/nitric/core/pkg/proto/topics/v1" 18 | 19 | type Ctx struct { 20 | id string 21 | Request Request 22 | Response *Response 23 | Extras map[string]interface{} 24 | } 25 | 26 | func (c *Ctx) ToClientMessage() *topicspb.ClientMessage { 27 | return &topicspb.ClientMessage{ 28 | Id: c.id, 29 | Content: &topicspb.ClientMessage_MessageResponse{ 30 | MessageResponse: &topicspb.MessageResponse{ 31 | Success: true, 32 | }, 33 | }, 34 | } 35 | } 36 | 37 | func NewCtx(msg *topicspb.ServerMessage) *Ctx { 38 | return &Ctx{ 39 | id: msg.Id, 40 | Request: &requestImpl{ 41 | topicName: msg.GetMessageRequest().TopicName, 42 | message: msg.GetMessageRequest().Message.GetStructPayload().AsMap(), 43 | }, 44 | Response: &Response{ 45 | Success: true, 46 | }, 47 | } 48 | } 49 | 50 | func (c *Ctx) WithError(err error) { 51 | c.Response = &Response{ 52 | Success: false, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /nitric/topics/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | type Request interface { 18 | TopicName() string 19 | Message() map[string]interface{} 20 | } 21 | 22 | type requestImpl struct { 23 | topicName string 24 | message map[string]interface{} 25 | } 26 | 27 | func (m *requestImpl) TopicName() string { 28 | return m.topicName 29 | } 30 | 31 | func (m *requestImpl) Message() map[string]interface{} { 32 | return m.message 33 | } 34 | -------------------------------------------------------------------------------- /nitric/topics/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | type Response struct { 18 | Success bool 19 | } 20 | -------------------------------------------------------------------------------- /nitric/topics/topic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/nitrictech/go-sdk/internal/handlers" 21 | "github.com/nitrictech/go-sdk/nitric/workers" 22 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 23 | topicspb "github.com/nitrictech/nitric/core/pkg/proto/topics/v1" 24 | ) 25 | 26 | // TopicPermission defines the available permissions on a topic 27 | type TopicPermission string 28 | 29 | const ( 30 | // TopicPublish is required to call Publish on a topic. 31 | TopicPublish TopicPermission = "publish" 32 | ) 33 | 34 | type SubscribableTopic interface { 35 | // Allow requests the given permissions to the topic. 36 | Allow(permission TopicPermission, permissions ...TopicPermission) *TopicClient 37 | 38 | // Subscribe will register and start a subscription handler that will be called for all events from this topic. 39 | // Valid function signatures for handler are: 40 | // 41 | // func() 42 | // func() error 43 | // func(*topics.Ctx) 44 | // func(*topics.Ctx) error 45 | // Handler[topics.Ctx] 46 | Subscribe(handler interface{}) 47 | } 48 | 49 | type subscribableTopic struct { 50 | name string 51 | manager *workers.Manager 52 | registerChan <-chan workers.RegisterResult 53 | } 54 | 55 | // NewTopic creates a new Topic with the give name. 56 | func NewTopic(name string) SubscribableTopic { 57 | topic := &subscribableTopic{ 58 | name: name, 59 | manager: workers.GetDefaultManager(), 60 | } 61 | 62 | topic.registerChan = topic.manager.RegisterResource(&v1.ResourceDeclareRequest{ 63 | Id: &v1.ResourceIdentifier{ 64 | Type: v1.ResourceType_Topic, 65 | Name: name, 66 | }, 67 | Config: &v1.ResourceDeclareRequest_Topic{ 68 | Topic: &v1.TopicResource{}, 69 | }, 70 | }) 71 | 72 | return topic 73 | } 74 | 75 | func (t *subscribableTopic) Allow(permission TopicPermission, permissions ...TopicPermission) *TopicClient { 76 | allPerms := append([]TopicPermission{permission}, permissions...) 77 | 78 | actions := []v1.Action{} 79 | for _, perm := range allPerms { 80 | switch perm { 81 | case TopicPublish: 82 | actions = append(actions, v1.Action_TopicPublish) 83 | default: 84 | panic(fmt.Sprintf("TopicPermission %s unknown", perm)) 85 | } 86 | } 87 | 88 | registerResult := <-t.registerChan 89 | if registerResult.Err != nil { 90 | panic(registerResult.Err) 91 | } 92 | 93 | err := t.manager.RegisterPolicy(registerResult.Identifier, actions...) 94 | if err != nil { 95 | panic(err) 96 | } 97 | 98 | client, err := NewTopicClient(t.name) 99 | if err != nil { 100 | panic(err) 101 | } 102 | 103 | return client 104 | } 105 | 106 | func (t *subscribableTopic) Subscribe(handler interface{}) { 107 | registrationRequest := &topicspb.RegistrationRequest{ 108 | TopicName: t.name, 109 | } 110 | 111 | typedHandler, err := handlers.HandlerFromInterface[Ctx](handler) 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | opts := &subscriptionWorkerOpts{ 117 | RegistrationRequest: registrationRequest, 118 | Handler: typedHandler, 119 | } 120 | 121 | worker := newSubscriptionWorker(opts) 122 | t.manager.AddWorker("SubscriptionWorker:"+t.name, worker) 123 | } 124 | -------------------------------------------------------------------------------- /nitric/topics/topics_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics_test 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | ) 23 | 24 | func TestTopics(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "Topics Suite") 27 | } 28 | -------------------------------------------------------------------------------- /nitric/topics/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import ( 18 | "context" 19 | 20 | errorsstd "errors" 21 | 22 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 23 | "github.com/nitrictech/go-sdk/internal/handlers" 24 | "github.com/nitrictech/go-sdk/nitric/errors" 25 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 26 | "github.com/nitrictech/go-sdk/nitric/workers" 27 | v1 "github.com/nitrictech/nitric/core/pkg/proto/topics/v1" 28 | ) 29 | 30 | type subscriptionWorker struct { 31 | client v1.SubscriberClient 32 | registrationRequest *v1.RegistrationRequest 33 | handler handlers.Handler[Ctx] 34 | } 35 | type subscriptionWorkerOpts struct { 36 | RegistrationRequest *v1.RegistrationRequest 37 | Handler handlers.Handler[Ctx] 38 | } 39 | 40 | // Start implements Worker. 41 | func (s *subscriptionWorker) Start(ctx context.Context) error { 42 | initReq := &v1.ClientMessage{ 43 | Content: &v1.ClientMessage_RegistrationRequest{ 44 | RegistrationRequest: s.registrationRequest, 45 | }, 46 | } 47 | 48 | createStream := func(ctx context.Context) (workers.Stream[v1.ClientMessage, v1.RegistrationResponse, *v1.ServerMessage], error) { 49 | return s.client.Subscribe(ctx) 50 | } 51 | 52 | handleSrvMsg := func(msg *v1.ServerMessage) (*v1.ClientMessage, error) { 53 | if msg.GetMessageRequest() != nil { 54 | handlerCtx := NewCtx(msg) 55 | 56 | err := s.handler(handlerCtx) 57 | if err != nil { 58 | handlerCtx.WithError(err) 59 | } 60 | 61 | return handlerCtx.ToClientMessage(), nil 62 | } 63 | 64 | return nil, errors.NewWithCause( 65 | codes.Internal, 66 | "SubscriptionWorker: Unhandled server message", 67 | errorsstd.New("unhandled server message"), 68 | ) 69 | } 70 | 71 | return workers.HandleStream(ctx, createStream, initReq, handleSrvMsg) 72 | } 73 | 74 | func newSubscriptionWorker(opts *subscriptionWorkerOpts) *subscriptionWorker { 75 | conn, err := grpcx.GetConnection() 76 | if err != nil { 77 | panic(errors.NewWithCause( 78 | codes.Unavailable, 79 | "NewSubscriptionWorker: Unable to reach SubscriberClient", 80 | err, 81 | )) 82 | } 83 | 84 | client := v1.NewSubscriberClient(conn) 85 | 86 | return &subscriptionWorker{ 87 | client: client, 88 | registrationRequest: opts.RegistrationRequest, 89 | handler: opts.Handler, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /nitric/websockets/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package websockets 16 | 17 | import websocketspb "github.com/nitrictech/nitric/core/pkg/proto/websockets/v1" 18 | 19 | type Ctx struct { 20 | id string 21 | Request Request 22 | Response *Response 23 | Extras map[string]interface{} 24 | } 25 | 26 | func (c *Ctx) ToClientMessage() *websocketspb.ClientMessage { 27 | return &websocketspb.ClientMessage{ 28 | Id: c.id, 29 | Content: &websocketspb.ClientMessage_WebsocketEventResponse{ 30 | WebsocketEventResponse: &websocketspb.WebsocketEventResponse{ 31 | WebsocketResponse: &websocketspb.WebsocketEventResponse_ConnectionResponse{ 32 | ConnectionResponse: &websocketspb.WebsocketConnectionResponse{ 33 | Reject: c.Response.Reject, 34 | }, 35 | }, 36 | }, 37 | }, 38 | } 39 | } 40 | 41 | func NewCtx(msg *websocketspb.ServerMessage) *Ctx { 42 | req := msg.GetWebsocketEventRequest() 43 | 44 | var eventType EventType 45 | switch req.WebsocketEvent.(type) { 46 | case *websocketspb.WebsocketEventRequest_Disconnection: 47 | eventType = EventType_Disconnect 48 | case *websocketspb.WebsocketEventRequest_Message: 49 | eventType = EventType_Message 50 | default: 51 | eventType = EventType_Connect 52 | } 53 | 54 | queryParams := make(map[string]*websocketspb.QueryValue) 55 | if eventType == EventType_Connect { 56 | for key, value := range req.GetConnection().GetQueryParams() { 57 | queryParams[key] = &websocketspb.QueryValue{ 58 | Value: value.Value, 59 | } 60 | } 61 | } 62 | 63 | var _message string = "" 64 | if req.GetMessage() != nil { 65 | _message = string(req.GetMessage().Body) 66 | } 67 | 68 | return &Ctx{ 69 | id: msg.Id, 70 | Request: &requestImpl{ 71 | socketName: req.SocketName, 72 | eventType: eventType, 73 | connectionId: req.ConnectionId, 74 | queryParams: queryParams, 75 | message: _message, 76 | }, 77 | Response: &Response{ 78 | Reject: false, 79 | }, 80 | } 81 | } 82 | 83 | func (c *Ctx) WithError(err error) { 84 | c.Response = &Response{ 85 | Reject: true, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /nitric/websockets/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package websockets 16 | 17 | import ( 18 | websocketspb "github.com/nitrictech/nitric/core/pkg/proto/websockets/v1" 19 | ) 20 | 21 | type EventType string 22 | 23 | var EventTypes = []EventType{EventType_Connect, EventType_Disconnect, EventType_Message} 24 | 25 | const ( 26 | EventType_Connect EventType = "connect" 27 | EventType_Disconnect EventType = "disconnect" 28 | EventType_Message EventType = "message" 29 | ) 30 | 31 | type Request interface { 32 | SocketName() string 33 | EventType() EventType 34 | ConnectionID() string 35 | QueryParams() map[string][]string 36 | Message() string 37 | } 38 | 39 | type requestImpl struct { 40 | socketName string 41 | eventType EventType 42 | connectionId string 43 | queryParams map[string]*websocketspb.QueryValue 44 | message string 45 | } 46 | 47 | func (w *requestImpl) SocketName() string { 48 | return w.socketName 49 | } 50 | 51 | func (w *requestImpl) EventType() EventType { 52 | return w.eventType 53 | } 54 | 55 | func (w *requestImpl) ConnectionID() string { 56 | return w.connectionId 57 | } 58 | 59 | func (w *requestImpl) QueryParams() map[string][]string { 60 | queryParams := map[string][]string{} 61 | 62 | for k, v := range w.queryParams { 63 | queryParams[k] = v.Value 64 | } 65 | 66 | return queryParams 67 | } 68 | 69 | func (w *requestImpl) Message() string { 70 | return w.message 71 | } 72 | -------------------------------------------------------------------------------- /nitric/websockets/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package websockets 16 | 17 | type Response struct { 18 | Reject bool 19 | } 20 | -------------------------------------------------------------------------------- /nitric/websockets/websocket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package websockets 16 | 17 | import ( 18 | "context" 19 | "strings" 20 | 21 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 22 | "github.com/nitrictech/go-sdk/internal/handlers" 23 | "github.com/nitrictech/go-sdk/nitric/workers" 24 | resourcesv1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 25 | websocketsv1 "github.com/nitrictech/nitric/core/pkg/proto/websockets/v1" 26 | ) 27 | 28 | // Websocket - Nitric Websocket API Resource 29 | type Websocket interface { 30 | // Name - Get the name of the Websocket API 31 | Name() string 32 | // On registers a handler for a specific event type on the websocket 33 | // Valid function signatures for handler are: 34 | // 35 | // func() 36 | // func() error 37 | // func(*websocket.Ctx) 38 | // func(*websocket.Ctx) error 39 | // Handler[websocket.Ctx] 40 | On(eventType EventType, handler interface{}) 41 | // Send a message to a specific connection 42 | Send(ctx context.Context, connectionId string, message []byte) error 43 | // Close a specific connection 44 | Close(ctx context.Context, connectionId string) error 45 | } 46 | 47 | type websocket struct { 48 | Websocket 49 | 50 | name string 51 | manager *workers.Manager 52 | client websocketsv1.WebsocketClient 53 | } 54 | 55 | // NewWebsocket - Create a new Websocket API resource 56 | func NewWebsocket(name string) Websocket { 57 | manager := workers.GetDefaultManager() 58 | 59 | registerResult := <-manager.RegisterResource(&resourcesv1.ResourceDeclareRequest{ 60 | Id: &resourcesv1.ResourceIdentifier{ 61 | Type: resourcesv1.ResourceType_Websocket, 62 | Name: name, 63 | }, 64 | }) 65 | if registerResult.Err != nil { 66 | panic(registerResult.Err) 67 | } 68 | 69 | actions := []resourcesv1.Action{resourcesv1.Action_WebsocketManage} 70 | 71 | err := manager.RegisterPolicy(registerResult.Identifier, actions...) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | conn, err := grpcx.GetConnection() 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | wClient := websocketsv1.NewWebsocketClient(conn) 82 | 83 | return &websocket{ 84 | manager: manager, 85 | client: wClient, 86 | name: name, 87 | } 88 | } 89 | 90 | func (w *websocket) Name() string { 91 | return w.name 92 | } 93 | 94 | func (w *websocket) On(eventType EventType, handler interface{}) { 95 | var _eventType websocketsv1.WebsocketEventType 96 | switch eventType { 97 | case EventType_Disconnect: 98 | _eventType = websocketsv1.WebsocketEventType_Disconnect 99 | case EventType_Message: 100 | _eventType = websocketsv1.WebsocketEventType_Message 101 | default: 102 | _eventType = websocketsv1.WebsocketEventType_Connect 103 | } 104 | 105 | registrationRequest := &websocketsv1.RegistrationRequest{ 106 | SocketName: w.name, 107 | EventType: _eventType, 108 | } 109 | 110 | typedHandler, err := handlers.HandlerFromInterface[Ctx](handler) 111 | if err != nil { 112 | panic(err) 113 | } 114 | 115 | opts := &websocketWorkerOpts{ 116 | RegistrationRequest: registrationRequest, 117 | Handler: typedHandler, 118 | } 119 | 120 | worker := newWebsocketWorker(opts) 121 | w.manager.AddWorker("WebsocketWorker:"+strings.Join([]string{ 122 | w.name, 123 | string(eventType), 124 | }, "-"), worker) 125 | } 126 | 127 | func (w *websocket) Send(ctx context.Context, connectionId string, message []byte) error { 128 | _, err := w.client.SendMessage(ctx, &websocketsv1.WebsocketSendRequest{ 129 | SocketName: w.name, 130 | ConnectionId: connectionId, 131 | Data: message, 132 | }) 133 | 134 | return err 135 | } 136 | 137 | func (w *websocket) Close(ctx context.Context, connectionId string) error { 138 | _, err := w.client.CloseConnection(ctx, &websocketsv1.WebsocketCloseConnectionRequest{ 139 | SocketName: w.name, 140 | ConnectionId: connectionId, 141 | }) 142 | 143 | return err 144 | } 145 | -------------------------------------------------------------------------------- /nitric/websockets/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package websockets 16 | 17 | import ( 18 | "context" 19 | errorsstd "errors" 20 | 21 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 22 | "github.com/nitrictech/go-sdk/internal/handlers" 23 | "github.com/nitrictech/go-sdk/nitric/errors" 24 | "github.com/nitrictech/go-sdk/nitric/errors/codes" 25 | "github.com/nitrictech/go-sdk/nitric/workers" 26 | v1 "github.com/nitrictech/nitric/core/pkg/proto/websockets/v1" 27 | ) 28 | 29 | type websocketWorker struct { 30 | client v1.WebsocketHandlerClient 31 | registrationRequest *v1.RegistrationRequest 32 | handler handlers.Handler[Ctx] 33 | } 34 | type websocketWorkerOpts struct { 35 | RegistrationRequest *v1.RegistrationRequest 36 | Handler handlers.Handler[Ctx] 37 | } 38 | 39 | // Start implements Worker. 40 | func (w *websocketWorker) Start(ctx context.Context) error { 41 | initReq := &v1.ClientMessage{ 42 | Content: &v1.ClientMessage_RegistrationRequest{ 43 | RegistrationRequest: w.registrationRequest, 44 | }, 45 | } 46 | 47 | createStream := func(ctx context.Context) (workers.Stream[v1.ClientMessage, v1.RegistrationResponse, *v1.ServerMessage], error) { 48 | return w.client.HandleEvents(ctx) 49 | } 50 | 51 | handlerSrvMsg := func(msg *v1.ServerMessage) (*v1.ClientMessage, error) { 52 | if msg.GetWebsocketEventRequest() != nil { 53 | handlerCtx := NewCtx(msg) 54 | 55 | err := w.handler(handlerCtx) 56 | if err != nil { 57 | handlerCtx.WithError(err) 58 | } 59 | return handlerCtx.ToClientMessage(), nil 60 | } 61 | 62 | return nil, errors.NewWithCause( 63 | codes.Internal, 64 | "WebsocketWorker: Unhandled server message", 65 | errorsstd.New("unhandled server message"), 66 | ) 67 | } 68 | 69 | return workers.HandleStream(ctx, createStream, initReq, handlerSrvMsg) 70 | } 71 | 72 | func newWebsocketWorker(opts *websocketWorkerOpts) *websocketWorker { 73 | conn, err := grpcx.GetConnection() 74 | if err != nil { 75 | panic(errors.NewWithCause( 76 | codes.Unavailable, 77 | "NewWebsocketWorker: Unable to reach WebsocketHandlerClient", 78 | err, 79 | )) 80 | } 81 | 82 | client := v1.NewWebsocketHandlerClient(conn) 83 | 84 | return &websocketWorker{ 85 | client: client, 86 | registrationRequest: opts.RegistrationRequest, 87 | handler: opts.Handler, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /nitric/workers/manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package workers 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "io" 21 | "os" 22 | "strings" 23 | "sync" 24 | 25 | multierror "github.com/missionMeteora/toolkit/errors" 26 | 27 | grpcx "github.com/nitrictech/go-sdk/internal/grpc" 28 | apierrors "github.com/nitrictech/go-sdk/nitric/errors" 29 | v1 "github.com/nitrictech/nitric/core/pkg/proto/resources/v1" 30 | ) 31 | 32 | type RegisterResult struct { 33 | Identifier *v1.ResourceIdentifier 34 | Err error 35 | } 36 | 37 | type Manager struct { 38 | workers map[string]StreamWorker 39 | 40 | rsc v1.ResourcesClient 41 | } 42 | 43 | var defaultManager = New() 44 | 45 | func GetDefaultManager() *Manager { 46 | return defaultManager 47 | } 48 | 49 | // New is used to create the top level resource manager. 50 | // Note: this is not required if you are using 51 | // resources.NewApi() and the like. These use a default manager instance. 52 | func New() *Manager { 53 | return &Manager{ 54 | workers: map[string]StreamWorker{}, 55 | } 56 | } 57 | 58 | func (m *Manager) AddWorker(name string, s StreamWorker) { 59 | m.workers[name] = s 60 | } 61 | 62 | func (m *Manager) resourceServiceClient() (v1.ResourcesClient, error) { 63 | conn, err := grpcx.GetConnection() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | if m.rsc == nil { 69 | m.rsc = v1.NewResourcesClient(conn) 70 | } 71 | return m.rsc, nil 72 | } 73 | 74 | func (m *Manager) RegisterResource(request *v1.ResourceDeclareRequest) <-chan RegisterResult { 75 | registerResourceChan := make(chan RegisterResult) 76 | 77 | go func() { 78 | rsc, err := m.resourceServiceClient() 79 | if err != nil { 80 | registerResourceChan <- RegisterResult{ 81 | Err: err, 82 | Identifier: nil, 83 | } 84 | 85 | return 86 | } 87 | 88 | _, err = rsc.Declare(context.Background(), request) 89 | if err != nil { 90 | registerResourceChan <- RegisterResult{ 91 | Err: err, 92 | Identifier: nil, 93 | } 94 | 95 | return 96 | } 97 | 98 | registerResourceChan <- RegisterResult{ 99 | Err: nil, 100 | Identifier: request.Id, 101 | } 102 | }() 103 | 104 | return registerResourceChan 105 | } 106 | 107 | func functionResourceDeclareRequest(subject *v1.ResourceIdentifier, actions []v1.Action) *v1.ResourceDeclareRequest { 108 | return &v1.ResourceDeclareRequest{ 109 | Id: &v1.ResourceIdentifier{ 110 | Type: v1.ResourceType_Policy, 111 | }, 112 | Config: &v1.ResourceDeclareRequest_Policy{ 113 | Policy: &v1.PolicyResource{ 114 | Principals: []*v1.ResourceIdentifier{ 115 | { 116 | Type: v1.ResourceType_Service, 117 | }, 118 | }, 119 | Actions: actions, 120 | Resources: []*v1.ResourceIdentifier{subject}, 121 | }, 122 | }, 123 | } 124 | } 125 | 126 | func (m *Manager) RegisterPolicy(res *v1.ResourceIdentifier, actions ...v1.Action) error { 127 | rsc, err := m.resourceServiceClient() 128 | if err != nil { 129 | return err 130 | } 131 | 132 | _, err = rsc.Declare(context.Background(), functionResourceDeclareRequest(res, actions)) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | return nil 138 | } 139 | 140 | func (m *Manager) Run(ctx context.Context) error { 141 | wg := sync.WaitGroup{} 142 | errList := &multierror.ErrorList{} 143 | 144 | for _, worker := range m.workers { 145 | wg.Add(1) 146 | go func(s StreamWorker) { 147 | defer wg.Done() 148 | 149 | if err := s.Start(ctx); err != nil { 150 | if isBuildEnvironment() && isEOF(err) { 151 | // ignore the EOF error when running code-as-config. 152 | return 153 | } 154 | 155 | errList.Push(err) 156 | } 157 | }(worker) 158 | } 159 | 160 | wg.Wait() 161 | 162 | return errList.Err() 163 | } 164 | 165 | // IsBuildEnvironment will return true if the code is running during config discovery. 166 | func isBuildEnvironment() bool { 167 | return strings.ToLower(os.Getenv("NITRIC_ENVIRONMENT")) == "build" 168 | } 169 | 170 | func isEOF(err error) bool { 171 | if err == nil { 172 | return false 173 | } 174 | var apiErr *apierrors.ApiError 175 | if errors.As(err, &apiErr) { 176 | err = apiErr.Unwrap() 177 | } 178 | if err == nil { 179 | return false 180 | } 181 | return errors.Is(err, io.EOF) || err.Error() == io.EOF.Error() 182 | } 183 | -------------------------------------------------------------------------------- /nitric/workers/workers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package workers 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "io" 22 | 23 | "google.golang.org/grpc" 24 | ) 25 | 26 | type StreamWorker interface { 27 | Start(context.Context) error 28 | } 29 | 30 | type StdServerMsg[RegistrationResponse any] interface { 31 | GetRegistrationResponse() *RegistrationResponse 32 | } 33 | 34 | type Stream[ClientMessage any, RegistrationResponse any, ServerMessage StdServerMsg[RegistrationResponse]] interface { 35 | Send(*ClientMessage) error 36 | Recv() (ServerMessage, error) 37 | grpc.ClientStream 38 | } 39 | 40 | // HandleStream runs a nitric worker, in the standard request/response pattern. 41 | // No changes needed here other than the updated types in the signature. 42 | func HandleStream[ClientMessage any, RegistrationResponse any, ServerMessage StdServerMsg[RegistrationResponse]]( 43 | ctx context.Context, 44 | createStream func(ctx context.Context) (Stream[ClientMessage, RegistrationResponse, ServerMessage], error), 45 | initReq *ClientMessage, 46 | handleServerMsg func(msg ServerMessage) (*ClientMessage, error), 47 | ) error { 48 | stream, err := createStream(ctx) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | err = stream.Send(initReq) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | for { 59 | select { 60 | case <-ctx.Done(): 61 | fmt.Printf("Context canceled, closing stream\n") 62 | // If the context is canceled, close the stream and return 63 | err := stream.CloseSend() 64 | if err != nil { 65 | return err 66 | } 67 | return nil 68 | 69 | default: 70 | // Receive the next message 71 | serverMsg, err := stream.Recv() 72 | 73 | if errors.Is(err, io.EOF) { 74 | // Close the stream and exit normally on EOF 75 | err = stream.CloseSend() 76 | if err != nil { 77 | return err 78 | } 79 | return nil 80 | } else if err != nil { 81 | return err 82 | } 83 | 84 | if serverMsg.GetRegistrationResponse() != nil { 85 | // No need to respond to the registration responses (they're just acks) 86 | continue 87 | } 88 | 89 | clientMsg, err := handleServerMsg(serverMsg) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | err = stream.Send(clientMsg) 95 | if err != nil { 96 | return err 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tools/readme.txt: -------------------------------------------------------------------------------- 1 | Protocol Buffers - Google's data interchange format 2 | Copyright 2008 Google Inc. 3 | https://developers.google.com/protocol-buffers/ 4 | This package contains a precompiled binary version of the protocol buffer 5 | compiler (protoc). This binary is intended for users who want to use Protocol 6 | Buffers in languages other than C++ but do not want to compile protoc 7 | themselves. To install, simply place this binary somewhere in your PATH. 8 | If you intend to use the included well known types then don't forget to 9 | copy the contents of the 'include' directory somewhere as well, for example 10 | into '/usr/local/include/'. 11 | Please refer to our official github site for more installation instructions: 12 | https://github.com/protocolbuffers/protobuf 13 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Nitric Technologies Pty Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | // +build tools 17 | 18 | package tools 19 | 20 | import ( 21 | _ "github.com/golang/mock/mockgen" 22 | _ "github.com/golang/protobuf/protoc-gen-go" 23 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint" 24 | _ "github.com/google/addlicense" 25 | _ "github.com/onsi/ginkgo/ginkgo" 26 | _ "github.com/uw-labs/lichen" 27 | _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" 28 | ) 29 | -------------------------------------------------------------------------------- /tools/tools.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | ifeq ($(PROCESSOR_ARCHITEW6432),AMD64) 3 | PROTOC_PLATFORM := win64 4 | else 5 | ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) 6 | PROTOC_PLATFORM := win64 7 | endif 8 | ifeq ($(PROCESSOR_ARCHITECTURE),x86) 9 | PROTOC_PLATFORM := win32 10 | endif 11 | endif 12 | else 13 | UNAME_S := $(shell uname -s) 14 | ifeq ($(UNAME_S),Linux) 15 | UNAME_P := $(shell uname -m) 16 | ifeq ($(UNAME_P),x86_64) 17 | PROTOC_PLATFORM := linux-x86_64 18 | endif 19 | ifneq ($(filter %86,$(UNAME_P)),) 20 | PROTOC_PLATFORM := linux-x86_32 21 | endif 22 | endif 23 | ifeq ($(UNAME_S),Darwin) 24 | UNAME_P := $(shell uname -m) 25 | ifeq ($(UNAME_P),arm64) 26 | PROTOC_PLATFORM := osx-aarch_64 27 | endif 28 | ifeq ($(UNAME_P),x86_64) 29 | PROTOC_PLATFORM := osx-x86_64 30 | endif 31 | endif 32 | endif 33 | 34 | ifndef PROTOC_PLATFORM 35 | $(error unsupported platform $(UNAME_S):$(UNAME_P)) 36 | endif 37 | 38 | TOOLS_DIR := ./tools 39 | TOOLS_BIN := $(TOOLS_DIR)/bin 40 | 41 | PROTOC_VERSION := 23.4 42 | PROTOC_RELEASES_PATH := https://github.com/protocolbuffers/protobuf/releases/download 43 | PROTOC_ZIP := protoc-$(PROTOC_VERSION)-$(PROTOC_PLATFORM).zip 44 | PROTOC_DOWNLOAD := $(PROTOC_RELEASES_PATH)/v$(PROTOC_VERSION)/$(PROTOC_ZIP) 45 | PROTOC := $(TOOLS_BIN)/protoc 46 | 47 | install-tools: $(PROTOC) ${GOPATH}/bin/protoc-gen-go ${GOPATH}/bin/protoc-gen-go-grpc 48 | 49 | $(PROTOC): $(TOOLS_DIR)/$(PROTOC_ZIP) 50 | unzip -o -d "$(TOOLS_DIR)" $< && touch $@ # avoid Prerequisite is newer than target `tools/bin/protoc'. 51 | rm $(TOOLS_DIR)/readme.txt 52 | 53 | $(TOOLS_DIR)/$(PROTOC_ZIP): 54 | curl --location $(PROTOC_DOWNLOAD) --output $@ 55 | 56 | ${GOPATH}/bin/protoc-gen-go: go.sum 57 | go install google.golang.org/protobuf/cmd/protoc-gen-go 58 | 59 | ${GOPATH}/bin/protoc-gen-go-grpc: go.sum 60 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc 61 | --------------------------------------------------------------------------------