hst/fs: implement overlay fstype
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m8s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 4m20s
Test / Hakurei (race detector) (push) Successful in 5m1s
Test / Flake checks (push) Successful in 1m27s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m8s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 3m59s
Test / Sandbox (race detector) (push) Successful in 4m20s
Test / Hakurei (race detector) (push) Successful in 5m1s
Test / Flake checks (push) Successful in 1m27s
This finally exposes overlay mounts in the high level hakurei API. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -81,6 +81,12 @@ func (f *FilesystemConfigJSON) MarshalJSON() ([]byte, error) {
|
||||
*FSEphemeral
|
||||
}{fsType{FilesystemEphemeral}, cv}
|
||||
|
||||
case *FSOverlay:
|
||||
v = &struct {
|
||||
fsType
|
||||
*FSOverlay
|
||||
}{fsType{FilesystemOverlay}, cv}
|
||||
|
||||
default:
|
||||
return nil, FSImplError{f.FilesystemConfig}
|
||||
}
|
||||
@@ -103,6 +109,9 @@ func (f *FilesystemConfigJSON) UnmarshalJSON(data []byte) error {
|
||||
case FilesystemEphemeral:
|
||||
*f = FilesystemConfigJSON{new(FSEphemeral)}
|
||||
|
||||
case FilesystemOverlay:
|
||||
*f = FilesystemConfigJSON{new(FSOverlay)}
|
||||
|
||||
default:
|
||||
return FSTypeError(t.Type)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
hst.FSImplError{Value: stubFS{"ephemeral"}},
|
||||
"\x00", "\x00"},
|
||||
|
||||
{"bad impl overlay", hst.FilesystemConfigJSON{FilesystemConfig: stubFS{"overlay"}},
|
||||
hst.FSImplError{Value: stubFS{"overlay"}},
|
||||
"\x00", "\x00"},
|
||||
|
||||
{"bind", hst.FilesystemConfigJSON{
|
||||
FilesystemConfig: &hst.FSBind{
|
||||
Dst: m("/etc"),
|
||||
@@ -55,6 +59,17 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
}, nil,
|
||||
`{"type":"ephemeral","dst":"/run/user/65534","write":true,"size":1024,"perm":448}`,
|
||||
`{"fs":{"type":"ephemeral","dst":"/run/user/65534","write":true,"size":1024,"perm":448},"magic":3236757504}`},
|
||||
|
||||
{"overlay", hst.FilesystemConfigJSON{
|
||||
FilesystemConfig: &hst.FSOverlay{
|
||||
Dst: m("/nix/store"),
|
||||
Lower: ms("/mnt-root/nix/.ro-store"),
|
||||
Upper: m("/mnt-root/nix/.rw-store/upper"),
|
||||
Work: m("/mnt-root/nix/.rw-store/work"),
|
||||
},
|
||||
}, nil,
|
||||
`{"type":"overlay","dst":"/nix/store","lower":["/mnt-root/nix/.ro-store"],"upper":"/mnt-root/nix/.rw-store/upper","work":"/mnt-root/nix/.rw-store/work"}`,
|
||||
`{"fs":{"type":"overlay","dst":"/nix/store","lower":["/mnt-root/nix/.ro-store"],"upper":"/mnt-root/nix/.rw-store/upper","work":"/mnt-root/nix/.rw-store/work"},"magic":3236757504}`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
98
hst/fsoverlay.go
Normal file
98
hst/fsoverlay.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/container"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(FSOverlay)) }
|
||||
|
||||
// FilesystemOverlay is the [FilesystemConfig.Type] name of an overlay mount point.
|
||||
const FilesystemOverlay = "overlay"
|
||||
|
||||
// FSOverlay represents an overlay mount point.
|
||||
type FSOverlay struct {
|
||||
// mount point in container
|
||||
Dst *container.Absolute `json:"dst"`
|
||||
|
||||
// any filesystem, does not need to be on a writable filesystem, must not be nil
|
||||
Lower []*container.Absolute `json:"lower"`
|
||||
// the upperdir is normally on a writable filesystem, leave as nil to mount Lower readonly
|
||||
Upper *container.Absolute `json:"upper,omitempty"`
|
||||
// the workdir needs to be an empty directory on the same filesystem as Upper, must not be nil if Upper is populated
|
||||
Work *container.Absolute `json:"work,omitempty"`
|
||||
}
|
||||
|
||||
func (o *FSOverlay) Valid() bool {
|
||||
if o == nil || o.Dst == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, a := range o.Lower {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if o.Upper != nil { // rw
|
||||
return o.Work != nil && len(o.Lower) > 0
|
||||
} else { // ro
|
||||
return len(o.Lower) >= 2
|
||||
}
|
||||
}
|
||||
|
||||
func (o *FSOverlay) Target() *container.Absolute {
|
||||
if !o.Valid() {
|
||||
return nil
|
||||
}
|
||||
return o.Dst
|
||||
}
|
||||
|
||||
func (o *FSOverlay) Host() []*container.Absolute {
|
||||
if !o.Valid() {
|
||||
return nil
|
||||
}
|
||||
p := make([]*container.Absolute, 0, 2+len(o.Lower))
|
||||
if o.Upper != nil && o.Work != nil {
|
||||
p = append(p, o.Upper, o.Work)
|
||||
}
|
||||
p = append(p, o.Lower...)
|
||||
return p
|
||||
}
|
||||
|
||||
func (o *FSOverlay) Apply(op *container.Ops) {
|
||||
if !o.Valid() {
|
||||
return
|
||||
}
|
||||
|
||||
if o.Upper != nil && o.Work != nil { // rw
|
||||
op.Overlay(o.Dst, o.Upper, o.Work, o.Lower...)
|
||||
} else { // ro
|
||||
op.OverlayReadonly(o.Dst, o.Lower...)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *FSOverlay) String() string {
|
||||
if !o.Valid() {
|
||||
return "<invalid>"
|
||||
}
|
||||
|
||||
lower := make([]string, len(o.Lower))
|
||||
for i, a := range o.Lower {
|
||||
lower[i] = container.EscapeOverlayDataSegment(a.String())
|
||||
}
|
||||
|
||||
if o.Upper != nil && o.Work != nil {
|
||||
return "w*" + strings.Join(append([]string{
|
||||
container.EscapeOverlayDataSegment(o.Dst.String()),
|
||||
container.EscapeOverlayDataSegment(o.Upper.String()),
|
||||
container.EscapeOverlayDataSegment(o.Work.String())},
|
||||
lower...), container.SpecialOverlayPath)
|
||||
} else {
|
||||
return "*" + strings.Join(append([]string{
|
||||
container.EscapeOverlayDataSegment(o.Dst.String())},
|
||||
lower...), container.SpecialOverlayPath)
|
||||
}
|
||||
}
|
||||
50
hst/fsoverlay_test.go
Normal file
50
hst/fsoverlay_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package hst_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
func TestFSOverlay(t *testing.T) {
|
||||
checkFs(t, []fsTestCase{
|
||||
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
|
||||
{"nil lower", &hst.FSOverlay{Dst: m("/etc"), Lower: []*container.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
||||
{"zero lower", &hst.FSOverlay{Dst: m("/etc"), Upper: m("/"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
||||
{"zero lower ro", &hst.FSOverlay{Dst: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||
{"short lower", &hst.FSOverlay{Dst: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||
|
||||
{"full", &hst.FSOverlay{
|
||||
Dst: m("/nix/store"),
|
||||
Lower: ms("/mnt-root/nix/.ro-store"),
|
||||
Upper: m("/mnt-root/nix/.rw-store/upper"),
|
||||
Work: m("/mnt-root/nix/.rw-store/work"),
|
||||
}, true, container.Ops{&container.MountOverlayOp{
|
||||
Target: m("/nix/store"),
|
||||
Lower: ms("/mnt-root/nix/.ro-store"),
|
||||
Upper: m("/mnt-root/nix/.rw-store/upper"),
|
||||
Work: m("/mnt-root/nix/.rw-store/work"),
|
||||
}}, m("/nix/store"), ms("/mnt-root/nix/.rw-store/upper", "/mnt-root/nix/.rw-store/work", "/mnt-root/nix/.ro-store"),
|
||||
"w*/nix/store:/mnt-root/nix/.rw-store/upper:/mnt-root/nix/.rw-store/work:/mnt-root/nix/.ro-store"},
|
||||
|
||||
{"ro", &hst.FSOverlay{
|
||||
Dst: m("/mnt/src"),
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
}, true, container.Ops{&container.MountOverlayOp{
|
||||
Target: m("/mnt/src"),
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
}}, m("/mnt/src"), ms("/tmp/.src0", "/tmp/.src1"),
|
||||
"*/mnt/src:/tmp/.src0:/tmp/.src1"},
|
||||
|
||||
{"ro work", &hst.FSOverlay{
|
||||
Dst: m("/mnt/src"),
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
Work: m("/tmp"),
|
||||
}, true, container.Ops{&container.MountOverlayOp{
|
||||
Target: m("/mnt/src"),
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
}}, m("/mnt/src"), ms("/tmp/.src0", "/tmp/.src1"),
|
||||
"*/mnt/src:/tmp/.src0:/tmp/.src1"},
|
||||
})
|
||||
}
|
||||
@@ -79,6 +79,12 @@ func Template() *Config {
|
||||
},
|
||||
Filesystem: []FilesystemConfigJSON{
|
||||
{&FSEphemeral{Dst: container.AbsFHSTmp, Write: true, Perm: 0755}},
|
||||
{&FSOverlay{
|
||||
Dst: container.MustAbs("/nix/store"),
|
||||
Lower: []*container.Absolute{container.MustAbs("/mnt-root/nix/.ro-store")},
|
||||
Upper: container.MustAbs("/mnt-root/nix/.rw-store/upper"),
|
||||
Work: container.MustAbs("/mnt-root/nix/.rw-store/work"),
|
||||
}},
|
||||
{&FSBind{Src: container.MustAbs("/nix/store")}},
|
||||
{&FSBind{Src: container.AbsFHSRun.Append("current-system")}},
|
||||
{&FSBind{Src: container.AbsFHSRun.Append("opengl-driver")}},
|
||||
|
||||
@@ -103,6 +103,15 @@ func TestTemplate(t *testing.T) {
|
||||
"write": true,
|
||||
"perm": 493
|
||||
},
|
||||
{
|
||||
"type": "overlay",
|
||||
"dst": "/nix/store",
|
||||
"lower": [
|
||||
"/mnt-root/nix/.ro-store"
|
||||
],
|
||||
"upper": "/mnt-root/nix/.rw-store/upper",
|
||||
"work": "/mnt-root/nix/.rw-store/work"
|
||||
},
|
||||
{
|
||||
"type": "bind",
|
||||
"src": "/nix/store"
|
||||
|
||||
Reference in New Issue
Block a user