From a977cec50c1ab0f07c16d8a3c14e080f916fb9cf Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 28 Jan 2024 12:08:31 -0500 Subject: [PATCH 1/7] improve cosign setup Signed-off-by: Adam Martin --- .github/workflows/release.yaml | 2 +- .gitignore | 3 +- .goreleaser.yaml | 8 ++ Makefile | 15 ++- cmd/hauler/main.go | 10 ++ pkg/cosign/cosign.go | 220 +++++++-------------------------- 6 files changed, 79 insertions(+), 179 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d1ef4c1..44e94b0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -24,7 +24,7 @@ jobs: with: distribution: goreleaser version: latest - args: release --rm-dist + args: release --rm-dist -p 1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 845a1d8..0e8bb58 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ dist/ tmp/ bin/ /store/ -/registry/ \ No newline at end of file +/registry/ +cmd/hauler/binaries \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9914e77..9f5361e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -3,9 +3,11 @@ before: hooks: - go mod tidy - go mod download + - rm -rf cmd/hauler/binaries env: - vpkg=github.com/rancherfederal/hauler/internal/version + - cosign_version=v2.2.2+carbide.1 builds: - main: cmd/hauler/main.go @@ -18,6 +20,12 @@ builds: - arm64 ldflags: - -s -w -X {{ .Env.vpkg }}.gitVersion={{ .Version }} -X {{ .Env.vpkg }}.gitCommit={{ .ShortCommit }} -X {{ .Env.vpkg }}.gitTreeState={{if .IsGitDirty}}dirty{{else}}clean{{end}} -X {{ .Env.vpkg }}.buildDate={{ .Date }} + hooks: + pre: + - mkdir -p cmd/hauler/binaries + - wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/{{ .Env.cosign_version }}/cosign-{{ .Os }}-{{ .Arch }}{{ if eq .Os "windows" }}.exe{{ end }} + post: + - rm -rf cmd/hauler/binaries env: - CGO_ENABLED=0 diff --git a/Makefile b/Makefile index bc504c5..670c36e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ SHELL:=/bin/bash -GO_BUILD_ENV=GOOS=linux GOARCH=amd64 +BUILD_OS=darwin +BUILD_ARCH=arm64 GO_FILES=$(shell go list ./... | grep -v /vendor/) +COSIGN_VERSION=v2.2.2+carbide.1 + BUILD_VERSION=$(shell cat VERSION) BUILD_TAG=$(BUILD_VERSION) @@ -10,14 +13,20 @@ BUILD_TAG=$(BUILD_VERSION) all: fmt vet install test build: + rm -rf cmd/hauler/binaries;\ + mkdir -p cmd/hauler/binaries;\ + wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(BUILD_OS)-$(BUILD_ARCH);\ mkdir bin;\ - GOENV=GOARCH=$(uname -m) CGO_ENABLED=0 go build -o bin ./cmd/...;\ + GOENV=GOARCH=$(BUILD_ARCH) CGO_ENABLED=0 go build -o bin ./cmd/...;\ build-all: fmt vet goreleaser build --rm-dist --snapshot install: - GOENV=GOARCH=$(uname -m) CGO_ENABLED=0 go install ./cmd/...;\ + rm -rf cmd/hauler/binaries;\ + mkdir -p cmd/hauler/binaries;\ + wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(BUILD_OS)-$(BUILD_ARCH);\ + GOENV=GOARCH=$(BUILD_ARCH) CGO_ENABLED=0 go install ./cmd/...;\ vet: go vet $(GO_FILES) diff --git a/cmd/hauler/main.go b/cmd/hauler/main.go index b9d9b20..4e29cf6 100644 --- a/cmd/hauler/main.go +++ b/cmd/hauler/main.go @@ -3,11 +3,16 @@ package main import ( "context" "os" + "embed" "github.com/rancherfederal/hauler/cmd/hauler/cli" + "github.com/rancherfederal/hauler/pkg/cosign" "github.com/rancherfederal/hauler/pkg/log" ) +//go:embed binaries/* +var binaries embed.FS + func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -15,6 +20,11 @@ func main() { logger := log.NewLogger(os.Stdout) ctx = logger.WithContext(ctx) + // ensure cosign binary is available + if err := cosign.EnsureBinaryExists(ctx, binaries); err != nil { + logger.Errorf("%v", err) + } + if err := cli.New().ExecuteContext(ctx); err != nil { logger.Errorf("%v", err) } diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index fb39d32..7e96d96 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -2,28 +2,19 @@ package cosign import ( "fmt" - "io" - "net/http" "os" "os/exec" "os/user" "path/filepath" "runtime" "context" - "strings" - "encoding/json" "time" "bufio" + "embed" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/pkg/content" "github.com/rancherfederal/hauler/pkg/store" "github.com/rancherfederal/hauler/pkg/log" - "github.com/rancherfederal/hauler/internal/mapper" - "github.com/rancherfederal/hauler/pkg/reference" - "github.com/rancherfederal/hauler/pkg/artifacts/file" - "github.com/rancherfederal/hauler/pkg/artifacts/file/getter" - "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" ) const maxRetries = 3 @@ -32,7 +23,7 @@ const retryDelay = time.Second * 5 // VerifyFileSignature verifies the digital signature of a file using Sigstore/Cosign. func VerifySignature(ctx context.Context, s *store.Layout, keyPath string, ref string) error { operation := func() error { - cosignBinaryPath, err := ensureCosignBinary(ctx, s) + cosignBinaryPath, err := getCosignPath(ctx) if err != nil { return err } @@ -52,7 +43,7 @@ func VerifySignature(ctx context.Context, s *store.Layout, keyPath string, ref s // SaveImage saves image and any signatures/attestations to the store. func SaveImage(ctx context.Context, s *store.Layout, ref string) error { operation := func() error { - cosignBinaryPath, err := ensureCosignBinary(ctx, s) + cosignBinaryPath, err := getCosignPath(ctx) if err != nil { return err } @@ -73,7 +64,7 @@ func SaveImage(ctx context.Context, s *store.Layout, ref string) error { func LoadImages(ctx context.Context, s *store.Layout, registry string, ropts content.RegistryOptions) error { l := log.FromContext(ctx) - cosignBinaryPath, err := ensureCosignBinary(ctx, s) + cosignBinaryPath, err := getCosignPath(ctx) if err != nil { return err } @@ -132,7 +123,7 @@ func LoadImages(ctx context.Context, s *store.Layout, registry string, ropts con // RegistryLogin - performs cosign login func RegistryLogin(ctx context.Context, s *store.Layout, registry string, ropts content.RegistryOptions) error { - cosignBinaryPath, err := ensureCosignBinary(ctx, s) + cosignBinaryPath, err := getCosignPath(ctx) if err != nil { return err } @@ -170,8 +161,41 @@ func RetryOperation(ctx context.Context, operation func() error) error { } -// ensureCosignBinary checks if the cosign binary exists in the specified directory and installs it if not. -func ensureCosignBinary(ctx context.Context, s *store.Layout) (string, error) { +func EnsureBinaryExists(ctx context.Context, bin embed.FS) (error) { + // Set up a path for the binary to be copied. + binaryPath, err := getCosignPath(ctx) + if err != nil { + return fmt.Errorf("Error: %v\n", err) + } + + // Determine the architecture so that we pull the correct embedded binary. + arch := runtime.GOARCH + rOS := runtime.GOOS + binaryName := "cosign" + if rOS == "windows" { + binaryName = fmt.Sprintf("cosign-%s-%s.exe", rOS, arch) + } else { + binaryName = fmt.Sprintf("cosign-%s-%s", rOS, arch) + } + + // retrieve the embedded binary + f, err := bin.ReadFile(fmt.Sprintf("binaries/%s", binaryName)) + if err != nil { + return fmt.Errorf("Error: %v\n", err) + } + + // write the binary to the filesystem + err = os.WriteFile(binaryPath, f, 0755) + if err != nil { + return fmt.Errorf("Error: %v\n", err) + } + + return nil +} + + +// getCosignPath returns the binary path +func getCosignPath(ctx context.Context) (string, error) { l := log.FromContext(ctx) // Get the current user's information @@ -192,170 +216,18 @@ func ensureCosignBinary(ctx context.Context, s *store.Layout) (string, error) { if err := os.MkdirAll(haulerDir, 0755); err != nil { return "", fmt.Errorf("Error creating .hauler directory: %v\n", err) } - l.Infof("Created .hauler directory at: %s", haulerDir) + l.Debugf("Created .hauler directory at: %s", haulerDir) } - // Check if the cosign binary exists in the specified directory. - binaryPath := filepath.Join(haulerDir, "cosign") - _, err = os.Stat(binaryPath) - if err == nil { - // Cosign binary is already installed in the specified directory. - return binaryPath, nil - } - - // Cosign binary is not found. - l.Infof("Cosign binary not found. Checking to see if it exists in the store...") - - // grab binary from store if it exists, otherwise try to download it from GitHub. - // if the binary has to be downloaded, then automatically add it to the store afterwards. - err = copyCosignFromStore(ctx, s, haulerDir) - if err != nil { - l.Warnf("%s", err) - err = downloadCosign(ctx, haulerDir) - if err != nil { - return "", err - } - err = addCosignToStore(ctx, s, binaryPath) - if err != nil { - return "", err - } - } - - // Make the binary executable. - if err := os.Chmod(filepath.Join(haulerDir, "cosign"), 0755); err != nil { - return "", fmt.Errorf("error setting executable permission: %v", err) - } - - return binaryPath, nil -} - -// used to check if the cosign binary is in the store and if so copy it to the .hauler directory -func copyCosignFromStore(ctx context.Context, s *store.Layout, destDir string) error { - l := log.FromContext(ctx) - - ref := "hauler/cosign:latest" - r, err := reference.Parse(ref) - if err != nil { - return err - } - - found := false - if err := s.Walk(func(reference string, desc ocispec.Descriptor) error { - - if !strings.Contains(reference, r.Name()) { - return nil - } - found = true - - rc, err := s.Fetch(ctx, desc) - if err != nil { - return err - } - defer rc.Close() - - var m ocispec.Manifest - if err := json.NewDecoder(rc).Decode(&m); err != nil { - return err - } - - mapperStore, err := mapper.FromManifest(m, destDir) - if err != nil { - return err - } - - pushedDesc, err := s.Copy(ctx, reference, mapperStore, "") - if err != nil { - return err - } - - l.Infof("extracted [%s] from store with digest [%s]", ref, pushedDesc.Digest.String()) - - return nil - }); err != nil { - return err - } - - if !found { - return fmt.Errorf("Reference [%s] not found in store. Hauler will attempt to download it from Github.", ref) - } - - return nil -} - -// adds the cosign binary to the store. -// this is to help with airgapped situations where you cannot access the internet. -func addCosignToStore(ctx context.Context, s *store.Layout, binaryPath string) error { - l := log.FromContext(ctx) - - fi := v1alpha1.File{ - Path: binaryPath, - } - - copts := getter.ClientOptions{ - NameOverride: fi.Name, - } - - f := file.NewFile(fi.Path, file.WithClient(getter.NewClient(copts))) - ref, err := reference.NewTagged(f.Name(fi.Path), reference.DefaultTag) - if err != nil { - return err - } - - desc, err := s.AddOCI(ctx, f, ref.Name()) - if err != nil { - return err - } - - l.Infof("added 'file' to store at [%s], with digest [%s]", ref.Name(), desc.Digest.String()) - return nil -} - - -// used to check if the cosign binary is in the store and if so copy it to the .hauler directory -func downloadCosign(ctx context.Context, haulerDir string) error { - l := log.FromContext(ctx) - - // Define the GitHub release URL and architecture-specific binary name. - releaseURL := "https://github.com/rancher-government-carbide/cosign/releases/latest/download" - - // Determine the architecture and add it to the binary name. - arch := runtime.GOARCH + // Determine the binary name. rOS := runtime.GOOS binaryName := "cosign" if rOS == "windows" { - binaryName = fmt.Sprintf("cosign-%s-%s.exe", rOS, arch) - } else { - binaryName = fmt.Sprintf("cosign-%s-%s", rOS, arch) + binaryName = "cosign.exe" } - - // Download the binary. - downloadURL := fmt.Sprintf("%s/%s", releaseURL, binaryName) - resp, err := http.Get(downloadURL) - if err != nil { - return fmt.Errorf("error downloading cosign binary: %v", err) - } - defer resp.Body.Close() - // Create the cosign binary file in the specified directory. - binaryFile, err := os.Create(filepath.Join(haulerDir, binaryName)) - if err != nil { - return fmt.Errorf("error creating cosign binary: %v", err) - } - defer binaryFile.Close() + // construct path to binary + binaryPath := filepath.Join(haulerDir, binaryName) - // Copy the downloaded binary to the file. - _, err = io.Copy(binaryFile, resp.Body) - if err != nil { - return fmt.Errorf("error saving cosign binary: %v", err) - } - - // Rename the binary to "cosign" - oldBinaryPath := filepath.Join(haulerDir, binaryName) - newBinaryPath := filepath.Join(haulerDir, "cosign") - if err := os.Rename(oldBinaryPath, newBinaryPath); err != nil { - return fmt.Errorf("error renaming cosign binary: %v", err) - } - - l.Infof("Cosign binary downloaded and installed to %s", haulerDir) - return nil + return binaryPath, nil } \ No newline at end of file From 0e6c3690b1ff19485939a13626d34d555c508647 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 28 Jan 2024 19:45:05 -0500 Subject: [PATCH 2/7] bump cosign version to v2.2.2+carbide.2 --- .goreleaser.yaml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9f5361e..bcd080e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -7,7 +7,7 @@ before: env: - vpkg=github.com/rancherfederal/hauler/internal/version - - cosign_version=v2.2.2+carbide.1 + - cosign_version=v2.2.2+carbide.2 builds: - main: cmd/hauler/main.go diff --git a/Makefile b/Makefile index 670c36e..53cdc0a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ BUILD_OS=darwin BUILD_ARCH=arm64 GO_FILES=$(shell go list ./... | grep -v /vendor/) -COSIGN_VERSION=v2.2.2+carbide.1 +COSIGN_VERSION=v2.2.2+carbide.2 BUILD_VERSION=$(shell cat VERSION) BUILD_TAG=$(BUILD_VERSION) From 8256aa55ceadf46813a6c40d87e430f9f03e9326 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 28 Jan 2024 19:46:55 -0500 Subject: [PATCH 3/7] adjust unit test gh action for latest updates --- .github/workflows/unittest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml index 7a12a8f..f4caeee 100644 --- a/.github/workflows/unittest.yaml +++ b/.github/workflows/unittest.yaml @@ -27,6 +27,7 @@ jobs: go-version: 1.21.x - name: Run Unit Tests run: | + touch cmd/hauler/binaries/dummy.txt go test -race -covermode=atomic -coverprofile=coverage.out ./pkg/... ./internal/... ./cmd/... - name: On Failure, Launch Debug Session if: ${{ failure() }} From a05d21c05212f9f24fca7e2f95e3731ebb56e3b7 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 28 Jan 2024 19:48:16 -0500 Subject: [PATCH 4/7] add platform flag for image add and sync Signed-off-by: Adam Martin --- cmd/hauler/cli/store/add.go | 12 ++++--- cmd/hauler/cli/store/info.go | 36 +++++++++++++-------- cmd/hauler/cli/store/sync.go | 14 ++++++-- pkg/apis/hauler.cattle.io/v1alpha1/image.go | 4 +++ pkg/cosign/cosign.go | 19 +++++++++-- 5 files changed, 61 insertions(+), 24 deletions(-) diff --git a/cmd/hauler/cli/store/add.go b/cmd/hauler/cli/store/add.go index 8a5d129..aa7983a 100644 --- a/cmd/hauler/cli/store/add.go +++ b/cmd/hauler/cli/store/add.go @@ -61,13 +61,15 @@ func storeFile(ctx context.Context, s *store.Layout, fi v1alpha1.File) error { type AddImageOpts struct { *RootOpts - Name string - Key string + Name string + Key string + Platform string } func (o *AddImageOpts) AddFlags(cmd *cobra.Command) { f := cmd.Flags() f.StringVarP(&o.Key, "key", "k", "", "(Optional) Path to the key for digital signature verification") + f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specific platform to save. i.e. linux/amd64. Defaults to all if flag is omitted.") } func AddImageCmd(ctx context.Context, o *AddImageOpts, s *store.Layout, reference string) error { @@ -86,10 +88,10 @@ func AddImageCmd(ctx context.Context, o *AddImageOpts, s *store.Layout, referenc l.Infof("signature verified for image [%s]", cfg.Name) } - return storeImage(ctx, s, cfg) + return storeImage(ctx, s, cfg, o.Platform) } -func storeImage(ctx context.Context, s *store.Layout, i v1alpha1.Image) error { +func storeImage(ctx context.Context, s *store.Layout, i v1alpha1.Image, platform string) error { l := log.FromContext(ctx) r, err := name.ParseReference(i.Name) @@ -97,7 +99,7 @@ func storeImage(ctx context.Context, s *store.Layout, i v1alpha1.Image) error { return err } - err = cosign.SaveImage(ctx, s, r.Name()) + err = cosign.SaveImage(ctx, s, r.Name(), platform) if err != nil { return err } diff --git a/cmd/hauler/cli/store/info.go b/cmd/hauler/cli/store/info.go index 8bebe53..b7ab91b 100644 --- a/cmd/hauler/cli/store/info.go +++ b/cmd/hauler/cli/store/info.go @@ -66,14 +66,14 @@ func InfoCmd(ctx context.Context, o *InfoOpts, s *store.Layout) error { return err } - i := newItem(s, desc, internalManifest, internalDesc.Platform.Architecture, o) + i := newItem(s, desc, internalManifest, fmt.Sprintf("%s/%s", internalDesc.Platform.OS, internalDesc.Platform.Architecture), o) var emptyItem item if i != emptyItem { items = append(items, i) } } - // handle single arch docker images - } else if desc.MediaType == consts.DockerManifestSchema2 { + // handle "non" multi-arch images + } else if desc.MediaType == consts.DockerManifestSchema2 || desc.MediaType == consts.OCIManifestSchema1 { var m ocispec.Manifest if err := json.NewDecoder(rc).Decode(&m); err != nil { return err @@ -90,11 +90,19 @@ func InfoCmd(ctx context.Context, o *InfoOpts, s *store.Layout) error { if err := json.NewDecoder(rc).Decode(&internalManifest); err != nil { return err } - - i := newItem(s, desc, m, internalManifest.Architecture, o) - var emptyItem item - if i != emptyItem { - items = append(items, i) + + if internalManifest.Architecture != "" { + i := newItem(s, desc, m, fmt.Sprintf("%s/%s", internalManifest.OS, internalManifest.Architecture), o) + var emptyItem item + if i != emptyItem { + items = append(items, i) + } + } else { + i := newItem(s, desc, m, "-", o) + var emptyItem item + if i != emptyItem { + items = append(items, i) + } } // handle the rest } else { @@ -132,7 +140,7 @@ func InfoCmd(ctx context.Context, o *InfoOpts, s *store.Layout) error { func buildTable(items ...item) { // Create a table for the results table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Reference", "Type", "Arch", "# Layers", "Size"}) + table.SetHeader([]string{"Reference", "Type", "Platform", "# Layers", "Size"}) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetRowLine(false) table.SetAutoMergeCellsByColumnIndex([]int{0}) @@ -142,7 +150,7 @@ func buildTable(items ...item) { row := []string{ i.Reference, i.Type, - i.Architecture, + i.Platform, fmt.Sprintf("%d", i.Layers), i.Size, } @@ -163,7 +171,7 @@ func buildJson(item ...item) string { type item struct { Reference string Type string - Architecture string + Platform string Layers int Size string } @@ -174,12 +182,12 @@ func (a byReferenceAndArch) Len() int { return len(a) } func (a byReferenceAndArch) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byReferenceAndArch) Less(i, j int) bool { if a[i].Reference == a[j].Reference { - return a[i].Architecture < a[j].Architecture + return a[i].Platform < a[j].Platform } return a[i].Reference < a[j].Reference } -func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, arch string, o *InfoOpts) item { +func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, plat string, o *InfoOpts) item { // skip listing cosign items if desc.Annotations["kind"] == "dev.cosignproject.cosign/atts" || desc.Annotations["kind"] == "dev.cosignproject.cosign/sigs" || @@ -217,7 +225,7 @@ func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, arch return item{ Reference: ref.Name(), Type: ctype, - Architecture: arch, + Platform: plat, Layers: len(m.Layers), Size: byteCountSI(size), } diff --git a/cmd/hauler/cli/store/sync.go b/cmd/hauler/cli/store/sync.go index 292c480..44877d4 100644 --- a/cmd/hauler/cli/store/sync.go +++ b/cmd/hauler/cli/store/sync.go @@ -29,6 +29,7 @@ type SyncOpts struct { ContentFiles []string Key string Products []string + Platform string } func (o *SyncOpts) AddFlags(cmd *cobra.Command) { @@ -37,6 +38,7 @@ func (o *SyncOpts) AddFlags(cmd *cobra.Command) { f.StringSliceVarP(&o.ContentFiles, "files", "f", []string{}, "Path to content files") f.StringVarP(&o.Key, "key", "k", "", "(Optional) Path to the key for signature verification") f.StringSliceVar(&o.Products, "products", []string{}, "Used for RGS Carbide customers to supply a product and version and Hauler will retrieve the images. i.e. '--product rancher=v2.7.6'") + f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specific platform to save. i.e. linux/amd64. Defaults to all if flag is omitted.") } func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error { @@ -52,7 +54,7 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error { img := v1alpha1.Image{ Name: manifestLoc, } - err := storeImage(ctx, s, img) + err := storeImage(ctx, s, img, o.Platform) if err != nil { return err } @@ -154,8 +156,14 @@ func processContent(ctx context.Context, fi *os.File, o *SyncOpts, s *store.Layo } l.Infof("signature verified for image [%s]", i.Name) } - - err = storeImage(ctx, s, i) + + // Check if the user provided a platform. + platform := o.Platform + if i.Platform != "" { + platform = i.Platform + } + + err = storeImage(ctx, s, i, platform) if err != nil { return err } diff --git a/pkg/apis/hauler.cattle.io/v1alpha1/image.go b/pkg/apis/hauler.cattle.io/v1alpha1/image.go index b7a2360..0b894e2 100644 --- a/pkg/apis/hauler.cattle.io/v1alpha1/image.go +++ b/pkg/apis/hauler.cattle.io/v1alpha1/image.go @@ -24,4 +24,8 @@ type Image struct { // Path is the path to the cosign public key used for verifying image signatures //Key string `json:"key,omitempty"` Key string `json:"key"` + + // Platform of the image to be pulled. If not specified, all platforms will be pulled. + //Platform string `json:"key,omitempty"` + Platform string `json:"platform"` } diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 7e96d96..2ceedde 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -11,6 +11,7 @@ import ( "time" "bufio" "embed" + "strings" "oras.land/oras-go/pkg/content" "github.com/rancherfederal/hauler/pkg/store" @@ -41,7 +42,7 @@ func VerifySignature(ctx context.Context, s *store.Layout, keyPath string, ref s } // SaveImage saves image and any signatures/attestations to the store. -func SaveImage(ctx context.Context, s *store.Layout, ref string) error { +func SaveImage(ctx context.Context, s *store.Layout, ref string, platform string) error { operation := func() error { cosignBinaryPath, err := getCosignPath(ctx) if err != nil { @@ -49,9 +50,23 @@ func SaveImage(ctx context.Context, s *store.Layout, ref string) error { } cmd := exec.Command(cosignBinaryPath, "save", ref, "--dir", s.Root) + // Conditionally add platform. + if platform != "" { + cmd.Args = append(cmd.Args, "--platform", platform) + } + output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("error adding image to store: %v, output: %s", err, output) + if strings.Contains(string(output), "specified reference is not a multiarch image") { + // Rerun the command without the platform flag + cmd = exec.Command(cosignBinaryPath, "save", ref, "--dir", s.Root) + output, err = cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error adding image to store: %v, output: %s", err, output) + } + } else { + return fmt.Errorf("error adding image to store: %v, output: %s", err, output) + } } return nil From e70379870f9df4037ecb690ceb02d39a897b888f Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 28 Jan 2024 19:51:22 -0500 Subject: [PATCH 5/7] another fix for the unit test gh action Signed-off-by: Adam Martin --- .github/workflows/unittest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml index f4caeee..c671380 100644 --- a/.github/workflows/unittest.yaml +++ b/.github/workflows/unittest.yaml @@ -27,6 +27,7 @@ jobs: go-version: 1.21.x - name: Run Unit Tests run: | + mkdir -p cmd/hauler/binaries touch cmd/hauler/binaries/dummy.txt go test -race -covermode=atomic -coverprofile=coverage.out ./pkg/... ./internal/... ./cmd/... - name: On Failure, Launch Debug Session From e8d084847d15cbf159c97c5fa4d382e080cf0f65 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Sun, 28 Jan 2024 21:15:27 -0500 Subject: [PATCH 6/7] remove extra debug statement Signed-off-by: Adam Martin --- pkg/cosign/cosign.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/cosign/cosign.go b/pkg/cosign/cosign.go index 2ceedde..20dfbab 100644 --- a/pkg/cosign/cosign.go +++ b/pkg/cosign/cosign.go @@ -211,8 +211,6 @@ func EnsureBinaryExists(ctx context.Context, bin embed.FS) (error) { // getCosignPath returns the binary path func getCosignPath(ctx context.Context) (string, error) { - l := log.FromContext(ctx) - // Get the current user's information currentUser, err := user.Current() if err != nil { @@ -231,7 +229,6 @@ func getCosignPath(ctx context.Context) (string, error) { if err := os.MkdirAll(haulerDir, 0755); err != nil { return "", fmt.Errorf("Error creating .hauler directory: %v\n", err) } - l.Debugf("Created .hauler directory at: %s", haulerDir) } // Determine the binary name. From 27acb239e4fe8a4ffcc42c70794473ce23c87f79 Mon Sep 17 00:00:00 2001 From: Adam Martin Date: Mon, 29 Jan 2024 13:41:53 -0500 Subject: [PATCH 7/7] clean up makefile --- Makefile | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 53cdc0a..5eb808c 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,8 @@ SHELL:=/bin/bash -BUILD_OS=darwin -BUILD_ARCH=arm64 GO_FILES=$(shell go list ./... | grep -v /vendor/) COSIGN_VERSION=v2.2.2+carbide.2 -BUILD_VERSION=$(shell cat VERSION) -BUILD_TAG=$(BUILD_VERSION) - .SILENT: all: fmt vet install test @@ -15,9 +10,9 @@ all: fmt vet install test build: rm -rf cmd/hauler/binaries;\ mkdir -p cmd/hauler/binaries;\ - wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(BUILD_OS)-$(BUILD_ARCH);\ + wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(shell go env GOOS)-$(shell go env GOARCH);\ mkdir bin;\ - GOENV=GOARCH=$(BUILD_ARCH) CGO_ENABLED=0 go build -o bin ./cmd/...;\ + CGO_ENABLED=0 go build -o bin ./cmd/...;\ build-all: fmt vet goreleaser build --rm-dist --snapshot @@ -25,8 +20,8 @@ build-all: fmt vet install: rm -rf cmd/hauler/binaries;\ mkdir -p cmd/hauler/binaries;\ - wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(BUILD_OS)-$(BUILD_ARCH);\ - GOENV=GOARCH=$(BUILD_ARCH) CGO_ENABLED=0 go install ./cmd/...;\ + wget -P cmd/hauler/binaries/ https://github.com/rancher-government-carbide/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(shell go env GOOS)-$(shell go env GOARCH);\ + CGO_ENABLED=0 go install ./cmd/...;\ vet: go vet $(GO_FILES)