From aac4ff5fb055345ae7c81cf584b76b0f9c642377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Clerget?= Date: Wed, 13 Dec 2023 18:50:53 +0100 Subject: [PATCH] Add integration tests and support for pushing static file/RPM in a stream fashion. Fix beskar registry transport issue. --- .github/workflows/ci.yml | 13 + .github/workflows/release.yml | 19 +- build/mage/build.go | 77 ++- build/mage/common.go | 68 ++- build/mage/go.mod | 8 +- build/mage/go.sum | 15 +- build/mage/test.go | 77 +++ go.work | 1 + integration/beskar_static_test.go | 183 +++++++ integration/beskar_test.go | 28 ++ integration/beskar_yum_test.go | 308 ++++++++++++ integration/go.mod | 52 ++ integration/go.sum | 463 ++++++++++++++++++ integration/main_integration_test.go | 164 +++++++ integration/pkg/backoff/backoff.go | 14 + integration/pkg/repoconfig/parse.go | 23 + integration/pkg/util/download_url.go | 28 ++ integration/pkg/util/tag.go | 11 + integration/testdata/repoconfig.yaml | 117 +++++ internal/pkg/beskar/registry.go | 2 - .../plugins/static/pkg/staticdb/repository.go | 4 +- internal/plugins/yum/api.go | 4 +- internal/plugins/yum/pkg/yumdb/repository.go | 4 +- internal/plugins/yum/pkg/yumrepository/api.go | 9 +- .../plugins/yum/pkg/yumrepository/handler.go | 20 +- pkg/decompress/decompress.go | 6 +- pkg/ioutil/multireader.go | 33 ++ pkg/oras/image.go | 48 +- pkg/oras/layer.go | 137 +++++- pkg/orasfile/file.go | 39 +- pkg/orasrpm/rpm.go | 84 ++-- pkg/plugins/yum/api/v1/api.go | 11 +- pkg/plugins/yum/api/v1/endpoint.go | 2 + pkg/plugins/yum/api/v1/http_client.go | 4 +- pkg/plugins/yum/api/v1/oas2.go | 1 + 35 files changed, 1960 insertions(+), 117 deletions(-) create mode 100644 integration/beskar_static_test.go create mode 100644 integration/beskar_test.go create mode 100644 integration/beskar_yum_test.go create mode 100644 integration/go.mod create mode 100644 integration/go.sum create mode 100644 integration/main_integration_test.go create mode 100644 integration/pkg/backoff/backoff.go create mode 100644 integration/pkg/repoconfig/parse.go create mode 100644 integration/pkg/util/download_url.go create mode 100644 integration/pkg/util/tag.go create mode 100644 integration/testdata/repoconfig.yaml create mode 100644 pkg/ioutil/multireader.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5f5a1c..97b76bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,19 @@ jobs: - name: Run linters run: ./scripts/mage lint:all + tests: + name: tests + runs-on: ubuntu-22.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: '1.20' + - uses: actions/checkout@v3 + - name: Run unit tests + run: ./scripts/mage test:unit + - name: Run integration tests + run: ./scripts/mage test:integration + build: name: build runs-on: ubuntu-22.04 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40531f2..08282e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,9 +16,22 @@ jobs: - name: Run linters run: ./scripts/mage lint:all + tests: + name: tests + runs-on: ubuntu-22.04 + steps: + - uses: actions/setup-go@v3 + with: + go-version: '1.20' + - uses: actions/checkout@v3 + - name: Run unit tests + run: ./scripts/mage test:unit + - name: Run integration tests + run: ./scripts/mage test:integration + release-beskar: name: release beskar - needs: lint + needs: [lint, tests] runs-on: ubuntu-22.04 steps: - uses: actions/setup-go@v3 @@ -32,7 +45,7 @@ jobs: release-beskar-yum: name: release beskar-yum - needs: lint + needs: [lint, tests] runs-on: ubuntu-22.04 steps: - uses: actions/setup-go@v3 @@ -46,7 +59,7 @@ jobs: release-beskar-static: name: release beskar-static - needs: lint + needs: [lint, tests] runs-on: ubuntu-22.04 steps: - uses: actions/setup-go@v3 diff --git a/build/mage/build.go b/build/mage/build.go index 65dad68..ea1579e 100644 --- a/build/mage/build.go +++ b/build/mage/build.go @@ -54,33 +54,41 @@ type binaryConfig struct { genAPI *genAPI buildTags []string baseImage string + integrationTest *integrationTest } const ( - beskarBinary = "beskar" - beskarctlBinary = "beskarctl" - beskarYUMBinary = "beskar-yum" - beskarStaticBinary = "beskar-static" + BeskarBinary = "beskar" + BeskarctlBinary = "beskarctl" + BeskarYUMBinary = "beskar-yum" + BeskarStaticBinary = "beskar-static" ) var binaries = map[string]binaryConfig{ - beskarBinary: { + BeskarBinary: { configFiles: map[string]string{ "internal/pkg/config/default/beskar.yaml": "/etc/beskar/beskar.yaml", }, useProto: true, buildTags: []string{"include_gcs"}, + integrationTest: &integrationTest{ + envs: map[string]string{ + "BESKAR_REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY": "/tmp/integration/registry", + "BESKAR_REGISTRY_LOG_ACCESSLOG_DISABLED": "true", + }, + }, }, - beskarctlBinary: {}, - beskarYUMBinary: { + BeskarctlBinary: {}, + BeskarYUMBinary: { configFiles: map[string]string{ "internal/plugins/yum/pkg/config/default/beskar-yum.yaml": "/etc/beskar/beskar-yum.yaml", }, execStmts: [][]string{ { - //"apk", "add", "-U", "bash", "--repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/", - "sh", "-c", "apt-get update -y && apt-get install -y --no-install-recommends ca-certificates createrepo-c && " + - "rm -rf /var/lib/apt/lists/* && rm -Rf /usr/share/doc && rm -Rf /usr/share/man && apt-get clean", + "apk", "add", "createrepo_c", "--repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/", + // NOTE: restore in case alpine createrepo_c package is broken again + //"sh", "-c", "apt-get update -y && apt-get install -y --no-install-recommends ca-certificates createrepo-c && " + + // "rm -rf /var/lib/apt/lists/* && rm -Rf /usr/share/doc && rm -Rf /usr/share/man && apt-get clean", }, }, genAPI: &genAPI{ @@ -88,10 +96,19 @@ var binaries = map[string]binaryConfig{ filename: "api.go", interfaceName: "YUM", }, - useProto: true, - baseImage: "debian:bullseye-slim", + useProto: true, + // NOTE: restore in case alpine createrepo_c package is broken again + //baseImage: "debian:bullseye-slim", + integrationTest: &integrationTest{ + isPlugin: true, + envs: map[string]string{ + "BESKARYUM_ADDR": "127.0.0.1:5200", + "BESKARYUM_GOSSIP_ADDR": "127.0.0.1:5201", + "BESKARYUM_STORAGE_FILESYSTEM_DIRECTORY": "/tmp/integration/beskar-yum", + }, + }, }, - beskarStaticBinary: { + BeskarStaticBinary: { configFiles: map[string]string{ "internal/plugins/static/pkg/config/default/beskar-static.yaml": "/etc/beskar/beskar-static.yaml", }, @@ -100,8 +117,15 @@ var binaries = map[string]binaryConfig{ filename: "api.go", interfaceName: "Static", }, - useProto: true, - baseImage: BaseImage, + useProto: true, + integrationTest: &integrationTest{ + isPlugin: true, + envs: map[string]string{ + "BESKARSTATIC_ADDR": "127.0.0.1:5300", + "BESKARSTATIC_GOSSIP_ADDR": "127.0.0.1:5301", + "BESKARSTATIC_STORAGE_FILESYSTEM_DIRECTORY": "/tmp/integration/beskar-static", + }, + }, }, } @@ -131,18 +155,18 @@ func (b Build) Proto(ctx context.Context) error { } func (b Build) Beskar(ctx context.Context) error { - return b.build(ctx, beskarBinary) + return b.build(ctx, BeskarBinary) } func (b Build) Beskarctl(ctx context.Context) error { - return b.build(ctx, beskarctlBinary) + return b.build(ctx, BeskarctlBinary) } func (b Build) Plugins(ctx context.Context) { mg.CtxDeps( ctx, - mg.F(b.Plugin, beskarYUMBinary), - mg.F(b.Plugin, beskarStaticBinary), + mg.F(b.Plugin, BeskarYUMBinary), + mg.F(b.Plugin, BeskarStaticBinary), ) } @@ -172,13 +196,13 @@ func (b Build) build(ctx context.Context, name string) error { } } + buildOpts, ok := getBuildOptions(ctx) + currentPlatform, err := getCurrentPlatform() if err != nil { return err } - buildOpts, ok := getBuildOptions(ctx) - if !ok { buildOpts = &buildOptions{ platforms: []dagger.Platform{ @@ -191,6 +215,17 @@ func (b Build) build(ctx context.Context, name string) error { } } + files, err := getGoFiles(filepath.Join("cmd", name)) + if err != nil { + return err + } + changed, err := target.Path(filepath.Join("build/output", name), files...) + if err != nil { + return err + } else if !changed && !ok { + return nil + } + client := buildOpts.client if client == nil { diff --git a/build/mage/common.go b/build/mage/common.go index f252a2e..1479c12 100644 --- a/build/mage/common.go +++ b/build/mage/common.go @@ -10,14 +10,15 @@ import ( "dagger.io/dagger" "golang.org/x/sys/cpu" + "golang.org/x/tools/go/packages" ) const ( - GoImage = "golang:1.21.4-alpine" + GoImage = "golang:1.21.5-alpine3.19" GolangCILintImage = "golangci/golangci-lint:v1.54.2-alpine" HelmImage = "alpine/helm:3.12.2" ProtolintImage = "yoheimuta/protolint:0.45.0" - BaseImage = "alpine:3.17" + BaseImage = "alpine:3.19" ProtocVersion = "v23.4" ProtocFileFormat = "protoc-23.4-linux-%s.zip" @@ -65,10 +66,28 @@ func getSource(client *dagger.Client) *dagger.Directory { "README.md", "go.work", "go.work.sum", + "integration", }, }) } +func getIntegrationSource(client *dagger.Client) *dagger.Directory { + return client.Host().Directory(".", dagger.HostDirectoryOpts{ + Include: []string{ + "go.mod", + "go.sum", + "integration", + "pkg", + }, + }) +} + +func getBuildBinaries(client *dagger.Client, binaries ...string) *dagger.Directory { + return client.Host().Directory("build/output", dagger.HostDirectoryOpts{ + Include: binaries, + }) +} + func getProtoSource(client *dagger.Client) *dagger.Directory { return client.Host().Directory("api") } @@ -122,3 +141,48 @@ func getPlatformBinarySuffix(platform string) string { platform = strings.TrimPrefix(platform, "linux/") return strings.ReplaceAll(platform, "/", "-") } + +func getGoFiles(dir string) ([]string, error) { + cfg := packages.Config{ + Mode: packages.NeedFiles | + packages.NeedEmbedFiles | + packages.NeedImports | + packages.NeedName | + packages.NeedDeps | + packages.NeedModule, + Dir: dir, + } + + initial, err := packages.Load(&cfg, ".") + if err != nil { + return nil, fmt.Errorf("while loading directory %s: %s", dir, err) + } + for _, pkg := range initial { + if len(pkg.Errors) > 0 { + return nil, fmt.Errorf("package error: %s", pkg.Errors[0]) + } + } + + fileMap := make(map[string]struct{}) + files := make([]string, 0) + + add := func(sourceFiles []string) { + for _, file := range sourceFiles { + if _, ok := fileMap[file]; !ok { + fileMap[file] = struct{}{} + files = append(files, file) + } + } + } + + packages.Visit(initial, nil, func(pkg *packages.Package) { + if pkg.Module == nil { + return + } + add(pkg.GoFiles) + add(pkg.OtherFiles) + add(pkg.EmbedFiles) + }) + + return files, nil +} diff --git a/build/mage/go.mod b/build/mage/go.mod index c956650..2bef115 100644 --- a/build/mage/go.mod +++ b/build/mage/go.mod @@ -5,7 +5,8 @@ go 1.20 require ( dagger.io/dagger v0.7.4 github.com/magefile/mage v1.15.0 - golang.org/x/sys v0.12.0 + golang.org/x/sys v0.14.0 + golang.org/x/tools v0.14.0 ) require ( @@ -15,7 +16,6 @@ require ( github.com/iancoleman/strcase v0.3.0 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/vektah/gqlparser/v2 v2.5.6 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect ) diff --git a/build/mage/go.sum b/build/mage/go.sum index efc7035..db0327c 100644 --- a/build/mage/go.sum +++ b/build/mage/go.sum @@ -30,15 +30,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/build/mage/test.go b/build/mage/test.go index 3e4a20e..37170c1 100644 --- a/build/mage/test.go +++ b/build/mage/test.go @@ -3,7 +3,9 @@ package mage import ( "context" "fmt" + "strings" + "dagger.io/dagger" "github.com/magefile/mage/mg" ) @@ -33,3 +35,78 @@ func (Test) Unit(ctx context.Context) error { return printOutput(ctx, unitTest) } + +type integrationTest struct { + envs map[string]string + isPlugin bool +} + +func (Test) Integration(ctx context.Context) error { + mg.CtxDeps( + ctx, + Build.Beskar, + Build.Plugins, + ) + + fmt.Println("Running integration tests") + fmt.Println("") + + client, err := getDaggerClient(ctx) + if err != nil { + return err + } + defer client.Close() + + var buildBinaries []string + var pluginBinaries []string + + for bin, config := range binaries { + if config.integrationTest == nil { + continue + } + buildBinaries = append(buildBinaries, bin) + if config.integrationTest.isPlugin { + pluginBinaries = append(pluginBinaries, bin) + } + } + + setEnvs := func(c *dagger.Container) *dagger.Container { + c = c.WithEnvVariable("BESKAR_PLUGINS", strings.Join(pluginBinaries, " ")) + + for _, config := range binaries { + if config.integrationTest == nil { + continue + } + for key, val := range config.integrationTest.envs { + c = c.WithEnvVariable(key, val) + } + // execute exec statements for plugins using alpine base image + if config.baseImage != "" && config.baseImage != BaseImage { + continue + } + for _, execStmt := range config.execStmts { + c = c.WithExec(execStmt) + } + } + + return c + } + + integrationTest := client.Container(). + From(GoImage). + With(goCache(client)). + WithEnvVariable("GOBIN", "/usr/bin"). + WithExec([]string{"go", "install", "github.com/onsi/ginkgo/v2/ginkgo@v2.13.2"}) + + integrationTest = integrationTest. + With(goCache(client)). + WithDirectory("/src", getIntegrationSource(client)). + WithDirectory("/app", getBuildBinaries(client, buildBinaries...)). + WithWorkdir("/src"). + With(setEnvs). + WithExec([]string{ + "ginkgo", "-v", "-p", "--timeout", "10m", "./integration", + }) + + return printOutput(ctx, integrationTest) +} diff --git a/go.work b/go.work index f40ff3f..3f9c335 100644 --- a/go.work +++ b/go.work @@ -3,4 +3,5 @@ go 1.21 use ( . ./build/mage + ./integration ) diff --git a/integration/beskar_static_test.go b/integration/beskar_static_test.go new file mode 100644 index 0000000..afcd100 --- /dev/null +++ b/integration/beskar_static_test.go @@ -0,0 +1,183 @@ +package integration_test + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/RussellLuo/kun/pkg/werror/gcode" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.ciq.dev/beskar/integration/pkg/backoff" + "go.ciq.dev/beskar/integration/pkg/repoconfig" + "go.ciq.dev/beskar/integration/pkg/util" + "go.ciq.dev/beskar/pkg/oras" + "go.ciq.dev/beskar/pkg/orasfile" + "go.ciq.dev/beskar/pkg/plugins/httpcodec" + staticv1 "go.ciq.dev/beskar/pkg/plugins/static/api/v1" +) + +func getBeskarStaticURL(path string) string { + return "http://" + BeskarAddr + "/artifacts/static/" + path +} + +func getBeskarStaticFileURL(repository, filename string) string { + return getBeskarStaticURL(filepath.Join(repository, "file", filename)) +} + +func beskarStaticClient() *staticv1.HTTPClient { + httpClient := &http.Client{ + Timeout: 30 * time.Second, + } + client, err := staticv1.NewHTTPClient(httpcodec.JSONCodec, httpClient, getBeskarStaticURL("api/v1")) + Expect(err).To(BeNil()) + + return client +} + +var _ = Describe("Beskar Static Plugin", func() { + Describe("Test", Ordered, func() { + repositoryConfig, err := repoconfig.Parse(repoconfigData) + Expect(err).To(BeNil()) + + repositoryName := "vault-rocky-8.3-ha-debug" + + repo, ok := repositoryConfig[repositoryName] + Expect(ok).To(BeTrue()) + + downloadBaseURL := repo.URL + repositoryAPIName := "artifacts/static/" + repositoryName + + testFiles := []string{ + "booth-debugsource-1.0-6.ac1d34c.git.el8.2.x86_64.rpm", + "clufter-bin-debuginfo-0.77.1-5.el8.x86_64.rpm", + } + + It("File Upload", func() { + for _, filename := range testFiles { + rc, err := util.DownloadFromURL(downloadBaseURL+"/"+filename, 10*time.Second) + Expect(err).To(BeNil()) + + pusher, err := orasfile.NewStaticFileStreamPusher( + rc, + filename, + repositoryName, + name.WithDefaultRegistry(BeskarAddr), + ) + Expect(err).To(BeNil()) + + err = oras.Push(pusher, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + Expect(err).To(BeNil()) + } + }) + + It("Query File List", func() { + var files []*staticv1.RepositoryFile + + err := backoff.Retry(func() error { + files, err = beskarStaticClient().ListRepositoryFiles(context.Background(), repositoryAPIName, nil) + if err != nil { + return err + } else if len(files) != len(testFiles) { + return fmt.Errorf("expected %d files, got %d", len(testFiles), len(files)) + } + return nil + }) + Expect(err).To(BeNil()) + }) + + It("Query File By Filename", func() { + for _, filename := range testFiles { + info, ok := repo.Files[filename] + Expect(ok).To(BeTrue()) + + file, err := beskarStaticClient().GetRepositoryFileByName(context.Background(), repositoryAPIName, filename) + Expect(err).To(BeNil()) + + Expect(file).ToNot(BeNil()) + Expect(file.Size).To(Equal(info.Size)) + Expect(file.Tag).To(Equal(util.GetTagFromFilename(filename))) + } + }) + + It("Query File By Tag", func() { + for _, filename := range testFiles { + info, ok := repo.Files[filename] + Expect(ok).To(BeTrue()) + + tag := util.GetTagFromFilename(filename) + + file, err := beskarStaticClient().GetRepositoryFileByTag(context.Background(), repositoryAPIName, tag) + Expect(err).To(BeNil()) + + Expect(file).ToNot(BeNil()) + Expect(file.Size).To(Equal(info.Size)) + Expect(file.Tag).To(Equal(tag)) + } + }) + + It("Access File Content", func() { + for _, filename := range testFiles { + info, ok := repo.Files[filename] + Expect(ok).To(BeTrue()) + + url := getBeskarStaticFileURL(repositoryName, filename) + + rc, err := util.DownloadFromURL(url, 10*time.Second) + Expect(err).To(BeNil()) + + h := sha256.New() + n, err := io.Copy(h, rc) + + Expect(err).To(BeNil()) + Expect(uint64(n)).To(Equal(info.Size)) + Expect(hex.EncodeToString(h.Sum(nil))).To(Equal(info.SHA256)) + } + }) + + It("Delete Repository Failure", func() { + err := beskarStaticClient().DeleteRepository(context.Background(), repositoryAPIName, false) + Expect(err).ToNot(BeNil()) + Expect(gcode.HTTPStatusCode(err)).To(Equal(http.StatusBadRequest)) + }) + + It("Remove File And List", func() { + tag := util.GetTagFromFilename(testFiles[0]) + + err := beskarStaticClient().RemoveRepositoryFile(context.Background(), repositoryAPIName, tag) + Expect(err).To(BeNil()) + + files, err := beskarStaticClient().ListRepositoryFiles(context.Background(), repositoryAPIName, nil) + Expect(err).To(BeNil()) + Expect(len(files)).To(Equal(len(testFiles) - 1)) + }) + + It("Check Repository Logs", func() { + logs, err := beskarStaticClient().ListRepositoryLogs(context.Background(), repositoryAPIName, nil) + Expect(err).To(BeNil()) + Expect(len(logs)).To(Equal(0)) + }) + + It("Delete Repository With Files", func() { + err := beskarStaticClient().DeleteRepository(context.Background(), repositoryAPIName, true) + Expect(err).To(BeNil()) + + storageDir := os.Getenv("BESKARSTATIC_STORAGE_FILESYSTEM_DIRECTORY") + Expect(storageDir).ToNot(BeEmpty()) + + // ensure it's cleaned up + entries, err := os.ReadDir(filepath.Join(storageDir, repositoryAPIName)) + Expect(err).To(BeNil()) + Expect(len(entries)).To(Equal(0)) + }) + }) +}) diff --git a/integration/beskar_test.go b/integration/beskar_test.go new file mode 100644 index 0000000..d00ecb0 --- /dev/null +++ b/integration/beskar_test.go @@ -0,0 +1,28 @@ +package integration_test + +import ( + "github.com/google/go-containerregistry/pkg/crane" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func getBeskarReference(path string) string { + return BeskarAddr + "/" + path +} + +var _ = Describe("Beskar", func() { + Describe("Test", Ordered, func() { + localImage := "library/alpine:latest" + remoteImage := "mirror.gcr.io/library/alpine:latest" + + It("Copy image", func() { + err := crane.Copy(remoteImage, getBeskarReference(localImage)) + Expect(err).To(BeNil()) + }) + + It("Delete image", func() { + err := crane.Delete(getBeskarReference(localImage)) + Expect(err).To(BeNil()) + }) + }) +}) diff --git a/integration/beskar_yum_test.go b/integration/beskar_yum_test.go new file mode 100644 index 0000000..39c77a5 --- /dev/null +++ b/integration/beskar_yum_test.go @@ -0,0 +1,308 @@ +package integration_test + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/RussellLuo/kun/pkg/werror/gcode" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.ciq.dev/beskar/integration/pkg/backoff" + "go.ciq.dev/beskar/integration/pkg/repoconfig" + "go.ciq.dev/beskar/integration/pkg/util" + "go.ciq.dev/beskar/pkg/oras" + "go.ciq.dev/beskar/pkg/orasrpm" + "go.ciq.dev/beskar/pkg/plugins/httpcodec" + yumv1 "go.ciq.dev/beskar/pkg/plugins/yum/api/v1" +) + +func getBeskarYUMURL(path string) string { + return "http://" + BeskarAddr + "/artifacts/yum/" + path +} + +func getBeskarYUMRPMURL(repository, filename string) string { + return getBeskarYUMURL(filepath.Join(repository, "repo", filename)) +} + +func beskarYUMClient() *yumv1.HTTPClient { + httpClient := &http.Client{ + Timeout: 30 * time.Second, + } + client, err := yumv1.NewHTTPClient(httpcodec.JSONCodec, httpClient, getBeskarYUMURL("api/v1")) + Expect(err).To(BeNil()) + + return client +} + +var _ = Describe("Beskar YUM Plugin", func() { + repositoryConfig, err := repoconfig.Parse(repoconfigData) + Expect(err).To(BeNil()) + + configRepo := "vault-rocky-8.3-ha-debug" + repo, ok := repositoryConfig[configRepo] + Expect(ok).To(BeTrue()) + + downloadBaseURL := repo.URL + + Describe("Test Non Mirror", Ordered, func() { + repositoryName := configRepo + "-non-mirror" + repositoryAPIName := "artifacts/yum/" + repositoryName + + testPackages := map[string]string{ + "booth-debugsource-1.0-6.ac1d34c.git.el8.2.x86_64.rpm": "", + "clufter-bin-debuginfo-0.77.1-5.el8.x86_64.rpm": "", + } + + It("Create Repository", func() { + properties := &yumv1.RepositoryProperties{ + GPGKey: []byte(repo.GPGKey), + } + + err := beskarYUMClient().CreateRepository(context.Background(), repositoryAPIName, properties) + Expect(err).To(BeNil()) + }) + + It("RPM Upload", func() { + for filename := range testPackages { + rc, err := util.DownloadFromURL(downloadBaseURL+"/"+filename, 10*time.Second) + Expect(err).To(BeNil()) + + pusher, err := orasrpm.NewRPMStreamPusher( + rc, + repositoryName, + name.WithDefaultRegistry(BeskarAddr), + ) + Expect(err).To(BeNil()) + + err = oras.Push(pusher, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + Expect(err).To(BeNil()) + } + }) + + It("Query Package List", func() { + var packages []*yumv1.RepositoryPackage + + err := backoff.Retry(func() error { + packages, err = beskarYUMClient().ListRepositoryPackages(context.Background(), repositoryAPIName, nil) + if err != nil { + return err + } else if len(packages) != len(testPackages) { + return fmt.Errorf("expected %d packages, got %d", len(testPackages), len(packages)) + } + return nil + }) + Expect(err).To(BeNil()) + + for _, pkg := range packages { + _, ok := testPackages[pkg.RPMName()] + Expect(ok).To(BeTrue()) + testPackages[pkg.RPMName()] = pkg.ID + } + }) + + It("Query Package By ID", func() { + for filename, id := range testPackages { + pkg, err := beskarYUMClient().GetRepositoryPackage(context.Background(), repositoryAPIName, id) + Expect(err).To(BeNil()) + + Expect(pkg).ToNot(BeNil()) + Expect(pkg.ID).To(Equal(id)) + Expect(pkg.Tag).To(Equal(util.GetTagFromFilename(filename))) + Expect(pkg.RPMName()).To(Equal(filename)) + Expect(pkg.Verified).To(BeTrue()) + } + }) + + It("Query File By Tag", func() { + for filename, id := range testPackages { + tag := util.GetTagFromFilename(filename) + + pkg, err := beskarYUMClient().GetRepositoryPackageByTag(context.Background(), repositoryAPIName, tag) + Expect(err).To(BeNil()) + + Expect(pkg).ToNot(BeNil()) + Expect(pkg.ID).To(Equal(id)) + Expect(pkg.Tag).To(Equal(util.GetTagFromFilename(filename))) + Expect(pkg.RPMName()).To(Equal(filename)) + Expect(pkg.Verified).To(BeTrue()) + } + }) + + It("Access Repository Artifacts", func() { + for filename := range testPackages { + info, ok := repo.Files[filename] + Expect(ok).To(BeTrue()) + + url := getBeskarYUMRPMURL(repositoryName, filename) + + rc, err := util.DownloadFromURL(url, 10*time.Second) + Expect(err).To(BeNil()) + + h := sha256.New() + n, err := io.Copy(h, rc) + + Expect(err).To(BeNil()) + Expect(uint64(n)).To(Equal(info.Size)) + Expect(hex.EncodeToString(h.Sum(nil))).To(Equal(info.SHA256)) + } + + url := getBeskarYUMRPMURL(repositoryName, "repodata/repomd.xml") + _, err := util.DownloadFromURL(url, 10*time.Second) + Expect(err).To(BeNil()) + }) + + It("Delete Repository Failure", func() { + err := beskarYUMClient().DeleteRepository(context.Background(), repositoryAPIName, false) + Expect(err).ToNot(BeNil()) + Expect(gcode.HTTPStatusCode(err)).To(Equal(http.StatusBadRequest)) + }) + + It("Remove Package And List", func() { + tag := "" + for filename := range testPackages { + tag = util.GetTagFromFilename(filename) + break + } + + err := beskarYUMClient().RemoveRepositoryPackageByTag(context.Background(), repositoryAPIName, tag) + Expect(err).To(BeNil()) + + packages, err := beskarYUMClient().ListRepositoryPackages(context.Background(), repositoryAPIName, nil) + Expect(err).To(BeNil()) + Expect(len(packages)).To(Equal(len(testPackages) - 1)) + }) + + It("Check Repository Logs", func() { + logs, err := beskarYUMClient().ListRepositoryLogs(context.Background(), repositoryAPIName, nil) + Expect(err).To(BeNil()) + Expect(len(logs)).To(Equal(0)) + }) + + It("Delete Repository With Packages", func() { + err := beskarYUMClient().DeleteRepository(context.Background(), repositoryAPIName, true) + Expect(err).To(BeNil()) + + storageDir := os.Getenv("BESKARYUM_STORAGE_FILESYSTEM_DIRECTORY") + Expect(storageDir).ToNot(BeEmpty()) + + // ensure it's cleaned up + entries, err := os.ReadDir(filepath.Join(storageDir, repositoryAPIName)) + Expect(err).To(BeNil()) + Expect(len(entries)).To(Equal(0)) + }) + }) + + Describe("Test Mirror", Ordered, func() { + repositoryName := configRepo + "-mirror" + repositoryAPIName := "artifacts/yum/" + repositoryName + + It("Create Repository", func() { + mirror := true + properties := &yumv1.RepositoryProperties{ + Mirror: &mirror, + MirrorURLs: []string{ + repo.MirrorURL, + }, + GPGKey: []byte(repo.GPGKey), + } + + err := beskarYUMClient().CreateRepository(context.Background(), repositoryAPIName, properties) + Expect(err).To(BeNil()) + }) + + It("Get Repository", func() { + repository, err := beskarYUMClient().GetRepository(context.Background(), repositoryAPIName) + Expect(err).To(BeNil()) + Expect(repository).ToNot(BeNil()) + Expect(repository.Mirror).ToNot(BeNil()) + Expect(*repository.Mirror).To(BeTrue()) + Expect(repository.MirrorURLs).To(Equal([]string{repo.MirrorURL})) + Expect(repository.GPGKey).To(Equal([]byte(repo.GPGKey))) + }) + + It("Sync Repository", func() { + err := beskarYUMClient().SyncRepository(context.Background(), repositoryAPIName, true) + Expect(err).To(BeNil()) + }) + + It("Sync Status", func() { + status, err := beskarYUMClient().GetRepositorySyncStatus(context.Background(), repositoryAPIName) + Expect(err).To(BeNil()) + Expect(status.Syncing).To(BeFalse()) + Expect(status.SyncError).To(BeEmpty()) + Expect(status.SyncedPackages).To(Equal(len(repo.Files))) + Expect(status.TotalPackages).To(Equal(len(repo.Files))) + Expect(status.StartTime).ToNot(BeEmpty()) + Expect(status.EndTime).ToNot(BeEmpty()) + }) + + It("Access Repository Artifacts", func() { + for filename := range repo.Files { + info, ok := repo.Files[filename] + Expect(ok).To(BeTrue()) + + url := getBeskarYUMRPMURL(repositoryName, filename) + + rc, err := util.DownloadFromURL(url, 10*time.Second) + Expect(err).To(BeNil()) + + h := sha256.New() + n, err := io.Copy(h, rc) + + Expect(err).To(BeNil()) + Expect(uint64(n)).To(Equal(info.Size)) + Expect(hex.EncodeToString(h.Sum(nil))).To(Equal(info.SHA256)) + } + + url := getBeskarYUMRPMURL(repositoryName, "repodata/repomd.xml") + _, err := util.DownloadFromURL(url, 10*time.Second) + Expect(err).To(BeNil()) + }) + + It("Delete Repository Failure", func() { + err := beskarYUMClient().DeleteRepository(context.Background(), repositoryAPIName, false) + Expect(err).ToNot(BeNil()) + Expect(gcode.HTTPStatusCode(err)).To(Equal(http.StatusBadRequest)) + }) + + It("Remove Mirror Package Failure", func() { + tag := "" + for filename := range repo.Files { + tag = util.GetTagFromFilename(filename) + break + } + + err := beskarYUMClient().RemoveRepositoryPackageByTag(context.Background(), repositoryAPIName, tag) + Expect(gcode.HTTPStatusCode(err)).To(Equal(http.StatusBadRequest)) + }) + + It("Check Repository Logs", func() { + logs, err := beskarYUMClient().ListRepositoryLogs(context.Background(), repositoryAPIName, nil) + Expect(err).To(BeNil()) + Expect(len(logs)).To(Equal(0)) + }) + + It("Delete Repository With Packages", func() { + err := beskarYUMClient().DeleteRepository(context.Background(), repositoryAPIName, true) + Expect(err).To(BeNil()) + + storageDir := os.Getenv("BESKARYUM_STORAGE_FILESYSTEM_DIRECTORY") + Expect(storageDir).ToNot(BeEmpty()) + + // ensure it's cleaned up + entries, err := os.ReadDir(filepath.Join(storageDir, repositoryAPIName)) + Expect(err).To(BeNil()) + Expect(len(entries)).To(Equal(0)) + }) + }) +}) diff --git a/integration/go.mod b/integration/go.mod new file mode 100644 index 0000000..c178122 --- /dev/null +++ b/integration/go.mod @@ -0,0 +1,52 @@ +module go.ciq.dev/beskar/integration + +go 1.21 + +toolchain go1.21.0 + +require ( + github.com/RussellLuo/kun v0.4.5 + github.com/cenkalti/backoff/v4 v4.2.1 + github.com/docker/cli v24.0.0+incompatible + github.com/google/go-containerregistry v0.17.0 + github.com/onsi/ginkgo/v2 v2.13.2 + github.com/onsi/gomega v1.30.0 + go.ciq.dev/beskar v0.0.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/RussellLuo/validating/v3 v3.0.0-beta.1 // indirect + github.com/cavaliergopher/rpm v1.2.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.0+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-kit/kit v0.10.0 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) + +replace go.ciq.dev/beskar => ../ diff --git a/integration/go.sum b/integration/go.sum new file mode 100644 index 0000000..0571504 --- /dev/null +++ b/integration/go.sum @@ -0,0 +1,463 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/RussellLuo/kun v0.4.5 h1:006wh9AhIqf/IDijB0TXju8FrlWI0UAaObRPvKZKXFM= +github.com/RussellLuo/kun v0.4.5/go.mod h1:ITHYogvZMuRxT3CWEZQ7d+B6KU0wZJgVnjY74RfoUjE= +github.com/RussellLuo/validating/v3 v3.0.0-beta.1 h1:uvS1bibGqNFbL9sdT+T63A88vOq3UlVVYuqw1rZynF4= +github.com/RussellLuo/validating/v3 v3.0.0-beta.1/go.mod h1:aXLMAOUVm1Abr2yLXA8g49WVSI6RiiCwn0TXv2iToU0= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cavaliergopher/rpm v1.2.0 h1:s0h+QeVK252QFTolkhGiMeQ1f+tMeIMhGl8B1HUmGUc= +github.com/cavaliergopher/rpm v1.2.0/go.mod h1:R0q3vTqa7RUvPofAZYrnjJ63hh2vngjFfphuXiExVos= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM= +github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.0+incompatible h1:z4bf8HvONXX9Tde5lGBMQ7yCJgNahmJumdrStZAbeY4= +github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +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/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +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/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= +github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +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.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/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +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.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= +github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+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.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +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= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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= +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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 h1:jynE66seADJbyWMUdeOyVTvPtBZt7L6LJHupGwxPZRM= +golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +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= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +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= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/integration/main_integration_test.go b/integration/main_integration_test.go new file mode 100644 index 0000000..41aa45a --- /dev/null +++ b/integration/main_integration_test.go @@ -0,0 +1,164 @@ +package integration_test + +import ( + _ "embed" + "fmt" + "net/http" + "os" + "os/exec" + "syscall" + "testing" + "time" + + "github.com/docker/cli/cli/config" + "github.com/docker/cli/cli/config/types" + "github.com/google/go-containerregistry/pkg/crane" + "go.ciq.dev/beskar/integration/pkg/backoff" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +const ( + BeskarAddr = "127.0.0.1:5100" + BeskarUsername = "beskar" + BeskarPassword = "beskar" + + SwaggerYAMLPath = "api/v1/doc/swagger.yaml" + + integrationDir = "/tmp/integration" +) + +//go:embed testdata/repoconfig.yaml +var repoconfigData []byte + +func TestSubstrateIntegration(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Beskar Integration Test Suite") +} + +var beskarCmd *exec.Cmd + +var _ = SynchronizedBeforeSuite( + // NOTE: This runs *only* on process #1 when run in parallel + func() []byte { + err := os.RemoveAll(integrationDir) + Expect(err).To(BeNil()) + + err = os.Mkdir(integrationDir, 0o700) + Expect(err).To(BeNil()) + + beskarCmd = exec.Command("/app/beskar") + beskarCmd.Stdout = GinkgoWriter + beskarCmd.Stderr = GinkgoWriter + + err = beskarCmd.Start() + Expect(err).To(BeNil()) + + err = storeBeskarCredentials() + Expect(err).To(BeNil()) + + err = checkBeskar() + Expect(err).To(BeNil()) + + err = checkBeskarYUMAPI() + Expect(err).To(BeNil()) + + err = checkBeskarStaticAPI() + Expect(err).To(BeNil()) + + return nil + }, + // NOTE: This runs on *all* processes when run in parallel + func(_ []byte) {}, +) + +var _ = SynchronizedAfterSuite( + // NOTE: If a function is included, it runs on *all* processes when run in parallel + func() {}, + // NOTE: This runs *only* on process #1 when run in parallel + func() { + defer os.RemoveAll(integrationDir) + + err := beskarCmd.Process.Signal(syscall.SIGINT) + Expect(err).To(BeNil()) + + err = beskarCmd.Wait() + Expect(err).To(BeNil()) + }, +) + +func storeBeskarCredentials() error { + cf, err := config.Load("") + if err != nil { + return err + } + creds := cf.GetCredentialsStore(BeskarAddr) + + if err := creds.Store(types.AuthConfig{ + ServerAddress: BeskarAddr, + Username: BeskarUsername, + Password: BeskarPassword, + }); err != nil { + return err + } + + return cf.Save() +} + +func checkBeskar() error { + return backoff.Retry(func() error { + _, err := crane.Catalog(BeskarAddr, crane.Insecure) + return err + }) +} + +func checkBeskarYUMAPI() error { + httpClient := &http.Client{ + Timeout: time.Second, + } + + req, err := http.NewRequest("GET", getBeskarYUMURL(SwaggerYAMLPath), nil) + if err != nil { + return err + } + + return backoff.Retry(func() error { + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad status code: %d", resp.StatusCode) + } + + return nil + }) +} + +func checkBeskarStaticAPI() error { + httpClient := &http.Client{ + Timeout: time.Second, + } + + req, err := http.NewRequest("GET", getBeskarStaticURL(SwaggerYAMLPath), nil) + if err != nil { + return err + } + + return backoff.Retry(func() error { + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad status code: %d", resp.StatusCode) + } + + return nil + }) +} diff --git a/integration/pkg/backoff/backoff.go b/integration/pkg/backoff/backoff.go new file mode 100644 index 0000000..3ca443f --- /dev/null +++ b/integration/pkg/backoff/backoff.go @@ -0,0 +1,14 @@ +package backoff + +import ( + "time" + + "github.com/cenkalti/backoff/v4" +) + +func Retry(fn backoff.Operation) error { + eb := backoff.NewExponentialBackOff() + eb.MaxElapsedTime = 20 * time.Second + eb.MaxInterval = 2 * time.Second + return backoff.Retry(fn, eb) +} diff --git a/integration/pkg/repoconfig/parse.go b/integration/pkg/repoconfig/parse.go new file mode 100644 index 0000000..1c44c76 --- /dev/null +++ b/integration/pkg/repoconfig/parse.go @@ -0,0 +1,23 @@ +package repoconfig + +import "gopkg.in/yaml.v3" + +type File struct { + SHA256 string `yaml:"sha256"` + Size uint64 `yaml:"size"` +} + +type Repository struct { + URL string `yaml:"url"` + MirrorURL string `yaml:"mirror-url"` + GPGKey string `yaml:"gpgkey"` + Files map[string]File `yaml:"files"` +} + +type Repositories map[string]Repository + +func Parse(data []byte) (Repositories, error) { + src := make(Repositories) + err := yaml.Unmarshal(data, src) + return src, err +} diff --git a/integration/pkg/util/download_url.go b/integration/pkg/util/download_url.go new file mode 100644 index 0000000..483ec8c --- /dev/null +++ b/integration/pkg/util/download_url.go @@ -0,0 +1,28 @@ +package util + +import ( + "fmt" + "io" + "net/http" + "time" +) + +func DownloadFromURL(url string, timeout time.Duration) (io.ReadCloser, error) { + httpClient := &http.Client{ + Timeout: timeout, + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("bad status code: %d", resp.StatusCode) + } + + return resp.Body, nil +} diff --git a/integration/pkg/util/tag.go b/integration/pkg/util/tag.go new file mode 100644 index 0000000..b83417e --- /dev/null +++ b/integration/pkg/util/tag.go @@ -0,0 +1,11 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" +) + +func GetTagFromFilename(filename string) string { + sum := md5.Sum([]byte(filename)) + return hex.EncodeToString(sum[:]) +} diff --git a/integration/testdata/repoconfig.yaml b/integration/testdata/repoconfig.yaml new file mode 100644 index 0000000..a39109a --- /dev/null +++ b/integration/testdata/repoconfig.yaml @@ -0,0 +1,117 @@ +vault-rocky-8.3-ha-debug: + url: https://dl.rockylinux.org/vault/rocky/8.3/HighAvailability/x86_64/debug/tree/Packages + mirror-url: https://dl.rockylinux.org/vault/rocky/8.3/HighAvailability/x86_64/debug/tree + gpgkey: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQINBGAofzYBEAC6yS1azw6f3wmaVd//3aSy6O2c9+jeetulRQvg2LvhRRS1eNqp + /x9tbBhfohu/tlDkGpYHV7diePgMml9SZDy1sKlI3tDhx6GZ3xwF0fd1vWBZpmNk + D9gRkUmYBeLotmcXQZ8ZpWLicosFtDpJEYpLUhuIgTKwt4gxJrHvkWsGQiBkJxKD + u3/RlL4IYA3Ot9iuCBflc91EyAw1Yj0gKcDzbOqjvlGtS3ASXgxPqSfU0uLC9USF + uKDnP2tcnlKKGfj0u6VkqISliSuRAzjlKho9Meond+mMIFOTT6qp4xyu+9Dj3IjZ + IC6rBXRU3xi8z0qYptoFZ6hx70NV5u+0XUzDMXdjQ5S859RYJKijiwmfMC7gZQAf + OkdOcicNzen/TwD/slhiCDssHBNEe86Wwu5kmDoCri7GJlYOlWU42Xi0o1JkVltN + D8ZId+EBDIms7ugSwGOVSxyZs43q2IAfFYCRtyKHFlgHBRe9/KTWPUrnsfKxGJgC + Do3Yb63/IYTvfTJptVfhQtL1AhEAeF1I+buVoJRmBEyYKD9BdU4xQN39VrZKziO3 + hDIGng/eK6PaPhUdq6XqvmnsZ2h+KVbyoj4cTo2gKCB2XA7O2HLQsuGduHzYKNjf + QR9j0djjwTrsvGvzfEzchP19723vYf7GdcLvqtPqzpxSX2FNARpCGXBw9wARAQAB + tDNSZWxlYXNlIEVuZ2luZWVyaW5nIDxpbmZyYXN0cnVjdHVyZUByb2NreWxpbnV4 + Lm9yZz6JAk4EEwEIADgWIQRwUcRwqSn0VM6+N7cVr12sbXRaYAUCYCh/NgIbDwUL + CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAVr12sbXRaYLFmEACSMvoO1FDdyAbu + 1m6xEzDhs7FgnZeQNzLZECv2j+ggFSJXezlNVOZ5I1I8umBan2ywfKQD8M+IjmrW + k9/7h9i54t8RS/RN7KNo7ECGnKXqXDPzBBTs1Gwo1WzltAoaDKUfXqQ4oJ4aCP/q + /XPVWEzgpJO1XEezvCq8VXisutyDiXEjjMIeBczxb1hbamQX+jLTIQ1MDJ4Zo1YP + zlUqrHW434XC2b1/WbSaylq8Wk9cksca5J+g3FqTlgiWozyy0uxygIRjb6iTzKXk + V7SYxeXp3hNTuoUgiFkjh5/0yKWCwx7aQqlHar9GjpxmBDAO0kzOlgtTw//EqTwR + KnYZLig9FW0PhwvZJUigr0cvs/XXTTb77z/i/dfHkrjVTTYenNyXogPtTtSyxqca + 61fbPf0B/S3N43PW8URXBRS0sykpX4SxKu+PwKCqf+OJ7hMEVAapqzTt1q9T7zyB + QwvCVx8s7WWvXbs2d6ZUrArklgjHoHQcdxJKdhuRmD34AuXWCLW+gH8rJWZpuNl3 + +WsPZX4PvjKDgMw6YMcV7zhWX6c0SevKtzt7WP3XoKDuPhK1PMGJQqQ7spegGB+5 + DZvsJS48Ip0S45Qfmj82ibXaCBJHTNZE8Zs+rdTjQ9DS5qvzRA1sRA1dBb/7OLYE + JmeWf4VZyebm+gc50szsg6Ut2yT8hw== + =AiP8 + -----END PGP PUBLIC KEY BLOCK----- + files: + booth-core-debuginfo-1.0-6.ac1d34c.git.el8.2.x86_64.rpm: + sha256: 70cd3f8f09bfd30bb423d0101c02389d1be326706c244829815f87b94fa875ff + size: 210964 + booth-debugsource-1.0-6.ac1d34c.git.el8.2.x86_64.rpm: + sha256: 4c175312dd8cbb81e8f9672bd49cd73b01918c475b3594c2f078caf57cb24022 + size: 69532 + clufter-bin-debuginfo-0.77.1-5.el8.x86_64.rpm: + sha256: c83e47640091e3aed88e27d0ea30f7d3060d7dea2f7746055bc05f3949baca5c + size: 62316 + clufter-debugsource-0.77.1-5.el8.x86_64.rpm: + sha256: 83f9678740caa2890fb8f45b4fd4972cd0b7f1c7dbf910a76628befc2e26c39e + size: 35084 + corosync-qdevice-debuginfo-3.0.0-4.el8.x86_64.rpm: + sha256: 5cb1ac43171c08fd7578e9ad281f1dc4576ab24775ac2db3c84c3be8661b7455 + size: 341300 + corosync-qdevice-debugsource-3.0.0-4.el8.x86_64.rpm: + sha256: 5d539d514f4659e40dd50a8357675ba30738c9138b829cf9e7b4eef7a71d425b + size: 131324 + corosync-qnetd-debuginfo-3.0.0-4.el8.x86_64.rpm: + sha256: 8db79297a0371ae48cc4e108093552498f26d246c18fe98f427fd51184050b87 + size: 177000 + fence-agents-aliyun-debuginfo-4.2.1-53.el8.2.x86_64.rpm: + sha256: f1009861428b7bd0d32c0bf819f3d7981fffc125df8da98a002eca15574c8871 + size: 237948 + kronosnet-debugsource-1.16-1.el8.i686.rpm: + sha256: 9e192376f8c7d2a67695d5b1fd429285b1f74100b08198ce4160f2c5ef1f73d0 + size: 155436 + kronosnet-debugsource-1.16-1.el8.x86_64.rpm: + sha256: 32f1d6f2ba884162f3261af79090989d46aa7cc985e0eb4bed7be6476b62b076 + size: 155404 + libknet1-compress-bzip2-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 005e7097f22afd944967552da719d2256015ab1e70197840f1154a5efdd6c2c5 + size: 51508 + libknet1-compress-lz4-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 7a4500bba81702deaf594ed54fe8cb068261078ff0fa3fa0ff69d5d12c9da7e9 + size: 59564 + libknet1-compress-lzma-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 097825da7fc7950520e25190a0ccf6beb7697a77c674f72290be4c1bc2bdc9a5 + size: 52548 + libknet1-compress-lzo2-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 1e8760bbb532b525f8d69aa16d51786dad8eec38060040d3c4ddbb58ce3c4e2e + size: 56688 + libknet1-compress-zlib-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 9a13fad23268f6b78cee48475a82593b8f7c997997f6597d63750d74e6a25584 + size: 50700 + libknet1-crypto-nss-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 286875af24122e3589cb1d21b8da40895defbc658294c7ebd29c340348c3aad9 + size: 96032 + libknet1-crypto-openssl-plugin-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: 2b1e35650fcc9f984396df045811196846317ae53c5aeb4b7a34814dc587db2a + size: 132076 + libknet1-debuginfo-1.16-1.el8.i686.rpm: + sha256: 7c162626f92f11c8574d7c861bee6d4d3c2966d1472f187b63e509026339123a + size: 216916 + libknet1-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: bda5e228bc2bcaa0a8d88a6551d7836ece0d4de4a2b25d3462dbb172c9256bcb + size: 234972 + libnozzle1-debuginfo-1.16-1.el8.i686.rpm: + sha256: b55e8c166d05dd96a2d0fc9314ce8a5547e1c8225cbbb350be1f557b9071c8bc + size: 82548 + libnozzle1-debuginfo-1.16-1.el8.x86_64.rpm: + sha256: af2f603c217c654bded49830fdf130862ecd7171888f56ac062e7ab90b47dbba + size: 84260 + pacemaker-cli-debuginfo-2.0.4-6.el8.2.x86_64.rpm: + sha256: 85932aead5452a8879daebe033671a7f41752c93a4609596641ec0d756180c4c + size: 357552 + pacemaker-remote-debuginfo-2.0.4-6.el8.2.x86_64.rpm: + sha256: fe30621d95d117e3ed749d0bd5ae41c52cd41e0853c08fdd6eacde2dc453e69a + size: 89868 + resource-agents-aliyun-debuginfo-4.1.1-68.el8.2.x86_64.rpm: + sha256: dc6e59602f319178eaa5d2ea268d206353db85ddc734883b8d6072752cd44f06 + size: 253352 + resource-agents-debuginfo-4.1.1-68.el8.2.x86_64.rpm: + sha256: 5eb1d81a540c06bba59cdfa98e8071eeedb229f5c664487e2260d48e515e2e6c + size: 169232 + resource-agents-debugsource-4.1.1-68.el8.2.x86_64.rpm: + sha256: a86631e7a4006c7a017b264fecc5b74564586400b31a11e86eece4c6a33b2320 + size: 171220 + spausedd-debuginfo-3.0.3-4.el8.x86_64.rpm: + sha256: 313ea57acd22cb4f7bc269fb86b1e9a164a85ce88a5f69bb5438f3732a5e082b + size: 42660 + + diff --git a/internal/pkg/beskar/registry.go b/internal/pkg/beskar/registry.go index 9c2639d..112e3b7 100644 --- a/internal/pkg/beskar/registry.go +++ b/internal/pkg/beskar/registry.go @@ -331,8 +331,6 @@ func (br *Registry) initManifestCache(caPEM *mtls.CAPEM) (_ *groupcache.Group, e transport := http.DefaultTransport.(*http.Transport).Clone() transport.TLSClientConfig = cacheClientConfig - transport.MaxIdleConnsPerHost = 16 - transport.ResponseHeaderTimeout = 2 * time.Second br.manifestCache = cache.NewCache(cacheAddr, &groupcache.HTTPPoolOptions{ Transport: func(context.Context) http.RoundTripper { diff --git a/internal/plugins/static/pkg/staticdb/repository.go b/internal/plugins/static/pkg/staticdb/repository.go index 7ef7406..c8b27bf 100644 --- a/internal/plugins/static/pkg/staticdb/repository.go +++ b/internal/plugins/static/pkg/staticdb/repository.go @@ -121,7 +121,7 @@ func (db *RepositoryDB) GetFileByTag(ctx context.Context, tag string) (*Reposito file := new(RepositoryFile) if !rows.Next() { - return nil, fmt.Errorf("failed to retrieve file with tag %s", tag) + return file, nil } if err := rows.StructScan(file); err != nil { return nil, err @@ -147,7 +147,7 @@ func (db *RepositoryDB) GetFileByName(ctx context.Context, name string) (*Reposi file := new(RepositoryFile) if !rows.Next() { - return nil, fmt.Errorf("failed to retrieve file %s", name) + return file, nil } if err := rows.StructScan(file); err != nil { return nil, err diff --git a/internal/plugins/yum/api.go b/internal/plugins/yum/api.go index 2fe8534..665f561 100644 --- a/internal/plugins/yum/api.go +++ b/internal/plugins/yum/api.go @@ -46,11 +46,11 @@ func (p *Plugin) GetRepository(ctx context.Context, repository string) (properti return p.repositoryManager.Get(ctx, repository).GetRepository(ctx) } -func (p *Plugin) SyncRepository(ctx context.Context, repository string) (err error) { +func (p *Plugin) SyncRepository(ctx context.Context, repository string, wait bool) (err error) { if err := checkRepository(repository); err != nil { return err } - return p.repositoryManager.Get(ctx, repository).SyncRepository(ctx) + return p.repositoryManager.Get(ctx, repository).SyncRepository(ctx, wait) } func (p *Plugin) GetRepositorySyncStatus(ctx context.Context, repository string) (syncStatus *apiv1.SyncStatus, err error) { diff --git a/internal/plugins/yum/pkg/yumdb/repository.go b/internal/plugins/yum/pkg/yumdb/repository.go index 452f45c..c8ea1d2 100644 --- a/internal/plugins/yum/pkg/yumdb/repository.go +++ b/internal/plugins/yum/pkg/yumdb/repository.go @@ -145,7 +145,7 @@ func (db *RepositoryDB) GetPackage(ctx context.Context, id string) (*RepositoryP pkg := new(RepositoryPackage) if !rows.Next() { - return nil, fmt.Errorf("failed to retrieve package with id %s", id) + return pkg, nil } if err := rows.StructScan(pkg); err != nil { return nil, err @@ -171,7 +171,7 @@ func (db *RepositoryDB) GetPackageByTag(ctx context.Context, tag string) (*Repos pkg := new(RepositoryPackage) if !rows.Next() { - return nil, fmt.Errorf("failed to retrieve package with tag %s", tag) + return pkg, nil } if err := rows.StructScan(pkg); err != nil { return nil, err diff --git a/internal/plugins/yum/pkg/yumrepository/api.go b/internal/plugins/yum/pkg/yumrepository/api.go index 42feb14..8251c93 100644 --- a/internal/plugins/yum/pkg/yumrepository/api.go +++ b/internal/plugins/yum/pkg/yumrepository/api.go @@ -267,7 +267,7 @@ func (h *Handler) GetRepository(ctx context.Context) (properties *apiv1.Reposito return properties, nil } -func (h *Handler) SyncRepository(context.Context) (err error) { +func (h *Handler) SyncRepository(_ context.Context, wait bool) (err error) { if !h.Started() { return werror.Wrap(gcode.ErrUnavailable, err) } else if !h.getMirror() { @@ -281,7 +281,12 @@ func (h *Handler) SyncRepository(context.Context) (err error) { } select { - case h.syncCh <- struct{}{}: + case h.syncCh <- wait: + if wait { + if err := <-h.syncErrCh; err != nil { + return werror.Wrap(gcode.ErrInternal, fmt.Errorf("synchronization failed: %w", err)) + } + } default: return werror.Wrap(gcode.ErrUnavailable, errors.New("something goes wrong")) } diff --git a/internal/plugins/yum/pkg/yumrepository/handler.go b/internal/plugins/yum/pkg/yumrepository/handler.go index 5f0d728..d5daffb 100644 --- a/internal/plugins/yum/pkg/yumrepository/handler.go +++ b/internal/plugins/yum/pkg/yumrepository/handler.go @@ -39,9 +39,10 @@ type Handler struct { statusDB *yumdb.StatusDB logDB *yumdb.LogDB - syncCh chan struct{} - reposync atomic.Pointer[yumdb.Reposync] - syncing atomic.Bool + syncCh chan bool + syncErrCh chan error + reposync atomic.Pointer[yumdb.Reposync] + syncing atomic.Bool propertyMutex sync.RWMutex created bool @@ -57,7 +58,8 @@ func NewHandler(logger *slog.Logger, repoHandler *repository.RepoHandler) *Handl RepoHandler: repoHandler, repoDir: filepath.Join(repoHandler.Params.Dir, repoHandler.Repository), logger: logger, - syncCh: make(chan struct{}, 1), + syncCh: make(chan bool, 1), + syncErrCh: make(chan error), } } @@ -209,6 +211,7 @@ func (h *Handler) cleanup() { close(h.Queued) close(h.syncCh) + close(h.syncErrCh) h.Params.Remove(h.Repository) _ = os.RemoveAll(h.repoDir) } @@ -420,12 +423,15 @@ func (h *Handler) Start(ctx context.Context) { select { case <-ctx.Done(): h.Stopped.Store(true) - case <-h.syncCh: + case wait := <-h.syncCh: go func() { err := h.repositorySync(ctx) if err != nil { h.logger.Error("reposistory sync", "error", err.Error()) } + if wait { + h.syncErrCh <- err + } }() case <-h.Queued: events := h.DequeueEvents() @@ -442,7 +448,9 @@ func (h *Handler) Start(ctx context.Context) { // all remaining events in database have been processed // start the repository sync if lastIndex != nil && events[len(events)-1].Digest == lastIndex.Digest { - h.syncCh <- struct{}{} + // false means that the sync operation won't wait for the + // sync to complete + h.syncCh <- false lastIndex = nil } diff --git a/pkg/decompress/decompress.go b/pkg/decompress/decompress.go index 2deb104..3205f39 100644 --- a/pkg/decompress/decompress.go +++ b/pkg/decompress/decompress.go @@ -49,7 +49,7 @@ var decompressors = []struct { type HashBuffer struct { hash.Hash buffer *bytes.Buffer - bytesRead int + bytesRead int64 } func NewHashBuffer(h hash.Hash, buffer *bytes.Buffer) *HashBuffer { @@ -64,7 +64,7 @@ func (hb *HashBuffer) Write(p []byte) (int, error) { if err != nil { return n, err } - hb.bytesRead += n + hb.bytesRead += int64(n) if hb.buffer != nil { if _, err := hb.buffer.Write(p); err != nil { return n, err @@ -73,7 +73,7 @@ func (hb *HashBuffer) Write(p []byte) (int, error) { return n, nil } -func (hb *HashBuffer) BytesRead() int { +func (hb *HashBuffer) BytesRead() int64 { return hb.bytesRead } diff --git a/pkg/ioutil/multireader.go b/pkg/ioutil/multireader.go new file mode 100644 index 0000000..1188e37 --- /dev/null +++ b/pkg/ioutil/multireader.go @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright (c) 2023, CIQ, Inc. All rights reserved +// SPDX-License-Identifier: Apache-2.0 + +package ioutil + +import ( + "io" +) + +type multiReader struct { + io.Reader + readers []io.Reader +} + +func (mr *multiReader) Close() error { + for _, reader := range mr.readers { + if closer, ok := reader.(io.Closer); ok { + if err := closer.Close(); err != nil { + return err + } + } + } + return nil +} + +// MultiReaderCloser wraps io.MultiReader and close readers +// implementing the io.Closer interface. +func MultiReaderCloser(readers ...io.Reader) io.ReadCloser { + return &multiReader{ + Reader: io.MultiReader(readers...), + readers: readers, + } +} diff --git a/pkg/oras/image.go b/pkg/oras/image.go index cef443a..7e04d8c 100644 --- a/pkg/oras/image.go +++ b/pkg/oras/image.go @@ -10,6 +10,7 @@ import ( "sync" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/stream" "github.com/google/go-containerregistry/pkg/v1/types" ) @@ -27,12 +28,14 @@ var _ v1.Image = &Image{} type Image struct { m sync.RWMutex + streamLayers []*StreamLayer + layers map[string]v1.Layer manifest *v1.Manifest rawConfig []byte } -// NewImage returns a Image instance for uploading SIF images +// NewImage returns a Image instance for uploading artifacts // to OCI registries. func NewImage() *Image { return &Image{ @@ -59,8 +62,22 @@ func (i *Image) AddConfig(mt types.MediaType, rawConfig []byte) error { return nil } -// AddSIFLayer adds a blob layer to the image manifest. +// AddLayer adds a blob layer to the image manifest. func (i *Image) AddLayer(layer Layer) error { + if sl, ok := layer.(*StreamLayer); ok { + i.streamLayers = append(i.streamLayers, sl) + return nil + } + + i.m.Lock() + defer i.m.Unlock() + + return i.addLayer(layer) +} + +// addLayer adds the layer to the image manifest, must +// be wrapped in a write lock section. +func (i *Image) addLayer(layer Layer) error { digest, err := layer.Digest() if err != nil { return err @@ -72,7 +89,6 @@ func (i *Image) AddLayer(layer Layer) error { return err } - i.m.Lock() i.layers[digest.String()] = layer i.manifest.Layers = append(i.manifest.Layers, v1.Descriptor{ MediaType: mt, @@ -81,7 +97,6 @@ func (i *Image) AddLayer(layer Layer) error { Annotations: layer.Annotations(), Platform: layer.Platform(), }) - i.m.Unlock() return nil } @@ -94,6 +109,9 @@ func (i *Image) Layers() ([]v1.Layer, error) { for _, sl := range i.layers { layers = append(layers, sl) } + for _, sl := range i.streamLayers { + layers = append(layers, sl) + } i.m.RUnlock() return layers, nil @@ -131,6 +149,28 @@ func (i *Image) ConfigFile() (*v1.ConfigFile, error) { // Digest returns the sha256 of this image's manifest. func (i *Image) Digest() (v1.Hash, error) { + notComputed := false + + i.m.Lock() + + for j := len(i.streamLayers) - 1; j >= 0; j-- { + if i.streamLayers[j].digest.Hex == "" { + notComputed = true + } else { + if err := i.addLayer(i.streamLayers[j]); err != nil { + i.m.Unlock() + return v1.Hash{}, err + } + i.streamLayers = append(i.streamLayers[:j], i.streamLayers[j+1:]...) + } + } + + i.m.Unlock() + + if notComputed { + return v1.Hash{}, stream.ErrNotComputed + } + b, err := i.RawManifest() if err != nil { return v1.Hash{}, fmt.Errorf("failed to get image sha manifest: %w", err) diff --git a/pkg/oras/layer.go b/pkg/oras/layer.go index 93805c7..e27cdae 100644 --- a/pkg/oras/layer.go +++ b/pkg/oras/layer.go @@ -4,6 +4,7 @@ package oras import ( + "crypto/sha256" "fmt" "io" "os" @@ -11,8 +12,10 @@ import ( "sync" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/stream" "github.com/google/go-containerregistry/pkg/v1/types" imagespec "github.com/opencontainers/image-spec/specs-go/v1" + "go.ciq.dev/beskar/pkg/decompress" ) type MediaTypeFilter func(types.MediaType) bool @@ -38,26 +41,32 @@ func GetLayerFilter(manifest *v1.Manifest, mediaTypeFilter MediaTypeFilter) (v1. return v1.Descriptor{}, fmt.Errorf("no recognized layer found in manifest") } -type LocalFileLayerOption func(*LocalFileLayer) +type LayerOption func(*LayerOptions) -func WithLocalFileLayerAnnotations(annotations map[string]string) LocalFileLayerOption { - return func(l *LocalFileLayer) { - l.annotations = annotations +func WithLayerAnnotations(annotations map[string]string) LayerOption { + return func(o *LayerOptions) { + o.annotations = annotations } } -func WithLocalFileLayerPlatform(platform *v1.Platform) LocalFileLayerOption { - return func(l *LocalFileLayer) { - l.platform = platform +func WithLayerPlatform(platform *v1.Platform) LayerOption { + return func(o *LayerOptions) { + o.platform = platform } } -func WithLocalFileLayerMediaType(mediaType string) LocalFileLayerOption { - return func(l *LocalFileLayer) { - l.mediaType = mediaType +func WithLayerMediaType(mediaType string) LayerOption { + return func(o *LayerOptions) { + o.mediaType = mediaType } } +type LayerOptions struct { + annotations map[string]string + platform *v1.Platform + mediaType string +} + // LocalFileLayer defines an OCI layer descriptor associated to a local file. type LocalFileLayer struct { path string @@ -65,21 +74,19 @@ type LocalFileLayer struct { digestOnce sync.Once digest v1.Hash - annotations map[string]string - platform *v1.Platform - mediaType string + options LayerOptions } // NewLocalLayer returns an OCI layer descriptor for the corresponding local file. -func NewLocalFileLayer(path string, options ...LocalFileLayerOption) *LocalFileLayer { +func NewLocalFileLayer(path string, options ...LayerOption) *LocalFileLayer { layer := &LocalFileLayer{ path: path, } for _, opt := range options { - opt(layer) + opt(&layer.options) } - if layer.annotations == nil { - layer.annotations = make(map[string]string) + if layer.options.annotations == nil { + layer.options.annotations = make(map[string]string) } return layer } @@ -134,18 +141,104 @@ func (l *LocalFileLayer) Size() (int64, error) { // MediaType returns the media type of the Layer. func (l *LocalFileLayer) MediaType() (types.MediaType, error) { - return types.MediaType(l.mediaType), nil + return types.MediaType(l.options.mediaType), nil } // Annotations returns annotations associated to this layer. func (l *LocalFileLayer) Annotations() map[string]string { - if l.annotations[imagespec.AnnotationTitle] == "" { - l.annotations[imagespec.AnnotationTitle] = filepath.Base(l.path) + if l.options.annotations[imagespec.AnnotationTitle] == "" { + l.options.annotations[imagespec.AnnotationTitle] = filepath.Base(l.path) } - return l.annotations + return l.options.annotations } // Platform returns platform information for this layer. func (l *LocalFileLayer) Platform() *v1.Platform { - return l.platform + return l.options.platform +} + +// StreamLayer defines an OCI layer descriptor associated to a stream. +type StreamLayer struct { + io.Reader + stream io.Reader + + digest v1.Hash + hashBuffer *decompress.HashBuffer + + options LayerOptions +} + +// NewStreamLayer returns an OCI layer descriptor for the corresponding stream. +func NewStreamLayer(stream io.Reader, options ...LayerOption) *StreamLayer { + hb := decompress.NewHashBuffer(sha256.New(), nil) + layer := &StreamLayer{ + Reader: io.TeeReader(stream, hb), + stream: stream, + hashBuffer: hb, + digest: v1.Hash{ + Algorithm: "sha256", + }, + } + for _, opt := range options { + opt(&layer.options) + } + if layer.options.annotations == nil { + layer.options.annotations = make(map[string]string) + } + return layer +} + +// Digest returns the Hash of the consumed stream. +func (sl *StreamLayer) Digest() (v1.Hash, error) { + if sl.digest.Hex == "" { + return sl.digest, stream.ErrNotComputed + } + return sl.digest, nil +} + +// DiffID returns the Hash of the uncompressed layer (not supported by ORAS). +func (sl *StreamLayer) DiffID() (v1.Hash, error) { + return v1.Hash{}, fmt.Errorf("not supported by ORAS") +} + +// Compressed returns an io.ReadCloser for the file content. +func (sl *StreamLayer) Compressed() (io.ReadCloser, error) { + return sl, nil +} + +// Uncompressed returns an io.ReadCloser for the uncompressed layer contents +// (not supported by ORAS). +func (sl *StreamLayer) Uncompressed() (io.ReadCloser, error) { + return nil, fmt.Errorf("not supported by ORAS") +} + +// Size returns the size of the consumed stream. +func (sl *StreamLayer) Size() (int64, error) { + return sl.hashBuffer.BytesRead(), nil +} + +// MediaType returns the media type of the Layer. +func (sl *StreamLayer) MediaType() (types.MediaType, error) { + return types.MediaType(sl.options.mediaType), nil +} + +// Annotations returns annotations associated to this layer. +func (sl *StreamLayer) Annotations() map[string]string { + return sl.options.annotations +} + +// Platform returns platform information for this layer. +func (sl *StreamLayer) Platform() *v1.Platform { + return sl.options.platform +} + +// Close closes the underlying stream and computes the digest. +func (sl *StreamLayer) Close() error { + sl.digest.Hex = sl.hashBuffer.Hex() + + if closer, ok := sl.stream.(io.Closer); ok { + return closer.Close() + } + + return nil } diff --git a/pkg/orasfile/file.go b/pkg/orasfile/file.go index 2dfb097..8d7d139 100644 --- a/pkg/orasfile/file.go +++ b/pkg/orasfile/file.go @@ -6,10 +6,12 @@ package orasfile import ( "crypto/md5" //nolint:gosec "fmt" + "io" "path/filepath" "strings" "github.com/google/go-containerregistry/pkg/name" + imagespec "github.com/opencontainers/image-spec/specs-go/v1" "go.ciq.dev/beskar/pkg/oras" ) @@ -18,7 +20,7 @@ const ( StaticFileLayerType = "application/vnd.ciq.static.v1.file" ) -func NewStaticFilePusher(path, repo string, opts ...name.Option) (oras.Pusher, error) { +func FileReference(filename, repo string, opts ...name.Option) (name.Reference, error) { if !strings.HasPrefix(repo, "artifacts/") { if !strings.HasPrefix(repo, "static/") { repo = filepath.Join("artifacts", "static", repo) @@ -27,19 +29,46 @@ func NewStaticFilePusher(path, repo string, opts ...name.Option) (oras.Pusher, e } } - filename := filepath.Base(path) //nolint:gosec fileTag := fmt.Sprintf("%x", md5.Sum([]byte(filename))) rawRef := filepath.Join(repo, "files:"+fileTag) - ref, err := name.ParseReference(rawRef, opts...) + return name.ParseReference(rawRef, opts...) +} + +func NewStaticFilePusher(path, repo string, opts ...name.Option) (oras.Pusher, error) { + filename := filepath.Base(path) + + ref, err := FileReference(filename, repo, opts...) + if err != nil { + return nil, err + } + + return oras.NewGenericPusher( + ref, + oras.NewManifestConfig(StaticFileConfigType, nil), + oras.NewLocalFileLayer( + path, + oras.WithLayerMediaType(StaticFileLayerType), + ), + ), nil +} + +func NewStaticFileStreamPusher(stream io.Reader, filename, repo string, opts ...name.Option) (oras.Pusher, error) { + ref, err := FileReference(filename, repo, opts...) if err != nil { - return nil, fmt.Errorf("while parsing reference %s: %w", rawRef, err) + return nil, err } return oras.NewGenericPusher( ref, oras.NewManifestConfig(StaticFileConfigType, nil), - oras.NewLocalFileLayer(path, oras.WithLocalFileLayerMediaType(StaticFileLayerType)), + oras.NewStreamLayer( + stream, + oras.WithLayerMediaType(StaticFileLayerType), + oras.WithLayerAnnotations(map[string]string{ + imagespec.AnnotationTitle: filename, + }), + ), ), nil } diff --git a/pkg/orasrpm/rpm.go b/pkg/orasrpm/rpm.go index fee9923..9807888 100644 --- a/pkg/orasrpm/rpm.go +++ b/pkg/orasrpm/rpm.go @@ -4,6 +4,7 @@ package orasrpm import ( + "bytes" "crypto/md5" //nolint:gosec "errors" "fmt" @@ -17,6 +18,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1" + "go.ciq.dev/beskar/pkg/ioutil" "go.ciq.dev/beskar/pkg/oras" ) @@ -44,10 +46,13 @@ type RPMPuller struct { writer io.Writer } +// Reference returns the reference for the puller. func (rp *RPMPuller) Reference() name.Reference { return rp.ref } +// IndexManifest returns the manifest digest corresponding +// to the current architecture. func (rp *RPMPuller) IndexManifest(index *v1.IndexManifest) *v1.Hash { for _, manifest := range index.Manifests { platform := manifest.Platform @@ -66,10 +71,12 @@ func (rp *RPMPuller) IndexManifest(index *v1.IndexManifest) *v1.Hash { return &index.Manifests[0].Digest } +// RawConfig sets the raw config, ignored for RPM. func (rp *RPMPuller) RawConfig(_ []byte) error { return nil } +// Config validates the config mediatype. func (rp *RPMPuller) Config(config v1.Descriptor) error { if config.MediaType != RPMConfigType { return ErrNoRPMConfig @@ -77,6 +84,7 @@ func (rp *RPMPuller) Config(config v1.Descriptor) error { return nil } +// Layers copy layers to the pull writer. func (rp *RPMPuller) Layers(layers []v1.Layer) error { for _, l := range layers { mt, err := l.MediaType() @@ -101,7 +109,8 @@ func (rp *RPMPuller) Layers(layers []v1.Layer) error { return fmt.Errorf("no RPM layer found for %s", rp.ref.Name()) } -func NewRPMPusher(path, repo string, opts ...name.Option) (oras.Pusher, error) { +// RPMReferenceLayer returns the reference and the layer options for a RPM package. +func RPMReferenceLayer(r io.Reader, repo string, opts ...name.Option) (name.Reference, []oras.LayerOption, error) { if !strings.HasPrefix(repo, "artifacts/") { if !strings.HasPrefix(repo, "yum/") { repo = filepath.Join("artifacts", "yum", repo) @@ -110,15 +119,9 @@ func NewRPMPusher(path, repo string, opts ...name.Option) (oras.Pusher, error) { } } - rpmFile, err := os.Open(path) + pkg, err := rpm.Read(r) if err != nil { - return nil, fmt.Errorf("while opening %s: %w", path, err) - } - defer rpmFile.Close() - - pkg, err := rpm.Read(rpmFile) - if err != nil { - return nil, fmt.Errorf("while reading %s metadata: %w", path, err) + return nil, nil, fmt.Errorf("while reading RPM metadata: %w", err) } arch := pkg.Architecture() @@ -133,40 +136,63 @@ func NewRPMPusher(path, repo string, opts ...name.Option) (oras.Pusher, error) { rawRef := filepath.Join(repo, "packages:"+pkgTag) ref, err := name.ParseReference(rawRef, opts...) if err != nil { - return nil, fmt.Errorf("while parsing reference %s: %w", rawRef, err) + return nil, nil, fmt.Errorf("while parsing reference %s: %w", rawRef, err) + } + + return ref, DefaultRPMLayerOptions(rpmName, arch), nil +} + +// NewRPMPuller returns a pusher instance to push a local RPM package. +func NewRPMPusher(path, repo string, opts ...name.Option) (oras.Pusher, error) { + rpmFile, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("while opening %s: %w", path, err) + } + defer rpmFile.Close() + + ref, rpmLayerOptions, err := RPMReferenceLayer(rpmFile, repo, opts...) + if err != nil { + return nil, err } return oras.NewGenericPusher( ref, oras.NewManifestConfig(RPMConfigType, nil), - NewRPMLayer(path, DefaultRPMLayerOptions(rpmName, arch)...), + oras.NewLocalFileLayer(path, rpmLayerOptions...), ), nil } -func DefaultRPMLayerOptions(rpmName, arch string) []oras.LocalFileLayerOption { - return []oras.LocalFileLayerOption{ - oras.WithLocalFileLayerPlatform( +// NewRPMStreamPusher returns a pusher instance to push a RPM package from a stream. +func NewRPMStreamPusher(stream io.Reader, repo string, opts ...name.Option) (oras.Pusher, error) { + buf := new(bytes.Buffer) + + ref, rpmLayerOptions, err := RPMReferenceLayer(io.TeeReader(stream, buf), repo, opts...) + if err != nil { + return nil, err + } + + return oras.NewGenericPusher( + ref, + oras.NewManifestConfig(RPMConfigType, nil), + oras.NewStreamLayer( + ioutil.MultiReaderCloser(buf, stream), + rpmLayerOptions..., + ), + ), nil +} + +// DefaultRPMLayerOptions returns the default layer options for a RPM package. +func DefaultRPMLayerOptions(rpmName, arch string) []oras.LayerOption { + return []oras.LayerOption{ + oras.WithLayerPlatform( &v1.Platform{ Architecture: arch, OS: "linux", }, ), - oras.WithLocalFileLayerAnnotations(map[string]string{ + oras.WithLayerAnnotations(map[string]string{ imagespec.AnnotationTitle: rpmName, }), - } -} - -// rpmLayer defines an OCI layer descriptor associated to a RPM package. -type rpmLayer struct { - *oras.LocalFileLayer -} - -// NewRPMLayer returns an OCI layer descriptor for the corresponding -// RPM package. -func NewRPMLayer(path string, options ...oras.LocalFileLayerOption) oras.Layer { - options = append(options, oras.WithLocalFileLayerMediaType(RPMPackageLayerType)) - return &rpmLayer{ - LocalFileLayer: oras.NewLocalFileLayer(path, options...), + oras.WithLayerMediaType(RPMPackageLayerType), } } diff --git a/pkg/plugins/yum/api/v1/api.go b/pkg/plugins/yum/api/v1/api.go index b1241fc..e9d9e5f 100644 --- a/pkg/plugins/yum/api/v1/api.go +++ b/pkg/plugins/yum/api/v1/api.go @@ -5,6 +5,7 @@ package apiv1 import ( "context" + "fmt" "regexp" ) @@ -62,6 +63,14 @@ type RepositoryPackage struct { GPGSignature string `json:"gpg_signature"` } +func (pkg RepositoryPackage) RPMName() string { + arch := pkg.Architecture + if pkg.SourceRPM == "" { + arch = "src" + } + return fmt.Sprintf("%s-%s-%s.%s.rpm", pkg.Name, pkg.Version, pkg.Release, arch) +} + // Mirror sync status. type SyncStatus struct { Syncing bool `json:"syncing"` @@ -104,7 +113,7 @@ type YUM interface { //nolint:interfacebloat // Sync YUM repository with an upstream repository. //kun:op GET /repository/sync //kun:success statusCode=200 - SyncRepository(ctx context.Context, repository string) (err error) + SyncRepository(ctx context.Context, repository string, wait bool) (err error) // Get YUM repository sync status. //kun:op GET /repository/sync:status diff --git a/pkg/plugins/yum/api/v1/endpoint.go b/pkg/plugins/yum/api/v1/endpoint.go index 38bba06..d49f21c 100644 --- a/pkg/plugins/yum/api/v1/endpoint.go +++ b/pkg/plugins/yum/api/v1/endpoint.go @@ -391,6 +391,7 @@ func MakeEndpointOfRemoveRepositoryPackageByTag(s YUM) endpoint.Endpoint { type SyncRepositoryRequest struct { Repository string `json:"repository"` + Wait bool `json:"wait"` } // ValidateSyncRepositoryRequest creates a validator for SyncRepositoryRequest. @@ -417,6 +418,7 @@ func MakeEndpointOfSyncRepository(s YUM) endpoint.Endpoint { err := s.SyncRepository( ctx, req.Repository, + req.Wait, ) return &SyncRepositoryResponse{ Err: err, diff --git a/pkg/plugins/yum/api/v1/http_client.go b/pkg/plugins/yum/api/v1/http_client.go index b701899..2e5cb9a 100644 --- a/pkg/plugins/yum/api/v1/http_client.go +++ b/pkg/plugins/yum/api/v1/http_client.go @@ -550,7 +550,7 @@ func (c *HTTPClient) RemoveRepositoryPackageByTag(ctx context.Context, repositor return nil } -func (c *HTTPClient) SyncRepository(ctx context.Context, repository string) (err error) { +func (c *HTTPClient) SyncRepository(ctx context.Context, repository string, wait bool) (err error) { codec := c.codecs.EncodeDecoder("SyncRepository") path := "/repository/sync" @@ -562,8 +562,10 @@ func (c *HTTPClient) SyncRepository(ctx context.Context, repository string) (err reqBody := struct { Repository string `json:"repository"` + Wait bool `json:"wait"` }{ Repository: repository, + Wait: wait, } reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) if err != nil { diff --git a/pkg/plugins/yum/api/v1/oas2.go b/pkg/plugins/yum/api/v1/oas2.go index 28e75c6..1c8402a 100644 --- a/pkg/plugins/yum/api/v1/oas2.go +++ b/pkg/plugins/yum/api/v1/oas2.go @@ -251,6 +251,7 @@ func getDefinitions(schema oas2.Schema) map[string]oas2.Definition { oas2.AddDefinition(defs, "SyncRepositoryRequestBody", reflect.ValueOf(&struct { Repository string `json:"repository"` + Wait bool `json:"wait"` }{})) oas2.AddResponseDefinitions(defs, schema, "SyncRepository", 200, (&SyncRepositoryResponse{}).Body())