diff --git a/.devcontainer.json b/.devcontainer.json index 1f8f6d2..0b9b0e7 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -1,10 +1,10 @@ { "image": "mcr.microsoft.com/devcontainers/go:1-bullseye", "features": { - "ghcr.io/devcontainers-contrib/features/protoc:1": {} + "ghcr.io/devcontainers/features/docker-in-docker:2": {} }, - "postCreateCommand": "go install google.golang.org/protobuf/cmd/protoc-gen-go@latest; go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest", + "postCreateCommand": "curl -L https://dl.dagger.io/dagger/install.sh | BIN_DIR=$HOME/.local/bin sh; sh -c \"$(curl --location https://taskfile.dev/install.sh)\" -- -d -b $HOME/.local/bin", "customizations": { "vscode": { diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 92f4160..780b62d 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -1,18 +1,24 @@ name: backup-adapter CI + on: - push: - branches: - - main pull_request: - branches: - - main workflow_dispatch: + jobs: - lint: + ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - name: Checkout + uses: actions/checkout@v4 + # We need the full history for the commitlint task with: - go-version: '>=1.21.0' - - run: make build + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + - name: Install Task + uses: arduino/setup-task@v2 + - name: Install Dagger + run: | + curl -L https://dl.dagger.io/dagger/install.sh | BIN_DIR=$HOME/.local/bin sh + - name: Run CI task + run: | + task ci diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index f419d8e..9297c40 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,11 +1,10 @@ +name: release-please + on: push: branches: - main -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} -name: release-please + jobs: release-please: runs-on: ubuntu-latest @@ -15,4 +14,4 @@ jobs: with: release-type: go package-name: cnpg-i - token: ${{secrets.REPO_PAT}} + token: ${{ secrets.REPO_PAT }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd96865 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.task/ +.idea/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..ba83ff4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,128 @@ +linters-settings: + lll: + line-length: 120 + gci: + sections: + - standard + - default + - prefix(github.com/cloudnative-pg/cloudnative-pg) + - blank + - dot + gosec: + excludes: + - G101 # remove this exclude when https://github.com/securego/gosec/issues/1001 is fixed + +linters: + # please, do not use `enable-all`: it's deprecated and will be removed soon. + # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint + disable-all: true + enable: + - asciicheck + - bodyclose + - dogsled + - dupl + - durationcheck + - errcheck + - exportloopref + - gci + - gocognit + - goconst + - gocritic + - gocyclo + - gofmt + - gofumpt + - goheader + - goimports + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - ginkgolinter + - importas + - ineffassign + - lll + - makezero + - misspell + - nakedret + - nestif + - prealloc + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - wastedassign + - whitespace + + # to be checked: + # - errorlint + # - forbidigo + # - forcetypeassert + # - goerr113 + # - ifshort + # - nilerr + # - nlreturn + # - noctx + # - nolintlint + # - paralleltest + # - promlinter + # - tagliatelle + # - wrapcheck + + # don't enable: + # - cyclop + # - depguard + # - exhaustive + # - exhaustivestruct + # - funlen + # - gochecknoglobals + # - gochecknoinits + # - godot + # - godox + # - gomnd + # - testpackage + # - wsl + + # deprecated: + # - deadcode + # - golint + # - interfacer + # - maligned + # - scopelint + # - structcheck + # - varcheck + +run: + skip-files: "zz_generated.*" + +issues: + exclude-rules: + # Allow dot imports for ginkgo and gomega + - source: ginkgo|gomega + linters: + - revive + text: "should not use dot imports" + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - goconst + # Exclude lll issues for lines with long annotations + - linters: + - lll + source: "//\\s*\\+" + # We have no control of this in zz_generated files and it looks like that excluding those files is not enough + # so we disable "ST1016: methods on the same type should have the same receiver name" in api directory + - linters: + - stylecheck + text: "ST1016:" + path: api/ + exclude-use-default: false diff --git a/Makefile b/Makefile deleted file mode 100644 index 8df0b62..0000000 --- a/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -.PHONY: build -build: - ./scripts/build.sh diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..3d9fabc --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,75 @@ +version: '3' + +tasks: + protolint: + desc: Run protolint + cmds: + - dagger -s call -m dagger/protolint lint --source . --args "--config_path=.protolint.yaml" --args proto/ stdout + sources: + - proto/**/*.proto + + protoc-gen-go-grpc: + desc: Compile proto files + deps: + - protolint + cmds: + - > + dagger -s call -m dagger/protoc-gen-go-grpc run --source . --go-opt module=github.com/cloudnative-pg/cnpg-i + --go-grpcopt module=github.com/cloudnative-pg/cnpg-i --proto-path proto -o . + sources: + - proto/**/*.proto + + lint: + desc: Run golangci-lint + deps: + - protoc-gen-go-grpc + cmds: + - > + dagger -s call -m github.com/sagikazarmark/daggerverse/golangci-lint@157bf2192a7a1e8672da2c4fee37d8710734c35a + run --source . --config .golangci.yml stdout + sources: + - ./**/*.go + + protoc-gen-doc: + desc: Generate documentation from proto files + cmds: + - dagger -s call -m dagger/protoc-gen-doc generate --proto-dir proto -o docs + sources: + - proto/**/*.proto + + build: + desc: Build the project + deps: + - lint + - protoc-gen-doc + cmds: + - > + dagger -s -m github.com/kpenfound/dagger-modules/golang@8d662e001caf8c16253226d0d456f2f0f374f009 + call base --version 1.22-bookworm build --source . --args ./... + + commitlint: + desc: Check for conventional commits + cmds: + - dagger -s call -m dagger/commitlint lint --source . --args "--from=origin/main" stdout + + uncommitted: + desc: Check for uncommitted changes + deps: + - build + cmds: + - dagger -s call -m dagger/uncommitted/ check-uncommitted --source . stdout + sources: + - ./** + + ci: + desc: Run the CI pipeline + deps: + - commitlint + - uncommitted + + clean: + desc: Remove autogenerated artifacts + cmds: + - rm -f docs/docs.md + - rm -rf .task/ + - rm -f pkg/*/*.pb.go diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..85ce1e0 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,12 @@ +const Configuration= { + extends: ['@commitlint/config-conventional'], + formatter: '@commitlint/format', + rules: { + 'body-empty': [1, 'never'], + 'body-case': [2, 'always', 'sentence-case'], + 'references-empty': [1, 'never'], + 'signed-off-by': [2, 'always', 'Signed-off-by:'], + }, +}; + +module.exports = Configuration; diff --git a/dagger/commitlint/dagger.json b/dagger/commitlint/dagger.json new file mode 100644 index 0000000..6ba221d --- /dev/null +++ b/dagger/commitlint/dagger.json @@ -0,0 +1,6 @@ +{ + "name": "commitlint", + "sdk": "go", + "source": "dagger", + "engineVersion": "v0.10.2" +} diff --git a/dagger/commitlint/dagger/.gitattributes b/dagger/commitlint/dagger/.gitattributes new file mode 100644 index 0000000..b94d9fd --- /dev/null +++ b/dagger/commitlint/dagger/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/querybuilder/** linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated diff --git a/dagger/commitlint/dagger/.gitignore b/dagger/commitlint/dagger/.gitignore new file mode 100644 index 0000000..26b5515 --- /dev/null +++ b/dagger/commitlint/dagger/.gitignore @@ -0,0 +1,3 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder diff --git a/dagger/commitlint/dagger/go.mod b/dagger/commitlint/dagger/go.mod new file mode 100644 index 0000000..f6e4153 --- /dev/null +++ b/dagger/commitlint/dagger/go.mod @@ -0,0 +1,13 @@ +module main + +go 1.21.3 + +require ( + github.com/99designs/gqlgen v0.17.31 + github.com/Khan/genqlient v0.6.0 + github.com/vektah/gqlparser/v2 v2.5.6 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.6.0 +) + +require github.com/stretchr/testify v1.9.0 // indirect diff --git a/dagger/commitlint/dagger/go.sum b/dagger/commitlint/dagger/go.sum new file mode 100644 index 0000000..6b16e9f --- /dev/null +++ b/dagger/commitlint/dagger/go.sum @@ -0,0 +1,35 @@ +github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= +github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= +github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= +github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= +github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dagger/commitlint/dagger/internal/querybuilder/marshal.go b/dagger/commitlint/dagger/internal/querybuilder/marshal.go new file mode 100644 index 0000000..1f5468a --- /dev/null +++ b/dagger/commitlint/dagger/internal/querybuilder/marshal.go @@ -0,0 +1,162 @@ +package querybuilder + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + gqlgen "github.com/99designs/gqlgen/graphql" + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" +) + +// GraphQLMarshaller is an internal interface for marshalling an object into GraphQL. +type GraphQLMarshaller interface { + // XXX_GraphQLType is an internal function. It returns the native GraphQL type name + XXX_GraphQLType() string + // XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object + XXX_GraphQLIDType() string + // XXX_GraphqlID is an internal function. It returns the underlying type ID + XXX_GraphQLID(ctx context.Context) (string, error) + json.Marshaler +} + +const ( + GraphQLMarshallerType = "XXX_GraphQLType" + GraphQLMarshallerIDType = "XXX_GraphQLIDType" + GraphQLMarshallerID = "XXX_GraphQLID" +) + +type enum interface { + IsEnum() +} + +var ( + gqlMarshaller = reflect.TypeOf((*GraphQLMarshaller)(nil)).Elem() + enumT = reflect.TypeOf((*enum)(nil)).Elem() +) + +func MarshalGQL(ctx context.Context, v any) (string, error) { + return marshalValue(ctx, reflect.ValueOf(v)) +} + +func marshalValue(ctx context.Context, v reflect.Value) (string, error) { + t := v.Type() + + if t.Implements(gqlMarshaller) { + return marshalCustom(ctx, v) + } + + switch t.Kind() { + case reflect.Bool: + return fmt.Sprintf("%t", v.Bool()), nil + case reflect.Int: + return fmt.Sprintf("%d", v.Int()), nil + case reflect.String: + if t.Implements(enumT) { + // enums render as their literal value + return v.String(), nil + } + + // escape strings following graphQL spec + // https://github.com/graphql/graphql-spec/blob/main/spec/Section%202%20--%20Language.md#string-value + var buf bytes.Buffer + gqlgen.MarshalString(v.String()).MarshalGQL(&buf) + return buf.String(), nil + case reflect.Pointer, reflect.Interface: + if v.IsNil() { + return "null", nil + } + return marshalValue(ctx, v.Elem()) + case reflect.Slice: + n := v.Len() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + m, err := marshalValue(gctx, v.Index(i)) + if err != nil { + return err + } + elems[i] = m + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + return fmt.Sprintf("[%s]", strings.Join(elems, ",")), nil + case reflect.Struct: + n := v.NumField() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + f := t.Field(i) + fv := v.Field(i) + name := f.Name + jsonTag := strings.Split(f.Tag.Get("json"), ",") + if jsonTag[0] != "" { + name = jsonTag[0] + } + isOptional := slices.Contains(jsonTag[1:], "omitempty") + if isOptional && IsZeroValue(fv.Interface()) { + return nil + } + m, err := marshalValue(gctx, fv) + if err != nil { + return err + } + if m != `""` && m != "null" { + elems[i] = fmt.Sprintf("%s:%s", name, m) + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + nonNullElems := make([]string, 0, n) + for _, elem := range elems { + if elem != "" { + nonNullElems = append(nonNullElems, elem) + } + } + return fmt.Sprintf("{%s}", strings.Join(nonNullElems, ",")), nil + default: + panic(fmt.Errorf("unsupported argument of kind %s", t.Kind())) + } +} + +func marshalCustom(ctx context.Context, v reflect.Value) (string, error) { + result := v.MethodByName(GraphQLMarshallerID).Call([]reflect.Value{ + reflect.ValueOf(ctx), + }) + if len(result) != 2 { + panic(result) + } + err := result[1].Interface() + if err != nil { + return "", err.(error) + } + + return fmt.Sprintf("%q", result[0].String()), nil +} + +func IsZeroValue(value any) bool { + v := reflect.ValueOf(value) + kind := v.Type().Kind() + switch kind { + case reflect.Pointer: + return v.IsNil() + case reflect.Slice, reflect.Array: + return v.Len() == 0 + default: + return v.IsZero() + } +} diff --git a/dagger/commitlint/dagger/internal/querybuilder/querybuilder.go b/dagger/commitlint/dagger/internal/querybuilder/querybuilder.go new file mode 100644 index 0000000..64a32e0 --- /dev/null +++ b/dagger/commitlint/dagger/internal/querybuilder/querybuilder.go @@ -0,0 +1,204 @@ +package querybuilder + +import ( + "context" + "encoding/json" + "fmt" + "runtime/debug" + "strings" + "sync" + + "github.com/Khan/genqlient/graphql" + "golang.org/x/sync/errgroup" +) + +func Query() *Selection { + return &Selection{} +} + +type Selection struct { + name string + alias string + args map[string]*argument + bind interface{} + + prev *Selection + + client graphql.Client +} + +func (s *Selection) path() []*Selection { + selections := []*Selection{} + for sel := s; sel.prev != nil; sel = sel.prev { + selections = append([]*Selection{sel}, selections...) + } + + return selections +} + +func (s *Selection) Root() *Selection { + return &Selection{ + client: s.client, + } +} + +func (s *Selection) SelectWithAlias(alias, name string) *Selection { + sel := &Selection{ + name: name, + prev: s, + alias: alias, + client: s.client, + } + return sel +} + +func (s *Selection) Select(name string) *Selection { + return s.SelectWithAlias("", name) +} + +func (s *Selection) Arg(name string, value any) *Selection { + sel := *s + if sel.args == nil { + sel.args = map[string]*argument{} + } + + sel.args[name] = &argument{ + value: value, + } + return &sel +} + +func (s *Selection) Bind(v interface{}) *Selection { + sel := *s + sel.bind = v + return &sel +} + +func (s *Selection) marshalArguments(ctx context.Context) error { + eg, gctx := errgroup.WithContext(ctx) + for _, sel := range s.path() { + for _, arg := range sel.args { + arg := arg + eg.Go(func() error { + return arg.marshal(gctx) + }) + } + } + + return eg.Wait() +} + +func (s *Selection) Build(ctx context.Context) (string, error) { + if err := s.marshalArguments(ctx); err != nil { + return "", err + } + + var b strings.Builder + b.WriteString("query") + + path := s.path() + + for _, sel := range path { + b.WriteRune('{') + + if sel.alias != "" { + b.WriteString(sel.alias) + b.WriteRune(':') + } + + b.WriteString(sel.name) + + if len(sel.args) > 0 { + b.WriteRune('(') + i := 0 + for name, arg := range sel.args { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(name) + b.WriteRune(':') + b.WriteString(arg.marshalled) + i++ + } + b.WriteRune(')') + } + } + + b.WriteString(strings.Repeat("}", len(path))) + return b.String(), nil +} + +func (s *Selection) unpack(data interface{}) error { + for _, i := range s.path() { + k := i.name + if i.alias != "" { + k = i.alias + } + + // Try to assert type of the value + switch f := data.(type) { + case map[string]interface{}: + data = f[k] + case []interface{}: + data = f + default: + fmt.Printf("type not found %s\n", f) + } + + if i.bind != nil { + marshalled, err := json.Marshal(data) + if err != nil { + return err + } + json.Unmarshal(marshalled, s.bind) + } + } + + return nil +} + +func (s *Selection) Client(c graphql.Client) *Selection { + sel := *s + sel.client = c + return &sel +} + +func (s *Selection) Execute(ctx context.Context) error { + if s.client == nil { + debug.PrintStack() + return fmt.Errorf("no client configured for selection") + } + + query, err := s.Build(ctx) + if err != nil { + return err + } + + var response any + err = s.client.MakeRequest(ctx, + &graphql.Request{ + Query: query, + }, + &graphql.Response{Data: &response}, + ) + if err != nil { + return err + } + + return s.unpack(response) +} + +type argument struct { + value any + + marshalled string + marshalledErr error + once sync.Once +} + +func (a *argument) marshal(ctx context.Context) error { + a.once.Do(func() { + a.marshalled, a.marshalledErr = MarshalGQL(ctx, a.value) + }) + return a.marshalledErr +} diff --git a/dagger/commitlint/dagger/main.go b/dagger/commitlint/dagger/main.go new file mode 100644 index 0000000..d9acd24 --- /dev/null +++ b/dagger/commitlint/dagger/main.go @@ -0,0 +1,37 @@ +// This module runs commitlint to validate conventional commits. +package main + +import "context" + +type Commitlint struct { + // +private + Ctr *Container +} + +func New( + // Commitlint image to use. + // +optional + // +default="commitlint/commitlint" + Image string, +) *Commitlint { + return &Commitlint{ + Ctr: dag.Container().From(Image), + } +} + +// Lint runs commitlint to lint commit messages. +// +// Example usage: dagger call lint --source /path/to/your/repo --args arg1 --args arg2 +func (m *Commitlint) Lint( + ctx context.Context, + // The directory of the repository. + source *Directory, + // +optional + // A list of arguments to pass to commitlint. + args []string, +) *Container { + return m.Ctr. + WithMountedDirectory("/src", source). + WithWorkdir("/src"). + WithExec(args) +} diff --git a/dagger/protoc-gen-doc/dagger.json b/dagger/protoc-gen-doc/dagger.json new file mode 100644 index 0000000..c29703f --- /dev/null +++ b/dagger/protoc-gen-doc/dagger.json @@ -0,0 +1,6 @@ +{ + "name": "protoc-gen-doc", + "sdk": "go", + "source": "dagger", + "engineVersion": "v0.10.2" +} diff --git a/dagger/protoc-gen-doc/dagger/.gitattributes b/dagger/protoc-gen-doc/dagger/.gitattributes new file mode 100644 index 0000000..b94d9fd --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/querybuilder/** linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated diff --git a/dagger/protoc-gen-doc/dagger/.gitignore b/dagger/protoc-gen-doc/dagger/.gitignore new file mode 100644 index 0000000..26b5515 --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/.gitignore @@ -0,0 +1,3 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder diff --git a/dagger/protoc-gen-doc/dagger/go.mod b/dagger/protoc-gen-doc/dagger/go.mod new file mode 100644 index 0000000..f6e4153 --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/go.mod @@ -0,0 +1,13 @@ +module main + +go 1.21.3 + +require ( + github.com/99designs/gqlgen v0.17.31 + github.com/Khan/genqlient v0.6.0 + github.com/vektah/gqlparser/v2 v2.5.6 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.6.0 +) + +require github.com/stretchr/testify v1.9.0 // indirect diff --git a/dagger/protoc-gen-doc/dagger/go.sum b/dagger/protoc-gen-doc/dagger/go.sum new file mode 100644 index 0000000..6b16e9f --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/go.sum @@ -0,0 +1,35 @@ +github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= +github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= +github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= +github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= +github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dagger/protoc-gen-doc/dagger/internal/querybuilder/marshal.go b/dagger/protoc-gen-doc/dagger/internal/querybuilder/marshal.go new file mode 100644 index 0000000..1f5468a --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/internal/querybuilder/marshal.go @@ -0,0 +1,162 @@ +package querybuilder + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + gqlgen "github.com/99designs/gqlgen/graphql" + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" +) + +// GraphQLMarshaller is an internal interface for marshalling an object into GraphQL. +type GraphQLMarshaller interface { + // XXX_GraphQLType is an internal function. It returns the native GraphQL type name + XXX_GraphQLType() string + // XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object + XXX_GraphQLIDType() string + // XXX_GraphqlID is an internal function. It returns the underlying type ID + XXX_GraphQLID(ctx context.Context) (string, error) + json.Marshaler +} + +const ( + GraphQLMarshallerType = "XXX_GraphQLType" + GraphQLMarshallerIDType = "XXX_GraphQLIDType" + GraphQLMarshallerID = "XXX_GraphQLID" +) + +type enum interface { + IsEnum() +} + +var ( + gqlMarshaller = reflect.TypeOf((*GraphQLMarshaller)(nil)).Elem() + enumT = reflect.TypeOf((*enum)(nil)).Elem() +) + +func MarshalGQL(ctx context.Context, v any) (string, error) { + return marshalValue(ctx, reflect.ValueOf(v)) +} + +func marshalValue(ctx context.Context, v reflect.Value) (string, error) { + t := v.Type() + + if t.Implements(gqlMarshaller) { + return marshalCustom(ctx, v) + } + + switch t.Kind() { + case reflect.Bool: + return fmt.Sprintf("%t", v.Bool()), nil + case reflect.Int: + return fmt.Sprintf("%d", v.Int()), nil + case reflect.String: + if t.Implements(enumT) { + // enums render as their literal value + return v.String(), nil + } + + // escape strings following graphQL spec + // https://github.com/graphql/graphql-spec/blob/main/spec/Section%202%20--%20Language.md#string-value + var buf bytes.Buffer + gqlgen.MarshalString(v.String()).MarshalGQL(&buf) + return buf.String(), nil + case reflect.Pointer, reflect.Interface: + if v.IsNil() { + return "null", nil + } + return marshalValue(ctx, v.Elem()) + case reflect.Slice: + n := v.Len() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + m, err := marshalValue(gctx, v.Index(i)) + if err != nil { + return err + } + elems[i] = m + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + return fmt.Sprintf("[%s]", strings.Join(elems, ",")), nil + case reflect.Struct: + n := v.NumField() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + f := t.Field(i) + fv := v.Field(i) + name := f.Name + jsonTag := strings.Split(f.Tag.Get("json"), ",") + if jsonTag[0] != "" { + name = jsonTag[0] + } + isOptional := slices.Contains(jsonTag[1:], "omitempty") + if isOptional && IsZeroValue(fv.Interface()) { + return nil + } + m, err := marshalValue(gctx, fv) + if err != nil { + return err + } + if m != `""` && m != "null" { + elems[i] = fmt.Sprintf("%s:%s", name, m) + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + nonNullElems := make([]string, 0, n) + for _, elem := range elems { + if elem != "" { + nonNullElems = append(nonNullElems, elem) + } + } + return fmt.Sprintf("{%s}", strings.Join(nonNullElems, ",")), nil + default: + panic(fmt.Errorf("unsupported argument of kind %s", t.Kind())) + } +} + +func marshalCustom(ctx context.Context, v reflect.Value) (string, error) { + result := v.MethodByName(GraphQLMarshallerID).Call([]reflect.Value{ + reflect.ValueOf(ctx), + }) + if len(result) != 2 { + panic(result) + } + err := result[1].Interface() + if err != nil { + return "", err.(error) + } + + return fmt.Sprintf("%q", result[0].String()), nil +} + +func IsZeroValue(value any) bool { + v := reflect.ValueOf(value) + kind := v.Type().Kind() + switch kind { + case reflect.Pointer: + return v.IsNil() + case reflect.Slice, reflect.Array: + return v.Len() == 0 + default: + return v.IsZero() + } +} diff --git a/dagger/protoc-gen-doc/dagger/internal/querybuilder/querybuilder.go b/dagger/protoc-gen-doc/dagger/internal/querybuilder/querybuilder.go new file mode 100644 index 0000000..64a32e0 --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/internal/querybuilder/querybuilder.go @@ -0,0 +1,204 @@ +package querybuilder + +import ( + "context" + "encoding/json" + "fmt" + "runtime/debug" + "strings" + "sync" + + "github.com/Khan/genqlient/graphql" + "golang.org/x/sync/errgroup" +) + +func Query() *Selection { + return &Selection{} +} + +type Selection struct { + name string + alias string + args map[string]*argument + bind interface{} + + prev *Selection + + client graphql.Client +} + +func (s *Selection) path() []*Selection { + selections := []*Selection{} + for sel := s; sel.prev != nil; sel = sel.prev { + selections = append([]*Selection{sel}, selections...) + } + + return selections +} + +func (s *Selection) Root() *Selection { + return &Selection{ + client: s.client, + } +} + +func (s *Selection) SelectWithAlias(alias, name string) *Selection { + sel := &Selection{ + name: name, + prev: s, + alias: alias, + client: s.client, + } + return sel +} + +func (s *Selection) Select(name string) *Selection { + return s.SelectWithAlias("", name) +} + +func (s *Selection) Arg(name string, value any) *Selection { + sel := *s + if sel.args == nil { + sel.args = map[string]*argument{} + } + + sel.args[name] = &argument{ + value: value, + } + return &sel +} + +func (s *Selection) Bind(v interface{}) *Selection { + sel := *s + sel.bind = v + return &sel +} + +func (s *Selection) marshalArguments(ctx context.Context) error { + eg, gctx := errgroup.WithContext(ctx) + for _, sel := range s.path() { + for _, arg := range sel.args { + arg := arg + eg.Go(func() error { + return arg.marshal(gctx) + }) + } + } + + return eg.Wait() +} + +func (s *Selection) Build(ctx context.Context) (string, error) { + if err := s.marshalArguments(ctx); err != nil { + return "", err + } + + var b strings.Builder + b.WriteString("query") + + path := s.path() + + for _, sel := range path { + b.WriteRune('{') + + if sel.alias != "" { + b.WriteString(sel.alias) + b.WriteRune(':') + } + + b.WriteString(sel.name) + + if len(sel.args) > 0 { + b.WriteRune('(') + i := 0 + for name, arg := range sel.args { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(name) + b.WriteRune(':') + b.WriteString(arg.marshalled) + i++ + } + b.WriteRune(')') + } + } + + b.WriteString(strings.Repeat("}", len(path))) + return b.String(), nil +} + +func (s *Selection) unpack(data interface{}) error { + for _, i := range s.path() { + k := i.name + if i.alias != "" { + k = i.alias + } + + // Try to assert type of the value + switch f := data.(type) { + case map[string]interface{}: + data = f[k] + case []interface{}: + data = f + default: + fmt.Printf("type not found %s\n", f) + } + + if i.bind != nil { + marshalled, err := json.Marshal(data) + if err != nil { + return err + } + json.Unmarshal(marshalled, s.bind) + } + } + + return nil +} + +func (s *Selection) Client(c graphql.Client) *Selection { + sel := *s + sel.client = c + return &sel +} + +func (s *Selection) Execute(ctx context.Context) error { + if s.client == nil { + debug.PrintStack() + return fmt.Errorf("no client configured for selection") + } + + query, err := s.Build(ctx) + if err != nil { + return err + } + + var response any + err = s.client.MakeRequest(ctx, + &graphql.Request{ + Query: query, + }, + &graphql.Response{Data: &response}, + ) + if err != nil { + return err + } + + return s.unpack(response) +} + +type argument struct { + value any + + marshalled string + marshalledErr error + once sync.Once +} + +func (a *argument) marshal(ctx context.Context) error { + a.once.Do(func() { + a.marshalled, a.marshalledErr = MarshalGQL(ctx, a.value) + }) + return a.marshalledErr +} diff --git a/dagger/protoc-gen-doc/dagger/main.go b/dagger/protoc-gen-doc/dagger/main.go new file mode 100644 index 0000000..2ae0aa2 --- /dev/null +++ b/dagger/protoc-gen-doc/dagger/main.go @@ -0,0 +1,39 @@ +package main + +import "fmt" + +type ProtocGenDoc struct { + // +private + Ctr *Container +} + +func New( + // ProtocGenDoc image to use. + // +optional + // +default="pseudomuto/protoc-gen-doc" + Image string, +) *ProtocGenDoc { + return &ProtocGenDoc{ + Ctr: dag.Container().From(Image), + } +} + +// Generate runs protoc-gen-doc on proto files, returning the generated documentation as a directory. +// +// Example usage: dagger call run --proto-dir /path/ --doc-opt "markdown,docs.md" +func (m *ProtocGenDoc) Generate( + // The directory of the proto files. + protoDir *Directory, + // +optional + // +default="markdown,docs.md" + // The doc_opt flag to pass to protoc-gen-doc. + docOpt string, +) *Directory { + const outDir = "/out" + + return m.Ctr. + WithMountedDirectory("/protos", protoDir). + WithExec([]string{"mkdir", outDir}, ContainerWithExecOpts{SkipEntrypoint: true}). + WithExec([]string{fmt.Sprintf("--doc_opt=%v", docOpt)}). + Directory(outDir) +} diff --git a/dagger/protoc-gen-go-grpc/dagger.json b/dagger/protoc-gen-go-grpc/dagger.json new file mode 100644 index 0000000..e1c99e2 --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger.json @@ -0,0 +1,6 @@ +{ + "name": "protoc-gen-go-grpc", + "sdk": "go", + "source": "dagger", + "engineVersion": "v0.10.2" +} diff --git a/dagger/protoc-gen-go-grpc/dagger/.gitattributes b/dagger/protoc-gen-go-grpc/dagger/.gitattributes new file mode 100644 index 0000000..6911ed2 --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/.gitattributes @@ -0,0 +1,3 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated diff --git a/dagger/protoc-gen-go-grpc/dagger/.gitignore b/dagger/protoc-gen-go-grpc/dagger/.gitignore new file mode 100644 index 0000000..26b5515 --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/.gitignore @@ -0,0 +1,3 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder diff --git a/dagger/protoc-gen-go-grpc/dagger/go.mod b/dagger/protoc-gen-go-grpc/dagger/go.mod new file mode 100644 index 0000000..1046b5f --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/go.mod @@ -0,0 +1,13 @@ +module dagger/protoc-gen-go-grpc + +go 1.21.7 + +require ( + github.com/99designs/gqlgen v0.17.31 + github.com/Khan/genqlient v0.6.0 + github.com/vektah/gqlparser/v2 v2.5.6 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.6.0 +) + +require github.com/stretchr/testify v1.9.0 // indirect diff --git a/dagger/protoc-gen-go-grpc/dagger/go.sum b/dagger/protoc-gen-go-grpc/dagger/go.sum new file mode 100644 index 0000000..6b16e9f --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/go.sum @@ -0,0 +1,35 @@ +github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= +github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= +github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= +github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= +github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dagger/protoc-gen-go-grpc/dagger/internal/querybuilder/marshal.go b/dagger/protoc-gen-go-grpc/dagger/internal/querybuilder/marshal.go new file mode 100644 index 0000000..1f5468a --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/internal/querybuilder/marshal.go @@ -0,0 +1,162 @@ +package querybuilder + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + gqlgen "github.com/99designs/gqlgen/graphql" + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" +) + +// GraphQLMarshaller is an internal interface for marshalling an object into GraphQL. +type GraphQLMarshaller interface { + // XXX_GraphQLType is an internal function. It returns the native GraphQL type name + XXX_GraphQLType() string + // XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object + XXX_GraphQLIDType() string + // XXX_GraphqlID is an internal function. It returns the underlying type ID + XXX_GraphQLID(ctx context.Context) (string, error) + json.Marshaler +} + +const ( + GraphQLMarshallerType = "XXX_GraphQLType" + GraphQLMarshallerIDType = "XXX_GraphQLIDType" + GraphQLMarshallerID = "XXX_GraphQLID" +) + +type enum interface { + IsEnum() +} + +var ( + gqlMarshaller = reflect.TypeOf((*GraphQLMarshaller)(nil)).Elem() + enumT = reflect.TypeOf((*enum)(nil)).Elem() +) + +func MarshalGQL(ctx context.Context, v any) (string, error) { + return marshalValue(ctx, reflect.ValueOf(v)) +} + +func marshalValue(ctx context.Context, v reflect.Value) (string, error) { + t := v.Type() + + if t.Implements(gqlMarshaller) { + return marshalCustom(ctx, v) + } + + switch t.Kind() { + case reflect.Bool: + return fmt.Sprintf("%t", v.Bool()), nil + case reflect.Int: + return fmt.Sprintf("%d", v.Int()), nil + case reflect.String: + if t.Implements(enumT) { + // enums render as their literal value + return v.String(), nil + } + + // escape strings following graphQL spec + // https://github.com/graphql/graphql-spec/blob/main/spec/Section%202%20--%20Language.md#string-value + var buf bytes.Buffer + gqlgen.MarshalString(v.String()).MarshalGQL(&buf) + return buf.String(), nil + case reflect.Pointer, reflect.Interface: + if v.IsNil() { + return "null", nil + } + return marshalValue(ctx, v.Elem()) + case reflect.Slice: + n := v.Len() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + m, err := marshalValue(gctx, v.Index(i)) + if err != nil { + return err + } + elems[i] = m + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + return fmt.Sprintf("[%s]", strings.Join(elems, ",")), nil + case reflect.Struct: + n := v.NumField() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + f := t.Field(i) + fv := v.Field(i) + name := f.Name + jsonTag := strings.Split(f.Tag.Get("json"), ",") + if jsonTag[0] != "" { + name = jsonTag[0] + } + isOptional := slices.Contains(jsonTag[1:], "omitempty") + if isOptional && IsZeroValue(fv.Interface()) { + return nil + } + m, err := marshalValue(gctx, fv) + if err != nil { + return err + } + if m != `""` && m != "null" { + elems[i] = fmt.Sprintf("%s:%s", name, m) + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + nonNullElems := make([]string, 0, n) + for _, elem := range elems { + if elem != "" { + nonNullElems = append(nonNullElems, elem) + } + } + return fmt.Sprintf("{%s}", strings.Join(nonNullElems, ",")), nil + default: + panic(fmt.Errorf("unsupported argument of kind %s", t.Kind())) + } +} + +func marshalCustom(ctx context.Context, v reflect.Value) (string, error) { + result := v.MethodByName(GraphQLMarshallerID).Call([]reflect.Value{ + reflect.ValueOf(ctx), + }) + if len(result) != 2 { + panic(result) + } + err := result[1].Interface() + if err != nil { + return "", err.(error) + } + + return fmt.Sprintf("%q", result[0].String()), nil +} + +func IsZeroValue(value any) bool { + v := reflect.ValueOf(value) + kind := v.Type().Kind() + switch kind { + case reflect.Pointer: + return v.IsNil() + case reflect.Slice, reflect.Array: + return v.Len() == 0 + default: + return v.IsZero() + } +} diff --git a/dagger/protoc-gen-go-grpc/dagger/internal/querybuilder/querybuilder.go b/dagger/protoc-gen-go-grpc/dagger/internal/querybuilder/querybuilder.go new file mode 100644 index 0000000..64a32e0 --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/internal/querybuilder/querybuilder.go @@ -0,0 +1,204 @@ +package querybuilder + +import ( + "context" + "encoding/json" + "fmt" + "runtime/debug" + "strings" + "sync" + + "github.com/Khan/genqlient/graphql" + "golang.org/x/sync/errgroup" +) + +func Query() *Selection { + return &Selection{} +} + +type Selection struct { + name string + alias string + args map[string]*argument + bind interface{} + + prev *Selection + + client graphql.Client +} + +func (s *Selection) path() []*Selection { + selections := []*Selection{} + for sel := s; sel.prev != nil; sel = sel.prev { + selections = append([]*Selection{sel}, selections...) + } + + return selections +} + +func (s *Selection) Root() *Selection { + return &Selection{ + client: s.client, + } +} + +func (s *Selection) SelectWithAlias(alias, name string) *Selection { + sel := &Selection{ + name: name, + prev: s, + alias: alias, + client: s.client, + } + return sel +} + +func (s *Selection) Select(name string) *Selection { + return s.SelectWithAlias("", name) +} + +func (s *Selection) Arg(name string, value any) *Selection { + sel := *s + if sel.args == nil { + sel.args = map[string]*argument{} + } + + sel.args[name] = &argument{ + value: value, + } + return &sel +} + +func (s *Selection) Bind(v interface{}) *Selection { + sel := *s + sel.bind = v + return &sel +} + +func (s *Selection) marshalArguments(ctx context.Context) error { + eg, gctx := errgroup.WithContext(ctx) + for _, sel := range s.path() { + for _, arg := range sel.args { + arg := arg + eg.Go(func() error { + return arg.marshal(gctx) + }) + } + } + + return eg.Wait() +} + +func (s *Selection) Build(ctx context.Context) (string, error) { + if err := s.marshalArguments(ctx); err != nil { + return "", err + } + + var b strings.Builder + b.WriteString("query") + + path := s.path() + + for _, sel := range path { + b.WriteRune('{') + + if sel.alias != "" { + b.WriteString(sel.alias) + b.WriteRune(':') + } + + b.WriteString(sel.name) + + if len(sel.args) > 0 { + b.WriteRune('(') + i := 0 + for name, arg := range sel.args { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(name) + b.WriteRune(':') + b.WriteString(arg.marshalled) + i++ + } + b.WriteRune(')') + } + } + + b.WriteString(strings.Repeat("}", len(path))) + return b.String(), nil +} + +func (s *Selection) unpack(data interface{}) error { + for _, i := range s.path() { + k := i.name + if i.alias != "" { + k = i.alias + } + + // Try to assert type of the value + switch f := data.(type) { + case map[string]interface{}: + data = f[k] + case []interface{}: + data = f + default: + fmt.Printf("type not found %s\n", f) + } + + if i.bind != nil { + marshalled, err := json.Marshal(data) + if err != nil { + return err + } + json.Unmarshal(marshalled, s.bind) + } + } + + return nil +} + +func (s *Selection) Client(c graphql.Client) *Selection { + sel := *s + sel.client = c + return &sel +} + +func (s *Selection) Execute(ctx context.Context) error { + if s.client == nil { + debug.PrintStack() + return fmt.Errorf("no client configured for selection") + } + + query, err := s.Build(ctx) + if err != nil { + return err + } + + var response any + err = s.client.MakeRequest(ctx, + &graphql.Request{ + Query: query, + }, + &graphql.Response{Data: &response}, + ) + if err != nil { + return err + } + + return s.unpack(response) +} + +type argument struct { + value any + + marshalled string + marshalledErr error + once sync.Once +} + +func (a *argument) marshal(ctx context.Context) error { + a.once.Do(func() { + a.marshalled, a.marshalledErr = MarshalGQL(ctx, a.value) + }) + return a.marshalledErr +} diff --git a/dagger/protoc-gen-go-grpc/dagger/main.go b/dagger/protoc-gen-go-grpc/dagger/main.go new file mode 100644 index 0000000..c7646e5 --- /dev/null +++ b/dagger/protoc-gen-go-grpc/dagger/main.go @@ -0,0 +1,91 @@ +// A generated module for ProtocGenGoGRPC functions + +package main + +import ( + "context" + "fmt" + "path" +) + +type ProtocGenGoGRPC struct { + // +private + Ctr *Container +} + +func New( + // Custom image to use to run protoc. + // +optional + // +default="golang:1.22-bookworm" + goImage string, + // +optional + // +default="26.1" + protobufVersion string, + // +optional + // +default="v1.33.0" + protocGenGoVersion string, + // +optional + // +default="v1.3.0" + protocGenGoGRPCVersion string, +) *ProtocGenGoGRPC { + protobufRelURL := fmt.Sprintf("https://github.com/protocolbuffers/protobuf/releases/download/v%v/protoc-%v-linux-x86_64.zip", + protobufVersion, protobufVersion) + + protobuf := dag.Container(). + From(goImage). + WithExec([]string{"apt", "update"}). + WithExec([]string{"apt", "install", "-y", "unzip"}). + WithExec([]string{"curl", "-LO", protobufRelURL}). + WithExec([]string{"unzip", "protoc-*.zip", "-d", "/usr/local"}). + WithExec([]string{"rm", "-rf", "protoc-*.zip"}). + WithExec([]string{"apt", "purge", "-y", "unzip"}). + WithExec([]string{"rm", "-rf", "/var/lib/apt/lists/*"}). + WithExec([]string{"go", "install", + fmt.Sprintf("google.golang.org/protobuf/cmd/protoc-gen-go@%v", protocGenGoVersion)}). + WithExec([]string{"go", "install", + fmt.Sprintf("google.golang.org/grpc/cmd/protoc-gen-go-grpc@%v", protocGenGoGRPCVersion)}) + + return &ProtocGenGoGRPC{ + Ctr: protobuf, + } +} + +// Container get the current container +func (m *ProtocGenGoGRPC) Container() *Container { + return m.Ctr +} + +// Run runs protoc on proto files, returning the generated go files as a directory. +func (m *ProtocGenGoGRPC) Run( + ctx context.Context, + // The source directory. + source *Directory, + // The path to the proto files, relative to the source directory. + protoPath string, + // go_opt flag to pass to protoc. + goOpt string, + // go-grpc_opt flag to pass to protoc. + goGRPCOpt string, +) (*Directory, error) { + args := []string{"/usr/local/bin/protoc"} + args = append(args, "--go_out=/out/") + args = append(args, fmt.Sprintf("--go_opt=%v", goOpt)) + args = append(args, "--go-grpc_out=/out/") + args = append(args, fmt.Sprintf("--go-grpc_opt=%v", goGRPCOpt)) + protos, err := source.Directory(protoPath).Entries(ctx) + if err != nil { + return nil, err + } + for i := range protos { + protos[i] = path.Join(protoPath, protos[i]) + } + args = append(args, protos...) + + buildDir := m.Ctr. + WithMountedDirectory("/src", source). + WithExec([]string{"mkdir", "-p", "/out"}). + WithWorkdir("/src"). + WithExec(args). + Directory("/out") + return buildDir, nil +} diff --git a/dagger/protolint/dagger.json b/dagger/protolint/dagger.json new file mode 100644 index 0000000..b45b5e6 --- /dev/null +++ b/dagger/protolint/dagger.json @@ -0,0 +1,6 @@ +{ + "name": "protolint", + "sdk": "go", + "source": "dagger", + "engineVersion": "v0.10.2" +} diff --git a/dagger/protolint/dagger/.gitattributes b/dagger/protolint/dagger/.gitattributes new file mode 100644 index 0000000..b94d9fd --- /dev/null +++ b/dagger/protolint/dagger/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/querybuilder/** linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated diff --git a/dagger/protolint/dagger/.gitignore b/dagger/protolint/dagger/.gitignore new file mode 100644 index 0000000..26b5515 --- /dev/null +++ b/dagger/protolint/dagger/.gitignore @@ -0,0 +1,3 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder diff --git a/dagger/protolint/dagger/go.mod b/dagger/protolint/dagger/go.mod new file mode 100644 index 0000000..f6e4153 --- /dev/null +++ b/dagger/protolint/dagger/go.mod @@ -0,0 +1,13 @@ +module main + +go 1.21.3 + +require ( + github.com/99designs/gqlgen v0.17.31 + github.com/Khan/genqlient v0.6.0 + github.com/vektah/gqlparser/v2 v2.5.6 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.6.0 +) + +require github.com/stretchr/testify v1.9.0 // indirect diff --git a/dagger/protolint/dagger/go.sum b/dagger/protolint/dagger/go.sum new file mode 100644 index 0000000..6b16e9f --- /dev/null +++ b/dagger/protolint/dagger/go.sum @@ -0,0 +1,35 @@ +github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= +github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= +github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= +github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= +github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dagger/protolint/dagger/internal/querybuilder/marshal.go b/dagger/protolint/dagger/internal/querybuilder/marshal.go new file mode 100644 index 0000000..1f5468a --- /dev/null +++ b/dagger/protolint/dagger/internal/querybuilder/marshal.go @@ -0,0 +1,162 @@ +package querybuilder + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + gqlgen "github.com/99designs/gqlgen/graphql" + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" +) + +// GraphQLMarshaller is an internal interface for marshalling an object into GraphQL. +type GraphQLMarshaller interface { + // XXX_GraphQLType is an internal function. It returns the native GraphQL type name + XXX_GraphQLType() string + // XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object + XXX_GraphQLIDType() string + // XXX_GraphqlID is an internal function. It returns the underlying type ID + XXX_GraphQLID(ctx context.Context) (string, error) + json.Marshaler +} + +const ( + GraphQLMarshallerType = "XXX_GraphQLType" + GraphQLMarshallerIDType = "XXX_GraphQLIDType" + GraphQLMarshallerID = "XXX_GraphQLID" +) + +type enum interface { + IsEnum() +} + +var ( + gqlMarshaller = reflect.TypeOf((*GraphQLMarshaller)(nil)).Elem() + enumT = reflect.TypeOf((*enum)(nil)).Elem() +) + +func MarshalGQL(ctx context.Context, v any) (string, error) { + return marshalValue(ctx, reflect.ValueOf(v)) +} + +func marshalValue(ctx context.Context, v reflect.Value) (string, error) { + t := v.Type() + + if t.Implements(gqlMarshaller) { + return marshalCustom(ctx, v) + } + + switch t.Kind() { + case reflect.Bool: + return fmt.Sprintf("%t", v.Bool()), nil + case reflect.Int: + return fmt.Sprintf("%d", v.Int()), nil + case reflect.String: + if t.Implements(enumT) { + // enums render as their literal value + return v.String(), nil + } + + // escape strings following graphQL spec + // https://github.com/graphql/graphql-spec/blob/main/spec/Section%202%20--%20Language.md#string-value + var buf bytes.Buffer + gqlgen.MarshalString(v.String()).MarshalGQL(&buf) + return buf.String(), nil + case reflect.Pointer, reflect.Interface: + if v.IsNil() { + return "null", nil + } + return marshalValue(ctx, v.Elem()) + case reflect.Slice: + n := v.Len() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + m, err := marshalValue(gctx, v.Index(i)) + if err != nil { + return err + } + elems[i] = m + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + return fmt.Sprintf("[%s]", strings.Join(elems, ",")), nil + case reflect.Struct: + n := v.NumField() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + f := t.Field(i) + fv := v.Field(i) + name := f.Name + jsonTag := strings.Split(f.Tag.Get("json"), ",") + if jsonTag[0] != "" { + name = jsonTag[0] + } + isOptional := slices.Contains(jsonTag[1:], "omitempty") + if isOptional && IsZeroValue(fv.Interface()) { + return nil + } + m, err := marshalValue(gctx, fv) + if err != nil { + return err + } + if m != `""` && m != "null" { + elems[i] = fmt.Sprintf("%s:%s", name, m) + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + nonNullElems := make([]string, 0, n) + for _, elem := range elems { + if elem != "" { + nonNullElems = append(nonNullElems, elem) + } + } + return fmt.Sprintf("{%s}", strings.Join(nonNullElems, ",")), nil + default: + panic(fmt.Errorf("unsupported argument of kind %s", t.Kind())) + } +} + +func marshalCustom(ctx context.Context, v reflect.Value) (string, error) { + result := v.MethodByName(GraphQLMarshallerID).Call([]reflect.Value{ + reflect.ValueOf(ctx), + }) + if len(result) != 2 { + panic(result) + } + err := result[1].Interface() + if err != nil { + return "", err.(error) + } + + return fmt.Sprintf("%q", result[0].String()), nil +} + +func IsZeroValue(value any) bool { + v := reflect.ValueOf(value) + kind := v.Type().Kind() + switch kind { + case reflect.Pointer: + return v.IsNil() + case reflect.Slice, reflect.Array: + return v.Len() == 0 + default: + return v.IsZero() + } +} diff --git a/dagger/protolint/dagger/internal/querybuilder/querybuilder.go b/dagger/protolint/dagger/internal/querybuilder/querybuilder.go new file mode 100644 index 0000000..64a32e0 --- /dev/null +++ b/dagger/protolint/dagger/internal/querybuilder/querybuilder.go @@ -0,0 +1,204 @@ +package querybuilder + +import ( + "context" + "encoding/json" + "fmt" + "runtime/debug" + "strings" + "sync" + + "github.com/Khan/genqlient/graphql" + "golang.org/x/sync/errgroup" +) + +func Query() *Selection { + return &Selection{} +} + +type Selection struct { + name string + alias string + args map[string]*argument + bind interface{} + + prev *Selection + + client graphql.Client +} + +func (s *Selection) path() []*Selection { + selections := []*Selection{} + for sel := s; sel.prev != nil; sel = sel.prev { + selections = append([]*Selection{sel}, selections...) + } + + return selections +} + +func (s *Selection) Root() *Selection { + return &Selection{ + client: s.client, + } +} + +func (s *Selection) SelectWithAlias(alias, name string) *Selection { + sel := &Selection{ + name: name, + prev: s, + alias: alias, + client: s.client, + } + return sel +} + +func (s *Selection) Select(name string) *Selection { + return s.SelectWithAlias("", name) +} + +func (s *Selection) Arg(name string, value any) *Selection { + sel := *s + if sel.args == nil { + sel.args = map[string]*argument{} + } + + sel.args[name] = &argument{ + value: value, + } + return &sel +} + +func (s *Selection) Bind(v interface{}) *Selection { + sel := *s + sel.bind = v + return &sel +} + +func (s *Selection) marshalArguments(ctx context.Context) error { + eg, gctx := errgroup.WithContext(ctx) + for _, sel := range s.path() { + for _, arg := range sel.args { + arg := arg + eg.Go(func() error { + return arg.marshal(gctx) + }) + } + } + + return eg.Wait() +} + +func (s *Selection) Build(ctx context.Context) (string, error) { + if err := s.marshalArguments(ctx); err != nil { + return "", err + } + + var b strings.Builder + b.WriteString("query") + + path := s.path() + + for _, sel := range path { + b.WriteRune('{') + + if sel.alias != "" { + b.WriteString(sel.alias) + b.WriteRune(':') + } + + b.WriteString(sel.name) + + if len(sel.args) > 0 { + b.WriteRune('(') + i := 0 + for name, arg := range sel.args { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(name) + b.WriteRune(':') + b.WriteString(arg.marshalled) + i++ + } + b.WriteRune(')') + } + } + + b.WriteString(strings.Repeat("}", len(path))) + return b.String(), nil +} + +func (s *Selection) unpack(data interface{}) error { + for _, i := range s.path() { + k := i.name + if i.alias != "" { + k = i.alias + } + + // Try to assert type of the value + switch f := data.(type) { + case map[string]interface{}: + data = f[k] + case []interface{}: + data = f + default: + fmt.Printf("type not found %s\n", f) + } + + if i.bind != nil { + marshalled, err := json.Marshal(data) + if err != nil { + return err + } + json.Unmarshal(marshalled, s.bind) + } + } + + return nil +} + +func (s *Selection) Client(c graphql.Client) *Selection { + sel := *s + sel.client = c + return &sel +} + +func (s *Selection) Execute(ctx context.Context) error { + if s.client == nil { + debug.PrintStack() + return fmt.Errorf("no client configured for selection") + } + + query, err := s.Build(ctx) + if err != nil { + return err + } + + var response any + err = s.client.MakeRequest(ctx, + &graphql.Request{ + Query: query, + }, + &graphql.Response{Data: &response}, + ) + if err != nil { + return err + } + + return s.unpack(response) +} + +type argument struct { + value any + + marshalled string + marshalledErr error + once sync.Once +} + +func (a *argument) marshal(ctx context.Context) error { + a.once.Do(func() { + a.marshalled, a.marshalledErr = MarshalGQL(ctx, a.value) + }) + return a.marshalledErr +} diff --git a/dagger/protolint/dagger/main.go b/dagger/protolint/dagger/main.go new file mode 100644 index 0000000..e7e8f36 --- /dev/null +++ b/dagger/protolint/dagger/main.go @@ -0,0 +1,36 @@ +package main + +import "context" + +type Protolint struct { + // +private + Ctr *Container +} + +func New( + // Protolint image to use. + // +optional + // +default="yoheimuta/protolint" + Image string, +) *Protolint { + return &Protolint{ + Ctr: dag.Container().From(Image), + } +} + +// Lint runs protolint on proto files. +// +// Example usage: dagger call run --source /path/ --args "-config_path=.protolint.yaml" --args . +func (m *Protolint) Lint( + ctx context.Context, + // The directory of the repository. + source *Directory, + // A list of arguments to pass to commitlint. + // +optional + args []string, +) *Container { + return m.Ctr. + WithMountedDirectory("/src", source). + WithWorkdir("/src"). + WithExec(append([]string{"lint"}, args...)) +} diff --git a/dagger/uncommitted/dagger.json b/dagger/uncommitted/dagger.json new file mode 100644 index 0000000..86fe622 --- /dev/null +++ b/dagger/uncommitted/dagger.json @@ -0,0 +1,6 @@ +{ + "name": "uncommitted", + "sdk": "go", + "source": "dagger", + "engineVersion": "v0.10.2" +} diff --git a/dagger/uncommitted/dagger/.gitattributes b/dagger/uncommitted/dagger/.gitattributes new file mode 100644 index 0000000..6911ed2 --- /dev/null +++ b/dagger/uncommitted/dagger/.gitattributes @@ -0,0 +1,3 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated diff --git a/dagger/uncommitted/dagger/.gitignore b/dagger/uncommitted/dagger/.gitignore new file mode 100644 index 0000000..26b5515 --- /dev/null +++ b/dagger/uncommitted/dagger/.gitignore @@ -0,0 +1,3 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder diff --git a/dagger/uncommitted/dagger/go.mod b/dagger/uncommitted/dagger/go.mod new file mode 100644 index 0000000..d75d95a --- /dev/null +++ b/dagger/uncommitted/dagger/go.mod @@ -0,0 +1,13 @@ +module dagger/uncommitted + +go 1.21.7 + +require ( + github.com/99designs/gqlgen v0.17.31 + github.com/Khan/genqlient v0.6.0 + github.com/vektah/gqlparser/v2 v2.5.6 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.6.0 +) + +require github.com/stretchr/testify v1.9.0 // indirect diff --git a/dagger/uncommitted/dagger/go.sum b/dagger/uncommitted/dagger/go.sum new file mode 100644 index 0000000..6b16e9f --- /dev/null +++ b/dagger/uncommitted/dagger/go.sum @@ -0,0 +1,35 @@ +github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= +github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= +github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= +github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= +github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dagger/uncommitted/dagger/internal/querybuilder/marshal.go b/dagger/uncommitted/dagger/internal/querybuilder/marshal.go new file mode 100644 index 0000000..1f5468a --- /dev/null +++ b/dagger/uncommitted/dagger/internal/querybuilder/marshal.go @@ -0,0 +1,162 @@ +package querybuilder + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + gqlgen "github.com/99designs/gqlgen/graphql" + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" +) + +// GraphQLMarshaller is an internal interface for marshalling an object into GraphQL. +type GraphQLMarshaller interface { + // XXX_GraphQLType is an internal function. It returns the native GraphQL type name + XXX_GraphQLType() string + // XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object + XXX_GraphQLIDType() string + // XXX_GraphqlID is an internal function. It returns the underlying type ID + XXX_GraphQLID(ctx context.Context) (string, error) + json.Marshaler +} + +const ( + GraphQLMarshallerType = "XXX_GraphQLType" + GraphQLMarshallerIDType = "XXX_GraphQLIDType" + GraphQLMarshallerID = "XXX_GraphQLID" +) + +type enum interface { + IsEnum() +} + +var ( + gqlMarshaller = reflect.TypeOf((*GraphQLMarshaller)(nil)).Elem() + enumT = reflect.TypeOf((*enum)(nil)).Elem() +) + +func MarshalGQL(ctx context.Context, v any) (string, error) { + return marshalValue(ctx, reflect.ValueOf(v)) +} + +func marshalValue(ctx context.Context, v reflect.Value) (string, error) { + t := v.Type() + + if t.Implements(gqlMarshaller) { + return marshalCustom(ctx, v) + } + + switch t.Kind() { + case reflect.Bool: + return fmt.Sprintf("%t", v.Bool()), nil + case reflect.Int: + return fmt.Sprintf("%d", v.Int()), nil + case reflect.String: + if t.Implements(enumT) { + // enums render as their literal value + return v.String(), nil + } + + // escape strings following graphQL spec + // https://github.com/graphql/graphql-spec/blob/main/spec/Section%202%20--%20Language.md#string-value + var buf bytes.Buffer + gqlgen.MarshalString(v.String()).MarshalGQL(&buf) + return buf.String(), nil + case reflect.Pointer, reflect.Interface: + if v.IsNil() { + return "null", nil + } + return marshalValue(ctx, v.Elem()) + case reflect.Slice: + n := v.Len() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + m, err := marshalValue(gctx, v.Index(i)) + if err != nil { + return err + } + elems[i] = m + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + return fmt.Sprintf("[%s]", strings.Join(elems, ",")), nil + case reflect.Struct: + n := v.NumField() + elems := make([]string, n) + eg, gctx := errgroup.WithContext(ctx) + for i := 0; i < n; i++ { + i := i + eg.Go(func() error { + f := t.Field(i) + fv := v.Field(i) + name := f.Name + jsonTag := strings.Split(f.Tag.Get("json"), ",") + if jsonTag[0] != "" { + name = jsonTag[0] + } + isOptional := slices.Contains(jsonTag[1:], "omitempty") + if isOptional && IsZeroValue(fv.Interface()) { + return nil + } + m, err := marshalValue(gctx, fv) + if err != nil { + return err + } + if m != `""` && m != "null" { + elems[i] = fmt.Sprintf("%s:%s", name, m) + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return "", err + } + nonNullElems := make([]string, 0, n) + for _, elem := range elems { + if elem != "" { + nonNullElems = append(nonNullElems, elem) + } + } + return fmt.Sprintf("{%s}", strings.Join(nonNullElems, ",")), nil + default: + panic(fmt.Errorf("unsupported argument of kind %s", t.Kind())) + } +} + +func marshalCustom(ctx context.Context, v reflect.Value) (string, error) { + result := v.MethodByName(GraphQLMarshallerID).Call([]reflect.Value{ + reflect.ValueOf(ctx), + }) + if len(result) != 2 { + panic(result) + } + err := result[1].Interface() + if err != nil { + return "", err.(error) + } + + return fmt.Sprintf("%q", result[0].String()), nil +} + +func IsZeroValue(value any) bool { + v := reflect.ValueOf(value) + kind := v.Type().Kind() + switch kind { + case reflect.Pointer: + return v.IsNil() + case reflect.Slice, reflect.Array: + return v.Len() == 0 + default: + return v.IsZero() + } +} diff --git a/dagger/uncommitted/dagger/internal/querybuilder/querybuilder.go b/dagger/uncommitted/dagger/internal/querybuilder/querybuilder.go new file mode 100644 index 0000000..64a32e0 --- /dev/null +++ b/dagger/uncommitted/dagger/internal/querybuilder/querybuilder.go @@ -0,0 +1,204 @@ +package querybuilder + +import ( + "context" + "encoding/json" + "fmt" + "runtime/debug" + "strings" + "sync" + + "github.com/Khan/genqlient/graphql" + "golang.org/x/sync/errgroup" +) + +func Query() *Selection { + return &Selection{} +} + +type Selection struct { + name string + alias string + args map[string]*argument + bind interface{} + + prev *Selection + + client graphql.Client +} + +func (s *Selection) path() []*Selection { + selections := []*Selection{} + for sel := s; sel.prev != nil; sel = sel.prev { + selections = append([]*Selection{sel}, selections...) + } + + return selections +} + +func (s *Selection) Root() *Selection { + return &Selection{ + client: s.client, + } +} + +func (s *Selection) SelectWithAlias(alias, name string) *Selection { + sel := &Selection{ + name: name, + prev: s, + alias: alias, + client: s.client, + } + return sel +} + +func (s *Selection) Select(name string) *Selection { + return s.SelectWithAlias("", name) +} + +func (s *Selection) Arg(name string, value any) *Selection { + sel := *s + if sel.args == nil { + sel.args = map[string]*argument{} + } + + sel.args[name] = &argument{ + value: value, + } + return &sel +} + +func (s *Selection) Bind(v interface{}) *Selection { + sel := *s + sel.bind = v + return &sel +} + +func (s *Selection) marshalArguments(ctx context.Context) error { + eg, gctx := errgroup.WithContext(ctx) + for _, sel := range s.path() { + for _, arg := range sel.args { + arg := arg + eg.Go(func() error { + return arg.marshal(gctx) + }) + } + } + + return eg.Wait() +} + +func (s *Selection) Build(ctx context.Context) (string, error) { + if err := s.marshalArguments(ctx); err != nil { + return "", err + } + + var b strings.Builder + b.WriteString("query") + + path := s.path() + + for _, sel := range path { + b.WriteRune('{') + + if sel.alias != "" { + b.WriteString(sel.alias) + b.WriteRune(':') + } + + b.WriteString(sel.name) + + if len(sel.args) > 0 { + b.WriteRune('(') + i := 0 + for name, arg := range sel.args { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(name) + b.WriteRune(':') + b.WriteString(arg.marshalled) + i++ + } + b.WriteRune(')') + } + } + + b.WriteString(strings.Repeat("}", len(path))) + return b.String(), nil +} + +func (s *Selection) unpack(data interface{}) error { + for _, i := range s.path() { + k := i.name + if i.alias != "" { + k = i.alias + } + + // Try to assert type of the value + switch f := data.(type) { + case map[string]interface{}: + data = f[k] + case []interface{}: + data = f + default: + fmt.Printf("type not found %s\n", f) + } + + if i.bind != nil { + marshalled, err := json.Marshal(data) + if err != nil { + return err + } + json.Unmarshal(marshalled, s.bind) + } + } + + return nil +} + +func (s *Selection) Client(c graphql.Client) *Selection { + sel := *s + sel.client = c + return &sel +} + +func (s *Selection) Execute(ctx context.Context) error { + if s.client == nil { + debug.PrintStack() + return fmt.Errorf("no client configured for selection") + } + + query, err := s.Build(ctx) + if err != nil { + return err + } + + var response any + err = s.client.MakeRequest(ctx, + &graphql.Request{ + Query: query, + }, + &graphql.Response{Data: &response}, + ) + if err != nil { + return err + } + + return s.unpack(response) +} + +type argument struct { + value any + + marshalled string + marshalledErr error + once sync.Once +} + +func (a *argument) marshal(ctx context.Context) error { + a.once.Do(func() { + a.marshalled, a.marshalledErr = MarshalGQL(ctx, a.value) + }) + return a.marshalledErr +} diff --git a/dagger/uncommitted/dagger/main.go b/dagger/uncommitted/dagger/main.go new file mode 100644 index 0000000..ad3e06a --- /dev/null +++ b/dagger/uncommitted/dagger/main.go @@ -0,0 +1,31 @@ +// This module checks for uncommitted git changes + +package main + +type Uncommitted struct { + // +private + Ctr *Container +} + +func New( + // Python image to use. + // +optional + // +default="python:3.12-alpine" + Image string, +) *Uncommitted { + return &Uncommitted{ + Ctr: dag.Container().From(Image). + WithExec([]string{"apk", "add", "git"}). + WithExec([]string{"pip", "install", "check-uncommitted-git-changes"}), + } +} + +// CheckUncommitted runs check_uncommitted_git_changes +// +// Example usage: dagger call check-uncommitted --source /path/to/your/repo +func (m *Uncommitted) CheckUncommitted(source *Directory) *Container { + return m.Ctr. + WithMountedDirectory("/src", source). + WithWorkdir("/src"). + WithExec([]string{"check_uncommitted_git_changes"}) +} diff --git a/docs/docs.md b/docs/docs.md new file mode 100644 index 0000000..1819b74 --- /dev/null +++ b/docs/docs.md @@ -0,0 +1,1191 @@ +# Protocol Documentation + + +## Table of Contents + +- [backup.proto](#backup-proto) + - [BackupCapabilitiesRequest](#cnpgi-backup-v1-BackupCapabilitiesRequest) + - [BackupCapabilitiesResult](#cnpgi-backup-v1-BackupCapabilitiesResult) + - [BackupCapability](#cnpgi-backup-v1-BackupCapability) + - [BackupCapability.RPC](#cnpgi-backup-v1-BackupCapability-RPC) + - [BackupRequest](#cnpgi-backup-v1-BackupRequest) + - [BackupRequest.ParametersEntry](#cnpgi-backup-v1-BackupRequest-ParametersEntry) + - [BackupResult](#cnpgi-backup-v1-BackupResult) + - [BackupResult.MetadataEntry](#cnpgi-backup-v1-BackupResult-MetadataEntry) + + - [BackupCapability.RPC.Type](#cnpgi-backup-v1-BackupCapability-RPC-Type) + + - [Backup](#cnpgi-backup-v1-Backup) + +- [identity.proto](#identity-proto) + - [GetPluginCapabilitiesRequest](#cnpgi-identity-v1-GetPluginCapabilitiesRequest) + - [GetPluginCapabilitiesResponse](#cnpgi-identity-v1-GetPluginCapabilitiesResponse) + - [GetPluginMetadataRequest](#cnpgi-identity-v1-GetPluginMetadataRequest) + - [GetPluginMetadataResponse](#cnpgi-identity-v1-GetPluginMetadataResponse) + - [GetPluginMetadataResponse.ManifestEntry](#cnpgi-identity-v1-GetPluginMetadataResponse-ManifestEntry) + - [PluginCapability](#cnpgi-identity-v1-PluginCapability) + - [PluginCapability.Service](#cnpgi-identity-v1-PluginCapability-Service) + - [ProbeRequest](#cnpgi-identity-v1-ProbeRequest) + - [ProbeResponse](#cnpgi-identity-v1-ProbeResponse) + + - [PluginCapability.Service.Type](#cnpgi-identity-v1-PluginCapability-Service-Type) + + - [Identity](#cnpgi-identity-v1-Identity) + +- [operator.proto](#operator-proto) + - [OperatorCapabilitiesRequest](#cnpgi-operator-v1-OperatorCapabilitiesRequest) + - [OperatorCapabilitiesResult](#cnpgi-operator-v1-OperatorCapabilitiesResult) + - [OperatorCapability](#cnpgi-operator-v1-OperatorCapability) + - [OperatorCapability.RPC](#cnpgi-operator-v1-OperatorCapability-RPC) + - [OperatorMutateClusterRequest](#cnpgi-operator-v1-OperatorMutateClusterRequest) + - [OperatorMutateClusterResult](#cnpgi-operator-v1-OperatorMutateClusterResult) + - [OperatorValidateClusterChangeRequest](#cnpgi-operator-v1-OperatorValidateClusterChangeRequest) + - [OperatorValidateClusterChangeResult](#cnpgi-operator-v1-OperatorValidateClusterChangeResult) + - [OperatorValidateClusterCreateRequest](#cnpgi-operator-v1-OperatorValidateClusterCreateRequest) + - [OperatorValidateClusterCreateResult](#cnpgi-operator-v1-OperatorValidateClusterCreateResult) + - [ValidationError](#cnpgi-operator-v1-ValidationError) + + - [OperatorCapability.RPC.Type](#cnpgi-operator-v1-OperatorCapability-RPC-Type) + + - [Operator](#cnpgi-operator-v1-Operator) + +- [operator_lifecycle.proto](#operator_lifecycle-proto) + - [OperatorLifecycleCapabilities](#cnpgi-operator_lifecycle-v1-OperatorLifecycleCapabilities) + - [OperatorLifecycleCapabilitiesRequest](#cnpgi-operator_lifecycle-v1-OperatorLifecycleCapabilitiesRequest) + - [OperatorLifecycleCapabilitiesResponse](#cnpgi-operator_lifecycle-v1-OperatorLifecycleCapabilitiesResponse) + - [OperatorLifecycleRequest](#cnpgi-operator_lifecycle-v1-OperatorLifecycleRequest) + - [OperatorLifecycleResponse](#cnpgi-operator_lifecycle-v1-OperatorLifecycleResponse) + - [OperatorOperationType](#cnpgi-operator_lifecycle-v1-OperatorOperationType) + + - [OperatorOperationType.Type](#cnpgi-operator_lifecycle-v1-OperatorOperationType-Type) + + - [OperatorLifecycle](#cnpgi-operator_lifecycle-v1-OperatorLifecycle) + +- [reconciler.proto](#reconciler-proto) + - [ReconcilerHooksCapabilitiesRequest](#cnpgi-reconciler-v1-ReconcilerHooksCapabilitiesRequest) + - [ReconcilerHooksCapabilitiesResult](#cnpgi-reconciler-v1-ReconcilerHooksCapabilitiesResult) + - [ReconcilerHooksCapability](#cnpgi-reconciler-v1-ReconcilerHooksCapability) + - [ReconcilerHooksRequest](#cnpgi-reconciler-v1-ReconcilerHooksRequest) + - [ReconcilerHooksResult](#cnpgi-reconciler-v1-ReconcilerHooksResult) + + - [ReconcilerHooksCapability.Kind](#cnpgi-reconciler-v1-ReconcilerHooksCapability-Kind) + - [ReconcilerHooksResult.Behavior](#cnpgi-reconciler-v1-ReconcilerHooksResult-Behavior) + + - [ReconcilerHooks](#cnpgi-reconciler-v1-ReconcilerHooks) + +- [wal.proto](#wal-proto) + - [SetFirstRequiredRequest](#cnpgi-wal-v1-SetFirstRequiredRequest) + - [SetFirstRequiredResult](#cnpgi-wal-v1-SetFirstRequiredResult) + - [WALArchiveRequest](#cnpgi-wal-v1-WALArchiveRequest) + - [WALArchiveRequest.ParametersEntry](#cnpgi-wal-v1-WALArchiveRequest-ParametersEntry) + - [WALArchiveResult](#cnpgi-wal-v1-WALArchiveResult) + - [WALCapabilitiesRequest](#cnpgi-wal-v1-WALCapabilitiesRequest) + - [WALCapabilitiesResult](#cnpgi-wal-v1-WALCapabilitiesResult) + - [WALCapability](#cnpgi-wal-v1-WALCapability) + - [WALCapability.RPC](#cnpgi-wal-v1-WALCapability-RPC) + - [WALRestoreRequest](#cnpgi-wal-v1-WALRestoreRequest) + - [WALRestoreRequest.ParametersEntry](#cnpgi-wal-v1-WALRestoreRequest-ParametersEntry) + - [WALRestoreResult](#cnpgi-wal-v1-WALRestoreResult) + - [WALStatusRequest](#cnpgi-wal-v1-WALStatusRequest) + - [WALStatusResult](#cnpgi-wal-v1-WALStatusResult) + - [WALStatusResult.AdditionalInformationEntry](#cnpgi-wal-v1-WALStatusResult-AdditionalInformationEntry) + + - [WALCapability.RPC.Type](#cnpgi-wal-v1-WALCapability-RPC-Type) + + - [WAL](#cnpgi-wal-v1-WAL) + +- [Scalar Value Types](#scalar-value-types) + + + + +
+ +## backup.proto + + + + + +### BackupCapabilitiesRequest +Intentionally empty. + + + + + + + + +### BackupCapabilitiesResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| capabilities | [BackupCapability](#cnpgi-backup-v1-BackupCapability) | repeated | All the capabilities that the controller service supports. This field is OPTIONAL. | + + + + + + + + +### BackupCapability + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| rpc | [BackupCapability.RPC](#cnpgi-backup-v1-BackupCapability-RPC) | | | + + + + + + + + +### BackupCapability.RPC + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [BackupCapability.RPC.Type](#cnpgi-backup-v1-BackupCapability-RPC-Type) | | | + + + + + + + + +### BackupRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster being backed up | +| backup_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Backup that is being taken | +| parameters | [BackupRequest.ParametersEntry](#cnpgi-backup-v1-BackupRequest-ParametersEntry) | repeated | This field is OPTIONAL. Value of this field is the configuration of this backup as set in the Backup or in the ScheduledBackup object | + + + + + + + + +### BackupRequest.ParametersEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + +### BackupResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| backup_id | [string](#string) | | This field is REQUIRED and contains a machine-readable ID of the backup that is being taken | +| backup_name | [string](#string) | | This field is OPTIONAL and contains a human-readable name of the backup that is being taken | +| started_at | [int64](#int64) | | This field is REQUIRED and contains the Unix timestamp of the start time of the backup | +| stopped_at | [int64](#int64) | | This field is REQUIRED and contains the Unix timestamp of the end time of the backup | +| begin_wal | [string](#string) | | This field is OPTIONAL and contains the current WAL when the backup was started | +| end_wal | [string](#string) | | This field is OPTIONAL and contains the current WAL at the end of the backup | +| begin_lsn | [string](#string) | | This field is OPTIONAL and contains the current LSN record when the backup was started | +| end_lsn | [string](#string) | | This field is OPTIONAL and contains the current LSN record when the backup has finished | +| backup_label_file | [bytes](#bytes) | | This field is OPTIONAL and contains the backup label of the backup that have been taken | +| tablespace_map_file | [bytes](#bytes) | | This field is OPTIONAL and contains the tablespace map of the backup that have been taken | +| instance_id | [string](#string) | | This field is OPTIONAL and contains the ID of the instance that have been backed up | +| online | [bool](#bool) | | This field is REQUIRED and is set to true for online/hot backups and to false otherwise. | +| metadata | [BackupResult.MetadataEntry](#cnpgi-backup-v1-BackupResult-MetadataEntry) | repeated | This field is OPTIONAL and contains all the plugin specific information that needs to be stored | + + + + + + + + +### BackupResult.MetadataEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + + + +### BackupCapability.RPC.Type + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| TYPE_UNSPECIFIED | 0 | | +| TYPE_BACKUP | 1 | TYPE_BACKUP indicates that the Plugin is able to take physical backups. This feature is required for every plugin exposing the Backup service | + + + + + + + + + +### Backup + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetCapabilities | [BackupCapabilitiesRequest](#cnpgi-backup-v1-BackupCapabilitiesRequest) | [BackupCapabilitiesResult](#cnpgi-backup-v1-BackupCapabilitiesResult) | GetCapabilities gets the capabilities of the Backup service | +| Backup | [BackupRequest](#cnpgi-backup-v1-BackupRequest) | [BackupResult](#cnpgi-backup-v1-BackupResult) | Backup takes a physical backup of PostgreSQL. | + + + + + + + + +## identity.proto + + + + + +### GetPluginCapabilitiesRequest +Intentionally empty. + + + + + + + + +### GetPluginCapabilitiesResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| capabilities | [PluginCapability](#cnpgi-identity-v1-PluginCapability) | repeated | All the capabilities that the controller service supports. This field is OPTIONAL. | + + + + + + + + +### GetPluginMetadataRequest +Intentionally empty. + + + + + + + + +### GetPluginMetadataResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| name | [string](#string) | | The name MUST follow domain name notation format (https://tools.ietf.org/html/rfc1035#section-2.3.1). It SHOULD include the plugin's host company name and the plugin name, to minimize the possibility of collisions. It MUST be 63 characters or less, beginning and ending with an alphanumeric character ([a-z0-9A-Z]) with dashes (-), dots (.), and alphanumerics between. This field is REQUIRED. | +| version | [string](#string) | | This field is REQUIRED. Value of this field is opaque. | +| display_name | [string](#string) | | A name to display for the plugin. This field is REQUIRED. | +| description | [string](#string) | | A description for the plugin. This field is REQUIRED. | +| project_url | [string](#string) | | URL of the home page of the plugin project. | +| repository_url | [string](#string) | | URL of the source code repository for the plugin project. | +| license | [string](#string) | | License of the plugin. This field is REQUIRED. | +| license_url | [string](#string) | | URL of the license of the plugin. This field is REQUIRED. | +| maturity | [string](#string) | | Maturity level (alpha, beta, stable) | +| vendor | [string](#string) | | Provider/vendor of the plugin, e.g. an organization | +| manifest | [GetPluginMetadataResponse.ManifestEntry](#cnpgi-identity-v1-GetPluginMetadataResponse-ManifestEntry) | repeated | This field is OPTIONAL. Values are opaque. | + + + + + + + + +### GetPluginMetadataResponse.ManifestEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + +### PluginCapability + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| service | [PluginCapability.Service](#cnpgi-identity-v1-PluginCapability-Service) | | | + + + + + + + + +### PluginCapability.Service + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [PluginCapability.Service.Type](#cnpgi-identity-v1-PluginCapability-Service-Type) | | | + + + + + + + + +### ProbeRequest +Intentionally empty. + + + + + + + + +### ProbeResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ready | [bool](#bool) | | This field is OPTIONAL. If not present, the caller SHALL assume that the plugin is in a ready state and is accepting calls to its Controller and/or Node services (according to the plugin's reported capabilities). | + + + + + + + + + + +### PluginCapability.Service.Type + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| TYPE_UNSPECIFIED | 0 | | +| TYPE_OPERATOR_SERVICE | 1 | TYPE_OPERATOR_SERVICE indicated that the Plugin provider RPCs for the Operator service. The presence of this capability determines whether the CO will attempt to invoke the REQUIRED Opearator RPCs, as well as specific RPCs as indicated by GetCapabilities. | +| TYPE_WAL_SERVICE | 2 | TYPE_WAL_SERVICE indicates that the Plugin provides RPCs for the WAL service. Plugins MAY provide this capability. The presence of this capability determines whether the CO will attempt to invoke the REQUIRED WALService RPCs, as well as specific RPCs as indicated by GetCapabilities. | +| TYPE_BACKUP_SERVICE | 3 | TYPE_BACKUP_SERVICE indicates that the Plugin provides RPCs for the Backup service. The presence of this capability determines whether the CO will attempt to invoke the REQUIRED Backup Service RPCs, as well as specific RPCs as indicated by GetCapabilities. | +| TYPE_LIFECYCLE_SERVICE | 4 | TYPE_LIFECYCLE_SERVICE indicates that the Plugin provides RPCs for the Lifecycle service. | +| TYPE_RECONCILER_HOOKS | 5 | TYPE_RECONCILER_HOOKS indicates that the Plugin provides RPCs to enhance the behavior of the reconcilers | + + + + + + + + + +### Identity + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetPluginMetadata | [GetPluginMetadataRequest](#cnpgi-identity-v1-GetPluginMetadataRequest) | [GetPluginMetadataResponse](#cnpgi-identity-v1-GetPluginMetadataResponse) | GetPluginMetadata gets the plugin metadata | +| GetPluginCapabilities | [GetPluginCapabilitiesRequest](#cnpgi-identity-v1-GetPluginCapabilitiesRequest) | [GetPluginCapabilitiesResponse](#cnpgi-identity-v1-GetPluginCapabilitiesResponse) | GetPluginCapabilities gets information about this plugin | +| Probe | [ProbeRequest](#cnpgi-identity-v1-ProbeRequest) | [ProbeResponse](#cnpgi-identity-v1-ProbeResponse) | Probe is used to tell if the plugin is ready to receive requests | + + + + + + + + +## operator.proto + + + + + +### OperatorCapabilitiesRequest +Intentionally empty. + + + + + + + + +### OperatorCapabilitiesResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| capabilities | [OperatorCapability](#cnpgi-operator-v1-OperatorCapability) | repeated | All the capabilities that the controller service supports. This field is OPTIONAL. | + + + + + + + + +### OperatorCapability + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| rpc | [OperatorCapability.RPC](#cnpgi-operator-v1-OperatorCapability-RPC) | | | + + + + + + + + +### OperatorCapability.RPC + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [OperatorCapability.RPC.Type](#cnpgi-operator-v1-OperatorCapability-RPC-Type) | | | + + + + + + + + +### OperatorMutateClusterRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster that should receive the default values | + + + + + + + + +### OperatorMutateClusterResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| json_patch | [bytes](#bytes) | | This field is OPTIONAL. Value of this field is a JSONPatch to be applied on the passed Cluster definition | + + + + + + + + +### OperatorValidateClusterChangeRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| old_cluster | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the current Cluster definition | +| new_cluster | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the updated Cluster definition | + + + + + + + + +### OperatorValidateClusterChangeResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| validation_errors | [ValidationError](#cnpgi-operator-v1-ValidationError) | repeated | This field is OPTIONAL. Value of this field is a set of validation errors | + + + + + + + + +### OperatorValidateClusterCreateRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster that is being created | + + + + + + + + +### OperatorValidateClusterCreateResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| validation_errors | [ValidationError](#cnpgi-operator-v1-ValidationError) | repeated | This field is OPTIONAL. Value of this field is a set of validation errors | + + + + + + + + +### ValidationError + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| path_components | [string](#string) | repeated | This field is REQUIRED. Value of this field is | +| value | [string](#string) | | This field is REQUIRED. Value of this field is the value that caused a validation error | +| message | [string](#string) | | This field is REQUIRED. Value of this field is a description of the validation error | + + + + + + + + + + +### OperatorCapability.RPC.Type + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| TYPE_UNSPECIFIED | 0 | | +| TYPE_VALIDATE_CLUSTER_CREATE | 1 | TYPE_VALIDATE_CLUSTER_CREATE indicates that the Plugin is able to reply to the ValidateClusterCreate RPC request | +| TYPE_VALIDATE_CLUSTER_CHANGE | 2 | TYPE_VALIDATE_CLUSTER_CHANGE indicates that the Plugin is able to reply to the ValidateClusterChange RPC request | +| TYPE_MUTATE_CLUSTER | 3 | TYPE_MUTATE_CLUSTER indicates that the Plugin is able to reply to the MutateCluster RPC request | +| TYPE_MUTATE_POD | 4 | TYPE_MUTATE_POD indicates that the Plugin is able to reply to the MutatePod RPC request | + + + + + + + + + +### Operator + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetCapabilities | [OperatorCapabilitiesRequest](#cnpgi-operator-v1-OperatorCapabilitiesRequest) | [OperatorCapabilitiesResult](#cnpgi-operator-v1-OperatorCapabilitiesResult) | GetCapabilities gets the capabilities of the WAL service | +| ValidateClusterCreate | [OperatorValidateClusterCreateRequest](#cnpgi-operator-v1-OperatorValidateClusterCreateRequest) | [OperatorValidateClusterCreateResult](#cnpgi-operator-v1-OperatorValidateClusterCreateResult) | ValidateCreate improves the behaviour of the validating webhook that is called on creation of the Cluster resources | +| ValidateClusterChange | [OperatorValidateClusterChangeRequest](#cnpgi-operator-v1-OperatorValidateClusterChangeRequest) | [OperatorValidateClusterChangeResult](#cnpgi-operator-v1-OperatorValidateClusterChangeResult) | ValidateClusterChange improves the behavior of the validating webhook of is called on updates of the Cluster resources | +| MutateCluster | [OperatorMutateClusterRequest](#cnpgi-operator-v1-OperatorMutateClusterRequest) | [OperatorMutateClusterResult](#cnpgi-operator-v1-OperatorMutateClusterResult) | MutateCluster fills in the defaults inside a Cluster resource | + + + + + + + + +## operator_lifecycle.proto + + + + + +### OperatorLifecycleCapabilities + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| group | [string](#string) | | The Kubernetes resource group (such as "apps" or empty for core resources) | +| kind | [string](#string) | | The Kubernetes Kind name (such as "Cluster" or "Pod") | +| operation_types | [OperatorOperationType](#cnpgi-operator_lifecycle-v1-OperatorOperationType) | repeated | The operation type | + + + + + + + + +### OperatorLifecycleCapabilitiesRequest +This message is intentionally empty + + + + + + + + +### OperatorLifecycleCapabilitiesResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| lifecycle_capabilities | [OperatorLifecycleCapabilities](#cnpgi-operator_lifecycle-v1-OperatorLifecycleCapabilities) | repeated | This message is OPTIONAL, containing the list of resources for which the lifecycle hook is called | + + + + + + + + +### OperatorLifecycleRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| operation_type | [OperatorOperationType](#cnpgi-operator_lifecycle-v1-OperatorOperationType) | | This field is REQUIRED. | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED | +| object_definition | [bytes](#bytes) | | This field is REQUIRED. | + + + + + + + + +### OperatorLifecycleResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| json_patch | [bytes](#bytes) | | This field is OPTIONAL. | + + + + + + + + +### OperatorOperationType + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [OperatorOperationType.Type](#cnpgi-operator_lifecycle-v1-OperatorOperationType-Type) | | | + + + + + + + + + + +### OperatorOperationType.Type +The operator type corresponds to the Kubernetes API method + +| Name | Number | Description | +| ---- | ------ | ----------- | +| TYPE_UNSPECIFIED | 0 | | +| TYPE_PATCH | 1 | | +| TYPE_UPDATE | 2 | | +| TYPE_CREATE | 3 | | +| TYPE_DELETE | 4 | | + + + + + + + + + +### OperatorLifecycle + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetCapabilities | [OperatorLifecycleCapabilitiesRequest](#cnpgi-operator_lifecycle-v1-OperatorLifecycleCapabilitiesRequest) | [OperatorLifecycleCapabilitiesResponse](#cnpgi-operator_lifecycle-v1-OperatorLifecycleCapabilitiesResponse) | GetCapabilities gets the capabilities of the Lifecycle service | +| LifecycleHook | [OperatorLifecycleRequest](#cnpgi-operator_lifecycle-v1-OperatorLifecycleRequest) | [OperatorLifecycleResponse](#cnpgi-operator_lifecycle-v1-OperatorLifecycleResponse) | LifecycleHook allows the plugin to manipulate the Kubernetes resources before the CNPG operator applies them to the Kubernetes cluster. | + + + + + + + + +## reconciler.proto + + + + + +### ReconcilerHooksCapabilitiesRequest + + + + + + + + + +### ReconcilerHooksCapabilitiesResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| reconciler_capabilities | [ReconcilerHooksCapability](#cnpgi-reconciler-v1-ReconcilerHooksCapability) | repeated | This message is OPTIONAL, containing the list of resources for which the lifecycle hook is called | + + + + + + + + +### ReconcilerHooksCapability + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| kind | [ReconcilerHooksCapability.Kind](#cnpgi-reconciler-v1-ReconcilerHooksCapability-Kind) | | kind is the controller Kind | + + + + + + + + +### ReconcilerHooksRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster corresponding to the Pod being applied | +| resource_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the resource being reconciled | + + + + + + + + +### ReconcilerHooksResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| behavior | [ReconcilerHooksResult.Behavior](#cnpgi-reconciler-v1-ReconcilerHooksResult-Behavior) | | This field is REQUIRED, and indicates the behavior that should be used for the current reconciliation loog. | +| requeue_after | [int64](#int64) | | This field is OPTIONAL. If true, the current reconciliation loop will be stopped and the operator will ensure that another one will be run in the requested number of seconds. IMPORTANT: the new reconciliation loop may start even before the number of specified seconds. | + + + + + + + + + + +### ReconcilerHooksCapability.Kind + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| KIND_UNSPECIFIED | 0 | | +| KIND_CLUSTER | 1 | KIND_CLUSTER indicates that the plugin will plug the Cluster reconciler | +| KIND_BACKUP | 2 | KIND_BACKUP indicates that the plugin will plug the Backup reconciler | + + + + + +### ReconcilerHooksResult.Behavior + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| BEHAVIOR_UNSPECIFIED | 0 | | +| BEHAVIOR_CONTINUE | 1 | BEHAVIOR_CONTINUE indicates that this reconciliation loop will proceed running. | +| BEHAVIOR_REQUEUE | 2 | BEHAVIOR_REQUEUE indicates that this reconciliation loop will be stopped and a new one will be requested | +| BEHAVIOR_TERMINATE | 3 | BEHAVIOR_TERMINATE indicates that this reconciliation loop will be marked as succeded and no other operations will be done. | + + + + + + + + + +### ReconcilerHooks + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetCapabilities | [ReconcilerHooksCapabilitiesRequest](#cnpgi-reconciler-v1-ReconcilerHooksCapabilitiesRequest) | [ReconcilerHooksCapabilitiesResult](#cnpgi-reconciler-v1-ReconcilerHooksCapabilitiesResult) | GetCapabilities gets the capabilities of the Backup service | +| Pre | [ReconcilerHooksRequest](#cnpgi-reconciler-v1-ReconcilerHooksRequest) | [ReconcilerHooksResult](#cnpgi-reconciler-v1-ReconcilerHooksResult) | Pre is executed before the operator executes the reconciliation loop | +| Post | [ReconcilerHooksRequest](#cnpgi-reconciler-v1-ReconcilerHooksRequest) | [ReconcilerHooksResult](#cnpgi-reconciler-v1-ReconcilerHooksResult) | Post is executed after the operator executes the reconciliation loop | + + + + + + + + +## wal.proto + + + + + +### SetFirstRequiredRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster corresponding to the Pod being applied | +| first_required_wal | [string](#string) | | This field is REQUIRED. Value of this field is the name of the first required WAL in the WAL archive for this cluster (normally based on the begin WAL of the first available base backup for the cluster) | + + + + + + + + +### SetFirstRequiredResult +Intentionally empty. + + + + + + + + +### WALArchiveRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster corresponding to the Pod being applied | +| source_file_name | [string](#string) | | This field is REQUIRED. Value of this field is the full path of the WAL file that should be archived | +| parameters | [WALArchiveRequest.ParametersEntry](#cnpgi-wal-v1-WALArchiveRequest-ParametersEntry) | repeated | This field is OPTIONAL. Values are opaque. | + + + + + + + + +### WALArchiveRequest.ParametersEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + +### WALArchiveResult +Intentionally empty. + + + + + + + + +### WALCapabilitiesRequest +Intentionally empty. + + + + + + + + +### WALCapabilitiesResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| capabilities | [WALCapability](#cnpgi-wal-v1-WALCapability) | repeated | All the capabilities that the controller service supports. This field is OPTIONAL. | + + + + + + + + +### WALCapability + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| rpc | [WALCapability.RPC](#cnpgi-wal-v1-WALCapability-RPC) | | | + + + + + + + + +### WALCapability.RPC + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| type | [WALCapability.RPC.Type](#cnpgi-wal-v1-WALCapability-RPC-Type) | | | + + + + + + + + +### WALRestoreRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster corresponding to the Pod being applied | +| source_wal_name | [string](#string) | | This field is REQUIRED. Value of this field is the name of the WAL to be retrieved from the archive, such as: 000000010000000100000012 | +| destination_file_name | [string](#string) | | This field is REQUIRED. Value of this field is the full path where the WAL file should be stored | +| parameters | [WALRestoreRequest.ParametersEntry](#cnpgi-wal-v1-WALRestoreRequest-ParametersEntry) | repeated | This field is OPTIONAL. Values are opaque. | + + + + + + + + +### WALRestoreRequest.ParametersEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + +### WALRestoreResult +Intentionally empty. + + + + + + + + +### WALStatusRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| cluster_definition | [bytes](#bytes) | | This field is REQUIRED. Value of this field is the JSON serialization of the Cluster corresponding to the Pod being applied | + + + + + + + + +### WALStatusResult + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| first_wal | [string](#string) | | This field is REQUIRED. Value of this field is the base name of the oldest archived WAL, such as: 000000010000000100000012 | +| last_wal | [string](#string) | | This field is REQUIRED. Value of this field is the base name of the newest archived WAL, such as: 000000010000000100000014 | +| additional_information | [WALStatusResult.AdditionalInformationEntry](#cnpgi-wal-v1-WALStatusResult-AdditionalInformationEntry) | repeated | This field is OPTIONAL. Value is opaque. | + + + + + + + + +### WALStatusResult.AdditionalInformationEntry + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| key | [string](#string) | | | +| value | [string](#string) | | | + + + + + + + + + + +### WALCapability.RPC.Type + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| TYPE_UNSPECIFIED | 0 | | +| TYPE_ARCHIVE_WAL | 1 | TYPE_ARCHIVE_WAL indicates that the Plugin is able to reply to the Archive RPC request | +| TYPE_RESTORE_WAL | 2 | TYPE_RESTORE_WAL indicates that the Plugin is able to reply to the Restore RPC request | +| TYPE_STATUS | 3 | TYPE_STATUS indicates that the Plugin is able to reply to the Status RPC request | +| TYPE_SET_FIRST_REQUIRED | 4 | TYPE_SET_FIRST_REQUIRED indicates that the Plugin is able to reply to the SetFirstRequired RPC request | + + + + + + + + + +### WAL + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| GetCapabilities | [WALCapabilitiesRequest](#cnpgi-wal-v1-WALCapabilitiesRequest) | [WALCapabilitiesResult](#cnpgi-wal-v1-WALCapabilitiesResult) | GetCapabilities gets the capabilities of the WAL service | +| Archive | [WALArchiveRequest](#cnpgi-wal-v1-WALArchiveRequest) | [WALArchiveResult](#cnpgi-wal-v1-WALArchiveResult) | Archive copies one WAL file into the archive | +| Restore | [WALRestoreRequest](#cnpgi-wal-v1-WALRestoreRequest) | [WALRestoreResult](#cnpgi-wal-v1-WALRestoreResult) | Restores copies WAL file from the archive to the data directory | +| Status | [WALStatusRequest](#cnpgi-wal-v1-WALStatusRequest) | [WALStatusResult](#cnpgi-wal-v1-WALStatusResult) | Status gets the statistics of the WAL file archive | +| SetFirstRequired | [SetFirstRequiredRequest](#cnpgi-wal-v1-SetFirstRequiredRequest) | [SetFirstRequiredResult](#cnpgi-wal-v1-SetFirstRequiredResult) | SetFirstRequired sets the first required WAL for the cluster | + + + + + +## Scalar Value Types + +| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | +| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | +| double | | double | double | float | float64 | double | float | Float | +| float | | float | float | float | float32 | float | float | Float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | +| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) | + diff --git a/pkg/backup/backup.pb.go b/pkg/backup/backup.pb.go index 400ec41..9581b81 100644 --- a/pkg/backup/backup.pb.go +++ b/pkg/backup/backup.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.0--rc1 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: proto/backup.proto package backup diff --git a/pkg/backup/backup_grpc.pb.go b/pkg/backup/backup_grpc.pb.go index cdf5ff0..3ee9245 100644 --- a/pkg/backup/backup_grpc.pb.go +++ b/pkg/backup/backup_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.0--rc1 +// - protoc v5.26.1 // source: proto/backup.proto package backup diff --git a/pkg/identity/identity.pb.go b/pkg/identity/identity.pb.go index 36067e3..c2eacb2 100644 --- a/pkg/identity/identity.pb.go +++ b/pkg/identity/identity.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.0--rc1 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: proto/identity.proto package identity diff --git a/pkg/identity/identity_grpc.pb.go b/pkg/identity/identity_grpc.pb.go index 6808506..c74fe7e 100644 --- a/pkg/identity/identity_grpc.pb.go +++ b/pkg/identity/identity_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.0--rc1 +// - protoc v5.26.1 // source: proto/identity.proto package identity diff --git a/pkg/lifecycle/operator_lifecycle.pb.go b/pkg/lifecycle/operator_lifecycle.pb.go index 7ed82b7..080482d 100644 --- a/pkg/lifecycle/operator_lifecycle.pb.go +++ b/pkg/lifecycle/operator_lifecycle.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.0--rc1 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: proto/operator_lifecycle.proto package lifecycle diff --git a/pkg/lifecycle/operator_lifecycle_grpc.pb.go b/pkg/lifecycle/operator_lifecycle_grpc.pb.go index 593501d..a4ad4f0 100644 --- a/pkg/lifecycle/operator_lifecycle_grpc.pb.go +++ b/pkg/lifecycle/operator_lifecycle_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.0--rc1 +// - protoc v5.26.1 // source: proto/operator_lifecycle.proto package lifecycle diff --git a/pkg/operator/extensions.go b/pkg/operator/extensions.go index 2243dcd..7069d38 100644 --- a/pkg/operator/extensions.go +++ b/pkg/operator/extensions.go @@ -1,10 +1,11 @@ -package operator +package operator //nolint:revive,stylecheck import ( "fmt" "strings" ) +// ValidationErrors is a list of validation errors. type ValidationErrors []*ValidationError func (v ValidationErrors) Error() string { diff --git a/pkg/operator/operator.pb.go b/pkg/operator/operator.pb.go index dfdb18f..60801cc 100644 --- a/pkg/operator/operator.pb.go +++ b/pkg/operator/operator.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.0--rc1 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: proto/operator.proto package operator diff --git a/pkg/operator/operator_grpc.pb.go b/pkg/operator/operator_grpc.pb.go index cc8e8d1..d1bc657 100644 --- a/pkg/operator/operator_grpc.pb.go +++ b/pkg/operator/operator_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.0--rc1 +// - protoc v5.26.1 // source: proto/operator.proto package operator diff --git a/pkg/reconciler/reconciler.pb.go b/pkg/reconciler/reconciler.pb.go index 1340caa..2059730 100644 --- a/pkg/reconciler/reconciler.pb.go +++ b/pkg/reconciler/reconciler.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.0--rc1 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: proto/reconciler.proto package reconciler diff --git a/pkg/reconciler/reconciler_grpc.pb.go b/pkg/reconciler/reconciler_grpc.pb.go index 9f896ec..06c33fa 100644 --- a/pkg/reconciler/reconciler_grpc.pb.go +++ b/pkg/reconciler/reconciler_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.0--rc1 +// - protoc v5.26.1 // source: proto/reconciler.proto package reconciler diff --git a/pkg/wal/wal.pb.go b/pkg/wal/wal.pb.go index 6f5f09e..4d50baa 100644 --- a/pkg/wal/wal.pb.go +++ b/pkg/wal/wal.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.0--rc1 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: proto/wal.proto package wal diff --git a/pkg/wal/wal_grpc.pb.go b/pkg/wal/wal_grpc.pb.go index 3f99f9f..dac6cdf 100644 --- a/pkg/wal/wal_grpc.pb.go +++ b/pkg/wal/wal_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.0--rc1 +// - protoc v5.26.1 // source: proto/wal.proto package wal diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index ec53dc9..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -cd "$(dirname "$0")/.." || exit - -# Recompile protobuf specification -protoc --go_out=. --go_opt=module=github.com/cloudnative-pg/cnpg-i \ - --go-grpc_out=. --go-grpc_opt=module=github.com/cloudnative-pg/cnpg-i \ - proto/*.proto - -# Compile client -go build `go list ./... | grep -v 'vendor'`