diff --git a/.dagger/.gitignore b/.dagger/.gitignore new file mode 100644 index 0000000..d10f24a --- /dev/null +++ b/.dagger/.gitignore @@ -0,0 +1,3 @@ +/dagger.gen.go +/internal/querybuilder/ +/querybuilder/ diff --git a/.dagger/ci.go b/.dagger/ci.go new file mode 100644 index 0000000..eeea3c8 --- /dev/null +++ b/.dagger/ci.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + + "golang.org/x/sync/errgroup" +) + +type CI struct { + Project *Container +} + +// Run runs all CI steps in parallel. +// It runs: unit tests, lint and build. +// Example usage: `dagger call ci run` +func (c *CI) Run(ctx context.Context) error { + wg, gctx := errgroup.WithContext(ctx) + + wg.Go(func() error { + _, err := c.UnitTest(gctx) + + return err + }) + + wg.Go(func() error { + _, err := c.Lint(gctx) + + return err + }) + + wg.Go(func() error { + _, err := c.Build().Stdout(gctx) + + return err + }) + + return wg.Wait() +} + +// UnitTest runs go unit tests on the project. +// Example usage: `dagger call ci unit-test` +func (c *CI) UnitTest(ctx context.Context) (string, error) { + return c. + Project. + Pipeline("unit-test"). + WithExec([]string{"go", "test", "-v", "-race", "./...",}). + Stdout(ctx) +} + +// Lint runs golangci-lint on the project. +// Example usage: `dagger call ci lint` +func (c *CI) Lint(ctx context.Context) (string, error) { + // We cannot use golangci-lint official image because it doesn't support go + // 1.21.6 for now + return c. + Project. + Pipeline("lint"). + + // Add binutils-gold for golang + // See: https://github.com/golang/go/issues/52399 + WithExec([]string{"apk", "add", "binutils-gold"}). + + // Manually install golangci-lint binary + WithExec([]string{"go", "install", "github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2"}). + WithExec([]string{"golangci-lint", "run", "-v", "--timeout", "5m"}). + Stdout(ctx) +} + +// Build returns a container with Agate binary built in it. +// Example usage: `dagger call ci build` +func (c *CI) Build() *Container { + return c. + Project. + Pipeline("build"). + WithExec([]string{"go", "build", "-o", "agate", "main.go"}) +} diff --git a/.dagger/dagger.json b/.dagger/dagger.json new file mode 100644 index 0000000..8b34929 --- /dev/null +++ b/.dagger/dagger.json @@ -0,0 +1,22 @@ +{ + "name": "agate", + "root": "..", + "sdk": "go", + "include": [ + "**/*.go", + "db/**/*.sql", + "go.mod", + "go.sum", + ".golangci.yaml", + ".dagger" + ], + "exclude": [ + ".github", + "**/*.md", + "**/*.env*", + "**/*.example", + "docker-compose*.yaml", + "Dockerfile", + "docs" + ] +} diff --git a/.dagger/go.mod b/.dagger/go.mod new file mode 100644 index 0000000..12e0ef3 --- /dev/null +++ b/.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-20231006140011-7918f672742d + golang.org/x/sync v0.4.0 +) + +require github.com/stretchr/testify v1.8.3 // indirect diff --git a/.dagger/go.sum b/.dagger/go.sum new file mode 100644 index 0000000..7d9944b --- /dev/null +++ b/.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.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +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/main.go b/.dagger/main.go new file mode 100644 index 0000000..cd12b8a --- /dev/null +++ b/.dagger/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "fmt" + "strconv" +) + +const ( + defaultGoImageVersion = "1.21.6-alpine3.19" + workdir = "/app" + envPrefix = "AGATE_INDEXER_" +) + +type Agate struct { + // Image version of the Go image to use (default to: `1.21.6-alpine3.19`). + GoImageVersion string +} + +func New(goImageVersion Optional[string]) *Agate { + return &Agate{ + GoImageVersion: goImageVersion.GetOr(defaultGoImageVersion), + } +} + +// CI gives access to CI commands. +func (m *Agate) CI() *CI { + return &CI{ + Project: m.Project(), + } +} + +// Database returns a PostgreSQL database. +// Example usage: `dagger up --native database` +func (m *Agate) Database( + username Optional[string], + password Optional[string], + dbName Optional[string], + port Optional[int], +) *Service { + var defaultPort int + + if !port.isSet { + defaultPortStr := GetHostEnv("DATABASE_PORT") + + defaultPortTmp, err := strconv.Atoi(defaultPortStr) + if err != nil { + fmt.Println(fmt.Errorf("invalid port: %w", err)) + return nil + } + + defaultPort = defaultPortTmp + } + + return dag. + Container(). + From("bitnami/postgresql:15.2.0"). + WithEnvVariable("POSTGRES_USER", username.GetOr(GetHostEnv("DATABASE_USERNAME"))). + WithSecretVariable("POSTGRES_PASSWORD", dag.SetSecret("dbPassword", password.GetOr(GetHostEnv("DATABASE_PASSWORD")))). + WithEnvVariable("POSTGRES_DATABASE", dbName.GetOr(GetHostEnv("DBNAME"))). + WithMountedCache("/var/lib/postgresql/data", dag.CacheVolume("pg-data")). + WithExposedPort(port.GetOr(defaultPort)). + AsService() +} + +// Binary returns Agate binary file to the host's current directory. +// Example usage: `dagger export binary` +func (m *Agate) Binary() *File { + return m.CI().Build().File("agate") +} + +// Project returns the container that will be used in further Dagger command. +// Open a shell inside it using `dagger shell project --entrypoint /bin/sh`. +func (m *Agate) Project() *Container { + return dag. + Container(). + From("golang:"+m.GoImageVersion). + WithWorkdir(workdir). + // Install build-base to build cgo packages. + WithExec([]string{"apk", "add", "build-base"}). + + // Add cache for go modules and go build. + WithMountedCache("/go/pkg/mod", dag.CacheVolume("gomod")). + WithMountedCache("/root/.cache/go-build", dag.CacheVolume("gobuild")). + + // Install go dependencies + WithFile("go.mod", source().File("go.mod")). + WithFile("go.sum", source().File("go.sum")). + WithExec([]string{"go", "mod", "download"}). + + // Add source code + WithMountedDirectory(workdir, source()) +} diff --git a/.dagger/source.go b/.dagger/source.go new file mode 100644 index 0000000..3e4c234 --- /dev/null +++ b/.dagger/source.go @@ -0,0 +1,26 @@ +package main + +import ( + "os" + "path/filepath" +) + +func source() *Directory { + return dag.Host().Directory(root(), HostDirectoryOpts{ + Exclude: []string{ + ".dagger", + }, + }) +} + +// Return the absolute path to the root of the project. +// This is required for now by Dagger but it should be fixed later. +// TODO: fix .. restriction when it's merge on dagger +func root() string { + workdir, err := os.Getwd() + if err != nil { + panic(err) + } + + return filepath.Join(workdir, "..") +} \ No newline at end of file diff --git a/.dagger/utils.go b/.dagger/utils.go new file mode 100644 index 0000000..45ae185 --- /dev/null +++ b/.dagger/utils.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "os" +) + + +// GetHostEnv is like os.Getenv but ensures that the env var is set. +func GetHostEnv(name string) string { + value, ok := os.LookupEnv(envPrefix + name) + if !ok { + fmt.Fprintf(os.Stderr, "env var %s must be set\n", name) + os.Exit(1) + } + return value +} \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..8c0487f --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,28 @@ +name: Continuous Integration + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + ci: + runs-on: ubuntu-latest + env: + DAGGER_CLOUD_TOKEN: ${{ secrets.DAGGER_CLOUD_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Dagger CLI + run: cd /usr/local && { curl -L https://dl.dagger.io/dagger/install.sh | sh; cd -; } + + - name: Execute dagger run + run: | + cd .dagger + dagger call ci run \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..6f75ff4 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,90 @@ +run: + timeout: 5m + +linters: + fix: true + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - cyclop + - decorder + - dogsled + - dupl + - durationcheck + - errchkjson + - errname + - errorlint + - execinquery + - exhaustive + - exhaustruct + - exportloopref + - forbidigo + - forcetypeassert + - funlen + - gochecknoglobals + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - goerr113 + - gofmt + - gofumpt + - goheader + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - grouper + - importas + - lll + - loggercheck + - maintidx + - makezero + - misspell + - musttag + - nakedret + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + - nonamedreturns + - nosprintfhostport + - paralleltest + - prealloc + - predeclared + - promlinter + - reassign + - rowserrcheck + - stylecheck + - tenv + - testableexamples + - thelper + - tparallel + - unconvert + - unparam + - usestdlibvars + - varnamelen + - wastedassign + - whitespace + - wrapcheck + - wsl + +linters-settings: + wrapcheck: + ignoreSigs: + # Defaults + - .Errorf( + - errors.New( + - errors.Unwrap( + - .Wrap( + - .Wrapf( + - .WithMessage( + - .WithMessagef( + - .WithStack( \ No newline at end of file