Skip to content

Commit 69b4b85

Browse files
authored
Merge pull request #5554 from fluxcd/migrate-dir
Extend `flux migrate` to work with local files
2 parents 1b46056 + a9b5be7 commit 69b4b85

24 files changed

+809
-21
lines changed

cmd/flux/migrate.go

Lines changed: 505 additions & 21 deletions
Large diffs are not rendered by default.

cmd/flux/migrate_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
Copyright 2025 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"bytes"
21+
"io/fs"
22+
"os"
23+
"testing"
24+
25+
. "github.com/onsi/gomega"
26+
"k8s.io/apimachinery/pkg/runtime/schema"
27+
)
28+
29+
type writeToMemoryFS struct {
30+
fs.FS
31+
32+
writtenFiles map[string][]byte
33+
}
34+
35+
func (m *writeToMemoryFS) WriteFile(name string, data []byte, perm os.FileMode) error {
36+
m.writtenFiles[name] = data
37+
return nil
38+
}
39+
40+
type writtenFile struct {
41+
file string
42+
goldenFile string
43+
}
44+
45+
func TestFileSystemMigrator(t *testing.T) {
46+
for _, tt := range []struct {
47+
name string
48+
path string
49+
outputGolden string
50+
writtenFiles []writtenFile
51+
err string
52+
}{
53+
{
54+
name: "errors out for single file that is a symlink",
55+
path: "testdata/migrate/file-system/single-file-link.yaml",
56+
err: "file testdata/migrate/file-system/single-file-link.yaml is irregular",
57+
},
58+
{
59+
name: "errors out for single file with wrong extension",
60+
path: "testdata/migrate/file-system/single-file-wrong-ext.json",
61+
err: "file testdata/migrate/file-system/single-file-wrong-ext.json does not match the specified extensions: .yaml, .yml",
62+
},
63+
{
64+
name: "migrate single file",
65+
path: "testdata/migrate/file-system/single-file.yaml",
66+
outputGolden: "testdata/migrate/file-system/single-file.yaml.output.golden",
67+
writtenFiles: []writtenFile{
68+
{
69+
file: "testdata/migrate/file-system/single-file.yaml",
70+
goldenFile: "testdata/migrate/file-system/single-file.yaml.golden",
71+
},
72+
},
73+
},
74+
{
75+
name: "migrate files in directory",
76+
path: "testdata/migrate/file-system/dir",
77+
outputGolden: "testdata/migrate/file-system/dir.output.golden",
78+
writtenFiles: []writtenFile{
79+
{
80+
file: "testdata/migrate/file-system/dir/some-dir/another-file.yaml",
81+
goldenFile: "testdata/migrate/file-system/dir.golden/some-dir/another-file.yaml",
82+
},
83+
{
84+
file: "testdata/migrate/file-system/dir/some-dir/another-file.yml",
85+
goldenFile: "testdata/migrate/file-system/dir.golden/some-dir/another-file.yml",
86+
},
87+
{
88+
file: "testdata/migrate/file-system/dir/some-file.yaml",
89+
goldenFile: "testdata/migrate/file-system/dir.golden/some-file.yaml",
90+
},
91+
{
92+
file: "testdata/migrate/file-system/dir/some-file.yml",
93+
goldenFile: "testdata/migrate/file-system/dir.golden/some-file.yml",
94+
},
95+
},
96+
},
97+
} {
98+
t.Run(tt.name, func(t *testing.T) {
99+
g := NewWithT(t)
100+
101+
// Store logger, replace with test logger, and restore at the end of the test.
102+
var testLogger bytes.Buffer
103+
oldLogger := logger
104+
logger = stderrLogger{&testLogger}
105+
t.Cleanup(func() { logger = oldLogger })
106+
107+
// Open current working directory as root and build write-to-memory filesystem.
108+
pathRoot, err := os.OpenRoot(".")
109+
g.Expect(err).ToNot(HaveOccurred())
110+
t.Cleanup(func() { pathRoot.Close() })
111+
fileSystem := &writeToMemoryFS{
112+
FS: pathRoot.FS(),
113+
writtenFiles: make(map[string][]byte),
114+
}
115+
116+
// Prepare other inputs.
117+
const yes = true
118+
const dryRun = false
119+
extensions := []string{".yaml", ".yml"}
120+
latestVersions := map[schema.GroupKind]string{
121+
{Group: "image.toolkit.fluxcd.io", Kind: "ImageRepository"}: "v1",
122+
{Group: "image.toolkit.fluxcd.io", Kind: "ImagePolicy"}: "v1",
123+
{Group: "image.toolkit.fluxcd.io", Kind: "ImageUpdateAutomation"}: "v1",
124+
}
125+
126+
// Run migration.
127+
err = NewFileSystemMigrator(fileSystem, yes, dryRun, tt.path, extensions, latestVersions).Run()
128+
if tt.err != "" {
129+
g.Expect(err).To(HaveOccurred())
130+
g.Expect(err.Error()).To(Equal(tt.err))
131+
return
132+
}
133+
g.Expect(err).ToNot(HaveOccurred())
134+
135+
// Assert logger output.
136+
b, err := os.ReadFile(tt.outputGolden)
137+
g.Expect(err).ToNot(HaveOccurred())
138+
g.Expect(string(b)).To(Equal(testLogger.String()),
139+
"logger output does not match golden file %s", tt.outputGolden)
140+
141+
// Assert which files were written.
142+
writtenFiles := make([]string, 0, len(fileSystem.writtenFiles))
143+
for name := range fileSystem.writtenFiles {
144+
writtenFiles = append(writtenFiles, name)
145+
}
146+
expectedWrittenFiles := make([]string, 0, len(tt.writtenFiles))
147+
for _, wf := range tt.writtenFiles {
148+
expectedWrittenFiles = append(expectedWrittenFiles, wf.file)
149+
}
150+
g.Expect(writtenFiles).To(ConsistOf(expectedWrittenFiles))
151+
152+
// Assert contents of written files.
153+
for _, wf := range tt.writtenFiles {
154+
b, err := os.ReadFile(wf.goldenFile)
155+
g.Expect(err).ToNot(HaveOccurred())
156+
g.Expect(fileSystem.writtenFiles[wf.file]).To(Equal(b),
157+
"file %s does not match golden file %s", wf.file, wf.goldenFile)
158+
}
159+
})
160+
}
161+
}

