├── .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 |
4 |
5 |
6 |
7 |
8 | Build nitric applications with Go
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
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 |
--------------------------------------------------------------------------------