aboutsummaryrefslogtreecommitdiffhomepage
path: root/hst/fs.go
blob: 1d33095126b404b288ca1022e62b0b2cd74566db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package hst

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"

	"hakurei.app/container"
)

// FilesystemConfig is an abstract representation of a mount point.
type FilesystemConfig interface {
	// Valid returns whether the configuration is valid.
	Valid() bool
	// Path returns the target path in the container.
	Path() *container.Absolute
	// Host returns a slice of all host paths used by this operation.
	Host() []*container.Absolute
	// Apply appends the [container.Op] implementing this operation.
	Apply(z *ApplyState)

	fmt.Stringer
}

// ApplyState holds the address of [container.Ops] and any relevant application state.
type ApplyState struct {
	// AutoEtcPrefix is the prefix for [container.AutoEtcOp].
	AutoEtcPrefix string

	*container.Ops
}

var (
	ErrFSNull = errors.New("unexpected null in mount point")
)

// FSTypeError is returned when [ContainerConfig.Filesystem] contains an entry with invalid type.
type FSTypeError string

func (f FSTypeError) Error() string { return fmt.Sprintf("invalid filesystem type %q", string(f)) }

// FSImplError is returned for unsupported implementations of [FilesystemConfig].
type FSImplError struct{ Value FilesystemConfig }

func (f FSImplError) Error() string {
	implType := reflect.TypeOf(f.Value)
	var name string
	for implType != nil && implType.Kind() == reflect.Ptr {
		name += "*"
		implType = implType.Elem()
	}
	if implType != nil {
		name += implType.Name()
	} else {
		name += "nil"
	}
	return fmt.Sprintf("implementation %s not supported", name)
}

// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
type FilesystemConfigJSON struct{ FilesystemConfig }

// Valid returns whether the [FilesystemConfigJSON] is valid.
func (f *FilesystemConfigJSON) Valid() bool {
	return f != nil && f.FilesystemConfig != nil && f.FilesystemConfig.Valid()
}

// fsType holds the string representation of a [FilesystemConfig]'s concrete type.
type fsType struct {
	Type string `json:"type"`
}

func (f *FilesystemConfigJSON) MarshalJSON() ([]byte, error) {
	if f == nil || f.FilesystemConfig == nil {
		return nil, ErrFSNull
	}
	var v any
	switch cv := f.FilesystemConfig.(type) {
	case *FSBind:
		v = &struct {
			fsType
			*FSBind
		}{fsType{FilesystemBind}, cv}

	case *FSEphemeral:
		v = &struct {
			fsType
			*FSEphemeral
		}{fsType{FilesystemEphemeral}, cv}

	case *FSOverlay:
		v = &struct {
			fsType
			*FSOverlay
		}{fsType{FilesystemOverlay}, cv}

	case *FSLink:
		v = &struct {
			fsType
			*FSLink
		}{fsType{FilesystemLink}, cv}

	default:
		return nil, FSImplError{f.FilesystemConfig}
	}

	return json.Marshal(v)
}

func (f *FilesystemConfigJSON) UnmarshalJSON(data []byte) error {
	t := new(fsType)
	if err := json.Unmarshal(data, &t); err != nil {
		return err
	}
	if t == nil {
		return ErrFSNull
	}
	switch t.Type {
	case FilesystemBind:
		*f = FilesystemConfigJSON{new(FSBind)}

	case FilesystemEphemeral:
		*f = FilesystemConfigJSON{new(FSEphemeral)}

	case FilesystemOverlay:
		*f = FilesystemConfigJSON{new(FSOverlay)}

	case FilesystemLink:
		*f = FilesystemConfigJSON{new(FSLink)}

	default:
		return FSTypeError(t.Type)
	}

	return json.Unmarshal(data, f.FilesystemConfig)
}