cmd/flux/testdata/migrate/file-system/dir.golden/some-dir/another-file

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./another-file.yaml
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
apiVersion: image.toolkit.fluxcd.io/v1
3+
kind: ImageUpdateAutomation
4+
---
5+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# This file has Windows line endings.
2+
3+
apiVersion: image.toolkit.fluxcd.io/v1
4+
kind: ImageUpdateAutomation
5+
---
6+

cmd/flux/testdata/migrate/file-system/dir.golden/some-file

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
some-file.yaml
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: image.toolkit.fluxcd.io/v1
2+
kind: ImageRepository
3+
---
4+
5+
6+
7+
apiVersion: image.toolkit.fluxcd.io/v1
8+
kind: ImagePolicy
9+
10+
11+
---
12+
13+
apiVersion: image.toolkit.fluxcd.io/v1/v2
14+
kind: ImagePolicy
15+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This file has Windows line endings.
2+
3+
apiVersion: image.toolkit.fluxcd.io/v1
4+
kind: ImageRepository
5+
---
6+
7+
8+
9+
apiVersion: image.toolkit.fluxcd.io/v1
10+
kind: ImagePolicy
11+
12+
13+
---
14+
15+
apiVersion: image.toolkit.fluxcd.io/v1/v2
16+
kind: ImagePolicy
17+

0 commit comments

Comments
 (0)