Skip to content

Commit c42d7b5

Browse files
authored
fix: pathing issues loading images with Zarf on Windows (#2106)
## 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
1 parent a1e82ba commit c42d7b5

22 files changed

+274
-145
lines changed

.github/actions/packages/action.yaml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,26 @@ inputs:
1010
description: 'Build the example packages'
1111
required: false
1212
default: 'true'
13+
os:
14+
description: 'Which OS to build for'
15+
required: false
16+
default: 'linux'
17+
shell:
18+
description: 'Which shell to build in'
19+
required: false
20+
default: 'bash'
1321

1422
runs:
1523
using: composite
1624
steps:
1725
- run: |
18-
make build-cli-linux-amd ARCH=amd64
19-
shell: bash
26+
make build-cli-${{ inputs.os }}-amd ARCH=amd64
27+
shell: ${{ inputs.shell }}
2028
- run: |
2129
make init-package ARCH=amd64
22-
shell: bash
30+
shell: ${{ inputs.shell }}
2331
if: ${{ inputs.init-package == 'true' }}
2432
- run: |
2533
make build-examples ARCH=amd64
26-
shell: bash
34+
shell: ${{ inputs.shell }}
2735
if: ${{ inputs.build-examples == 'true' }}

.github/workflows/test-windows.yml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,24 @@ jobs:
3333
- name: Setup golang
3434
uses: ./.github/actions/golang
3535

36-
- name: Build windows binary
37-
run: make build-cli-windows-amd
36+
- name: Run Windows unit tests
37+
run: make test-unit
3838
shell: pwsh
3939

40-
# Builds an init package manually off of the v0.23.6 release since
41-
# Windows in GitHub cannot natively build linux containers and
42-
# the tests this workflow runs do not use the agent at all!
40+
- name: Build Windows binary and zarf packages
41+
uses: ./.github/actions/packages
42+
with:
43+
init-package: "false"
44+
os: windows
45+
shell: pwsh
46+
47+
# TODO: (@WSTARR) Builds an init package manually off of the v0.30.1
48+
# release since Windows in GitHub cannot natively build linux containers
49+
# and the tests this workflow run do not use the agent at all!
4350
- name: Build init-package
4451
run: |
45-
make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.25.2
46-
47-
- name: Build zarf packages
48-
run: make build-examples ARCH=amd64
49-
shell: pwsh
52+
make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.30.1
5053
51-
- name: Run windows tests
54+
- name: Run windows E2E tests
5255
run: make test-e2e ARCH=amd64 -e SKIP_K8S=true
5356
shell: pwsh

examples/component-actions/zarf.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ components:
117117
- name: SNAKE_SOUND
118118
# marks this variable as sensitive to prevent it from being output in the Zarf log
119119
sensitive: true
120-
# autoIndent tells Zarf to maintain spacing for any newlines when templating into a yaml file
120+
# autoIndent tells Zarf to maintain spacing for any newlines when templating into a yaml file
121121
autoIndent: true
122122
# onSuccess will only run if steps in this component are successful
123123
onSuccess:
@@ -185,13 +185,13 @@ components:
185185
actions:
186186
onCreate:
187187
after:
188-
- description: Cloudflare 1.1.1.1 site to be available
188+
- description: Github.com to be available
189189
maxTotalSeconds: 15
190190
wait:
191191
# wait for a network address to return a 200 OK response
192192
network:
193193
protocol: https
194-
address: 1.1.1.1
194+
address: github.com
195195
code: 200
196196

197197
- name: on-deploy-with-wait-action

src/pkg/layout/package.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ func (pp *PackagePaths) SetFromLayers(layers []ocispec.Descriptor) {
177177
// SetFromPaths maps paths to package paths.
178178
func (pp *PackagePaths) SetFromPaths(paths []string) {
179179
for _, rel := range paths {
180-
switch path := rel; {
180+
// Convert from the standard '/' to the OS path separator for Windows support
181+
switch path := filepath.FromSlash(rel); {
181182
case path == ZarfYAML:
182183
pp.ZarfYAML = filepath.Join(pp.Base, path)
183184
case path == Signature:
@@ -213,16 +214,20 @@ func (pp *PackagePaths) SetFromPaths(paths []string) {
213214
// Files returns a map of all the files in the package.
214215
func (pp *PackagePaths) Files() map[string]string {
215216
pathMap := make(map[string]string)
217+
216218
stripBase := func(path string) string {
217219
rel, _ := filepath.Rel(pp.Base, path)
218-
return rel
220+
// Convert from the OS path separator to the standard '/' for Windows support
221+
return filepath.ToSlash(rel)
219222
}
223+
220224
add := func(path string) {
221225
if path == "" {
222226
return
223227
}
224228
pathMap[stripBase(path)] = path
225229
}
230+
226231
add(pp.ZarfYAML)
227232
add(pp.Signature)
228233
add(pp.Checksums)

src/pkg/layout/package_test.go

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package layout
66

77
import (
8+
"runtime"
89
"strings"
910
"testing"
1011

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

1819
raw := &PackagePaths{
1920
Base: "test",
20-
ZarfYAML: "test/zarf.yaml",
21-
Checksums: "test/checksums.txt",
21+
ZarfYAML: normalizePath("test/zarf.yaml"),
22+
Checksums: normalizePath("test/checksums.txt"),
2223
Components: Components{
23-
Base: "test/components",
24+
Base: normalizePath("test/components"),
2425
},
2526
}
2627

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

3132
expected := map[string]string{
32-
"zarf.yaml": "test/zarf.yaml",
33-
"checksums.txt": "test/checksums.txt",
33+
"zarf.yaml": normalizePath("test/zarf.yaml"),
34+
"checksums.txt": normalizePath("test/checksums.txt"),
3435
}
3536

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

4950
expected = map[string]string{
50-
"zarf.yaml": "test/zarf.yaml",
51-
"checksums.txt": "test/checksums.txt",
52-
"zarf.yaml.sig": "test/zarf.yaml.sig",
51+
"zarf.yaml": normalizePath("test/zarf.yaml"),
52+
"checksums.txt": normalizePath("test/checksums.txt"),
53+
"zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
5354
}
5455

5556
require.Equal(t, expected, files)
56-
5757
pp = pp.AddImages()
5858

5959
files = pp.Files()
6060

61+
// Note that the map key will always be the forward "Slash" (/) version of the file path (never \)
6162
expected = map[string]string{
62-
"zarf.yaml": "test/zarf.yaml",
63-
"checksums.txt": "test/checksums.txt",
64-
"zarf.yaml.sig": "test/zarf.yaml.sig",
65-
"images/index.json": "test/images/index.json",
66-
"images/oci-layout": "test/images/oci-layout",
63+
"zarf.yaml": normalizePath("test/zarf.yaml"),
64+
"checksums.txt": normalizePath("test/checksums.txt"),
65+
"zarf.yaml.sig": normalizePath("test/zarf.yaml.sig"),
66+
"images/index.json": normalizePath("test/images/index.json"),
67+
"images/oci-layout": normalizePath("test/images/oci-layout"),
6768
}
6869

6970
require.Equal(t, expected, files)
@@ -79,10 +80,10 @@ func TestPackageFiles(t *testing.T) {
7980
"zarf.yaml",
8081
"checksums.txt",
8182
"sboms.tar",
82-
"components/c1.tar",
83-
"images/index.json",
84-
"images/oci-layout",
85-
"images/blobs/sha256/" + strings.Repeat("1", 64),
83+
normalizePath("components/c1.tar"),
84+
normalizePath("images/index.json"),
85+
normalizePath("images/oci-layout"),
86+
normalizePath("images/blobs/sha256/" + strings.Repeat("1", 64)),
8687
}
8788

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

9495
expected = map[string]string{
95-
"zarf.yaml": "test/zarf.yaml",
96-
"checksums.txt": "test/checksums.txt",
97-
"sboms.tar": "test/sboms.tar",
98-
"components/c1.tar": "test/components/c1.tar",
99-
"images/index.json": "test/images/index.json",
100-
"images/oci-layout": "test/images/oci-layout",
101-
"images/blobs/sha256/" + strings.Repeat("1", 64): "test/images/blobs/sha256/" + strings.Repeat("1", 64),
96+
"zarf.yaml": normalizePath("test/zarf.yaml"),
97+
"checksums.txt": normalizePath("test/checksums.txt"),
98+
"sboms.tar": normalizePath("test/sboms.tar"),
99+
"components/c1.tar": normalizePath("test/components/c1.tar"),
100+
"images/index.json": normalizePath("test/images/index.json"),
101+
"images/oci-layout": normalizePath("test/images/oci-layout"),
102+
"images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
102103
}
103104

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

125126
expected = map[string]string{
126-
"zarf.yaml": "test/zarf.yaml",
127-
"checksums.txt": "test/checksums.txt",
128-
"sboms.tar": "test/sboms.tar",
129-
"components/c1.tar": "test/components/c1.tar",
130-
"components/c2.tar": "test/components/c2.tar",
131-
"images/index.json": "test/images/index.json",
132-
"images/oci-layout": "test/images/oci-layout",
133-
"images/blobs/sha256/" + strings.Repeat("1", 64): "test/images/blobs/sha256/" + strings.Repeat("1", 64),
134-
"images/blobs/sha256/" + strings.Repeat("2", 64): "test/images/blobs/sha256/" + strings.Repeat("2", 64),
127+
"zarf.yaml": normalizePath("test/zarf.yaml"),
128+
"checksums.txt": normalizePath("test/checksums.txt"),
129+
"sboms.tar": normalizePath("test/sboms.tar"),
130+
"components/c1.tar": normalizePath("test/components/c1.tar"),
131+
"components/c2.tar": normalizePath("test/components/c2.tar"),
132+
"images/index.json": normalizePath("test/images/index.json"),
133+
"images/oci-layout": normalizePath("test/images/oci-layout"),
134+
"images/blobs/sha256/" + strings.Repeat("1", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("1", 64)),
135+
"images/blobs/sha256/" + strings.Repeat("2", 64): normalizePath("test/images/blobs/sha256/" + strings.Repeat("2", 64)),
135136
}
136137

137138
require.Equal(t, expected, files)
138139
}
140+
141+
// normalizePath ensures that the filepaths being generated are normalized to the host OS.
142+
func normalizePath(path string) string {
143+
if runtime.GOOS != "windows" {
144+
return path
145+
}
146+
147+
return strings.ReplaceAll(path, "/", "\\")
148+
}

src/pkg/oci/manifest.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ func NewZarfOCIManifest(manifest *ocispec.Manifest) *ZarfOCIManifest {
3535
}
3636

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

src/pkg/oci/pull.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ var (
3232

3333
// FileDescriptorExists returns true if the given file exists in the given directory with the expected SHA.
3434
func (o *OrasRemote) FileDescriptorExists(desc ocispec.Descriptor, destinationDir string) bool {
35-
destinationPath := filepath.Join(destinationDir, desc.Annotations[ocispec.AnnotationTitle])
35+
rel := desc.Annotations[ocispec.AnnotationTitle]
36+
destinationPath := filepath.Join(destinationDir, rel)
37+
3638
info, err := os.Stat(destinationPath)
3739
if err != nil {
3840
return false
@@ -252,7 +254,10 @@ func (o *OrasRemote) PullLayer(desc ocispec.Descriptor, destinationDir string) e
252254
if err != nil {
253255
return err
254256
}
255-
return utils.WriteFile(filepath.Join(destinationDir, desc.Annotations[ocispec.AnnotationTitle]), b)
257+
258+
rel := desc.Annotations[ocispec.AnnotationTitle]
259+
260+
return utils.WriteFile(filepath.Join(destinationDir, rel), b)
256261
}
257262

258263
// PullPackagePaths pulls multiple files from the remote repository and saves them to `destinationDir`.

src/pkg/packager/common.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,6 @@ func (p *Packager) attemptClusterChecks() (err error) {
261261
if existingInitPackage, _ := p.cluster.GetDeployedPackage("init"); existingInitPackage != nil {
262262
// Use the build version instead of the metadata since this will support older Zarf versions
263263
deprecated.PrintBreakingChanges(existingInitPackage.Data.Build.Version)
264-
} else {
265-
message.Warnf("Unable to retrieve the initialized Zarf version. There is potential for breaking changes.")
266264
}
267265

268266
spinner.Success()

src/pkg/packager/compose.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ func (p *Packager) composeComponents() error {
2222
// filter by architecture
2323
if !composer.CompatibleComponent(component, arch, p.cfg.CreateOpts.Flavor) {
2424
continue
25-
} else {
26-
// if a match was found, strip flavor and architecture to reduce bloat in the package definition
27-
component.Only.Cluster.Architecture = ""
28-
component.Only.Flavor = ""
2925
}
3026

27+
// if a match was found, strip flavor and architecture to reduce bloat in the package definition
28+
component.Only.Cluster.Architecture = ""
29+
component.Only.Flavor = ""
30+
3131
// build the import chain
3232
chain, err := composer.NewImportChain(component, arch, p.cfg.CreateOpts.Flavor)
3333
if err != nil {

src/pkg/packager/create.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ func (p *Packager) generatePackageChecksums() (string, error) {
608608
if rel == layout.ZarfYAML || rel == layout.Checksums {
609609
continue
610610
}
611+
611612
sum, err := utils.GetSHA256OfFile(abs)
612613
if err != nil {
613614
return "", err

src/pkg/packager/sources/oci.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,15 @@ func (s *OCISource) LoadPackage(dst *layout.PackagePaths, unarchiveAll bool) (er
7474
}
7575

7676
if !dst.IsLegacyLayout() {
77+
spinner := message.NewProgressSpinner("Validating pulled layer checksums")
78+
defer spinner.Stop()
79+
7780
if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, isPartial); err != nil {
7881
return err
7982
}
8083

84+
spinner.Success()
85+
8186
if err := ValidatePackageSignature(dst, s.PublicKeyPath); err != nil {
8287
return err
8388
}
@@ -131,8 +136,15 @@ func (s *OCISource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool,
131136
}
132137

133138
if !dst.IsLegacyLayout() {
134-
if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil {
135-
return err
139+
if wantSBOM {
140+
spinner := message.NewProgressSpinner("Validating SBOM checksums")
141+
defer spinner.Stop()
142+
143+
if err := ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, true); err != nil {
144+
return err
145+
}
146+
147+
spinner.Success()
136148
}
137149

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

191+
spinner := message.NewProgressSpinner("Validating full package checksums")
192+
defer spinner.Stop()
193+
179194
if err := ValidatePackageIntegrity(loaded, pkg.Metadata.AggregateChecksum, false); err != nil {
180195
return "", err
181196
}
182197

198+
spinner.Success()
199+
183200
isSkeleton := strings.HasSuffix(s.Repo().Reference.Reference, oci.SkeletonSuffix)
184201
name := NameFromMetadata(&pkg, isSkeleton)
185202

0 commit comments

Comments
 (0)