Skip to content

Commit eece880

Browse files
committed
feat: implement storage mount/unmount as well as storage ensure
1 parent 51df711 commit eece880

2 files changed

Lines changed: 209 additions & 0 deletions

File tree

tasks/storage_ensure_task.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package tasks
2+
3+
import (
4+
"errors"
5+
"omakase/subprocess"
6+
)
7+
8+
type StorageEnsureTask struct {
9+
App string `required:"true" yaml:"app"`
10+
Chown string `required:"false" yaml:"chown"`
11+
State string `required:"true" yaml:"state" default:"present"`
12+
}
13+
14+
func (t StorageEnsureTask) DesiredState() string {
15+
return t.State
16+
}
17+
18+
func (t StorageEnsureTask) Execute() TaskOutputState {
19+
funcMap := map[string]func(string, string) TaskOutputState{
20+
"present": ensureStorage,
21+
"absent": removeStorage,
22+
}
23+
24+
fn := funcMap[t.State]
25+
return fn(t.App, t.Chown)
26+
}
27+
28+
func ensureStorage(app, chown string) TaskOutputState {
29+
state := TaskOutputState{
30+
Changed: false,
31+
State: "absent",
32+
}
33+
34+
// ensure chown is a valid value
35+
chownValues := map[string]bool{
36+
"heroku": true,
37+
"herokuish": true,
38+
"packeto": true,
39+
"root": true,
40+
"false": true,
41+
}
42+
if !chownValues[chown] {
43+
state.Error = errors.New("invalid chown value specified")
44+
return state
45+
}
46+
47+
// todo: implement a check to see if the folder exists?
48+
result, err := subprocess.CallExecCommand(subprocess.ExecCommandInput{
49+
Command: "dokku",
50+
Args: []string{
51+
"--quiet",
52+
"storage:ensure-directory",
53+
"--chown",
54+
chown,
55+
app,
56+
},
57+
})
58+
if err != nil {
59+
state.Error = err
60+
state.Message = result.StderrContents()
61+
return state
62+
}
63+
64+
state.Changed = true
65+
state.State = "present"
66+
return state
67+
}
68+
69+
func removeStorage(app, chown string) TaskOutputState {
70+
state := TaskOutputState{
71+
Changed: false,
72+
State: "absent",
73+
}
74+
75+
state.Error = errors.New("the absent state is not supported for storage:ensure")
76+
return state
77+
}
78+
79+
func init() {
80+
RegisterTask(&StorageEnsureTask{})
81+
}

tasks/storage_mount_task.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package tasks
2+
3+
import (
4+
"fmt"
5+
"omakase/subprocess"
6+
)
7+
8+
type StorageMountTask struct {
9+
App string `required:"true" yaml:"app"`
10+
HostDir string `required:"true" yaml:"host_dir"`
11+
ContainerDir string `required:"true" yaml:"container_dir"`
12+
State string `required:"true" yaml:"state" default:"present"`
13+
}
14+
15+
func (t StorageMountTask) DesiredState() string {
16+
return t.State
17+
}
18+
19+
func (t StorageMountTask) Execute() TaskOutputState {
20+
funcMap := map[string]func(string, string, string) TaskOutputState{
21+
"present": mountStorage,
22+
"absent": unmountStorage,
23+
}
24+
25+
fn := funcMap[t.State]
26+
return fn(t.App, t.HostDir, t.ContainerDir)
27+
}
28+
29+
func mountExists(app, hostDir, containerDir string) bool {
30+
result, err := subprocess.CallExecCommand(subprocess.ExecCommandInput{
31+
Command: "dokku",
32+
Args: []string{
33+
"--quiet",
34+
"storage:list",
35+
app,
36+
"--format",
37+
"json",
38+
},
39+
})
40+
if err != nil {
41+
return false
42+
}
43+
44+
var mounts []struct {
45+
HostDir string `json:"host_dir"`
46+
ContainerDir string `json:"container_dir"`
47+
}
48+
49+
err = json.Unmarshal(result.StdoutBytes(), &mounts)
50+
if err != nil {
51+
return false
52+
}
53+
54+
for _, mount := range mounts {
55+
if mount.HostDir == hostDir && mount.ContainerDir == containerDir {
56+
return true
57+
}
58+
}
59+
return false
60+
}
61+
62+
func mountStorage(app, hostDir, containerDir string) TaskOutputState {
63+
state := TaskOutputState{
64+
Changed: false,
65+
State: "present",
66+
}
67+
68+
// check if the mount already exists
69+
if mountExists(app, hostDir, containerDir) {
70+
state.Changed = false
71+
state.State = "present"
72+
return state
73+
}
74+
75+
result, err := subprocess.CallExecCommand(subprocess.ExecCommandInput{
76+
Command: "dokku",
77+
Args: []string{
78+
"--quiet",
79+
"storage:mount",
80+
app,
81+
fmt.Sprintf("%s:%s", hostDir, containerDir),
82+
},
83+
})
84+
if err != nil {
85+
state.Error = err
86+
state.Message = result.StderrContents()
87+
return state
88+
}
89+
90+
state.Changed = true
91+
state.State = "present"
92+
return state
93+
}
94+
95+
func unmountStorage(app, hostDir, containerDir string) TaskOutputState {
96+
state := TaskOutputState{
97+
Changed: false,
98+
State: "present",
99+
}
100+
if !mountExists(app, hostDir, containerDir) {
101+
state.Changed = false
102+
state.State = "absent"
103+
return state
104+
}
105+
106+
result, err := subprocess.CallExecCommand(subprocess.ExecCommandInput{
107+
Command: "dokku",
108+
Args: []string{
109+
"--quiet",
110+
"storage:unmount",
111+
app,
112+
fmt.Sprintf("%s:%s", hostDir, containerDir),
113+
},
114+
})
115+
if err != nil {
116+
state.Error = err
117+
state.Message = result.StderrContents()
118+
return state
119+
}
120+
121+
state.Changed = true
122+
state.State = "absent"
123+
return state
124+
}
125+
126+
func init() {
127+
RegisterTask(&StorageMountTask{})
128+
}

0 commit comments

Comments
 (0)