diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..682868c --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 2780aa6..0000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Docker build - -on: - push: - branches: [main, golang] - pull_request: - branches: [main] - -jobs: - build: - name: Docker Build - runs-on: ubuntu-latest - if: github.event_name == 'push' || github.event_name == 'pull_request' - steps: - - uses: actions/checkout@master - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Build - id: docker_build - uses: docker/build-push-action@v4 - with: - platforms: linux/amd64 - push: false - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/golang-tests.yml b/.github/workflows/golang-tests.yml deleted file mode 100644 index 09e676f..0000000 --- a/.github/workflows/golang-tests.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Golang tests - -on: - push: - branches: [main, golang] - pull_request: - branches: [main] - -jobs: - test: - name: Golang tests - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@master - - uses: actions/setup-go@v4 - with: - go-version: 1.19 - - name: run golang tests - env: - SOPS_AGE_KEY_FILE: ${{ github.workspace }}/test_assets/keys.txt - GITOPS_ROOT_DIR: ${{ github.workspace }} - run: go test ./... diff --git a/.github/workflows/publish-homebrew.yml b/.github/workflows/publish-homebrew.yml index d3e8f40..43e62c1 100644 --- a/.github/workflows/publish-homebrew.yml +++ b/.github/workflows/publish-homebrew.yml @@ -9,7 +9,7 @@ jobs: name: Publish homebrew formula runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Setup git user run: | git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" @@ -19,4 +19,4 @@ jobs: - name: print sha256 run: cat sha256.txt - name: publish homebrew formula - run: ./.github/scripts/homebrew/publish.sh -u mxcd -t ${{ secrets.CI_PAT }} -v ${{ github.ref_name }} -s $(cat sha256.txt) + run: ./hack/homebrew/publish.sh -u mxcd -t ${{ secrets.CI_PAT }} -v ${{ github.ref_name }} -s $(cat sha256.txt) diff --git a/.github/workflows/build.yml b/.github/workflows/release.yml similarity index 57% rename from .github/workflows/build.yml rename to .github/workflows/release.yml index bd88208..b97a192 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Build +name: Release on: push: @@ -6,7 +6,7 @@ on: - 2.* jobs: - test: + binaries: name: Build strategy: fail-fast: false @@ -25,10 +25,10 @@ jobs: artifact_suffix: "" runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@master - - uses: actions/setup-go@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: 1.24 - name: golang build env: GOARCH: ${{ matrix.arch }} @@ -42,3 +42,26 @@ jobs: file: ${{ github.workspace }}/cmd/gitops/gitops${{ matrix.artifact_suffix }} asset_name: gitops_${{ matrix.os_name }}_${{ matrix.arch }}${{ matrix.artifact_suffix }} tag: ${{ github.ref }} + docker: + strategy: + fail-fast: false + matrix: + image: [gitops, repo-server] + name: docker build + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build + id: docker_build + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64 + push: true + tags: ghcr.io/mxcd/gitops/${{ matrix.image }}:${{ github.ref_name }} + file: docker/${{ matrix.image }}/Dockerfile \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c804bf5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,48 @@ +name: Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + golang: + name: Golang tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - uses: actions/setup-go@v5 + with: + go-version: 1.24 + - name: start soft-serve + run: ./hack/soft-serve/start.sh + - name: run golang tests + env: + SOPS_AGE_KEY_FILE: ${{ github.workspace }}/test_assets/keys.txt + GITOPS_ROOT_DIR: ${{ github.workspace }} + run: go test ./internal/... + docker: + strategy: + fail-fast: false + matrix: + image: [gitops, repo-server] + name: docker build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build + id: docker_build + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64 + push: false + file: docker/${{ matrix.image }}/Dockerfile \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95577b6..4024ca1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,16 @@ homebrew-gitops/ .gitops-state.yaml +sandbox/ + +hack/soft-serve/data/ +hack/soft-serve/gitops-test/ +hack/soft-serve/ssh-key +hack/soft-serve/ssh-key.pub + +.vscode/launch.json + +.env # Ignore "main" binary main @@ -11,6 +21,7 @@ main gitops # Actually... dont ignore gitops directory !cmd/gitops/ +!docker/gitops/ # Created by https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,goland+all # Edit at https://www.toptal.com/developers/gitignore?templates=go,visualstudiocode,goland+all diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 52f487c..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "gitops secrets apply k8s", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "cmd/gitops", - "args": ["secrets", "a", "k8s", "-auto-approve"] - } - ] -} diff --git a/cmd/gitops/main.go b/cmd/gitops/main.go index 7db7625..a919ad3 100644 --- a/cmd/gitops/main.go +++ b/cmd/gitops/main.go @@ -4,8 +4,12 @@ import ( "os" "github.com/TwiN/go-color" + "github.com/mxcd/gitops-cli/internal/finalizer" "github.com/mxcd/gitops-cli/internal/k8s" + "github.com/mxcd/gitops-cli/internal/kubernetes" + "github.com/mxcd/gitops-cli/internal/patch" "github.com/mxcd/gitops-cli/internal/state" + "github.com/mxcd/gitops-cli/internal/templating" "github.com/mxcd/gitops-cli/internal/util" log "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -89,7 +93,7 @@ func main() { }, Action: func(c *cli.Context) error { initApplication(c) - return applyKubernetes(c) + return kubernetes.ApplyKubernetes(c) }, }, { @@ -114,7 +118,7 @@ func main() { Action: func(c *cli.Context) error { initApplication(c) - return planKubernetes(c) + return kubernetes.PlanKubernetes(c) }, }, }, @@ -124,7 +128,7 @@ func main() { Usage: "Test the templating of secrets", Action: func(c *cli.Context) error { initApplication(c) - return testTemplating(c) + return templating.TestTemplating(c) }, }, }, @@ -146,7 +150,7 @@ func main() { for _, cluster := range clusters { println(color.InBlue(cluster.Name), " => ", cluster.ConfigFile) } - return exitApplication(c, true) + return finalizer.ExitApplication(c, true) }, }, { @@ -170,7 +174,7 @@ func main() { if err != nil { return err } - return exitApplication(c, true) + return finalizer.ExitApplication(c, true) }, }, { @@ -185,7 +189,7 @@ func main() { if err != nil { return err } - return exitApplication(c, true) + return finalizer.ExitApplication(c, true) }, }, { @@ -198,11 +202,75 @@ func main() { for _, clusterClient := range clusterClients { clusterClient.PrettyPrint() } - return exitApplication(c, false) + return finalizer.ExitApplication(c, false) }, }, }, }, + { + Name: "patch", + Usage: "Patch a single file in a GitOps cluster repository", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repository-server", + Value: "", + Usage: "GitOps repo server to be used for the patch", + EnvVars: []string{"GITOPS_REPOSITORY_SERVER"}, + }, + &cli.StringFlag{ + Name: "repository-server-api-key", + Value: "", + Usage: "GitOps repo server to be used for the patch", + EnvVars: []string{"GITOPS_REPOSITORY_SERVER_API_KEY"}, + }, + &cli.StringFlag{ + Name: "repository", + Value: "", + Usage: "Repo to be used for the patch", + EnvVars: []string{"GITOPS_REPOSITORY"}, + }, + &cli.StringFlag{ + Name: "branch", + Value: "main", + Usage: "Branch to be used for the patch", + EnvVars: []string{"GITOPS_REPOSITORY_BRANCH"}, + }, + &cli.StringFlag{ + Name: "basicauth", + Value: "", + Usage: "BasicAuth for authenticating against the GitOps repository", + EnvVars: []string{"GITOPS_REPOSITORY_BASICAUTH"}, + }, + &cli.StringFlag{ + Name: "ssh-key", + Value: "", + Usage: "SSH key for authenticating against the GitOps repository", + EnvVars: []string{"GITOPS_REPOSITORY_SSH_KEY"}, + }, + &cli.StringFlag{ + Name: "ssh-key-file", + Value: "", + Usage: "SSH key file for authenticating against the GitOps repository", + EnvVars: []string{"GITOPS_REPOSITORY_SSH_KEY_FILE"}, + }, + &cli.StringFlag{ + Name: "ssh-key-passphrase", + Value: "", + Usage: "SSH key passphrase for authenticating against the GitOps repository", + EnvVars: []string{"GITOPS_REPOSITORY_SSH_KEY_PASSPHRASE"}, + }, + &cli.StringFlag{ + Name: "actor", + Value: "", + Usage: "Username of the actor to be used for the commit", + EnvVars: []string{"GITOPS_ACTOR"}, + }, + }, + Action: func(c *cli.Context) error { + initApplication(c) + return patch.PatchCommand(c) + }, + }, }, } @@ -211,3 +279,12 @@ func main() { log.Fatal(err) } } + +func initApplication(c *cli.Context) error { + util.PrintLogo(c) + util.SetLogLevel(c) + util.SetCliContext(c) + util.GetRootDir() + err := state.LoadState(c) + return err +} diff --git a/cmd/gitops/templating.go b/cmd/gitops/templating.go deleted file mode 100644 index 8f113b9..0000000 --- a/cmd/gitops/templating.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "github.com/mxcd/gitops-cli/internal/util" - log "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" -) - -func testTemplating(c *cli.Context) error { - secretFiles, err := util.GetSecretFiles() - if err != nil { - log.Fatal(err) - } - for _, secretFile := range secretFiles { - log.Debug(secretFile) - decryptedFile, err := util.DecryptFile(secretFile) - if err != nil { - log.Fatal(err) - } - log.Trace(string(decryptedFile)) - } - return nil -} \ No newline at end of file diff --git a/cmd/gitops/vault.go b/cmd/gitops/vault.go deleted file mode 100644 index c9ecbf5..0000000 --- a/cmd/gitops/vault.go +++ /dev/null @@ -1,2 +0,0 @@ -package main - diff --git a/cmd/repo-server/main.go b/cmd/repo-server/main.go new file mode 100644 index 0000000..f512969 --- /dev/null +++ b/cmd/repo-server/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "os" + "time" + + "github.com/rs/zerolog/log" + + "github.com/mxcd/gitops-cli/internal/git" + "github.com/mxcd/gitops-cli/internal/patch" + "github.com/mxcd/gitops-cli/internal/repo_server/server" + "github.com/mxcd/gitops-cli/internal/repo_server/util" + "github.com/mxcd/go-config/config" +) + +func main() { + + if err := util.InitConfig(); err != nil { + log.Panic().Err(err).Msg("error initializing config") + } + config.Print() + + if err := util.InitLogger(); err != nil { + log.Panic().Err(err).Msg("error initializing logger") + } + + gitConnection, err := git.NewGitConnection(getGitConnectionOptions()) + if err != nil { + log.Panic().Err(err).Msg("error initializing git connection") + } + log.Info().Msg("Cloning git repository") + startTime := time.Now() + + err = gitConnection.Clone() + if err != nil { + log.Panic().Err(err).Msg("error cloning git repository") + } + log.Info().Msgf("Cloned git repository in %s", time.Since(startTime)) + + gitPatcher, err := patch.NewGitPatcher(&patch.GitPatcherOptions{ + GitConnection: gitConnection, + }) + if err != nil { + log.Panic().Err(err).Msg("error initializing git patcher") + } + + err = gitPatcher.Prepare(&patch.PrepareOptions{Clone: false}) + if err != nil { + log.Panic().Err(err).Msg("error preparing git patcher") + } + + server, err := server.NewServer(&server.RouterOptions{ + DevMode: config.Get().Bool("DEV"), + Port: config.Get().Int("PORT"), + ApiBaseUrl: config.Get().String("API_BASE_URL"), + ApiKeys: config.Get().StringArray("API_KEYS"), + }, gitPatcher) + + if err != nil { + log.Panic().Err(err).Msg("error initializing server") + } + + server.RegisterMiddlewares() + server.RegisterRoutes() + + log.Info().Msgf("Starting server on port %d", config.Get().Int("PORT")) + err = server.Run() + if err != nil { + log.Panic().Err(err).Msg("error running server") + } +} + +func getGitConnectionOptions() *git.ConnectionOptions { + var authentication *git.Authentication + var err error + if config.Get().String("GITOPS_REPOSITORY_BASICAUTH") != "" { + authentication, err = git.GetAuthFromBasicAuthString(config.Get().String("GITOPS_REPOSITORY_BASICAUTH")) + if err != nil { + log.Panic().Err(err).Msg("error getting basic auth from string") + } + } else if config.Get().String("GITOPS_REPOSITORY_SSH_KEY") != "" || config.Get().String("GITOPS_REPOSITORY_SSH_KEY_FILE") != "" { + var sshKey []byte + if config.Get().String("GITOPS_REPOSITORY_SSH_KEY") != "" { + sshKey = []byte(config.Get().String("GITOPS_REPOSITORY_SSH_KEY")) + } else { + sshKey, err = os.ReadFile(config.Get().String("GITOPS_REPOSITORY_SSH_KEY_FILE")) + if err != nil { + log.Panic().Err(err).Msg("error reading ssh key file") + } + } + + var passphrase *string = nil + if config.Get().String("GITOPS_REPOSITORY_SSH_KEY_PASSPHRASE") != "" { + _passphrase := config.Get().String("GITOPS_REPOSITORY_SSH_KEY_PASSPHRASE") + passphrase = &_passphrase + } + + authentication, err = git.GetAuthFromSshKey(sshKey, passphrase) + + if err != nil { + log.Panic().Err(err).Msg("error getting ssh key from string") + } + } + + return &git.ConnectionOptions{ + Branch: config.Get().String("GITOPS_REPOSITORY_BRANCH"), + Repository: config.Get().String("GITOPS_REPOSITORY"), + Authentication: authentication, + IgnoreSshHostKey: config.Get().Bool("GITOPS_REPOSITORY_IGNORE_SSL_HOSTKEY"), + } +} diff --git a/Dockerfile b/docker/gitops/Dockerfile similarity index 85% rename from Dockerfile rename to docker/gitops/Dockerfile index bdbd383..0dd79a6 100644 --- a/Dockerfile +++ b/docker/gitops/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20-alpine3.17 AS builder +FROM golang:1.24-alpine3.20 AS builder RUN apk add --no-cache git WORKDIR /usr/src @@ -12,7 +12,7 @@ COPY internal internal WORKDIR /usr/src/cmd/gitops RUN go build -o gitops -ldflags="-s -w" . -FROM alpine:3.17 +FROM alpine:3.20 WORKDIR /usr/bin COPY --from=builder /usr/src/cmd/gitops/gitops . ENTRYPOINT ["/usr/bin/gitops"] diff --git a/docker/repo-server/Dockerfile b/docker/repo-server/Dockerfile new file mode 100644 index 0000000..9749acf --- /dev/null +++ b/docker/repo-server/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.24-alpine3.20 AS builder +RUN apk add --no-cache git + +WORKDIR /usr/src +COPY go.mod /usr/src/go.mod +COPY go.sum /usr/src/go.sum +RUN go mod download + +COPY cmd cmd +COPY internal internal + +WORKDIR /usr/src/cmd/repo-server +RUN go build -o server -ldflags="-s -w" . + +FROM alpine:3.20 +WORKDIR /usr/bin +COPY --from=builder /usr/src/cmd/repo-server/server . +ENTRYPOINT ["/usr/bin/server"] +EXPOSE 8080 \ No newline at end of file diff --git a/go.mod b/go.mod index 2adebd1..4f77186 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,13 @@ module github.com/mxcd/gitops-cli -go 1.19 +go 1.21.0 + +toolchain go1.23.2 require ( + github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.1.2 + github.com/ldez/go-git-cmd-wrapper/v2 v2.8.0 github.com/schollz/progressbar/v3 v3.13.0 github.com/sirupsen/logrus v1.9.0 github.com/urfave/cli/v2 v2.25.0 @@ -14,27 +18,50 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/bytedance/sonic v1.12.6 // indirect + github.com/bytedance/sonic/loader v0.2.1 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.7 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.23.0 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/hashicorp/vault-client-go v0.2.0 // indirect github.com/imdario/mergo v0.3.6 // indirect + github.com/jedib0t/go-pretty/v6 v6.4.6 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.11.0 // indirect + github.com/onsi/gomega v1.27.10 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.12.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect @@ -58,7 +85,7 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/TwiN/go-color v1.4.0 github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f github.com/armon/go-metrics v0.3.10 // indirect @@ -71,7 +98,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/googleapis/gax-go/v2 v2.2.0 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect @@ -97,8 +124,8 @@ require ( github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/lib/pq v1.10.5 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -107,32 +134,34 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mxcd/go-config v1.5.1 github.com/oklog/run v1.1.0 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.3 // indirect + github.com/rs/zerolog v1.33.0 github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.9.0 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/crypto v0.31.0 + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/api v0.74.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect google.golang.org/grpc v1.45.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b8c5dc4..9b46280 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,7 @@ filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= @@ -81,11 +82,13 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= -github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/TwiN/go-color v1.4.0 h1:fNbOwOrvup5oj934UragnW0B1WKaAkkB85q19Y7h4ng= github.com/TwiN/go-color v1.4.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -109,7 +112,14 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk= +github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= @@ -122,6 +132,12 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -131,6 +147,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -140,7 +158,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -162,7 +182,13 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= +github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= +github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -173,8 +199,8 @@ github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9p github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -183,9 +209,22 @@ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXym github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -224,8 +263,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -246,8 +286,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -268,6 +308,7 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= @@ -304,8 +345,6 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= @@ -336,8 +375,6 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault-client-go v0.2.0 h1:Zzf5D2kj7QmBZE2ZTdril1aJlujMptPatxslTkdDF+U= -github.com/hashicorp/vault-client-go v0.2.0/go.mod h1:C9rbJeHeI1Dy/MXXd5YLrzRfAH27n6mARnhpvaW/8gk= github.com/hashicorp/vault/api v1.5.0 h1:Bp6yc2bn7CWkOrVIzFT/Qurzx528bdavF3nz590eu28= github.com/hashicorp/vault/api v1.5.0/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM= github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo= @@ -351,6 +388,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= +github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= @@ -358,6 +397,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -370,16 +411,25 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ldez/go-git-cmd-wrapper/v2 v2.8.0 h1:+lD1DY7jRCy0hBTgrlVSRQtfQvyCr4SGOlf+C+eXBtM= +github.com/ldez/go-git-cmd-wrapper/v2 v2.8.0/go.mod h1:0eUeas7XtKDPKQbB0KijfaMPbuQ/cIprtoTRiwaUoFg= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -390,15 +440,19 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -434,26 +488,37 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/mxcd/go-config v1.5.1 h1:LtDCavhn8hZAqCPN1tfEQDFIlIP8uUYsnDin83tnpBU= +github.com/mxcd/go-config v1.5.1/go.mod h1:nNT4MTSdQZblD9bYq5SSn+OtaWqQ6sU51VHHMbY3GIo= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.0 h1:O9+X96OcDjkmmZyfaG996kV7yq8HsoU2h1XRRQcefG8= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -474,6 +539,12 @@ github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -492,8 +563,9 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -501,10 +573,16 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8= github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -514,6 +592,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= go.mozilla.org/sops/v3 v3.7.3 h1:CYx02LnWTATWv6NqWJIt4JCKVKSnGV+MsRiDpvwWQhg= @@ -529,18 +608,21 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= +golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -551,8 +633,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -578,6 +658,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -620,8 +704,12 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -653,6 +741,10 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -706,7 +798,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -714,18 +805,27 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -735,14 +835,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -798,6 +899,10 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -964,14 +1069,15 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1013,6 +1119,7 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/.github/scripts/homebrew/gitops.rb b/hack/homebrew/gitops.rb similarity index 100% rename from .github/scripts/homebrew/gitops.rb rename to hack/homebrew/gitops.rb diff --git a/.github/scripts/homebrew/publish.sh b/hack/homebrew/publish.sh similarity index 100% rename from .github/scripts/homebrew/publish.sh rename to hack/homebrew/publish.sh diff --git a/hack/soft-serve/docker-compose.yml b/hack/soft-serve/docker-compose.yml new file mode 100644 index 0000000..11ed113 --- /dev/null +++ b/hack/soft-serve/docker-compose.yml @@ -0,0 +1,12 @@ +services: + soft-serve: + image: charmcli/soft-serve:latest + container_name: soft-serve + ports: + - 23231:23231 + - 23232:23232 + - 23233:23233 + - 9418:9418 + environment: + SOFT_SERVE_INITIAL_ADMIN_KEYS: "$SOFT_SERVE_INITIAL_ADMIN_KEYS" + restart: unless-stopped \ No newline at end of file diff --git a/hack/soft-serve/fixtures/values.yaml b/hack/soft-serve/fixtures/values.yaml new file mode 100644 index 0000000..060e37a --- /dev/null +++ b/hack/soft-serve/fixtures/values.yaml @@ -0,0 +1,7 @@ +service: + global: + namespace: foo + image: + repository: bar + tag: asdf1234 + \ No newline at end of file diff --git a/hack/soft-serve/start.sh b/hack/soft-serve/start.sh new file mode 100755 index 0000000..51f9526 --- /dev/null +++ b/hack/soft-serve/start.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +BASE="$(git rev-parse --show-toplevel)" +[[ $? -eq 0 ]] || { + echo 'Run this script from inside the repository (cannot determine toplevel directory)' + exit 1 +} + +log() { + if [[ $VERBOSE == 'true' ]]; then + echo $1 + fi +} + +# cleanup +rm -rf $BASE/hack/soft-serve/ssh-key +rm -rf $BASE/hack/soft-serve/ssh-key.pub +rm -rf $BASE/hack/soft-serve/data +rm -rf $BASE/hack/soft-serve/gitops-test + +remove_ssh_key() { + ssh-add -d $BASE/hack/soft-serve/ssh-key.pub +} + +trap remove_ssh_key INT + +# generate ssh key +ssh-keygen -t ed25519 -N '' -f $BASE/hack/soft-serve/ssh-key + +# set config vars +export SOFT_SERVE_INITIAL_ADMIN_KEYS=$(cat $BASE/hack/soft-serve/ssh-key.pub) + +mkdir -p $BASE/hack/soft-serve/data + +docker compose -f $BASE/hack/soft-serve/docker-compose.yml up -d + +sleep 1 + +ssh-add $BASE/hack/soft-serve/ssh-key +echo "Creating repo" +ssh -p 23231 -o StrictHostKeychecking=no -i $BASE/hack/soft-serve/ssh-key localhost repo create gitops-test + +echo "Cloning repo" +export GIT_SSH_COMMAND="ssh -o StrictHostKeychecking=no -i $BASE/hack/soft-serve/ssh-key" +git clone ssh://localhost:23231/gitops-test.git $BASE/hack/soft-serve/gitops-test +mkdir -p $BASE/hack/soft-serve/gitops-test/applications/dev/service-test +cp $BASE/hack/soft-serve/fixtures/values.yaml $BASE/hack/soft-serve/gitops-test/applications/dev/service-test/values.yaml +cd $BASE/hack/soft-serve/gitops-test +git config user.name "Soft-Serve" +git config user.email "soft-serve@localhost" +git checkout -b main +git add . +git commit -m "feat: add service-test application" +git push origin main + +cd $BASE \ No newline at end of file diff --git a/hack/soft-serve/stop.sh b/hack/soft-serve/stop.sh new file mode 100755 index 0000000..3656a96 --- /dev/null +++ b/hack/soft-serve/stop.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +BASE="$(git rev-parse --show-toplevel)" +[[ $? -eq 0 ]] || { + echo 'Run this script from inside the repository (cannot determine toplevel directory)' + exit 1 +} + +log() { + if [[ $VERBOSE == 'true' ]]; then + echo $1 + fi +} + +docker compose -f $BASE/hack/soft-serve/docker-compose.yml down + +# cleanup +rm -rf $BASE/hack/soft-serve/ssh-key +rm -rf $BASE/hack/soft-serve/ssh-key.pub +rm -rf $BASE/hack/soft-serve/data +rm -rf $BASE/hack/soft-serve/gitops-test diff --git a/internal/finalizer/finalizer.go b/internal/finalizer/finalizer.go new file mode 100644 index 0000000..79a95aa --- /dev/null +++ b/internal/finalizer/finalizer.go @@ -0,0 +1,14 @@ +package finalizer + +import ( + "github.com/mxcd/gitops-cli/internal/state" + "github.com/urfave/cli/v2" +) + +func ExitApplication(c *cli.Context, writeState bool) error { + var err error + if writeState { + err = state.GetState().Save(c) + } + return err +} diff --git a/internal/git/clone.go b/internal/git/clone.go new file mode 100644 index 0000000..01b4719 --- /dev/null +++ b/internal/git/clone.go @@ -0,0 +1,163 @@ +package git + +import ( + "fmt" + "net/url" + "os" + "strings" + "time" + + "github.com/ldez/go-git-cmd-wrapper/v2/clone" + "github.com/ldez/go-git-cmd-wrapper/v2/git" + "github.com/rs/zerolog/log" +) + +type CloneProtocol string + +const ( + CloneProtocolSsh CloneProtocol = "ssh" + CloneProtocolHttps CloneProtocol = "https" + CloneProtocolHttp CloneProtocol = "http" +) + +func (c *Connection) Clone() error { + if c.Options.Directory == "" { + directoryName, err := os.MkdirTemp(os.TempDir(), "gitops-repo-") + if err != nil { + log.Error().Err(err).Msg("Error creating temporary directory for cloning") + return err + } + c.Options.Directory = directoryName + } else { + err := os.MkdirAll(c.Options.Directory, 0755) + if err != nil { + log.Error().Err(err).Msg("Error creating directory for cloning") + return err + } + } + + repositoryUrl := c.Options.Repository + cloneProtocol := CloneProtocolHttps + + if strings.HasPrefix(repositoryUrl, "ssh://") || strings.HasPrefix(repositoryUrl, "git@") { + cloneProtocol = CloneProtocolSsh + } + + if strings.HasPrefix(repositoryUrl, "http://") { + cloneProtocol = CloneProtocolHttp + } + + if strings.HasPrefix(repositoryUrl, "https://") { + cloneProtocol = CloneProtocolHttps + } + + if cloneProtocol == CloneProtocolHttp || cloneProtocol == CloneProtocolHttps { + return c.CloneHttps(cloneProtocol) + } else if cloneProtocol == CloneProtocolSsh { + return c.CloneSsh(cloneProtocol) + } else { + return fmt.Errorf("unsupported clone protocol: %s", cloneProtocol) + } + +} + +func (c *Connection) CloneSsh(cloneProtocol CloneProtocol) error { + startTime := time.Now() + + repositoryUrl := c.Options.Repository + repositoryBaseUrl := strings.TrimPrefix(repositoryUrl, "ssh://") + repositoryBaseUrl = strings.TrimSuffix(repositoryBaseUrl, ".git") + + repositoryUrlSplit := strings.Split(repositoryBaseUrl, "@") + sshUsername := "git" + if c.Options.Authentication != nil && c.Options.Authentication.SshUsername != nil { + sshUsername = *c.Options.Authentication.SshUsername + } + + if len(repositoryUrlSplit) == 1 { + repositoryUrl = fmt.Sprintf("ssh://%s@%s.git", sshUsername, repositoryUrlSplit[0]) + } else { + repositoryUrl = fmt.Sprintf("ssh://%s@%s.git", repositoryUrlSplit[0], repositoryUrlSplit[1]) + } + + lock.Lock() + defer lock.Unlock() + + log.Debug().Msgf("Using SSH URL: %s", repositoryUrl) + + privateKeyFile, err := c.provideSshAuthentication() + if err != nil { + return err + } + defer cleanSshAuthentication(privateKeyFile) + + msg, err := git.Clone( + clone.Repository(repositoryUrl), + clone.Directory(c.Options.Directory), + ) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to clone repository") + return err + } + + log.Debug().Msgf("Cloned repository %s on branch %s in %d ms", c.Options.Repository, c.Options.Branch, time.Since(startTime).Milliseconds()) + return nil +} + +func (c *Connection) CloneHttps(cloneProtocol CloneProtocol) error { + startTime := time.Now() + + repositoryUrl := c.Options.Repository + repositoryBaseUrl := repositoryUrl + + if strings.HasPrefix(repositoryUrl, "http://") { + repositoryBaseUrl = strings.TrimPrefix(repositoryUrl, "http://") + } + + if strings.HasPrefix(repositoryUrl, "https://") { + repositoryBaseUrl = strings.TrimPrefix(repositoryUrl, "https://") + } + + if cloneProtocol == CloneProtocolHttp { + repositoryUrl = fmt.Sprintf("http://%s", repositoryBaseUrl) + } else if cloneProtocol == CloneProtocolHttps { + repositoryUrl = fmt.Sprintf("https://%s", repositoryBaseUrl) + } + + log.Debug().Msgf("Using http URL: %s", repositoryUrl) + + parsedUrl, err := url.Parse(repositoryUrl) + if err != nil { + log.Error().Err(err).Msg("Error parsing repository URL") + return err + } + + if c.Options.Authentication != nil && c.Options.Authentication.BasicAuth != nil { + auth := c.Options.Authentication.BasicAuth + parsedUrl.User = url.UserPassword(auth.Username, auth.Password) + } + + repositoryUrl = parsedUrl.String() + + lock.Lock() + defer lock.Unlock() + + if parsedUrl.Scheme == "https" && c.Options.SkipSslVerify { + if err := os.Setenv("GIT_SSL_NO_VERIFY", "1"); err != nil { + log.Error().Err(err).Msg("Error setting GIT_SSL_NO_VERIFY") + return err + } + } + + msg, err := git.Clone( + clone.Repository(repositoryUrl), + clone.Directory(c.Options.Directory), + ) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to clone repository") + return err + } + + log.Debug().Msgf("Cloned repository %s on branch %s in %d ms", c.Options.Repository, c.Options.Branch, time.Since(startTime).Milliseconds()) + return nil +} diff --git a/internal/git/commit.go b/internal/git/commit.go new file mode 100644 index 0000000..3958775 --- /dev/null +++ b/internal/git/commit.go @@ -0,0 +1,72 @@ +package git + +import ( + "fmt" + + "github.com/ldez/go-git-cmd-wrapper/v2/add" + "github.com/ldez/go-git-cmd-wrapper/v2/commit" + "github.com/ldez/go-git-cmd-wrapper/v2/config" + "github.com/ldez/go-git-cmd-wrapper/v2/git" + "github.com/ldez/go-git-cmd-wrapper/v2/revparse" + "github.com/ldez/go-git-cmd-wrapper/v2/types" + "github.com/rs/zerolog/log" +) + +func (c *Connection) Commit(files []string, message string) (string, error) { + directory := c.Options.Directory + if directory == "" { + return "", fmt.Errorf("directory is not specified") + } + + msg, err := git.Add(runGitIn(directory), add.PathSpec(files...)) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to add files") + return "", err + } + + msg, err = git.Config(config.Entry("user.name", c.Options.Signature.Name), runGitIn(directory)) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to set user.name") + return "", err + } + + msg, err = git.Config(config.Entry("user.email", c.Options.Signature.Email), runGitIn(directory)) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to set user.email") + return "", err + } + + msg, err = git.Commit(runGitIn(directory), commit.Message(message)) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to commit") + return "", err + } + + currentCommitId, err := git.RevParse(runGitIn(directory), revparse.Args("HEAD")) + if err != nil { + log.Error().Err(err).Str("output", currentCommitId).Msg("Failed to get current commit ID") + return "", err + } + + return currentCommitId, nil +} + +func (c *Connection) HasChanges() (bool, error) { + directory := c.Options.Directory + if directory == "" { + return false, fmt.Errorf("directory is not specified") + } + + git.Raw("update-index", runGitIn(directory), func(g *types.Cmd) { + g.AddOptions("--refresh") + }) + + _, err := git.Raw("diff-files", runGitIn(directory), func(g *types.Cmd) { + g.AddOptions("--quiet") + }) + if err != nil { + return true, nil + } + + return false, nil +} diff --git a/internal/git/git.go b/internal/git/git.go new file mode 100644 index 0000000..f00fe75 --- /dev/null +++ b/internal/git/git.go @@ -0,0 +1,182 @@ +package git + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "strings" + "sync" + + "github.com/ldez/go-git-cmd-wrapper/v2/git" + "github.com/ldez/go-git-cmd-wrapper/v2/types" + "github.com/rs/zerolog/log" + "golang.org/x/crypto/ssh" +) + +type ConnectionOptions struct { + Directory string + Repository string + Branch string + Authentication *Authentication + IgnoreSshHostKey bool + SkipSslVerify bool + Signature *Signature +} + +type Authentication struct { + BasicAuth *BasicAuth + SshKey *SshKey + SshUsername *string +} + +type BasicAuth struct { + Username string + Password string +} + +type SshKey struct { + PrivateKey []byte + Passphrase *string + Signer *ssh.Signer +} + +type Signature struct { + Name string + Email string +} + +type Connection struct { + Options *ConnectionOptions +} + +var lock = &sync.Mutex{} + +func NewGitConnection(options *ConnectionOptions) (*Connection, error) { + connection := &Connection{ + Options: options, + } + + if options.Signature == nil { + connection.Options.Signature = &Signature{ + Name: "GitOps CLI CI User", + Email: "gitops@example.com", + } + } else { + connection.Options.Signature = options.Signature + } + + return connection, nil +} + +func GetAuthFromSshKey(sshKey []byte, sshKeyPassphrase *string) (*Authentication, error) { + var signer ssh.Signer + if sshKeyPassphrase != nil { + _signer, err := ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(*sshKeyPassphrase)) + if err != nil { + return nil, err + } + signer = _signer + } else { + _signer, err := ssh.ParsePrivateKey(sshKey) + if err != nil { + return nil, err + } + signer = _signer + } + + return &Authentication{ + SshKey: &SshKey{ + PrivateKey: sshKey, + Passphrase: sshKeyPassphrase, + Signer: &signer, + }, + }, nil +} + +func GetAuthFromUsernamePassword(username string, password string) (*Authentication, error) { + return &Authentication{ + BasicAuth: &BasicAuth{ + Username: username, + Password: password, + }, + }, nil +} + +func GetAuthFromBasicAuthString(basicAuth string) (*Authentication, error) { + split := strings.Split(basicAuth, ":") + if len(split) != 2 { + return nil, errors.New("invalid basic auth string") + } + return &Authentication{ + BasicAuth: &BasicAuth{ + Username: split[0], + Password: split[1], + }, + }, nil +} + +func runGitIn(path string) types.Option { + return git.CmdExecutor( + func(ctx context.Context, name string, debug bool, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = path + + output, err := cmd.CombinedOutput() + return strings.TrimSuffix(string(output), "\n"), err + }, + ) +} + +func (c *Connection) provideSshAuthentication() (*os.File, error) { + // use ssh authentication only if provided + if c.Options.Authentication != nil && c.Options.Authentication.SshKey != nil { + key := c.Options.Authentication.SshKey + // TODO add support for passphrase and ssh-agent + if key.Passphrase != nil { + log.Panic().Msg("Passphrase support is not implemented") + } + + privateKeyFile, err := os.CreateTemp("", "git-ssh-key-*") + if err != nil { + log.Error().Err(err).Msg("Error creating temporary file for SSH key") + return nil, err + } + + if _, err := privateKeyFile.Write([]byte(key.PrivateKey)); err != nil { + log.Error().Err(err).Msg("Error writing SSH key to temporary file") + os.Remove(privateKeyFile.Name()) + return nil, err + } + if err := privateKeyFile.Chmod(0600); err != nil { + log.Error().Err(err).Msg("Error setting permissions on SSH key file") + os.Remove(privateKeyFile.Name()) + return nil, err + } + + strictHostKeyChecking := "" + if c.Options.IgnoreSshHostKey { + strictHostKeyChecking = "-o StrictHostKeyChecking=no" + } + + sshCommand := fmt.Sprintf("ssh -i %s %s", privateKeyFile.Name(), strictHostKeyChecking) + if err := os.Setenv("GIT_SSH_COMMAND", sshCommand); err != nil { + log.Error().Err(err).Msg("Error setting GIT_SSH_COMMAND") + os.Remove(privateKeyFile.Name()) + return nil, err + } + + return privateKeyFile, nil + } + + return nil, nil +} + +func cleanSshAuthentication(privateKeyFile *os.File) { + if privateKeyFile != nil { + if err := os.Remove(privateKeyFile.Name()); err != nil { + log.Error().Err(err).Msg("Error removing temporary SSH key file") + } + } +} diff --git a/internal/git/git_test.go b/internal/git/git_test.go new file mode 100644 index 0000000..625299f --- /dev/null +++ b/internal/git/git_test.go @@ -0,0 +1,195 @@ +package git + +import ( + "log" + "os" + "path" + "testing" + + "github.com/google/uuid" + "github.com/mxcd/gitops-cli/internal/util" + "github.com/stretchr/testify/assert" +) + +func getSshKeyData(t *testing.T) []byte { + baseDir, err := util.GetGitRepoRoot() + assert.NoError(t, err) + assert.NotEmpty(t, baseDir) + + sshKeyPath := path.Join(baseDir, "hack", "soft-serve", "ssh-key") + assert.FileExists(t, sshKeyPath) + + sshKey, err := os.ReadFile(sshKeyPath) + assert.NoError(t, err) + assert.NotEmpty(t, sshKey) + + return sshKey +} + +func TestNewGitConnection(t *testing.T) { + + sshKey := getSshKeyData(t) + + baseDir, err := util.GetGitRepoRoot() + assert.NoError(t, err) + + authentication, err := GetAuthFromSshKey(sshKey, nil) + assert.NoError(t, err) + assert.NotNil(t, authentication) + + uuid := uuid.New().String() + + options := &ConnectionOptions{ + Directory: path.Join(baseDir, "sandbox", "gitops-test-"+uuid), + Repository: "ssh://git@localhost:23231/gitops-test.git", + Branch: "main", + Authentication: authentication, + IgnoreSshHostKey: true, + } + gitConnection, err := NewGitConnection(options) + assert.NoError(t, err) + assert.NotNil(t, gitConnection) + + err = gitConnection.Clone() + assert.NoError(t, err) +} + +func cloneTempRepository(t *testing.T) *Connection { + sshKey := getSshKeyData(t) + + baseDir, err := util.GetGitRepoRoot() + assert.NoError(t, err) + + uuid := uuid.New().String() + directoryName := path.Join(baseDir, "sandbox", "gitops-test-"+uuid) + + authentication, err := GetAuthFromSshKey(sshKey, nil) + assert.NoError(t, err) + assert.NotNil(t, authentication) + + options := &ConnectionOptions{ + Directory: directoryName, + Repository: "ssh://git@localhost:23231/gitops-test.git", + Branch: "main", + Authentication: authentication, + IgnoreSshHostKey: true, + } + gitConnection, err := NewGitConnection(options) + assert.NoError(t, err) + assert.NotNil(t, gitConnection) + + err = gitConnection.Clone() + assert.NoError(t, err) + + return gitConnection +} + +func TestGitPullFastForward(t *testing.T) { + + tempConnectionA := cloneTempRepository(t) + assert.NotNil(t, tempConnectionA) + log.Println("tempConnectionA cloned") + + tempConnectionB := cloneTempRepository(t) + assert.NotNil(t, tempConnectionB) + log.Println("tempConnectionB cloned") + + uuid := uuid.New().String() + testFileName := "test-file-" + uuid + testFilePath := path.Join(tempConnectionA.Options.Directory, testFileName) + err := os.WriteFile(testFilePath, []byte("test"), 0644) + assert.NoError(t, err) + + log.Printf("file written to %s", testFilePath) + + hash, err := tempConnectionA.Commit([]string{testFileName}, "Test commit") + assert.NoError(t, err) + assert.NotEmpty(t, hash) + + err = tempConnectionA.Push() + assert.NoError(t, err) + + err = tempConnectionB.Pull() + assert.NoError(t, err) + + testFilePath = path.Join(tempConnectionB.Options.Directory, testFileName) + _, err = os.Stat(testFilePath) + assert.NoError(t, err) + data, err := os.ReadFile(testFilePath) + assert.NoError(t, err) + assert.Equal(t, "test", string(data)) +} + +func TestGitPullRebase(t *testing.T) { + + // Clone repository A + tempConnectionA := cloneTempRepository(t) + assert.NotNil(t, tempConnectionA) + log.Println("tempConnectionA cloned") + + // Clone repository B + tempConnectionB := cloneTempRepository(t) + assert.NotNil(t, tempConnectionB) + log.Println("tempConnectionB cloned") + + // Create a new file in repository A, commit and push it + uuidA := uuid.New().String() + testFileNameA := "test-file-" + uuidA + testFilePathA := path.Join(tempConnectionA.Options.Directory, testFileNameA) + err := os.WriteFile(testFilePathA, []byte("test A"), 0644) + assert.NoError(t, err) + + log.Printf("file written to %s", testFilePathA) + + hashA, err := tempConnectionA.Commit([]string{testFileNameA}, "Test commit A") + assert.NoError(t, err) + assert.NotEmpty(t, hashA) + + err = tempConnectionA.Push() + assert.NoError(t, err) + + // Create a new file in repository B, commit and push it + uuidB := uuid.New().String() + testFileNameB := "test-file-" + uuidB + testFilePathB := path.Join(tempConnectionB.Options.Directory, testFileNameB) + err = os.WriteFile(testFilePathB, []byte("test B"), 0644) + assert.NoError(t, err) + + log.Printf("file written to %s", testFilePathB) + + hashB, err := tempConnectionB.Commit([]string{testFileNameB}, "Test commit B") + assert.NoError(t, err) + assert.NotEmpty(t, hashB) + + // Push is expected to fail because of changes from repository A in remote + err = tempConnectionB.Push() + assert.Error(t, err) + + // Pull is expected to rebase the changes from repository A + err = tempConnectionB.Pull() + assert.NoError(t, err) + + // check if test file A is added to repo B + testFilePath := path.Join(tempConnectionB.Options.Directory, testFileNameA) + _, err = os.Stat(testFilePath) + assert.NoError(t, err) + data, err := os.ReadFile(testFilePath) + assert.NoError(t, err) + assert.Equal(t, "test A", string(data)) + + // Push is expected to succeed after rebase + err = tempConnectionB.Push() + assert.NoError(t, err) + + // Pull is expected to pull the changes from repository B + err = tempConnectionA.Pull() + assert.NoError(t, err) + + // check if test file B is added to repo A + testFilePath = path.Join(tempConnectionA.Options.Directory, testFileNameB) + _, err = os.Stat(testFilePath) + assert.NoError(t, err) + data, err = os.ReadFile(testFilePath) + assert.NoError(t, err) + assert.Equal(t, "test B", string(data)) +} diff --git a/internal/git/pull.go b/internal/git/pull.go new file mode 100644 index 0000000..754d348 --- /dev/null +++ b/internal/git/pull.go @@ -0,0 +1,43 @@ +package git + +import ( + "fmt" + "time" + + "github.com/ldez/go-git-cmd-wrapper/v2/git" + "github.com/ldez/go-git-cmd-wrapper/v2/pull" + "github.com/rs/zerolog/log" +) + +func (c *Connection) Pull() error { + directory := c.Options.Directory + if directory == "" { + return fmt.Errorf("directory is not specified") + } + + startTime := time.Now() + + lock.Lock() + defer lock.Unlock() + + privateKeyFile, err := c.provideSshAuthentication() + if err != nil { + return err + } + defer cleanSshAuthentication(privateKeyFile) + + msg, err := git.Pull( + runGitIn(directory), + pull.Repository("origin"), + pull.Refspec(c.Options.Branch), + pull.Rebase("true"), + ) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to pull repository") + return err + } + + log.Debug().Msgf("Pulled from origin/%s in %d ms", c.Options.Branch, time.Since(startTime).Milliseconds()) + + return nil +} diff --git a/internal/git/push.go b/internal/git/push.go new file mode 100644 index 0000000..6618565 --- /dev/null +++ b/internal/git/push.go @@ -0,0 +1,42 @@ +package git + +import ( + "fmt" + "time" + + "github.com/ldez/go-git-cmd-wrapper/v2/git" + "github.com/ldez/go-git-cmd-wrapper/v2/push" + "github.com/rs/zerolog/log" +) + +func (c *Connection) Push() error { + directory := c.Options.Directory + if directory == "" { + return fmt.Errorf("directory is not specified") + } + + startTime := time.Now() + + lock.Lock() + defer lock.Unlock() + + privateKeyFile, err := c.provideSshAuthentication() + if err != nil { + return err + } + defer cleanSshAuthentication(privateKeyFile) + + msg, err := git.Push( + runGitIn(directory), + push.Remote("origin"), + push.RefSpec(c.Options.Branch), + ) + if err != nil { + log.Error().Err(err).Str("output", msg).Msg("Failed to push to remote") + return err + } + + log.Debug().Msgf("Pushed to origin %s in %d ms", c.Options.Branch, time.Since(startTime).Milliseconds()) + + return nil +} diff --git a/cmd/gitops/kubernetes.go b/internal/kubernetes/kubernetes.go similarity index 96% rename from cmd/gitops/kubernetes.go rename to internal/kubernetes/kubernetes.go index 694e6a1..161356f 100644 --- a/cmd/gitops/kubernetes.go +++ b/internal/kubernetes/kubernetes.go @@ -1,4 +1,4 @@ -package main +package kubernetes import ( "fmt" @@ -6,6 +6,7 @@ import ( "github.com/TwiN/go-color" "github.com/google/uuid" + "github.com/mxcd/gitops-cli/internal/finalizer" "github.com/mxcd/gitops-cli/internal/k8s" "github.com/mxcd/gitops-cli/internal/plan" "github.com/mxcd/gitops-cli/internal/secret" @@ -17,7 +18,7 @@ import ( k8sErrors "k8s.io/apimachinery/pkg/api/errors" ) -func applyKubernetes(c *cli.Context) error { +func ApplyKubernetes(c *cli.Context) error { p, err := createKubernetesPlan(c) if err != nil { return err @@ -26,7 +27,7 @@ func applyKubernetes(c *cli.Context) error { // exit if there is nothing to do if p.NothingToDo() { println(color.InGreen("No changes to apply.")) - exitApplication(c, true) + finalizer.ExitApplication(c, true) return nil } @@ -55,11 +56,11 @@ func applyKubernetes(c *cli.Context) error { println("") println(color.InGreen("All changes applied.")) - exitApplication(c, true) + finalizer.ExitApplication(c, true) return nil } -func planKubernetes(c *cli.Context) error { +func PlanKubernetes(c *cli.Context) error { clusterLimitString := getClusterLimit(c) p, err := createKubernetesPlan(c) @@ -78,7 +79,7 @@ func planKubernetes(c *cli.Context) error { applyString := fmt.Sprintf("gitops secrets%s apply kubernetes %s", dirLimitString, clusterLimitString) println(color.InBold("use"), color.InGreen(color.InBold(applyString)), color.InBold("to apply these changes to your cluster")) } - exitApplication(c, false) + finalizer.ExitApplication(c, false) return nil } diff --git a/internal/patch/method_git.go b/internal/patch/method_git.go new file mode 100644 index 0000000..36ddb62 --- /dev/null +++ b/internal/patch/method_git.go @@ -0,0 +1,201 @@ +package patch + +import ( + "fmt" + "os" + "path" + "time" + + "github.com/mxcd/gitops-cli/internal/git" + "github.com/mxcd/gitops-cli/internal/yaml" + "github.com/urfave/cli/v2" + + "github.com/rs/zerolog/log" +) + +type GitPatcherOptions struct { + GitConnectionOptions *git.ConnectionOptions + GitConnection *git.Connection +} + +type GitPatcher struct { + Options *GitPatcherOptions + GitConnection *git.Connection +} + +func GetGitConnectionOptionsFromCli(c *cli.Context) (*git.ConnectionOptions, error) { + + var authentication *git.Authentication = nil + + if c.String("basicauth") != "" { + auth, err := git.GetAuthFromBasicAuthString(c.String("basicauth")) + if err != nil { + return nil, err + } + authentication = auth + } else if c.String("ssh-key") != "" || c.String("ssh-key-file") != "" { + var sshKey []byte + if c.String("ssh-key") != "" { + sshKey = []byte(c.String("ssh-key")) + } else { + file, err := os.ReadFile(c.String("ssh-key-file")) + if err != nil { + return nil, err + } + sshKey = file + } + + var passphrase *string + if c.String("ssh-key-passphrase") != "" { + _passphrase := c.String("ssh-key-passphrase") + passphrase = &_passphrase + } + + auth, err := git.GetAuthFromSshKey(sshKey, passphrase) + if err != nil { + return nil, err + } + authentication = auth + } + + options := &git.ConnectionOptions{ + Repository: c.String("repository"), + Branch: c.String("branch"), + Authentication: authentication, + } + + return options, nil +} + +func GetGitPatcherOptionsFromCli(c *cli.Context) (*GitPatcherOptions, error) { + + gitConnectionOptions, err := GetGitConnectionOptionsFromCli(c) + if err != nil { + return nil, err + } + + options := &GitPatcherOptions{ + GitConnectionOptions: gitConnectionOptions, + } + + return options, nil +} + +func NewGitPatcher(options *GitPatcherOptions) (*GitPatcher, error) { + return &GitPatcher{ + Options: options, + }, nil +} + +func (p *GitPatcher) Prepare(options *PrepareOptions) error { + + if p.Options.GitConnection != nil { + p.GitConnection = p.Options.GitConnection + } else { + gitConnection, err := git.NewGitConnection(p.Options.GitConnectionOptions) + if err != nil { + return err + } + p.GitConnection = gitConnection + } + + if options != nil && options.Clone { + err := p.GitConnection.Clone() + if err != nil { + return err + } + } + + return nil +} + +func (p *GitPatcher) Patch(patchTasks []PatchTask) error { + + err := p.GitConnection.Pull() + if err != nil { + return err + } + + for _, patchTask := range patchTasks { + relativeFilePath := patchTask.FilePath + + absoluteFilePath := path.Join(p.GitConnection.Options.Directory, relativeFilePath) + + fileStat, err := os.Stat(absoluteFilePath) + if err != nil { + log.Error().Err(err).Msg("Failed to stat file") + } + + fileContents, err := os.ReadFile(absoluteFilePath) + if err != nil { + log.Error().Err(err).Msg("Failed to read file") + return err + } + + log.Debug().Msgf("original yaml file: %s", string(fileContents)) + + for _, patch := range patchTask.Patches { + selector := patch.Selector + value := patch.Value + + log.Debug().Msgf("patching file with selector '%s' and value '%s'", selector, value) + patchedData, err := yaml.PatchYaml(fileContents, selector, value) + if err != nil { + return err + } + log.Debug().Msgf("patched yaml file:\n%s", string(patchedData)) + + fileContents = patchedData + } + + err = os.WriteFile(absoluteFilePath, fileContents, fileStat.Mode()) + if err != nil { + log.Error().Err(err).Msg("Failed to write file") + return err + } + + log.Debug().Msg("checking for changes") + hasChanges, err := p.GitConnection.HasChanges() + if err != nil { + return err + } + + if !hasChanges { + log.Info().Msg("No changes detected, exiting") + return nil + } else { + log.Debug().Msg("Changes detected, committing") + } + + commitFooter := "" + + if patchTask.Actor != "" { + commitFooter = fmt.Sprintf("\n\nTriggered by: %s", patchTask.Actor) + } + + commitHash, err := p.GitConnection.Commit([]string{relativeFilePath}, fmt.Sprintf("feat(gitops): patching %s%s", relativeFilePath, commitFooter)) + if err != nil { + return err + } + log.Info().Msgf("Created patch commit: %s", commitHash) + } + + executePush := func() error { + err := p.GitConnection.Pull() + if err != nil { + log.Error().Err(err).Msg("Error pulling prior to push") + return err + } + return p.GitConnection.Push() + } + + for i := 0; i < 5; i++ { + err = executePush() + if err == nil { + break + } + time.Sleep(time.Duration(i+1) * time.Second) + } + + return err +} diff --git a/internal/patch/method_git_test.go b/internal/patch/method_git_test.go new file mode 100644 index 0000000..58399bc --- /dev/null +++ b/internal/patch/method_git_test.go @@ -0,0 +1,75 @@ +package patch + +import ( + "os" + "path" + "testing" + + "github.com/google/uuid" + "github.com/mxcd/gitops-cli/internal/git" + "github.com/mxcd/gitops-cli/internal/util" + "github.com/stretchr/testify/assert" +) + +func getSshKeyData(t *testing.T) []byte { + baseDir, err := util.GetGitRepoRoot() + assert.NoError(t, err) + assert.NotEmpty(t, baseDir) + + sshKeyPath := path.Join(baseDir, "hack", "soft-serve", "ssh-key") + assert.FileExists(t, sshKeyPath) + + sshKey, err := os.ReadFile(sshKeyPath) + assert.NoError(t, err) + assert.NotEmpty(t, sshKey) + + return sshKey +} + +func TestGitSshPatch(t *testing.T) { + sshKey := getSshKeyData(t) + baseDir, err := util.GetGitRepoRoot() + assert.NoError(t, err) + + uuidA := uuid.New().String() + repositoryPathA := path.Join(baseDir, "sandbox", "gitops-test-"+uuidA) + err = os.MkdirAll(repositoryPathA, 0755) + assert.NoError(t, err) + + gitConnectionOptionsA := &git.ConnectionOptions{ + Repository: "ssh://localhost:23231/gitops-test.git", + Directory: repositoryPathA, + Branch: "main", + IgnoreSshHostKey: true, + Authentication: &git.Authentication{ + SshKey: &git.SshKey{ + PrivateKey: sshKey, + }, + }, + } + + patcher, err := NewGitPatcher(&GitPatcherOptions{ + GitConnectionOptions: gitConnectionOptionsA, + }) + assert.NoError(t, err) + assert.NotNil(t, patcher) + + err = patcher.Prepare(&PrepareOptions{ + Clone: true, + }) + assert.NotNil(t, patcher.GitConnection) + assert.NoError(t, err) + + patchTask := PatchTask{ + FilePath: "applications/dev/service-test/values.yaml", + Patches: []Patch{ + { + Selector: ".service.image.tag", + Value: "v1.0.1", + }, + }, + } + + err = patcher.Patch([]PatchTask{patchTask}) + assert.NoError(t, err) +} diff --git a/internal/patch/method_server.go b/internal/patch/method_server.go new file mode 100644 index 0000000..d6f5b75 --- /dev/null +++ b/internal/patch/method_server.go @@ -0,0 +1,96 @@ +package patch + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" +) + +type RepositoryServerPatcher struct { + RepositoryServerURL string + RepositoryServerApiKey string +} + +func NewRepoServerPatcher(c *cli.Context) (*RepositoryServerPatcher, error) { + repositoryServerURL := c.String("repository-server") + repositoryServerApiKey := c.String("repository-server-api-key") + if repositoryServerURL == "" { + log.Error().Msg("Repository server URL not provided") + return nil, errors.New("repository server URL not provided") + } + if repositoryServerApiKey == "" { + log.Error().Msg("Repository server API key not provided") + return nil, errors.New("repository server API key not provided") + } + + log.Debug().Msgf("Using repository server URL: %s", repositoryServerURL) + + return &RepositoryServerPatcher{ + RepositoryServerURL: repositoryServerURL, + RepositoryServerApiKey: repositoryServerApiKey, + }, nil +} + +func (p *RepositoryServerPatcher) Prepare(options *PrepareOptions) error { + log.Debug().Msg("RepoServerPatcher: Prepare method called, no action required") + return nil +} + +func (p *RepositoryServerPatcher) Patch(patchTasks []PatchTask) error { + if len(patchTasks) == 0 { + log.Warn().Msg("No patch tasks provided, skipping patching") + return nil + } + + if len(patchTasks) > 1 { + log.Warn().Msg("More than one patch task provided, only the first one will be applied") + } + + jsonData, err := json.Marshal(patchTasks[0]) + if err != nil { + log.Error().Err(err).Msg("Failed to marshal patch tasks") + return fmt.Errorf("failed to marshal patch tasks: %w", err) + } + log.Debug().Msgf("Patch task JSON: %s", string(jsonData)) + + requestURL := fmt.Sprintf("%s/patch", p.RepositoryServerURL) + log.Debug().Msgf("Request URL: %s", requestURL) + + req, err := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(jsonData)) + if err != nil { + log.Error().Err(err).Msg("Failed to create HTTP request") + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-API-Key", p.RepositoryServerApiKey) + + log.Info().Msg("Sending patch request to repository server") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Error().Err(err).Msg("Failed to send request to repository server") + return fmt.Errorf("failed to send request to repository server: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Msg("Failed to read response body") + return fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + log.Error().Msgf("Repository server returned status %d: %s", resp.StatusCode, string(body)) + return fmt.Errorf("repository server returned status %d: %s", resp.StatusCode, string(body)) + } + + log.Info().Msg("Patch applied successfully via repository server.") + return nil +} diff --git a/internal/patch/patch.go b/internal/patch/patch.go new file mode 100644 index 0000000..790e28e --- /dev/null +++ b/internal/patch/patch.go @@ -0,0 +1,110 @@ +package patch + +import ( + "errors" + + log "github.com/rs/zerolog/log" + + "github.com/urfave/cli/v2" +) + +type PatchTask struct { + Actor string `json:"actor"` + FilePath string `json:"filePath"` + Patches []Patch `json:"patches"` +} + +type Patch struct { + Selector string `json:"selector"` + Value string `json:"value"` +} + +type PrepareOptions struct { + Clone bool +} + +type PatchMethod interface { + Prepare(options *PrepareOptions) error + Patch(patchTasks []PatchTask) error +} + +func PatchCommand(c *cli.Context) error { + + var patchMethod PatchMethod + + if c.String("repository") != "" { + // patcherOptions, err := GetGitPatcherOptionsFromCli(c) + // if err != nil { + // return err + // } + + // patcher, err := NewGitPatcher(patcherOptions) + // if err != nil { + // return err + // } + + // patchMethod = patcher + log.Panic().Msg("Git patcher not implemented") + } else if c.String("repository-server") != "" { + method, err := NewRepoServerPatcher(c) + if err != nil { + return err + } + patchMethod = method + } else { + return errors.New("no repository specified") + } + + err := patchMethod.Prepare(&PrepareOptions{Clone: true}) + if err != nil { + return err + } + + patchTask, err := GetPatchTaskFromCli(c) + if err != nil { + return err + } + + err = patchMethod.Patch([]PatchTask{patchTask}) + if err != nil { + return err + } + + return nil +} + +func GetPatchTaskFromCli(c *cli.Context) (PatchTask, error) { + filePath := c.Args().First() + if filePath == "" { + return PatchTask{}, errors.New("no file specified") + } + + actor := "" + + if c.String("actor") != "" { + cliActor := c.String("actor") + if cliActor != "" { + actor = cliActor + } + } + + patches := []Patch{} + args := c.Args().Tail() + + if len(args)%2 != 0 { + return PatchTask{}, errors.New("invalid number of arguments. patches must be in the form of 'selector value'") + } + + for i := 0; i < len(args); i += 2 { + patches = append(patches, Patch{ + Selector: args[i], + Value: args[i+1], + }) + } + + return PatchTask{ + Actor: actor, + FilePath: filePath, + Patches: patches, + }, nil +} diff --git a/internal/repo_server/server/api_key_middleware.go b/internal/repo_server/server/api_key_middleware.go new file mode 100644 index 0000000..cd4ac89 --- /dev/null +++ b/internal/repo_server/server/api_key_middleware.go @@ -0,0 +1,37 @@ +package server + +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +type ApiKeyAuthMiddlewareConfig struct { + AllowedApiKeys []string + UnprotectedRoutes []string +} + +func GetApiKeyAuthMiddleware(config *ApiKeyAuthMiddlewareConfig) gin.HandlerFunc { + return func(c *gin.Context) { + + for _, route := range config.UnprotectedRoutes { + if c.Request.URL.Path == route { + c.Next() + return + } + } + + apiKeyHeaderValue := c.GetHeader("X-API-Key") + if apiKeyHeaderValue == "" { + c.AbortWithError(401, fmt.Errorf("no api key provided")) + } + + for _, validApiKey := range config.AllowedApiKeys { + if apiKeyHeaderValue == validApiKey { + c.Next() + return + } + } + c.AbortWithError(401, fmt.Errorf("invalid API key")) + } +} diff --git a/internal/repo_server/server/health_controller.go b/internal/repo_server/server/health_controller.go new file mode 100644 index 0000000..7f3fa19 --- /dev/null +++ b/internal/repo_server/server/health_controller.go @@ -0,0 +1,14 @@ +package server + +import "github.com/gin-gonic/gin" + +func (s *Server) registerHealthRoute() error { + s.Engine.GET(s.Options.ApiBaseUrl+"/health", s.getHealthHandler()) + return nil +} + +func (s *Server) getHealthHandler() gin.HandlerFunc { + return func(c *gin.Context) { + c.JSON(200, gin.H{"status": "ok"}) + } +} diff --git a/internal/repo_server/server/patch_controller.go b/internal/repo_server/server/patch_controller.go new file mode 100644 index 0000000..7e19f0f --- /dev/null +++ b/internal/repo_server/server/patch_controller.go @@ -0,0 +1,36 @@ +package server + +import ( + "sync" + + "github.com/gin-gonic/gin" + "github.com/mxcd/gitops-cli/internal/patch" +) + +func (s *Server) registerPatchRoute() error { + s.Engine.PUT(s.Options.ApiBaseUrl+"/patch", s.getPatchHandler()) + return nil +} + +func (s *Server) getPatchHandler() gin.HandlerFunc { + lock := sync.Mutex{} + + return func(c *gin.Context) { + var input patch.PatchTask + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(400, gin.H{"error": "invalid input"}) + return + } + + lock.Lock() + defer lock.Unlock() + + err := s.GitPatcher.Patch([]patch.PatchTask{input}) + if err != nil { + c.JSON(500, gin.H{"error": "error executing patching"}) + return + } + + c.JSON(200, gin.H{"message": "ok"}) + } +} diff --git a/internal/repo_server/server/server.go b/internal/repo_server/server/server.go new file mode 100644 index 0000000..2ad4b6e --- /dev/null +++ b/internal/repo_server/server/server.go @@ -0,0 +1,71 @@ +package server + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/mxcd/gitops-cli/internal/patch" +) + +type RouterOptions struct { + DevMode bool + Port int + ApiBaseUrl string + ApiKeys []string +} + +type Server struct { + Engine *gin.Engine + HttpServer *http.Server + Options *RouterOptions + GitPatcher *patch.GitPatcher +} + +func NewServer(options *RouterOptions, gitPatcher *patch.GitPatcher) (*Server, error) { + if !options.DevMode { + gin.SetMode(gin.ReleaseMode) + } + + engine := gin.New() + router := &Server{ + Options: options, + Engine: engine, + HttpServer: &http.Server{ + Addr: fmt.Sprintf(":%d", options.Port), + Handler: engine, + }, + GitPatcher: gitPatcher, + } + + return router, nil +} + +func (s *Server) RegisterMiddlewares() { + apiKeyAuthMiddleware := GetApiKeyAuthMiddleware(&ApiKeyAuthMiddlewareConfig{ + AllowedApiKeys: s.Options.ApiKeys, + UnprotectedRoutes: []string{ + s.Options.ApiBaseUrl + "/health", + }, + }) + s.Engine.Use(apiKeyAuthMiddleware) +} + +func (s *Server) RegisterRoutes() error { + s.registerHealthRoute() + s.registerPatchRoute() + + return nil +} + +func (s *Server) Run() error { + if err := s.HttpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + return err + } + return nil +} + +func (s *Server) Shutdown(ctx context.Context) { + s.HttpServer.Shutdown(ctx) +} diff --git a/internal/repo_server/util/config.go b/internal/repo_server/util/config.go new file mode 100644 index 0000000..377f77f --- /dev/null +++ b/internal/repo_server/util/config.go @@ -0,0 +1,27 @@ +package util + +import "github.com/mxcd/go-config/config" + +func InitConfig() error { + err := config.LoadConfig([]config.Value{ + config.String("LOG_LEVEL").NotEmpty().Default("info"), + config.Int("PORT").Default(8080), + + config.Bool("DEV").Default(false), + + config.String("API_BASE_URL").Default("/api/v1"), + + config.String("GITOPS_REPOSITORY").NotEmpty(), + config.String("GITOPS_REPOSITORY_BRANCH").NotEmpty().Default("main"), + config.Bool("GITOPS_REPOSITORY_IGNORE_SSL_HOSTKEY").Default(false), + config.String("GITOPS_REPOSITORY_HOST_KEY").Default(""), + + config.String("GITOPS_REPOSITORY_BASICAUTH").Sensitive().Default(""), + config.String("GITOPS_REPOSITORY_SSH_KEY").Sensitive().Default(""), + config.String("GITOPS_REPOSITORY_SSH_KEY_FILE").Sensitive().Default(""), + config.String("GITOPS_REPOSITORY_SSH_KEY_PASSPHRASE").Sensitive().Default(""), + + config.StringArray("API_KEYS").NotEmpty().Sensitive(), + }) + return err +} diff --git a/internal/repo_server/util/logging.go b/internal/repo_server/util/logging.go new file mode 100644 index 0000000..607d090 --- /dev/null +++ b/internal/repo_server/util/logging.go @@ -0,0 +1,58 @@ +package util + +import ( + "os" + "time" + + "github.com/mxcd/go-config/config" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/rs/zerolog/pkgerrors" +) + +func InitLogger() error { + setLogLevel() + setLogOutput() + return nil +} + +func setLogOutput() { + dev := config.Get().Bool("DEV") + + zerolog.TimeFieldFormat = "2006-01-02T15:04:05.000Z" + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack + if dev { + log.Logger = log.Logger.Output(zerolog.ConsoleWriter{ + Out: os.Stdout, + NoColor: false, + TimeFormat: time.RFC3339, + }).With().Caller().Logger() + } else { + log.Logger = log.Logger.With().Caller().Logger() + } +} + +func setLogLevel() { + logLevel := config.Get().String("LOG_LEVEL") + if logLevel == "" { + logLevel = "info" + } + switch logLevel { + case "trace": + zerolog.SetGlobalLevel(zerolog.TraceLevel) + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warn": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "warning": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "err": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + case "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } +} diff --git a/internal/secret/diff.go b/internal/secret/diff.go index ae5d9d8..bd9e430 100644 --- a/internal/secret/diff.go +++ b/internal/secret/diff.go @@ -8,16 +8,17 @@ import ( ) type SecretDiffType string + var SecretDiffTypeUnchanged SecretDiffType = "unchanged" var SecretDiffTypeAdded SecretDiffType = "added" var SecretDiffTypeRemoved SecretDiffType = "removed" var SecretDiffTypeChanged SecretDiffType = "changed" type SecretDiffEntry struct { - Type SecretDiffType - Key string - OldValue string - NewValue string + Type SecretDiffType + Key string + OldValue string + NewValue string Sensitive bool } type SecretDiff struct { @@ -38,15 +39,15 @@ type SecretDiff struct { func CompareSecrets(oldSecret *Secret, newSecret *Secret) *SecretDiff { diffEntries := []SecretDiffEntry{} - + // if both secrets are nil, they are equal if oldSecret == nil && newSecret == nil { return &SecretDiff{ - Equal: true, - Type: SecretDiffTypeUnchanged, - Name: "", + Equal: true, + Type: SecretDiffTypeUnchanged, + Name: "", Namespace: "", - Entries: diffEntries, + Entries: diffEntries, } } @@ -54,19 +55,19 @@ func CompareSecrets(oldSecret *Secret, newSecret *Secret) *SecretDiff { if oldSecret == nil && newSecret != nil { for key, value := range newSecret.Data { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeAdded, - Key: fmt.Sprintf("data.%s", key), - OldValue: "", - NewValue: value, + Type: SecretDiffTypeAdded, + Key: fmt.Sprintf("data.%s", key), + OldValue: "", + NewValue: value, Sensitive: true, }) } return &SecretDiff{ - Equal: false, - Type: SecretDiffTypeAdded, - Name: newSecret.Name, + Equal: false, + Type: SecretDiffTypeAdded, + Name: newSecret.Name, Namespace: newSecret.Namespace, - Entries: diffEntries, + Entries: diffEntries, } } @@ -74,68 +75,68 @@ func CompareSecrets(oldSecret *Secret, newSecret *Secret) *SecretDiff { if oldSecret != nil && newSecret == nil { for key, value := range oldSecret.Data { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeRemoved, - Key: fmt.Sprintf("data.%s", key), - OldValue: value, - NewValue: "", + Type: SecretDiffTypeRemoved, + Key: fmt.Sprintf("data.%s", key), + OldValue: value, + NewValue: "", Sensitive: true, }) } return &SecretDiff{ - Equal: false, - Type: SecretDiffTypeRemoved, - Name: oldSecret.Name, + Equal: false, + Type: SecretDiffTypeRemoved, + Name: oldSecret.Name, Namespace: oldSecret.Namespace, - Entries: diffEntries, + Entries: diffEntries, } } - + if oldSecret.TargetType != newSecret.TargetType { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeChanged, - Key: "targetType", - OldValue: string(oldSecret.TargetType), - NewValue: string(newSecret.TargetType), + Type: SecretDiffTypeChanged, + Key: "targetType", + OldValue: string(oldSecret.TargetType), + NewValue: string(newSecret.TargetType), Sensitive: false, }) } if oldSecret.Target != newSecret.Target { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeChanged, - Key: "target", - OldValue: string(oldSecret.Target), - NewValue: string(newSecret.Target), + Type: SecretDiffTypeChanged, + Key: "target", + OldValue: string(oldSecret.Target), + NewValue: string(newSecret.Target), Sensitive: false, }) } if oldSecret.Name != newSecret.Name { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeChanged, - Key: "name", - OldValue: oldSecret.Name, - NewValue: newSecret.Name, + Type: SecretDiffTypeChanged, + Key: "name", + OldValue: oldSecret.Name, + NewValue: newSecret.Name, Sensitive: false, }) } if oldSecret.Namespace != newSecret.Namespace { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeChanged, - Key: "namespace", - OldValue: oldSecret.Namespace, - NewValue: newSecret.Namespace, + Type: SecretDiffTypeChanged, + Key: "namespace", + OldValue: oldSecret.Namespace, + NewValue: newSecret.Namespace, Sensitive: false, }) } if oldSecret.Type != newSecret.Type { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeChanged, - Key: "type", - OldValue: oldSecret.Type, - NewValue: newSecret.Type, + Type: SecretDiffTypeChanged, + Key: "type", + OldValue: oldSecret.Type, + NewValue: newSecret.Type, Sensitive: false, }) } @@ -144,20 +145,20 @@ func CompareSecrets(oldSecret *Secret, newSecret *Secret) *SecretDiff { // check if key is in new secret if _, ok := newSecret.Data[key]; !ok { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeRemoved, - Key: fmt.Sprintf("data.%s", key), - OldValue: value, - NewValue: "", + Type: SecretDiffTypeRemoved, + Key: fmt.Sprintf("data.%s", key), + OldValue: value, + NewValue: "", Sensitive: true, }) } else { // check if value is equal if value != newSecret.Data[key] { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeChanged, - Key: fmt.Sprintf("data.%s", key), - OldValue: value, - NewValue: newSecret.Data[key], + Type: SecretDiffTypeChanged, + Key: fmt.Sprintf("data.%s", key), + OldValue: value, + NewValue: newSecret.Data[key], Sensitive: true, }) } @@ -168,10 +169,10 @@ func CompareSecrets(oldSecret *Secret, newSecret *Secret) *SecretDiff { // check if key is in old secret if _, ok := oldSecret.Data[key]; !ok { diffEntries = append(diffEntries, SecretDiffEntry{ - Type: SecretDiffTypeAdded, - Key: fmt.Sprintf("data.%s", key), - OldValue: "", - NewValue: value, + Type: SecretDiffTypeAdded, + Key: fmt.Sprintf("data.%s", key), + OldValue: "", + NewValue: value, Sensitive: true, }) } @@ -180,28 +181,28 @@ func CompareSecrets(oldSecret *Secret, newSecret *Secret) *SecretDiff { var diffName = "" if oldSecret != nil { diffName = oldSecret.Name - } else if newSecret != nil{ + } else if newSecret != nil { diffName = newSecret.Name } var diffNamespace = "" if oldSecret != nil { diffNamespace = oldSecret.Namespace - } else if newSecret != nil{ + } else if newSecret != nil { diffNamespace = newSecret.Namespace } - diff := SecretDiff { - Name: diffName, + diff := SecretDiff{ + Name: diffName, Namespace: diffNamespace, - Entries: diffEntries, + Entries: diffEntries, } - + if len(diffEntries) > 0 { - diff.Type = SecretDiffTypeChanged; + diff.Type = SecretDiffTypeChanged diff.Equal = false } else { - diff.Type = SecretDiffTypeUnchanged; + diff.Type = SecretDiffTypeUnchanged diff.Equal = true } @@ -238,12 +239,12 @@ func (d *SecretDiff) Print() { return } switch d.Type { - case SecretDiffTypeAdded: - println(color.InGreen(combinedSecretName), color.InGreen(": "), color.InBold(color.InGreen("add"))) - case SecretDiffTypeRemoved: - println(color.InRed(combinedSecretName), color.InRed(": "), color.InBold(color.InRed("remove"))) - case SecretDiffTypeChanged: - println(color.InYellow(combinedSecretName), color.InYellow(": "), color.InBold(color.InYellow("change"))) + case SecretDiffTypeAdded: + println(color.InGreen(combinedSecretName), color.InGreen(": "), color.InBold(color.InGreen("add"))) + case SecretDiffTypeRemoved: + println(color.InRed(combinedSecretName), color.InRed(": "), color.InBold(color.InRed("remove"))) + case SecretDiffTypeChanged: + println(color.InYellow(combinedSecretName), color.InYellow(": "), color.InBold(color.InYellow("change"))) } printDetailedChanges() } @@ -255,4 +256,4 @@ func (d *SecretDiff) GetEntry(key string) *SecretDiffEntry { } } return nil -} \ No newline at end of file +} diff --git a/internal/secret/loader.go b/internal/secret/loader.go index d2c970b..60974cf 100644 --- a/internal/secret/loader.go +++ b/internal/secret/loader.go @@ -52,7 +52,7 @@ func LoadLocalSecretsLimited(targetTypeFilter SecretTargetType, directoryLimit s ) for _, secretFileName := range secretFileNames { bar.Add(1) - secret, err := FromPath(secretFileName, directoryLimit) + secret, err := FromPath(secretFileName) if err != nil { bar.Finish() return nil, err diff --git a/internal/secret/secret.go b/internal/secret/secret.go index adcb38f..db896a7 100644 --- a/internal/secret/secret.go +++ b/internal/secret/secret.go @@ -47,7 +47,6 @@ type Secret struct { } type SecretTargetType string - var SecretTargetTypeVault SecretTargetType = "vault" var SecretTargetTypeKubernetes SecretTargetType = "k8s" var SecretTargetTypeAll SecretTargetType = "all" @@ -58,7 +57,7 @@ type SecretFile struct { Name string `yaml:"name,omitempty"` Namespace string `yaml:"namespace" default:"default"` Type string `yaml:"type" default:"Opaque"` - Data map[string]string `yaml:"data"` + Data map[string]string `yaml:"data"` ID string `yaml:"id,omitempty"` } @@ -66,7 +65,7 @@ type TemplateData struct { Values map[interface{}]interface{} } -func (s *Secret) Load(directoryLimit string) error { +func (s *Secret) Load() error { if s.Path == "" { return errors.New("secret path is empty") } @@ -79,7 +78,7 @@ func (s *Secret) Load(directoryLimit string) error { // execute templating on the secret file data data := TemplateData{ - Values: templating.GetValuesForPath(s.Path, directoryLimit), + Values: templating.GetValuesForPath(s.Path), } stringData := string(decryptedFileContent) tmpl, err := template.New(s.Path).Parse(stringData) @@ -104,7 +103,7 @@ func (s *Secret) Load(directoryLimit string) error { yaml.UnmarshalStrict(s.BinaryData, &secretFile) s.TargetType = secretFile.TargetType - + if secretFile.Target != "" { s.Target = secretFile.Target } else { @@ -129,13 +128,13 @@ func (s *Secret) Load(directoryLimit string) error { } else { s.Type = "Opaque" } - + s.Data = secretFile.Data if util.GetCliContext().Bool("print") { s.PrettyPrint() } - + return nil } @@ -161,15 +160,16 @@ func (s *Secret) PrettyPrint() { } } -func FromPath(path string, directoryLimit string) (*Secret, error) { - s := Secret{ +func FromPath(path string) (*Secret, error) { + s := Secret { Path: path, } - err := s.Load(directoryLimit) + err := s.Load() if err != nil { return nil, err } return &s, nil } + diff --git a/internal/secret/secret_test.go b/internal/secret/secret_test.go index cf6be66..9aa0e62 100644 --- a/internal/secret/secret_test.go +++ b/internal/secret/secret_test.go @@ -12,7 +12,7 @@ func TestLoadSecret1(t *testing.T) { secret := Secret { Path: f, } - err := secret.Load("") + err := secret.Load() if err != nil { t.Error(err) @@ -31,7 +31,7 @@ func TestLoadSecret2(t *testing.T) { secret := Secret { Path: f, } - err := secret.Load("") + err := secret.Load() if err != nil { t.Error(err) diff --git a/internal/templating/templating.go b/internal/templating/templating.go index e7b8e06..c7a61d8 100644 --- a/internal/templating/templating.go +++ b/internal/templating/templating.go @@ -8,6 +8,7 @@ import ( "strings" log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" "github.com/mxcd/gitops-cli/internal/util" "gopkg.in/yaml.v2" @@ -25,22 +26,17 @@ type TemplateValuesPath struct { var loaded = false -func LoadValues(directoryLimit string) error { +func LoadValues() error { log.Trace("Loading values files") secretFiles, err := util.GetSecretFiles() if err != nil { return err } var valuesFiles []string - for _, secretFileName := range secretFiles { - if !strings.HasSuffix(secretFileName, "values.gitops.secret.enc.yaml") && !strings.HasSuffix(secretFileName, "values.gitops.secret.enc.yml") { - continue + for _, secretFile := range secretFiles { + if strings.HasSuffix(secretFile, "values.gitops.secret.enc.yaml") || strings.HasSuffix(secretFile, "values.gitops.secret.enc.yml") { + valuesFiles = append(valuesFiles, secretFile) } - if !valuesFileApplicable(secretFileName, directoryLimit) { - log.Trace("Skipping values file due to directory filter: ", secretFileName) - continue - } - valuesFiles = append(valuesFiles, secretFileName) } for _, valuesFile := range valuesFiles { @@ -107,9 +103,9 @@ func (t TemplateValues) merge() { } } -func GetValuesForPath(path string, directoryLimit string) map[interface{}]interface{} { +func GetValuesForPath(path string) map[interface{}]interface{} { if !loaded { - err := LoadValues(directoryLimit) + err := LoadValues() if err != nil { log.Panic(err) } @@ -134,31 +130,18 @@ func GetValuesForPath(path string, directoryLimit string) map[interface{}]interf return values } -func valuesFileApplicable(secretFile, directoryLimit string) bool { - secretFile = filepath.Clean(secretFile) - directoryLimit = filepath.Clean(directoryLimit) - - if directoryLimit == "." { - return true +func TestTemplating(c *cli.Context) error { + secretFiles, err := util.GetSecretFiles() + if err != nil { + log.Fatal(err) } - - secretFileParentDir := filepath.Dir(secretFile) - secretFileDirectoryElements := strings.Split(secretFileParentDir, string(filepath.Separator)) - directoryLimitElements := strings.Split(directoryLimit, string(filepath.Separator)) - - if len(secretFileDirectoryElements) <= len(directoryLimitElements) { - for i, secretFileDirectoryElement := range secretFileDirectoryElements { - if secretFileDirectoryElement != directoryLimitElements[i] { - return false - } - } - } else { - for i, directoryLimitElement := range directoryLimitElements { - if directoryLimitElement != secretFileDirectoryElements[i] { - return false - } + for _, secretFile := range secretFiles { + log.Debug(secretFile) + decryptedFile, err := util.DecryptFile(secretFile) + if err != nil { + log.Fatal(err) } + log.Trace(string(decryptedFile)) } - - return true + return nil } diff --git a/internal/templating/templating_test.go b/internal/templating/templating_test.go index 53e0f5a..cab727f 100644 --- a/internal/templating/templating_test.go +++ b/internal/templating/templating_test.go @@ -9,18 +9,18 @@ import ( func TestMapMerge1(t *testing.T) { a := map[interface{}]interface{}{ - "foo": "bar", + "foo": "bar", "fizz": "buzz", } b := map[interface{}]interface{}{ "fizz": "fizz", } c := map[interface{}]interface{}{ - "foo": "bar", + "foo": "bar", "fizz": "fizz", } d := mergeMaps(a, b) - assert.Equal(t, c, d, "Maps should be equal") + assert.Equal(t, c, d, "Maps should be equal") } func TestMapMerge2(t *testing.T) { @@ -39,7 +39,7 @@ func TestMapMerge2(t *testing.T) { "d": "4", } d := mergeMaps(a, b) - assert.Equal(t, c, d, "Maps should be equal") + assert.Equal(t, c, d, "Maps should be equal") } func TestMapMerge3(t *testing.T) { @@ -55,7 +55,7 @@ func TestMapMerge3(t *testing.T) { "b": 42, } d := mergeMaps(a, b) - assert.Equal(t, c, d, "Maps should be equal") + assert.Equal(t, c, d, "Maps should be equal") } func TestMapMerge4(t *testing.T) { @@ -71,7 +71,7 @@ func TestMapMerge4(t *testing.T) { "b": []interface{}{"2", "3"}, } d := mergeMaps(a, b) - assert.Equal(t, c, d, "Maps should be equal") + assert.Equal(t, c, d, "Maps should be equal") } func TestTemplateValuesMerge(t *testing.T) { @@ -159,57 +159,31 @@ func TestTemplateValuesMerge(t *testing.T) { assert.Equal(t, expectedMergedTemplateValues, templateValues, "TemplateValues should be equal") } + func TestValuesFileLoading(t *testing.T) { c := util.GetDummyCliContext() util.SetCliContext(c) util.ComputeRootDir(c) - - LoadValues("") - + + LoadValues() + valuesSet1 := map[interface{}]interface{}{ - "namespace": "gitops-dev", - "stage": "dev", + "namespace": "gitops-dev", + "stage": "dev", "databaseUsername": "my-very-strong-username", "databasePassword": "my-very-strong-password", } - mergedValues1 := GetValuesForPath("test_assets/implicit-name.gitops.secret.enc.yml", "") + mergedValues1 := GetValuesForPath("test_assets/implicit-name.gitops.secret.enc.yml") assert.Equal(t, valuesSet1, mergedValues1, "Values should be equal") valuesSet2 := map[interface{}]interface{}{ - "namespace": "gitops-dev", - "stage": "sub-dev", + "namespace": "gitops-dev", + "stage": "sub-dev", "databaseUsername": "my-very-strong-username", "databasePassword": "my-very-strong-password", - "key": "fooo", + "key": "fooo", } - mergedValues2 := GetValuesForPath("test_assets/subdirectory/subdir-secret.gitops.secret.enc.yml", "") + mergedValues2 := GetValuesForPath("test_assets/subdirectory/subdir-secret.gitops.secret.enc.yml") assert.Equal(t, valuesSet2, mergedValues2, "Values should be equal") -} - -func TestIsInParentPath(t *testing.T) { - tests := []struct { - name string - path1 string - path2 string - expected bool - }{ - {"Direct Parent", "a/b/c.txt", "a/b/d/", true}, - {"Grandparent", "a/b/c.txt", "a/b/d/e/", true}, - {"Not a Parent", "a/b/c.txt", "f/g/h/", false}, - {"Same Directory", "a/b/c.txt", "a/b/", true}, - {"Root Directory", "c.txt", "/", false}, - {"Path2 is Parent", "a/b/c/d.txt", "a/b/c", true}, - {"Nested under filter", "a/b/c/d.txt", "a/", true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := valuesFileApplicable(tt.path1, tt.path2) - - if result != tt.expected { - t.Errorf("isInParentPath(%q, %q) = %v; expected %v", tt.path1, tt.path2, result, tt.expected) - } - }) - } -} +} \ No newline at end of file diff --git a/cmd/gitops/util.go b/internal/util/application.go similarity index 54% rename from cmd/gitops/util.go rename to internal/util/application.go index 2af6233..2bae75d 100644 --- a/cmd/gitops/util.go +++ b/internal/util/application.go @@ -1,32 +1,13 @@ -package main +package util import ( - "github.com/mxcd/gitops-cli/internal/state" - "github.com/mxcd/gitops-cli/internal/util" log "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) -func initApplication(c *cli.Context) error { - printLogo(c) - setLogLevel(c) - util.SetCliContext(c) - util.GetRootDir() - err := state.LoadState(c) - return err -} - -func exitApplication(c *cli.Context, writeState bool) error { - var err error - if writeState { - err = state.GetState().Save(c) - } - return err -} - -func setLogLevel(c *cli.Context) { +func SetLogLevel(c *cli.Context) { log.SetLevel(log.InfoLevel) - + if c.Bool("verbose") { log.SetLevel(log.DebugLevel) } @@ -36,7 +17,7 @@ func setLogLevel(c *cli.Context) { } } -func printLogo(c *cli.Context) { +func PrintLogo(c *cli.Context) { if c.Bool("no-logo") { return } @@ -48,4 +29,4 @@ func printLogo(c *cli.Context) { println("/_/ \\__, /_/\\__/\\____/ .___/____/") println(" /____/ /_/") println("") -} \ No newline at end of file +} diff --git a/internal/yaml/patcher.go b/internal/yaml/patcher.go new file mode 100644 index 0000000..d558b53 --- /dev/null +++ b/internal/yaml/patcher.go @@ -0,0 +1,105 @@ +package yaml + +import ( + "bytes" + "errors" + "strings" + + "gopkg.in/yaml.v3" +) + +func PatchYaml(yamlData []byte, selector string, value string) ([]byte, error) { + var node yaml.Node + err := yaml.Unmarshal(yamlData, &node) + if err != nil { + return []byte{}, err + } + + var patchedNode *yaml.Node + if strings.HasPrefix(selector, ".") { + selector = selector[1:] + patchedNode, err = patchYamlData(&node, selector, value) + if err != nil { + return []byte{}, err + } + } else { + patchedNode, err = patchYamlDataWithSearch(&node, selector, value) + if err != nil { + return []byte{}, err + } + } + + var b bytes.Buffer + yamlEncoder := yaml.NewEncoder(&b) + yamlEncoder.SetIndent(2) + err = yamlEncoder.Encode(patchedNode) + if err != nil { + return []byte{}, err + } + + return b.Bytes(), nil +} + +func patchYamlData(data *yaml.Node, selector string, value string) (*yaml.Node, error) { + selectorParts := strings.Split(selector, ".") + + if err := findAndPatchNode(data, selectorParts, value); err != nil { + return nil, err + } + return data, nil +} + +func findAndPatchNode(node *yaml.Node, selectorParts []string, value string) error { + if len(selectorParts) == 0 { + node.Value = value + return nil + } + + for i, child := range node.Content { + if child.Kind == yaml.MappingNode { + for j := 0; j < len(child.Content); j += 2 { + keyNode := child.Content[j] + valueNode := child.Content[j+1] + if keyNode.Value == selectorParts[0] { + return findAndPatchNode(valueNode, selectorParts[1:], value) + } + } + } else if child.Kind == yaml.ScalarNode { + if child.Value == selectorParts[0] { + return findAndPatchNode(node.Content[i+1], selectorParts[1:], value) + } + } + } + + return errors.New("selector not found") +} + +func patchYamlDataWithSearch(data *yaml.Node, selector string, value string) (*yaml.Node, error) { + if err := findAndPatchNodeWithSearch(data, selector, value); err != nil { + return nil, err + } + return data, nil +} + +func findAndPatchNodeWithSearch(node *yaml.Node, selector string, value string) error { + if node.Kind == yaml.MappingNode { + for i := 0; i < len(node.Content); i += 2 { + keyNode := node.Content[i] + valueNode := node.Content[i+1] + if keyNode.Kind == yaml.ScalarNode && keyNode.Value == selector { + valueNode.Value = value + return nil + } + if err := findAndPatchNodeWithSearch(valueNode, selector, value); err == nil { + return nil + } + } + } else if node.Kind == yaml.SequenceNode || node.Kind == yaml.DocumentNode { + for _, child := range node.Content { + if err := findAndPatchNodeWithSearch(child, selector, value); err == nil { + return nil + } + } + } + return errors.New("key not found") +} diff --git a/internal/yaml/patcher_test.go b/internal/yaml/patcher_test.go new file mode 100644 index 0000000..316b74b --- /dev/null +++ b/internal/yaml/patcher_test.go @@ -0,0 +1,123 @@ +package yaml + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type TestCase struct { + originalYaml string + selector string + value string + expectedYaml string +} + +var testCases = []TestCase{ + { + originalYaml: `foo: bar +`, + selector: ".foo", + value: "baz", + expectedYaml: `foo: baz +`, + }, + { + originalYaml: `some: + nested: + structure: + foo: bar +`, + selector: ".some.nested.structure.foo", + value: "baz", + expectedYaml: `some: + nested: + structure: + foo: baz +`, + }, + { + originalYaml: `some: + nested: + # some comment + structure: + foo: bar +`, + selector: ".some.nested.structure.foo", + value: "baz", + expectedYaml: `some: + nested: + # some comment + structure: + foo: baz +`, + }, + { + originalYaml: `some: + nested: + # some comment + structure: + foo: bar +`, + selector: "foo", + value: "baz", + expectedYaml: `some: + nested: + # some comment + structure: + foo: baz +`, + }, + { + originalYaml: `some: + nested: + # some comment + structure: + - fizz: buzz + - foo: bar +`, + selector: "foo", + value: "baz", + expectedYaml: `some: + nested: + # some comment + structure: + - fizz: buzz + - foo: baz +`, + }, + { + originalYaml: `some: + nested: + # some comment + fizz: + buzz: bar + structure: + foo: bar +`, + selector: ".some.nested.structure.foo", + value: "baz", + expectedYaml: `some: + nested: + # some comment + fizz: + buzz: bar + structure: + foo: baz +`, + }, +} + +func TestYamlPatching(t *testing.T) { + test := func(testCase TestCase) { + + pathedYamlString, err := PatchYaml([]byte(testCase.originalYaml), testCase.selector, testCase.value) + assert.NoError(t, err) + + assert.Equal(t, testCase.expectedYaml, string(pathedYamlString), "Patched data should be equal to expected data") + } + + for _, testCase := range testCases { + test(testCase) + } +} diff --git a/tapes/plan/plan-k8s.tape b/tapes/plan/plan-k8s.tape new file mode 100644 index 0000000..3005279 --- /dev/null +++ b/tapes/plan/plan-k8s.tape @@ -0,0 +1,69 @@ +# VHS documentation +# +# Output: +# Output .gif Create a GIF output at the given +# Output .mp4 Create an MP4 output at the given +# Output .webm Create a WebM output at the given +# +# Require: +# Require Ensure a program is on the $PATH to proceed +# +# Settings: +# Set FontSize Set the font size of the terminal +# Set FontFamily Set the font family of the terminal +# Set Height Set the height of the terminal +# Set Width Set the width of the terminal +# Set LetterSpacing Set the font letter spacing (tracking) +# Set LineHeight Set the font line height +# Set LoopOffset % Set the starting frame offset for the GIF loop +# Set Theme Set the theme of the terminal +# Set Padding Set the padding of the terminal +# Set Framerate Set the framerate of the recording +# Set PlaybackSpeed Set the playback speed of the recording +# Set MarginFill Set the file or color the margin will be filled with. +# Set Margin Set the size of the margin. Has no effect if MarginFill isn't set. +# Set BorderRadius Set terminal border radius, in pixels. +# Set WindowBar Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight) +# Set WindowBarSize Set window bar size, in pixels. Default is 40. +# Set TypingSpeed