Skip to content

Commit fadc971

Browse files
authored
feat: Add 'volume import' command (#245)
Reviewed-by: Justin Chadwell <justin@unikraft.com> Approved-by: Justin Chadwell <justin@unikraft.com>
2 parents 1220cac + 42c8ea0 commit fadc971

17 files changed

Lines changed: 1154 additions & 60 deletions

File tree

cmd/unikraft/testdata/TestGolden/volumes/help

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/unikraft/testdata/TestGolden/volumes/import/dir

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/unikraft/testdata/TestGolden/volumes/import/invalid-port

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/unikraft/testdata/TestGolden/volumes/import/invalid-port-high

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/unikraft/testdata/TestGolden/volumes/import/missing-source

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/unikraft/testdata/TestGolden/volumes/import/serve

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/unikraft/volumes_test.go

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
package main
77

8-
import "testing"
8+
import (
9+
"regexp"
10+
"testing"
11+
)
912

1013
func volumesTests(t *testing.T, r *testRunner) {
1114
t.Run("help", func(t *testing.T) {
@@ -16,6 +19,7 @@ func volumesTests(t *testing.T, r *testRunner) {
1619
{args: []string{unikraftCmd, "volume", "wait", "--help"}},
1720
{args: []string{unikraftCmd, "volume", "create", "--help"}},
1821
{args: []string{unikraftCmd, "volume", "clone", "--help"}},
22+
{args: []string{unikraftCmd, "volume", "import", "--help"}},
1923
{args: []string{unikraftCmd, "volume", "edit", "--help"}},
2024
{args: []string{unikraftCmd, "volume", "delete", "--help"}},
2125
})
@@ -59,4 +63,118 @@ func volumesTests(t *testing.T, r *testRunner) {
5963
{args: []string{unikraftCmd, "volume", "delete", "test-$UNIQ_VOLUME", "test-$UNIQ_VOLUME_CLONE"}},
6064
})
6165
})
66+
67+
t.Run("import", func(t *testing.T) {
68+
// Offline: missing --source errors before any network call.
69+
t.Run("missing-source", func(t *testing.T) {
70+
r.run(t, []command{
71+
{args: []string{unikraftCmd, "volume", "import", "my-volume"}, allowErr: true},
72+
})
73+
})
74+
75+
// Offline: port below the allowed range errors before any network call.
76+
t.Run("invalid-port", func(t *testing.T) {
77+
r.run(t, []command{
78+
{args: []string{unikraftCmd, "volume", "import", "my-volume", "--source", ".", "--port", "80"}, allowErr: true},
79+
})
80+
})
81+
82+
// Offline: port above the allowed range errors before any network call.
83+
t.Run("invalid-port-high", func(t *testing.T) {
84+
r.run(t, []command{
85+
{args: []string{unikraftCmd, "volume", "import", "my-volume", "--source", ".", "--port", "99999"}, allowErr: true},
86+
})
87+
})
88+
89+
// Import a small directory into a freshly created volume.
90+
t.Run("dir", func(t *testing.T) {
91+
r.
92+
online().
93+
withCleaners([]cleaner{
94+
// free size differs on every run
95+
{
96+
pattern: regexp.MustCompile(`free=[0-9]+.?[0-9]*MiB`),
97+
repl: "free=10MiB",
98+
},
99+
}).
100+
withContext(map[string]string{
101+
"hello.txt": "hello from volume import\n",
102+
}).
103+
run(t, []command{
104+
{args: []string{unikraftCmd, "volume", "create", "--output", "quiet", "--set", "name=test-$UNIQ_VOLUME", "--set", "size=10", "--set", "metro=" + metroName}},
105+
{args: []string{unikraftCmd, "volume", "import", "test-$UNIQ_VOLUME", "--source", "."}},
106+
{args: []string{unikraftCmd, "volume", "inspect", "test-$UNIQ_VOLUME"}},
107+
{args: []string{unikraftCmd, "volume", "delete", "test-$UNIQ_VOLUME"}},
108+
})
109+
})
110+
111+
// Import a file into a freshly created volume and test connection.
112+
t.Run("serve", func(t *testing.T) {
113+
r.
114+
online().
115+
withCleaners(instanceCleaners).
116+
withCleaners([]cleaner{
117+
{
118+
// free size differs on every run
119+
pattern: regexp.MustCompile(`free=[0-9]+.?[0-9]*MiB`),
120+
repl: "free=50MiB",
121+
},
122+
}).
123+
withContext(map[string]string{
124+
"index.html": "<html><body>hello from volume import</body></html>\n",
125+
}).
126+
run(t, []command{
127+
// Create a volume to hold the custom web content
128+
{args: []string{
129+
unikraftCmd, "volume", "create",
130+
"--output", "quiet",
131+
"--set", "name=test-$UNIQ_VOL",
132+
"--set", "size=50",
133+
"--set", "metro=" + metroName,
134+
}},
135+
// Import the custom index.html into the volume
136+
{args: []string{unikraftCmd, "volume", "import", "test-$UNIQ_VOL", "--source", "."}},
137+
{args: []string{
138+
unikraftCmd, "instance", "create",
139+
"--set", "name=test-$UNIQ_INST",
140+
"--set", "metro=" + metroName,
141+
"--set", "image=test/nginx-compat:latest",
142+
"--set", "autostart=true",
143+
"--set", "resources.memory=256",
144+
"--set", "resources.vcpus=1",
145+
"--set", "volumes=test-$UNIQ_VOL:/wwwroot:ro",
146+
"--set", "service.services=443:8080/tls+http",
147+
"--set", "service.domains=name=$UNIQ_DOMAIN",
148+
}},
149+
// Capture the assigned FQDN
150+
{
151+
args: []string{
152+
unikraftCmd, "instance", "inspect", "test-$UNIQ_INST",
153+
"--output", "template=" + `{{ (index .service.domains 0).fqdn }}`,
154+
},
155+
captureEnv: "FQDN",
156+
},
157+
{args: []string{unikraftCmd, "instance", "wait", "--until", "state==running", "--timeout", "30s", "test-$UNIQ_INST"}},
158+
// Curl the instance and write the body to a file for content verification.
159+
{args: []string{
160+
"curl",
161+
"-k",
162+
"--fail",
163+
"--silent",
164+
"--show-error",
165+
"--output", "response.html",
166+
"--retry", "10",
167+
"--retry-delay", "2",
168+
"--retry-all-errors",
169+
"--connect-timeout", "5",
170+
"--max-time", "10",
171+
"https://$FQDN",
172+
}},
173+
// Assert the imported content is served.
174+
{args: []string{"grep", "hello from volume import", "response.html"}},
175+
{args: []string{unikraftCmd, "instance", "delete", "test-$UNIQ_INST"}},
176+
{args: []string{unikraftCmd, "volume", "delete", "test-$UNIQ_VOL"}},
177+
})
178+
})
179+
})
62180
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ require (
3535
github.com/sirupsen/logrus v1.9.4
3636
github.com/stretchr/testify v1.11.1
3737
github.com/tidwall/gjson v1.18.0
38-
github.com/unikraft/go-archivefs v0.0.0-20260414073833-365b9a2f34bd
39-
github.com/unikraft/go-cpio v0.0.0-20260209140144-8e02f12b23e0
38+
github.com/unikraft/go-archivefs v0.0.0-20260416145000-5840b16b2c08
39+
github.com/unikraft/go-cpio v0.0.0-20260415131742-4f1984ca41f3
4040
golang.org/x/net v0.53.0
4141
golang.org/x/sync v0.20.0
4242
gopkg.in/yaml.v3 v3.0.1

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,10 @@ github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/
372372
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
373373
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
374374
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc=
375-
github.com/unikraft/go-archivefs v0.0.0-20260414073833-365b9a2f34bd h1:J3+GCdIQOYNOBVf+lAMpZPXBqRHIHa665OnahbGsND4=
376-
github.com/unikraft/go-archivefs v0.0.0-20260414073833-365b9a2f34bd/go.mod h1:NLWtSTdsTRLGQN87zqm9+vN01Ei9JX+FWwuOme2wJXw=
377-
github.com/unikraft/go-cpio v0.0.0-20260209140144-8e02f12b23e0 h1:RNkUuza8WzrA3FL5aTmmO/IGXFppbevPVc2TSRPLsgg=
378-
github.com/unikraft/go-cpio v0.0.0-20260209140144-8e02f12b23e0/go.mod h1:OFJWAQVOq1qppx75rzF7qDoE5TqGjM1oCXW5N06Lftg=
375+
github.com/unikraft/go-archivefs v0.0.0-20260416145000-5840b16b2c08 h1:jXNZRYJ2Zq0xi9C9mEltqImgXmTRodK2la18uIoyIH0=
376+
github.com/unikraft/go-archivefs v0.0.0-20260416145000-5840b16b2c08/go.mod h1:NLWtSTdsTRLGQN87zqm9+vN01Ei9JX+FWwuOme2wJXw=
377+
github.com/unikraft/go-cpio v0.0.0-20260415131742-4f1984ca41f3 h1:C8GONmMNDK3spdZEHBIffmq1b1hjCm9nZrquv3+4H+U=
378+
github.com/unikraft/go-cpio v0.0.0-20260415131742-4f1984ca41f3/go.mod h1:OFJWAQVOq1qppx75rzF7qDoE5TqGjM1oCXW5N06Lftg=
379379
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
380380
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
381381
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=

internal/builder/build.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type BuildOpts struct {
3131

3232
type RootfsOpts struct {
3333
Path string
34+
Type RootfsType
3435

3536
// Output params
3637
Format kraftfile.FsType
@@ -51,6 +52,9 @@ type RootfsType string
5152

5253
const (
5354
RootfsTypeDockerfile RootfsType = "dockerfile"
55+
RootfsTypeCpio RootfsType = "cpio"
56+
RootfsTypeErofs RootfsType = "erofs"
57+
RootfsTypeDir RootfsType = "dir"
5458
)
5559

5660
// Build a unikraft image based on the provided build options.

0 commit comments

Comments
 (0)