From 0a0ca5d4883c4e40dfeb5b174200096819add6a7 Mon Sep 17 00:00:00 2001 From: Yannik Daellenbach Date: Tue, 9 Apr 2024 09:54:36 +0200 Subject: [PATCH] Init "CI" dagger module --- .github/workflows/ci.yaml | 30 +++ .github/workflows/main.yml | 55 ------ argocd/repoClient_test.go | 12 +- ci/.gitattributes | 3 + ci/.gitignore | 4 + ci/build.go | 52 ++++++ ci/go.mod | 35 ++++ ci/go.sum | 87 +++++++++ ci/main.go | 363 ++++--------------------------------- ci/main_test.go | 150 --------------- dagger.json | 6 + 11 files changed, 262 insertions(+), 535 deletions(-) create mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/main.yml create mode 100644 ci/.gitattributes create mode 100644 ci/.gitignore create mode 100644 ci/build.go create mode 100644 ci/go.mod create mode 100644 ci/go.sum delete mode 100644 ci/main_test.go create mode 100644 dagger.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..b73adf3 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: + - "main" + tags: + - "v*" + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dagger/dagger-for-github@v5 + with: + verb: call + args: --source=. build image stderr + version: "0.11.0" + test: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: dagger/dagger-for-github@v5 + with: + verb: call + args: --source=. test + version: "0.11.0" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 44c27f2..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: 'ci' - -on: - push: - branches: - - main - tags: - - "v*" - pull_request: - types: [opened, reopened, synchronize] - -permissions: - id-token: write # Important for at least docker gha cache - contents: read - -jobs: - dagger: - runs-on: ubuntu-latest - services: - argocd-redis: - image: redis - ports: - - "6379:6379" - # Set health checks to wait until redis has started - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: '>=1.20' - - - name: Install - run: go get dagger.io/dagger@latest - - - name: Expose GitHub Runtime - uses: crazy-max/ghaction-github-runtime@v3 - - - name: Release and deploy with Dagger - run: | - export _EXPERIMENTAL_DAGGER_CACHE_CONFIG="type=gha,mode=max,url=$ACTIONS_CACHE_URL,token=$ACTIONS_RUNTIME_TOKEN" - go run ci/main.go - env: - REGISTRY_PASSWORD: '${{ secrets.REGISTRY_PASSWORD }}' - REGISTRY_USER: '${{ secrets.REGISTRY_USER }}' - GITHUB_ACCESS_TOKEN: '${{ secrets.GH_ACCESS_TOKEN }}' - REGISTRY_URL: '${{ secrets.REGISTRY_URL }}' - diff --git a/argocd/repoClient_test.go b/argocd/repoClient_test.go index fb8fe65..e15522c 100644 --- a/argocd/repoClient_test.go +++ b/argocd/repoClient_test.go @@ -1,9 +1,17 @@ package argocd -import "testing" +import ( + "net" + "testing" +) func TestRender(t *testing.T) { - err := Render("../testdata/argocd/helm_app.yaml", "reposerver:8081", "../out/argocd/app/", RepoCredentails{}) + const repoServerAddr = "reposerver:8081" + if _, err := net.LookupHost(repoServerAddr); err != nil { + t.Skipf("no reposerver host found: %v", err) + } + + err := Render("../testdata/argocd/helm_app.yaml", repoServerAddr, "../out/argocd/app/", RepoCredentails{}) if err != nil { t.Error(err) } diff --git a/ci/.gitattributes b/ci/.gitattributes new file mode 100644 index 0000000..6911ed2 --- /dev/null +++ b/ci/.gitattributes @@ -0,0 +1,3 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated diff --git a/ci/.gitignore b/ci/.gitignore new file mode 100644 index 0000000..7ebabcc --- /dev/null +++ b/ci/.gitignore @@ -0,0 +1,4 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder +/internal/telemetry diff --git a/ci/build.go b/ci/build.go new file mode 100644 index 0000000..98c747f --- /dev/null +++ b/ci/build.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" +) + +type Build struct { + // +private + Source *Directory +} + +func (ci *CI) Build() *Build { + return &Build{ + Source: ci.Source, + } +} + +func golangBaseImage(source *Directory) *Container { + return dag.Container(). + From(fmt.Sprintf("golang:%s", goVersion)). + WithExec([]string{"apt", "update"}). + WithExec([]string{"apt", "install", "musl-tools", "-y"}). + WithMountedCache("/go/src", dag.CacheVolume("go-mod")). + WithMountedCache("/root/.cache/go-build", dag.CacheVolume("go-build")). + WithWorkdir("/src"). + WithFile("go.mod", source.File("go.mod")). + WithFile("go.sum", source.File("go.sum")). + WithExec([]string{"go", "mod", "download"}). + WithDirectory("/src", source, ContainerWithDirectoryOpts{ + Exclude: []string{ + "ci/", + "build/", + }, + }) +} + +func (b *Build) binary() *File { + return golangBaseImage(b.Source). + WithExec([]string{"go", "build", "-o", "goff"}). + File("goff") +} + +// build `goff` container image +func (b *Build) Image() *Container { + return dag.Container(). + From(alpineBaseImage). + WithExec([]string{"addgroup", "-g", "1001", "goff"}). + WithExec([]string{"adduser", "-D", "-u", "1001", "-G", "goff", "goff"}). + WithFile("/usr/local/bin/kustomize", dag.Container().From(kustomizeImage).File("/app/kustomize")). + WithFile("/bin/goff", b.binary()). + WithExec([]string{"apk", "add", "git", "helm"}) +} diff --git a/ci/go.mod b/ci/go.mod new file mode 100644 index 0000000..cd3a77a --- /dev/null +++ b/ci/go.mod @@ -0,0 +1,35 @@ +module dagger/ci + +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 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.6.0 + google.golang.org/grpc v1.62.1 +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa // indirect + google.golang.org/protobuf v1.33.0 // indirect +) diff --git a/ci/go.sum b/ci/go.sum new file mode 100644 index 0000000..74ba3df --- /dev/null +++ b/ci/go.sum @@ -0,0 +1,87 @@ +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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +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= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +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/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa h1:RBgMaUMP+6soRkik4VoN8ojR2nex2TqZwjSSogic+eo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/ci/main.go b/ci/main.go index 1db735c..2825075 100644 --- a/ci/main.go +++ b/ci/main.go @@ -1,344 +1,51 @@ package main -import ( - "context" - "fmt" - "os" - "strings" - - "dagger.io/dagger" - "golang.org/x/sync/errgroup" +import "context" + +const ( + goVersion = "1.22" + alpineBaseImage = "docker.io/alpine:3.19.1" + kustomizeImage = "registry.k8s.io/kustomize/kustomize:v5.4.1" + redisImage = "redis" + argocdImage = "quay.io/argoproj/argocd:latest" ) -type GoffPipeline struct { - RefType string - RefName string - RegistryUser string - RegistrySecret string - RegistryUrl string - DefaultImageTag string - Release Releaser -} - -type Releaser interface { - releaseFiles(ctx context.Context, version string, files []string, client *dagger.Client) error - releaseDocs(ctx context.Context, version string, daggerClient *dagger.Client) error +type CI struct { + // Project source directory. + // +private + Source *Directory } -func main() { - gp := NewFromGithub() - err := gp.run() - if err != nil { - panic(err) - } -} - -func (g *GoffPipeline) run() error { - - // create Dagger client - ctx := context.Background() - daggerClient, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout)) - if err != nil { - return err +func New( + // Project source directory. + source *Directory, +) *CI { + return &CI{ + Source: source, } - defer daggerClient.Close() - - golang := createGoBase(daggerClient) - - //new error group for concurrent execution - errGroup, _ := errgroup.WithContext(ctx) - - errGroup.Go(func() error { - return runTests(ctx, golang, daggerClient) - }) - - errGroup.Go(func() error { - if g.RefName == "main" || g.isReleaseTag() { - return buildAndPushDevImage(ctx, golang, g, daggerClient) - } - return nil - - }) - - err = errGroup.Wait() - if err != nil { - return err - } - - //If version tag, build binary releases and release them on github - if g.isReleaseTag() { - files, err := g.build(ctx, daggerClient, golang) - - if err != nil { - return err - } - - err = g.Release.releaseFiles(ctx, g.RefName, files, daggerClient) - - if err != nil { - return err - } - - err = g.Release.releaseDocs(ctx, g.RefName, daggerClient) - if err != nil { - return err - } - - //Build patched ArgoCD Repo server - return g.buildArgoCdRepoServer(ctx, daggerClient) - - } - - return nil -} - -// create golang base -func createGoBase(daggerClient *dagger.Client) *dagger.Container { - goMod := daggerClient.CacheVolume("go") - goChache := daggerClient.CacheVolume("go-cache") - - base := daggerClient.Container(dagger.ContainerOpts{Platform: "linux/amd64"}). - From("golang:1.22"). - WithMountedCache("/go/src", goMod). - WithMountedCache("/root/.cache/go-build", goChache) - - //install musl for alpine builds on base image - base = base. - WithWorkdir("/src"). - WithExec([]string{"apt", "update"}). - WithExec([]string{"apt", "install", "musl-tools", "-y"}) - - // get working directory on host - modDir := daggerClient.Host().Directory(".", dagger.HostDirectoryOpts{ - Include: []string{"go.mod", "go.sum"}, - }) - - //download go modules - golang := base.WithDirectory("/src", modDir). - WithExec([]string{"go", "mod", "download"}) - - //get working directory on host, load files, exclude build and ci directory - source := daggerClient.Host().Directory(".", dagger.HostDirectoryOpts{ - Exclude: []string{"ci/", "build/"}, - }) - - golang = golang.WithDirectory("/src", source) - return golang } -func buildAndPushDevImage(ctx context.Context, golang *dagger.Container, g *GoffPipeline, daggerClient *dagger.Client) error { - - goffGitlab, err := golang. - WithExec([]string{"mkdir", "-p", "/app"}). - WithEnvVariable("CC", "musl-gcc"). - WithExec([]string{"go", "build", "-o", "/app/goff", "github.com/puzzle/goff"}). - WithExec([]string{"go", "install", "gitlab.com/gitlab-org/cli/cmd/glab@main"}). +func (ci *CI) Test(ctx context.Context) error { + // Sadly integration tests do not work at the moment :( + // https://github.com/dagger/dagger/issues/6951 + _, err := golangBaseImage(ci.Source). + WithExec([]string{"go", "test", "-v", "./..."}). + WithServiceBinding("reposerver", ci.RepoServer()). Sync(ctx) - - if err != nil { - return err - } - - goffBin := goffGitlab.File("/app/goff") - glabBin := goffGitlab.File("/go/bin/glab") - - kustomizeContainer := daggerClient.Container(). - From("registry.k8s.io/kustomize/kustomize:v5.4.1") - - goffContainer := daggerClient.Container(). - From("docker.io/alpine:3.18"). - WithExec([]string{"addgroup", "-g", "1001", "goff"}). - WithExec([]string{"adduser", "-D", "-u", "1001", "-G", "goff", "goff"}). - WithFile("/usr/local/bin/kustomize", kustomizeContainer.File("/app/kustomize")). - WithFile("/bin/goff", goffBin). - WithFile("/bin/glab", glabBin). - WithExec([]string{"apk", "add", "git", "helm"}) - - goffContainer = goffContainer. - WithEntrypoint([]string{"/bin/goff"}). - WithUser("goff") - - secret := daggerClient.SetSecret("reg-secret", g.RegistrySecret) - - imageName := g.getImageFullUrl("goff") - - _, err = goffContainer.WithRegistryAuth(g.RegistryUrl, g.RegistryUser, secret).Publish(ctx, imageName) - return err - } -func runTests(ctx context.Context, golang *dagger.Container, daggerClient *dagger.Client) error { - - redis, err := daggerClient.Container().From("registry.puzzle.ch/docker.io/redis"). - WithExposedPort(6379).AsService().Start(ctx) +func (ci *CI) RepoServer() *Service { + redis := dag.Container(). + From(redisImage). + WithExposedPort(6379). + AsService() - if err != nil { - return err - } - - repoServer, err := daggerClient.Container().From("quay.io/puzzle/argocd-repo-server:latest"). + return dag.Container(). + From(argocdImage). + WithServiceBinding("redis", redis). + WithDefaultArgs([]string{"--redis", "redis:6379"}). WithExposedPort(8081). - WithServiceBinding("argocd-redis", redis). - AsService().Start(ctx) - - if err != nil { - return err - } - - _, err = golang. - WithServiceBinding("reposerver", repoServer). - WithExec([]string{"go", "test", "./...", "-v"}).Sync(ctx) - - repoServer.Stop(ctx) - redis.Stop(ctx) - - return err -} - -// Build repo server for GitHub actions becuase they don't yet support overriding the entrypoint -func (g *GoffPipeline) buildArgoCdRepoServer(ctx context.Context, client *dagger.Client) error { - - repoServerContainer := client.Container().From("quay.io/argoproj/argocd:latest"). - WithUser("root"). - WithExec([]string{"apt", "update"}). - WithExec([]string{"apt", "install", "netcat", "-y"}). - WithUser("argocd"). - WithEntrypoint([]string{"argocd-repo-server"}) - - secret := client.SetSecret("reg-secret", g.RegistrySecret) - - _, err := repoServerContainer.WithRegistryAuth(g.RegistryUrl, g.RegistryUser, secret).Publish(ctx, g.getImageFullUrl("argocd-repo-server")) - - return err - -} - -func (g *GoffPipeline) build(ctx context.Context, client *dagger.Client, golang *dagger.Container) ([]string, error) { - - targets := make(map[string][]string) - targets["linux"] = []string{"amd64", "386", "arm"} - targets["windows"] = []string{"amd64", "386"} - targets["darwin"] = []string{"amd64"} - - files := make([]string, 0) - - errGroup, ctx := errgroup.WithContext(ctx) - - for os, target := range targets { - for i := range target { - arch := target[i] - oss := os - errGroup.Go(func() error { - var buildErr error - outFile := fmt.Sprintf("./build/goff-%s-%s-%s", oss, arch, g.RefName) - _, buildErr = golang. - WithEnvVariable("GOOS", oss). - WithEnvVariable("GOARCH", arch). - WithEnvVariable("CC", ""). - WithExec([]string{"go", "build", "-o", outFile, "github.com/puzzle/goff"}). - File(outFile).Export(ctx, outFile) - - if buildErr != nil { - return buildErr - } - - files = append(files, outFile) - - return nil - }) - } - } - - err := errGroup.Wait() - - return files, err -} - -type GitHubReleaser struct { - GithubAccessToken string -} - -func (g *GitHubReleaser) releaseFiles(ctx context.Context, version string, files []string, client *dagger.Client) error { - - buildDir := client.Host().Directory("build/") - ghContainer := client.Container().From("ghcr.io/supportpal/github-gh-cli"). - WithEnvVariable("GITHUB_TOKEN", g.GithubAccessToken). - WithDirectory("/build", buildDir). - WithExec([]string{"gh", "-R", "puzzle/goff", "release", "create", version}) - - for _, f := range files { - ghContainer = ghContainer. - WithExec([]string{"gh", "-R", "puzzle/goff", "release", "upload", version, f}) - } - - _, err := ghContainer.Sync(context.Background()) - - return err -} - -func (g *GitHubReleaser) releaseDocs(ctx context.Context, version string, daggerClient *dagger.Client) error { - mkdocs := daggerClient.Container().From("python:3-slim") - - token := daggerClient.SetSecret("token", g.GithubAccessToken) - - _, err := mkdocs. - WithEnvVariable("GOFF_VERSION", version). - WithExec([]string{"apt-get", "update"}). - WithExec([]string{"apt-get", "install", "git", "-y"}). - WithWorkdir("/src"). - WithExec([]string{"pip", "install", "mkdocs", "mkdocs-markdownextradata-plugin"}). - WithDirectory("/src", daggerClient.Host().Directory(".", dagger.HostDirectoryOpts{ - Include: []string{"mkdocs.yml", "docs/", ".git"}, - })). - WithSecretVariable("GH_PUSH_TOKEN", token). - WithExec([]string{"git", "remote", "set-url", "origin", "https://schlapzz:$GH_PUSH_TOKEN@github.com/puzzle/goff.git"}). - WithExec([]string{"git", "config", "--global", "user.email", "schlatter@puzzle.ch"}). - WithExec([]string{"git", "config", "--global", "user.name", "schlapzz"}). - WithExec([]string{"git", "fetch"}). - WithExec([]string{"git", "checkout", "origin/gh-pages"}). - WithExec([]string{"mkdocs", "build"}). - WithExec([]string{"rm", "-rf", "docs"}). - WithExec([]string{"mv", "site", "docs"}). - WithExec([]string{"git", "add", "."}). - WithExec([]string{"git", "commit", "-m", "test"}). - WithExec([]string{"git", "push", "--force"}). - Sync(ctx) - - return err -} - -func (g *GoffPipeline) isReleaseTag() bool { - return g.RefType == "tag" && strings.HasPrefix(g.RefName, "v") -} - -func (g *GoffPipeline) getImageFullUrl(name string) string { - tag := g.DefaultImageTag - if g.isReleaseTag() { - tag = g.RefName - } - return fmt.Sprintf("%s/puzzle/%s:%s", g.RegistryUrl, name, tag) -} - -// Load configuration from Github and custom environment variables -func NewFromGithub() GoffPipeline { - return GoffPipeline{ - RefType: mustLoadEnv("GITHUB_REF_TYPE"), - RefName: mustLoadEnv("GITHUB_REF_NAME"), - RegistrySecret: mustLoadEnv("REGISTRY_PASSWORD"), - RegistryUser: mustLoadEnv("REGISTRY_USER"), - RegistryUrl: mustLoadEnv("REGISTRY_URL"), - DefaultImageTag: "latest", - Release: &GitHubReleaser{ - GithubAccessToken: mustLoadEnv("GITHUB_ACCESS_TOKEN"), - }, - } -} - -func mustLoadEnv(env string) string { - val, found := os.LookupEnv(env) - if !found { - panic(fmt.Errorf("env var '%s' not set", env)) - } - return val + WithEntrypoint([]string{"argocd-repo-server"}). + AsService() } diff --git a/ci/main_test.go b/ci/main_test.go deleted file mode 100644 index 7f6fa41..0000000 --- a/ci/main_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -import ( - "context" - "crypto/rand" - "encoding/hex" - "errors" - "fmt" - mrand "math/rand" - "os" - "testing" - - "dagger.io/dagger" - "github.com/stretchr/testify/assert" -) - -type ReleaseStub struct { -} - -// releaseFiles implements Releaser -func (*ReleaseStub) releaseFiles(ctx context.Context, version string, files []string, client *dagger.Client) error { - //Do nothing - return nil -} - -func (*ReleaseStub) releaseDocs(ctx context.Context, version string, daggerClient *dagger.Client) error { - return nil -} - -type ReleaseStubErr struct { -} - -// releaseFiles implements Releaser which return error -func (*ReleaseStubErr) releaseFiles(ctx context.Context, version string, files []string, client *dagger.Client) error { - return errors.New("shoul not be called") -} - -func (*ReleaseStubErr) releaseDocs(ctx context.Context, version string, daggerClient *dagger.Client) error { - return errors.New("shoul not be called") -} - -var _ Releaser = &ReleaseStub{} - -func randomHex(n int) string { - bytes := make([]byte, n) - if _, err := rand.Read(bytes); err != nil { - panic(err) - } - return hex.EncodeToString(bytes) -} - -func TestMain(t *testing.T) { - os.Chdir("..") - ctx := context.Background() - daggerClient, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout)) - if err != nil { - t.Fail() - } - defer daggerClient.Close() - - testNonMainBranch(ctx, t, daggerClient) - testMainBranch(ctx, t, daggerClient) - testNonReleaseTag(ctx, t, daggerClient) - testReleaseTag(ctx, t, daggerClient) -} - -func testNonMainBranch(ctx context.Context, t *testing.T, client *dagger.Client) { - gp := &GoffPipeline{ - RefType: "branch", - RefName: "dev", - RegistryUser: "admin", - RegistrySecret: "secret", - RegistryUrl: "ttl.sh", - Release: &ReleaseStub{}, - DefaultImageTag: randomHex(12), - } - - err := gp.run() - - assert.Nil(t, err) - - _, err = client.Container().From(gp.getImageFullUrl("goff")).WithExec([]string{"--help"}).Sync(ctx) - assert.Error(t, err, "container with name '%s' should not exists", gp.getImageFullUrl("goff")) - -} - -func testMainBranch(ctx context.Context, t *testing.T, daggerClient *dagger.Client) { - gpMain := &GoffPipeline{ - RefType: "branch", - RefName: "main", - RegistryUser: "admin", - RegistrySecret: "secret", - RegistryUrl: "ttl.sh", - Release: &ReleaseStubErr{}, - DefaultImageTag: randomHex(12), - } - - err := gpMain.run() - assert.Nil(t, err) - - _, err = daggerClient.Container().From(gpMain.getImageFullUrl("goff")).WithExec([]string{"--help"}).Sync(ctx) - assert.Nil(t, err, "container '%s' should exists and be functional", gpMain.getImageFullUrl("goff")) - - _, err = daggerClient.Container().From(gpMain.getImageFullUrl("goff")).WithExec([]string{"kustomize", "--version"}).Sync(ctx) - assert.Nil(t, err, "container '%s' should exists and be functional", gpMain.getImageFullUrl("goff")) - - // Test custom `kustomize` - goffContainer := daggerClient.Container().From(gpMain.getImageFullUrl("goff")) - kustomize4 := daggerClient.Container(). - From("registry.k8s.io/kustomize/kustomize:v4.5.7") - goffContainer = goffContainer.WithFile("/opt/kustomize4", kustomize4.File("/app/kustomize")) - _, err = goffContainer.WithExec([]string{"kustomize", "--binary=/opt/kustomize4", "--version"}).Sync(ctx) - assert.Nil(t, err, "container '%s' should exists and be functional", gpMain.getImageFullUrl("goff")) -} - -func testNonReleaseTag(ctx context.Context, t *testing.T, daggerClient *dagger.Client) { - gpWrongTag := &GoffPipeline{ - RefType: "tag", - RefName: "birnenbaum", - RegistryUser: "admin", - RegistrySecret: "secret", - RegistryUrl: "ttl.sh", - Release: &ReleaseStubErr{}, - DefaultImageTag: randomHex(12), - } - - err := gpWrongTag.run() - assert.Nil(t, err) - - _, err = daggerClient.Container().From(gpWrongTag.getImageFullUrl("goff")).WithExec([]string{"--help"}).Sync(ctx) - assert.Error(t, err, "container with name '%s' should not exists", gpWrongTag.getImageFullUrl("goff")) -} - -func testReleaseTag(ctx context.Context, t *testing.T, daggerClient *dagger.Client) { - gprelease := &GoffPipeline{ - RefType: "tag", - RefName: fmt.Sprintf("v0.%d.%d", mrand.Intn(200), mrand.Intn(200)), - RegistryUser: "admin", - RegistrySecret: "secret", - RegistryUrl: "ttl.sh", - Release: &ReleaseStub{}, - DefaultImageTag: randomHex(12), - } - - err := gprelease.run() - assert.Nil(t, err) - - _, err = daggerClient.Container().From(gprelease.getImageFullUrl("goff")).WithExec([]string{"--help"}).Sync(ctx) - assert.Nil(t, err, "container with name '%s' should exists", gprelease.getImageFullUrl("goff")) -} diff --git a/dagger.json b/dagger.json new file mode 100644 index 0000000..91f4b8e --- /dev/null +++ b/dagger.json @@ -0,0 +1,6 @@ +{ + "name": "ci", + "sdk": "go", + "source": "ci", + "engineVersion": "v0.11.0" +}