Skip to content

Commit 019c0ff

Browse files
authored
Support host mounts via COMPLEMENT_HOST_MOUNTS (#312)
* Support host mounts via COMPLEMENT_HOST_MOUNTS Usage: ``` COMPLEMENT_HOST_MOUNTS='/my/local/dir:/container/dir;/another/local/dir:/container/dir2:ro' which is: - /my/local/dir:/container/dir - /another/local/dir:/container/dir2:ro which is of the form: - HOST:CONTAINER[:ro] where :ro makes the mount read-only. ``` * Host mounts are optional * Add host mounts to README
1 parent b0e1b55 commit 019c0ff

File tree

4 files changed

+73
-0
lines changed

4 files changed

+73
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,26 @@ If you're looking to run against a custom Dockerfile, it must meet the following
7777
- The homeserver needs to accept the server name given by the environment variable `SERVER_NAME` at runtime.
7878
- The homeserver needs to assume dockerfile `CMD` or `ENTRYPOINT` instructions will be run multiple times.
7979

80+
81+
### Developing locally
82+
83+
If you want to write Complement tests _and_ hack on a homeserver implementation at the same time it can be very awkward
84+
to have to `docker build` the image all the time. To resolve this, Complement support "host mounts" which mount a directory
85+
from the host to the container. This is set via `COMPLEMENT_HOST_MOUNTS`:
86+
87+
```
88+
COMPLEMENT_HOST_MOUNTS='/my/local/dir:/container/dir;/another/local/dir:/container/dir2:ro'
89+
90+
which is:
91+
- /my/local/dir:/container/dir
92+
- /another/local/dir:/container/dir2:ro
93+
94+
which is of the form:
95+
- HOST:CONTAINER[:ro] where :ro makes the mount read-only.
96+
```
97+
98+
For example, for Dendrite: `COMPLEMENT_HOST_MOUNTS='/your/local/dendrite:/dendrite:ro;/your/go/path:/go:ro'`.
99+
80100
### Getting prettier output
81101

82102
The default output isn't particularly nice to read. You can use [gotestfmt](https://github.com/haveyoudebuggedit/gotestfmt)

internal/config/config.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import (
1515
"time"
1616
)
1717

18+
type HostMount struct {
19+
HostPath string
20+
ContainerPath string
21+
ReadOnly bool
22+
}
23+
1824
type Complement struct {
1925
BaseImageURI string
2026
BaseImageArgs []string
@@ -23,6 +29,7 @@ type Complement struct {
2329
BestEffort bool
2430
SpawnHSTimeout time.Duration
2531
KeepBlueprints []string
32+
HostMounts []HostMount
2633
// The namespace for all complement created blueprints and deployments
2734
PackageNamespace string
2835
// Certificate Authority generated values for this run of complement. Homeservers will use this
@@ -44,6 +51,14 @@ func NewConfigFromEnvVars() *Complement {
4451
cfg.SpawnHSTimeout = time.Duration(50*parseEnvWithDefault("COMPLEMENT_VERSION_CHECK_ITERATIONS", 100)) * time.Millisecond
4552
}
4653
cfg.KeepBlueprints = strings.Split(os.Getenv("COMPLEMENT_KEEP_BLUEPRINTS"), " ")
54+
var err error
55+
hostMounts := os.Getenv("COMPLEMENT_HOST_MOUNTS")
56+
if hostMounts != "" {
57+
cfg.HostMounts, err = newHostMounts(strings.Split(hostMounts, ";"))
58+
if err != nil {
59+
panic("COMPLEMENT_HOST_MOUNTS parse error: " + err.Error())
60+
}
61+
}
4762
if cfg.BaseImageURI == "" {
4863
panic("COMPLEMENT_BASE_IMAGE must be set")
4964
}
@@ -95,6 +110,26 @@ func parseEnvWithDefault(key string, def int) int {
95110
return def
96111
}
97112

113+
func newHostMounts(mounts []string) ([]HostMount, error) {
114+
var hostMounts []HostMount
115+
for _, m := range mounts {
116+
segments := strings.Split(m, ":")
117+
if len(segments) < 2 {
118+
return nil, fmt.Errorf("mount '%s' malformed", m)
119+
}
120+
var ro string
121+
if len(segments) == 3 {
122+
ro = segments[2]
123+
}
124+
hostMounts = append(hostMounts, HostMount{
125+
HostPath: segments[0],
126+
ContainerPath: segments[1],
127+
ReadOnly: ro == "ro" || ro == "readonly",
128+
})
129+
}
130+
return hostMounts, nil
131+
}
132+
98133
// Generate a certificate and private key
99134
func generateCAValues() (*x509.Certificate, *rsa.PrivateKey, error) {
100135
// valid for 10 years

internal/docker/builder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,19 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) {
252252
// something went wrong, but we have a container which may have interesting logs
253253
printLogs(d.Docker, res.containerID, res.contextStr)
254254
}
255+
if delErr := d.Docker.ContainerRemove(context.Background(), res.containerID, types.ContainerRemoveOptions{
256+
Force: true,
257+
}); delErr != nil {
258+
d.log("%s: failed to remove container which failed to deploy: %s", res.contextStr, delErr)
259+
}
255260
}
256261
// kill the container
257262
defer func(r result) {
258263
killErr := d.Docker.ContainerKill(context.Background(), r.containerID, "KILL")
259264
if killErr != nil {
260265
d.log("%s : Failed to kill container %s: %s\n", r.contextStr, r.containerID, killErr)
261266
}
267+
262268
}(res)
263269
results[i] = res
264270
}

internal/docker/deployer.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ func deployImage(
180180
extraHosts = []string{HostnameRunningComplement + ":172.17.0.1"}
181181
}
182182

183+
for _, m := range cfg.HostMounts {
184+
mounts = append(mounts, mount.Mount{
185+
Source: m.HostPath,
186+
Target: m.ContainerPath,
187+
ReadOnly: m.ReadOnly,
188+
Type: mount.TypeBind,
189+
})
190+
}
191+
if len(mounts) > 0 {
192+
log.Printf("Using host mounts: %+v", mounts)
193+
}
194+
183195
env := []string{
184196
"SERVER_NAME=" + hsName,
185197
}

0 commit comments

Comments
 (0)