Skip to content

Commit

Permalink
fix: pathing issues loading images with Zarf on Windows (#2106)
Browse files Browse the repository at this point in the history
## Description

This fixes path issues when Zarf tries to load images on Windows.

## Related Issue

Fixes #2102 

## Type of change

- [X] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Other (security config, docs update, etc)

## Checklist before merging

- [ ] Test, docs, adr added or updated as needed
- [X] [Contributor Guide
Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow)
followed
  • Loading branch information
Racer159 authored Nov 8, 2023
1 parent a1e82ba commit c42d7b5
Show file tree
Hide file tree
Showing 22 changed files with 274 additions and 145 deletions.
16 changes: 12 additions & 4 deletions .github/actions/packages/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,26 @@ inputs:
description: 'Build the example packages'
required: false
default: 'true'
os:
description: 'Which OS to build for'
required: false
default: 'linux'
shell:
description: 'Which shell to build in'
required: false
default: 'bash'

runs:
using: composite
steps:
- run: |
make build-cli-linux-amd ARCH=amd64
shell: bash
make build-cli-${{ inputs.os }}-amd ARCH=amd64
shell: ${{ inputs.shell }}
- run: |
make init-package ARCH=amd64
shell: bash
shell: ${{ inputs.shell }}
if: ${{ inputs.init-package == 'true' }}
- run: |
make build-examples ARCH=amd64
shell: bash
shell: ${{ inputs.shell }}
if: ${{ inputs.build-examples == 'true' }}
25 changes: 14 additions & 11 deletions .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,24 @@ jobs:
- name: Setup golang
uses: ./.github/actions/golang

- name: Build windows binary
run: make build-cli-windows-amd
- name: Run Windows unit tests
run: make test-unit
shell: pwsh

# Builds an init package manually off of the v0.23.6 release since
# Windows in GitHub cannot natively build linux containers and
# the tests this workflow runs do not use the agent at all!
- name: Build Windows binary and zarf packages
uses: ./.github/actions/packages
with:
init-package: "false"
os: windows
shell: pwsh

# TODO: (@WSTARR) Builds an init package manually off of the v0.30.1
# release since Windows in GitHub cannot natively build linux containers
# and the tests this workflow run do not use the agent at all!
- name: Build init-package
run: |
make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.25.2
- name: Build zarf packages
run: make build-examples ARCH=amd64
shell: pwsh
make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.30.1
- name: Run windows tests
- name: Run windows E2E tests
run: make test-e2e ARCH=amd64 -e SKIP_K8S=true
shell: pwsh
6 changes: 3 additions & 3 deletions examples/component-actions/zarf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ components:
- name: SNAKE_SOUND
# marks this variable as sensitive to prevent it from being output in the Zarf log
sensitive: true
# autoIndent tells Zarf to maintain spacing for any newlines when templating into a yaml file
# autoIndent tells Zarf to maintain spacing for any newlines when templating into a yaml file
autoIndent: true
# onSuccess will only run if steps in this component are successful
onSuccess:
Expand Down Expand Up @@ -185,13 +185,13 @@ components:
actions:
onCreate:
after:
- description: Cloudflare 1.1.1.1 site to be available
- description: Github.com to be available
maxTotalSeconds: 15
wait:
# wait for a network address to return a 200 OK response
network:
protocol: https
address: 1.1.1.1
address: github.com
code: 200

- name: on-deploy-with-wait-action
Expand Down
9 changes: 7 additions & 2 deletions src/pkg/layout/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ func (pp *PackagePaths) SetFromLayers(layers []ocispec.Descriptor) {
// SetFromPaths maps paths to package paths.
func (pp *PackagePaths) SetFromPaths(paths []string) {
for _, rel := range paths {
switch path := rel; {
// Convert from the standard '/' to the OS path separator for Windows support
switch path := filepath.FromSlash(rel); {
case path == ZarfYAML:
pp.ZarfYAML = filepath.Join(pp.Base, path)
case path == Signature:
Expand Down Expand Up @@ -213,16 +214,20 @@ func (pp *PackagePaths) SetFromPaths(paths []string) {
// Files returns a map of all the files in the package.
func (pp *PackagePaths) Files() map[string]string {
pathMap := make(map[string]string)

stripBase := func(path string) string {
rel, _ := filepath.Rel(pp.Base, path)
return rel
// Convert from the OS path separator to the standard '/' for Windows support
return filepath.ToSlash(rel)
}

add := func(path string) {
if path == "" {
return
}
pathMap[stripBase(path)] = path
}

add(pp.ZarfYAML)
add(pp.Signature)
add(pp.Checksums)
Expand Down
78 changes: 44 additions & 34 deletions src/pkg/layout/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package layout

import (
"runtime"
"strings"
"testing"

Expand All @@ -17,10 +18,10 @@ func TestPackageFiles(t *testing.T) {

raw := &PackagePaths{
Base: "test",
ZarfYAML: "test/zarf.yaml",
Checksums: "test/checksums.txt",
ZarfYAML: normalizePath("test/zarf.yaml"),
Checksums: normalizePath("test/checksums.txt"),
Components: Components{
Base: "test/components",
Base: normalizePath("test/components"),
},
}

Expand All @@ -29,8 +30,8 @@ func TestPackageFiles(t *testing.T) {
files := pp.Files()

expected := map[string]string{
"zarf.yaml": "test/zarf.yaml",
"checksums.txt": "test/checksums.txt",
"zarf.yaml": normalizePath("test/zarf.yaml"),
"checksums.txt": normalizePath("test/checksums.txt"),
}

require.Equal(t, expected, files)
Expand All @@ -47,23 +48,23 @@ func TestPackageFiles(t *testing.T) {
files = pp.Files()

expected = map[string]string{
"zarf.yaml": "test/zarf.yaml",
"checksums.txt": "test/checksums.txt",
"zarf.yaml.sig": "test/zarf.yaml.sig",
"zarf.yaml": normalizePath("test/zarf.yaml"),
"checksums.txt": normalizePath("test/checksums.txt"),
"zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
}

require.Equal(t, expected, files)

pp = pp.AddImages()

files = pp.Files()

// Note that the map key will always be the forward "Slash" (/) version of the file path (never \)
expected = map[string]string{
"zarf.yaml": "test/zarf.yaml",
"checksums.txt": "test/checksums.txt",
"zarf.yaml.sig": "test/zarf.yaml.sig",
"images/index.json": "test/images/index.json",
"images/oci-layout": "test/images/oci-layout",
"zarf.yaml": normalizePath("test/zarf.yaml"),
"checksums.txt": normalizePath("test/checksums.txt"),
"zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
"images/index.json": normalizePath("test/images/index.json"),
"images/oci-layout": normalizePath("test/images/oci-layout"),
}

require.Equal(t, expected, files)
Expand All @@ -79,10 +80,10 @@ func TestPackageFiles(t *testing.T) {
"zarf.yaml",
"checksums.txt",
"sboms.tar",
"components/c1.tar",
"images/index.json",
"images/oci-layout",
"images/blobs/sha256/" + strings.Repeat("1", 64),
normalizePath("components/c1.tar"),
normalizePath("images/index.json"),
normalizePath("images/oci-layout"),
normalizePath("images/blobs/sha256/" + strings.Repeat("1", 64)),
}

pp = New("test")
Expand All @@ -92,13 +93,13 @@ func TestPackageFiles(t *testing.T) {
files = pp.Files()

expected = map[string]string{
"zarf.yaml": "test/zarf.yaml",
"checksums.txt": "test/checksums.txt",
"sboms.tar": "test/sboms.tar",
"components/c1.tar": "test/components/c1.tar",
"images/index.json": "test/images/index.json",
"images/oci-layout": "test/images/oci-layout",
"images/blobs/sha256/" + strings.Repeat("1", 64): "test/images/blobs/sha256/" + strings.Repeat("1", 64),
"zarf.yaml": normalizePath("test/zarf.yaml"),
"checksums.txt": normalizePath("test/checksums.txt"),
"sboms.tar": normalizePath("test/sboms.tar"),
"components/c1.tar": normalizePath("test/components/c1.tar"),
"images/index.json": normalizePath("test/images/index.json"),
"images/oci-layout": normalizePath("test/images/oci-layout"),
"images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
}

require.Len(t, pp.Images.Blobs, 1)
Expand All @@ -123,16 +124,25 @@ func TestPackageFiles(t *testing.T) {
files = pp.Files()

expected = map[string]string{
"zarf.yaml": "test/zarf.yaml",
"checksums.txt": "test/checksums.txt",
"sboms.tar": "test/sboms.tar",
"components/c1.tar": "test/components/c1.tar",
"components/c2.tar": "test/components/c2.tar",
"images/index.json": "test/images/index.json",
"images/oci-layout": "test/images/oci-layout",
"images/blobs/sha256/" + strings.Repeat("1", 64): "test/images/blobs/sha256/" + strings.Repeat("1", 64),
"images/blobs/sha256/" + strings.Repeat("2", 64): "test/images/blobs/sha256/" + strings.Repeat("2", 64),
"zarf.yaml": normalizePath("test/zarf.yaml"),
"checksums.txt": normalizePath("test/checksums.txt"),
"sboms.tar": normalizePath("test/sboms.tar"),
"components/c1.tar": normalizePath("test/components/c1.tar"),
"components/c2.tar": normalizePath("test/components/c2.tar"),
"images/index.json": normalizePath("test/images/index.json"),
"images/oci-layout": normalizePath("test/images/oci-layout"),
"images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
"images/blobs/sha256/" + strings.Repeat("2", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("2", 64)),
}

require.Equal(t, expected, files)
}

// normalizePath ensures that the filepaths being generated are normalized to the host OS.
func normalizePath(path string) string {
if runtime.GOOS != "windows" {
return path
}

return strings.ReplaceAll(path, "/", "\\")
}
5 changes: 3 additions & 2 deletions src/pkg/oci/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ func NewZarfOCIManifest(manifest *ocispec.Manifest) *ZarfOCIManifest {
}

// Locate returns the descriptor for the first layer with the given path or digest.
func (m *ZarfOCIManifest) Locate(uri string) ocispec.Descriptor {
func (m *ZarfOCIManifest) Locate(pathOrDigest string) ocispec.Descriptor {
return helpers.Find(m.Layers, func(layer ocispec.Descriptor) bool {
return layer.Annotations[ocispec.AnnotationTitle] == uri || layer.Digest.Encoded() == uri
// Convert from the OS path separator to the standard '/' for Windows support
return layer.Annotations[ocispec.AnnotationTitle] == filepath.ToSlash(pathOrDigest) || layer.Digest.Encoded() == pathOrDigest
})
}

Expand Down
9 changes: 7 additions & 2 deletions src/pkg/oci/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ var (

// FileDescriptorExists returns true if the given file exists in the given directory with the expected SHA.
func (o *OrasRemote) FileDescriptorExists(desc ocispec.Descriptor, destinationDir string) bool {
destinationPath := filepath.Join(destinationDir, desc.Annotations[ocispec.AnnotationTitle])
rel := desc.Annotations[ocispec.AnnotationTitle]
destinationPath := filepath.Join(destinationDir, rel)

info, err := os.Stat(destinationPath)
if err != nil {
return false
Expand Down Expand Up @@ -252,7 +254,10 @@ func (o *OrasRemote) PullLayer(desc ocispec.Descriptor, destinationDir string) e
if err != nil {
return err
}
return utils.WriteFile(filepath.Join(destinationDir, desc.Annotations[ocispec.AnnotationTitle]), b)

rel := desc.Annotations[ocispec.AnnotationTitle]

return utils.WriteFile(filepath.Join(destinationDir, rel), b)
}

// PullPackagePaths pulls multiple files from the remote repository and saves them to `destinationDir`.
Expand Down
2 changes: 0 additions & 2 deletions src/pkg/packager/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,6 @@ func (p *Packager) attemptClusterChecks() (err error) {
if existingInitPackage, _ := p.cluster.GetDeployedPackage("init"); existingInitPackage != nil {
// Use the build version instead of the metadata since this will support older Zarf versions
deprecated.PrintBreakingChanges(existingInitPackage.Data.Build.Version)
} else {
message.Warnf("Unable to retrieve the initialized Zarf version. There is potential for breaking changes.")
}

spinner.Success()
Expand Down
8 changes: 4 additions & 4 deletions src/pkg/packager/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ func (p *Packager) composeComponents() error {
// filter by architecture
if !composer.CompatibleComponent(component, arch, p.cfg.CreateOpts.Flavor) {
continue
} else {
// if a match was found, strip flavor and architecture to reduce bloat in the package definition
component.Only.Cluster.Architecture = ""
component.Only.Flavor = ""
}

// if a match was found, strip flavor and architecture to reduce bloat in the package definition
component.Only.Cluster.Architecture = ""
component.Only.Flavor = ""

// build the import chain
chain, err := composer.NewImportChain(component, arch, p.cfg.CreateOpts.Flavor)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions src/pkg/packager/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ func (p *Packager) generatePackageChecksums() (string, error) {
if rel == layout.ZarfYAML || rel == layout.Checksums {
continue
}

sum, err := utils.GetSHA256OfFile(abs)
if err != nil {
return "", err
Expand Down
21 changes: 19 additions & 2 deletions src/pkg/packager/sources/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,15 @@ func (s *OCISource) LoadPackage(dst *layout.PackagePaths, unarchiveAll bool) (er
}

if !dst.IsLegacyLayout() {
spinner := message.NewProgressSpinner("Validating pulled layer checksums")
defer spinner.Stop()

if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, isPartial); err != nil {
return err
}

spinner.Success()

if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
return err
}
Expand Down Expand Up @@ -131,8 +136,15 @@ func (s *OCISource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool,
}

if !dst.IsLegacyLayout() {
if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil {
return err
if wantSBOM {
spinner := message.NewProgressSpinner("Validating SBOM checksums")
defer spinner.Stop()

if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil {
return err
}

spinner.Success()
}

if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
Expand Down Expand Up @@ -176,10 +188,15 @@ func (s *OCISource) Collect(dir string) (string, error) {
return "", err
}

spinner := message.NewProgressSpinner("Validating full package checksums")
defer spinner.Stop()

if err := ValidatePackageIntegrity(loaded, pkg.Metadata.AggregateChecksum, false); err != nil {
return "", err
}

spinner.Success()

isSkeleton := strings.HasSuffix(s.Repo().Reference.Reference, oci.SkeletonSuffix)
name := NameFromMetadata(&pkg, isSkeleton)

Expand Down
Loading

0 comments on commit c42d7b5

Please sign in to comment.