From 5ec2bb836e2bffb473a5759dd24c8737ea34bce5 Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Thu, 21 Nov 2024 21:45:26 +0000 Subject: [PATCH] feat: Support repository path prefix for serve bundle (#810) This allows for specifying different repositories for a previously built bundle, very useful when building air-gapped environments to mimic Harbor which requires inserting a prefix for the project name. --- cmd/mindthegap/serve/bundle/bundle.go | 48 ++++++++++++++-- devbox.json | 52 ++++++++++-------- devbox.lock | 18 ++++++ test/e2e/imagebundle/serve_bundle_test.go | 67 +++++++++++++++++++++++ 4 files changed, 156 insertions(+), 29 deletions(-) diff --git a/cmd/mindthegap/serve/bundle/bundle.go b/cmd/mindthegap/serve/bundle/bundle.go index 4d8a3d6a..921f9062 100644 --- a/cmd/mindthegap/serve/bundle/bundle.go +++ b/cmd/mindthegap/serve/bundle/bundle.go @@ -26,11 +26,12 @@ func NewCommand( bundleCmdName string, ) (cmd *cobra.Command, stopCh chan struct{}) { var ( - bundleFiles []string - listenAddress string - listenPort uint16 - tlsCertificate string - tlsKey string + bundleFiles []string + listenAddress string + listenPort uint16 + tlsCertificate string + tlsKey string + repositoriesPrefix string ) stopCh = make(chan struct{}) @@ -71,6 +72,12 @@ func NewCommand( return err } + if repositoriesPrefix != "" { + if err := addRepositoryPrefixToImages(tempDir, repositoriesPrefix); err != nil { + return err + } + } + // Write out the merged image bundle config to the target directory for completeness. if imagesCfg != nil { if err := config.WriteSanitizedImagesConfig(*imagesCfg, filepath.Join(tempDir, "images.yaml")); err != nil { @@ -123,6 +130,37 @@ func NewCommand( Uint16Var(&listenPort, "listen-port", 0, "Port to listen on (0 means use any free port)") cmd.Flags().StringVar(&tlsCertificate, "tls-cert-file", "", "TLS certificate file") cmd.Flags().StringVar(&tlsKey, "tls-private-key-file", "", "TLS private key file") + cmd.Flags().StringVar(&repositoriesPrefix, "repositories-prefix", "", + "Prefix to prepend to all repositories in the bundle when serving") return cmd, stopCh } + +func addRepositoryPrefixToImages(tempDir, newPrefix string) error { + originalDirRepositoriesPrefix := filepath.Join(tempDir, "docker", "registry", "v2", "repositories") + existingRepositories, err := os.ReadDir(originalDirRepositoriesPrefix) + if err != nil { + return fmt.Errorf("failed to read existing repositories: %w", err) + } + + newRepositoriesDir := filepath.Join(originalDirRepositoriesPrefix, newPrefix) + err = os.MkdirAll(newRepositoriesDir, 0o755) + if err != nil { + return fmt.Errorf("failed to create repositories prefix directory: %w", err) + } + + for _, existingRepository := range existingRepositories { + if existingRepository.IsDir() && + filepath.Join(originalDirRepositoriesPrefix, existingRepository.Name()) != newRepositoriesDir { + err = os.Rename( + filepath.Join(originalDirRepositoriesPrefix, existingRepository.Name()), + filepath.Join(newRepositoriesDir, existingRepository.Name()), + ) + if err != nil { + return fmt.Errorf("failed to move existing repository: %w", err) + } + } + } + + return nil +} diff --git a/devbox.json b/devbox.json index 1b77111b..0c07dd7a 100644 --- a/devbox.json +++ b/devbox.json @@ -1,26 +1,30 @@ { - "packages": [ - "actionlint@latest", - "coreutils@latest", - "crane@latest", - "fd@latest", - "ginkgo@latest", - "git@latest", - "gnused@latest", - "gnugrep@latest", - "go@latest", - "go-task@latest", - "gojq@latest", - "golangci-lint@latest", - "golines@latest", - "goreleaser@latest", - "gotestsum@latest", - "ko@latest", - "kubernetes-helm@latest", - "pre-commit@latest", - "shfmt@latest", - "upx@latest", - "path:./hack/flakes#go-mod-upgrade", - "path:./hack/flakes#govulncheck" - ] + "packages": { + "actionlint": "latest", + "coreutils": "latest", + "crane": "latest", + "darwin.Security": { + "version": "latest", + "platforms": ["aarch64-darwin"] + }, + "fd": "latest", + "ginkgo": "latest", + "git": "latest", + "gnused": "latest", + "gnugrep": "latest", + "go": "latest", + "go-task": "latest", + "gojq": "latest", + "golangci-lint": "latest", + "golines": "latest", + "goreleaser": "latest", + "gotestsum": "latest", + "ko": "latest", + "kubernetes-helm": "latest", + "pre-commit": "latest", + "shfmt": "latest", + "upx": "latest", + "path:./hack/flakes#go-mod-upgrade": "", + "path:./hack/flakes#govulncheck": "" + } } diff --git a/devbox.lock b/devbox.lock index e9eb4da1..6ae2d0c4 100644 --- a/devbox.lock +++ b/devbox.lock @@ -205,6 +205,24 @@ } } }, + "darwin.Security@latest": { + "last_modified": "2024-11-16T04:25:12Z", + "resolved": "github:NixOS/nixpkgs/34a626458d686f1b58139620a8b2793e9e123bba#darwin.Security", + "source": "devbox-search", + "version": "11.0", + "systems": { + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/nvzqysba0ga8w7b93y2jsfmw6bicrk35-Security-11.0", + "default": true + } + ], + "store_path": "/nix/store/nvzqysba0ga8w7b93y2jsfmw6bicrk35-Security-11.0" + } + } + }, "fd@latest": { "last_modified": "2024-11-03T14:18:04Z", "resolved": "github:NixOS/nixpkgs/4ae2e647537bcdbb82265469442713d066675275#fd", diff --git a/test/e2e/imagebundle/serve_bundle_test.go b/test/e2e/imagebundle/serve_bundle_test.go index c401fb2c..e5d89a85 100644 --- a/test/e2e/imagebundle/serve_bundle_test.go +++ b/test/e2e/imagebundle/serve_bundle_test.go @@ -156,6 +156,73 @@ var _ = Describe("Serve Image Bundle", func() { Eventually(done).Should(BeClosed()) }) + It("With repositories prefix", func() { + ipAddr := helpers.GetFirstNonLoopbackIP(GinkgoT()) + + tempCertDir := GinkgoT().TempDir() + caCertFile, _, certFile, keyFile := helpers.GenerateCertificateAndKeyWithIPSAN( + GinkgoT(), + tempCertDir, + ipAddr, + ) + + helpers.CreateBundle( + GinkgoT(), + bundleFile, + filepath.Join("testdata", "create-success.yaml"), + "linux/"+runtime.GOARCH, + ) + + port, err := freeport.GetFreePort() + Expect(err).NotTo(HaveOccurred()) + cmd.SetArgs([]string{ + "--image-bundle", bundleFile, + "--listen-address", ipAddr.String(), + "--listen-port", strconv.Itoa(port), + "--tls-cert-file", certFile, + "--tls-private-key-file", keyFile, + "--repositories-prefix", "/some/test/prefix", + }) + + done := make(chan struct{}) + go func() { + defer GinkgoRecover() + + Expect(cmd.Execute()).To(Succeed()) + + close(done) + }() + + helpers.WaitForTCPPort(GinkgoT(), ipAddr.String(), port) + + testRoundTripper, err := httputils.TLSConfiguredRoundTripper( + remote.DefaultTransport, + net.JoinHostPort(ipAddr.String(), strconv.Itoa(port)), + false, + caCertFile, + ) + Expect(err).NotTo(HaveOccurred()) + + helpers.ValidateImageIsAvailable( + GinkgoT(), + ipAddr.String(), + port, + "", + "some/test/prefix/stefanprodan/podinfo", + "6.2.0", + []*v1.Platform{{ + OS: "linux", + Architecture: runtime.GOARCH, + }}, + false, + remote.WithTransport(testRoundTripper), + ) + + close(stopCh) + + Eventually(done).Should(BeClosed()) + }) + It("Bundle does not exist", func() { cmd.SetArgs([]string{ "--image-bundle", bundleFile,