Skip to content

Commit 8e341d5

Browse files
author
Mathieu Velten
authored
Some changes to calls to Docker API for podman compatibility (#463)
* Some changes to calls to Docker API for podman compatibility - Podman expects the network name specified when launching the container to match the name used when creating it, the spec is unclear about that - Adding labels when commiting a container needs to use "changes" query parameter instead of a POST json, which is unspecified - Add a config to specify the hostname where Complement on the host can be reached when inside a container. Podman uses "host.containers.internal" * Use quotes for labels in changes * Remove debug + makes AS labels on one line * Remove hack, not needed anymore * a bit too far... * Add doc * Convert from labels to changes * Replace networkID by networkName * Add/fix some comments * Add comments
1 parent 1a83f79 commit 8e341d5

File tree

9 files changed

+80
-59
lines changed

9 files changed

+80
-59
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ If you are using [ufw](https://code.launchpad.net/ufw), this can be done with:
5454
sudo ufw allow in on br-+
5555
```
5656

57+
### Running using Podman
58+
59+
It is possible to run the test suite using Podman and the compatibility layer for Docker API.
60+
Rootless mode is also supported.
61+
62+
To do so you should:
63+
- `systemctl --user start podman.service` to start the rootless API daemon (can also be enabled).
64+
- `DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock BUILDAH_FORMAT=docker COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT=host.containers.internal ...`
65+
66+
Docker image format is needed because OCI format doesn't support the HEALTHCHECK directive unfortunately.
67+
5768
### Running against Dendrite
5869

5970
For instance, for Dendrite:

internal/config/config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ type Complement struct {
7878
CAPrivateKey *rsa.PrivateKey
7979

8080
BestEffort bool
81+
82+
// Name: COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT
83+
// Default: host.docker.internal
84+
// Description: The hostname of Complement from the perspective of a Homeserver running inside a container.
85+
// This can be useful for container runtimes using another hostname to access the host from a container,
86+
// like Podman that uses `host.containers.internal` instead.
87+
HostnameRunningComplement string
8188
}
8289

8390
var hsRegex = regexp.MustCompile(`COMPLEMENT_BASE_IMAGE_(.+)=(.+)$`)
@@ -129,6 +136,13 @@ func NewConfigFromEnvVars(pkgNamespace, baseImageURI string) *Complement {
129136
panic("package namespace must be set")
130137
}
131138

139+
HostnameRunningComplement := os.Getenv("COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT")
140+
if HostnameRunningComplement != "" {
141+
cfg.HostnameRunningComplement = HostnameRunningComplement
142+
} else {
143+
cfg.HostnameRunningComplement = "host.docker.internal"
144+
}
145+
132146
return cfg
133147
}
134148

internal/docker/builder.go

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"time"
2222

2323
"github.com/docker/docker/api/types"
24-
"github.com/docker/docker/api/types/container"
2524
"github.com/docker/docker/client"
2625
"github.com/docker/docker/pkg/stdcopy"
2726
"github.com/docker/go-connections/nat"
@@ -32,8 +31,6 @@ import (
3231
)
3332

3433
var (
35-
// HostnameRunningComplement is the hostname of Complement from the perspective of a Homeserver.
36-
HostnameRunningComplement = "host.docker.internal"
3734
// HostnameRunningDocker is the hostname of the docker daemon from the perspective of Complement.
3835
HostnameRunningDocker = "localhost"
3936
)
@@ -240,15 +237,15 @@ func (d *Builder) ConstructBlueprint(bprint b.Blueprint) error {
240237
func (d *Builder) construct(bprint b.Blueprint) (errs []error) {
241238
d.log("Constructing blueprint '%s'", bprint.Name)
242239

243-
networkID, err := createNetworkIfNotExists(d.Docker, d.Config.PackageNamespace, bprint.Name)
240+
networkName, err := createNetworkIfNotExists(d.Docker, d.Config.PackageNamespace, bprint.Name)
244241
if err != nil {
245242
return []error{err}
246243
}
247244

248245
runner := instruction.NewRunner(bprint.Name, d.Config.BestEffort, d.Config.DebugLoggingEnabled)
249246
results := make([]result, len(bprint.Homeservers))
250247
for i, hs := range bprint.Homeservers {
251-
res := d.constructHomeserver(bprint.Name, runner, hs, networkID)
248+
res := d.constructHomeserver(bprint.Name, runner, hs, networkName)
252249
if res.err != nil {
253250
errs = append(errs, res.err)
254251
if res.containerID != "" {
@@ -336,9 +333,7 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) {
336333
Author: "Complement",
337334
Pause: true,
338335
Reference: "localhost/complement:" + res.contextStr,
339-
Config: &container.Config{
340-
Labels: labels,
341-
},
336+
Changes: toChanges(labels),
342337
})
343338
if err != nil {
344339
d.log("%s : failed to ContainerCommit: %s\n", res.contextStr, err)
@@ -351,11 +346,22 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) {
351346
return errs
352347
}
353348

349+
// Convert a map of labels to a list of changes directive in Dockerfile format.
350+
// Labels keys and values can't be multiline (eg. can't contain `\n` character)
351+
// neither can they contain unescaped `"` character.
352+
func toChanges(labels map[string]string) []string {
353+
var changes []string
354+
for k, v := range labels {
355+
changes = append(changes, fmt.Sprintf("LABEL \"%s\"=\"%s\"", k, v))
356+
}
357+
return changes
358+
}
359+
354360
// construct this homeserver and execute its instructions, keeping the container alive.
355-
func (d *Builder) constructHomeserver(blueprintName string, runner *instruction.Runner, hs b.Homeserver, networkID string) result {
361+
func (d *Builder) constructHomeserver(blueprintName string, runner *instruction.Runner, hs b.Homeserver, networkName string) result {
356362
contextStr := fmt.Sprintf("%s.%s.%s", d.Config.PackageNamespace, blueprintName, hs.Name)
357363
d.log("%s : constructing homeserver...\n", contextStr)
358-
dep, err := d.deployBaseImage(blueprintName, hs, contextStr, networkID)
364+
dep, err := d.deployBaseImage(blueprintName, hs, contextStr, networkName)
359365
if err != nil {
360366
log.Printf("%s : failed to deployBaseImage: %s\n", contextStr, err)
361367
containerID := ""
@@ -383,7 +389,7 @@ func (d *Builder) constructHomeserver(blueprintName string, runner *instruction.
383389
}
384390

385391
// deployBaseImage runs the base image and returns the baseURL, containerID or an error.
386-
func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, contextStr, networkID string) (*HomeserverDeployment, error) {
392+
func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, contextStr, networkName string) (*HomeserverDeployment, error) {
387393
asIDToRegistrationMap := asIDToRegistrationFromLabels(labelsForApplicationServices(hs))
388394
var baseImageURI string
389395
if hs.BaseImageURI == nil {
@@ -399,28 +405,29 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context
399405
return deployImage(
400406
d.Docker, baseImageURI, fmt.Sprintf("complement_%s", contextStr),
401407
d.Config.PackageNamespace, blueprintName, hs.Name, asIDToRegistrationMap, contextStr,
402-
networkID, d.Config,
408+
networkName, d.Config,
403409
)
404410
}
405411

412+
// Multilines label using Dockerfile syntax is unsupported, let's inline \n instead
406413
func generateASRegistrationYaml(as b.ApplicationService) string {
407-
return fmt.Sprintf("id: %s\n", as.ID) +
408-
fmt.Sprintf("hs_token: %s\n", as.HSToken) +
409-
fmt.Sprintf("as_token: %s\n", as.ASToken) +
410-
fmt.Sprintf("url: '%s'\n", as.URL) +
411-
fmt.Sprintf("sender_localpart: %s\n", as.SenderLocalpart) +
412-
fmt.Sprintf("rate_limited: %v\n", as.RateLimited) +
413-
"namespaces:\n" +
414-
" users:\n" +
415-
" - exclusive: false\n" +
416-
" regex: .*\n" +
417-
" rooms: []\n" +
418-
" aliases: []\n"
414+
return fmt.Sprintf("id: %s\\n", as.ID) +
415+
fmt.Sprintf("hs_token: %s\\n", as.HSToken) +
416+
fmt.Sprintf("as_token: %s\\n", as.ASToken) +
417+
fmt.Sprintf("url: '%s'\\n", as.URL) +
418+
fmt.Sprintf("sender_localpart: %s\\n", as.SenderLocalpart) +
419+
fmt.Sprintf("rate_limited: %v\\n", as.RateLimited) +
420+
"namespaces:\\n" +
421+
" users:\\n" +
422+
" - exclusive: false\\n" +
423+
" regex: .*\\n" +
424+
" rooms: []\\n" +
425+
" aliases: []\\n"
419426
}
420427

421-
// createNetworkIfNotExists creates a docker network and returns its id.
422-
// ID is guaranteed not to be empty when err == nil
423-
func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkID string, err error) {
428+
// createNetworkIfNotExists creates a docker network and returns its name.
429+
// Name is guaranteed not to be empty when err == nil
430+
func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkName string, err error) {
424431
// check if a network already exists for this blueprint
425432
nws, err := docker.NetworkList(context.Background(), types.NetworkListOptions{
426433
Filters: label(
@@ -436,10 +443,11 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName
436443
if len(nws) > 1 {
437444
log.Printf("WARNING: createNetworkIfNotExists got %d networks for pkg=%s blueprint=%s", len(nws), pkgNamespace, blueprintName)
438445
}
439-
return nws[0].ID, nil
446+
return nws[0].Name, nil
440447
}
448+
networkName = "complement_" + pkgNamespace + "_" + blueprintName
441449
// make a user-defined network so we get DNS based on the container name
442-
nw, err := docker.NetworkCreate(context.Background(), "complement_"+pkgNamespace+"_"+blueprintName, types.NetworkCreate{
450+
nw, err := docker.NetworkCreate(context.Background(), networkName, types.NetworkCreate{
443451
Labels: map[string]string{
444452
complementLabel: blueprintName,
445453
"complement_blueprint": blueprintName,
@@ -458,7 +466,7 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName
458466
if nw.ID == "" {
459467
return "", fmt.Errorf("%s: unexpected empty ID while creating networkID", blueprintName)
460468
}
461-
return nw.ID, nil
469+
return networkName, nil
462470
}
463471

464472
func printLogs(docker *client.Client, containerID, contextStr string) {

internal/docker/deployer.go

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
66
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
7+
// http://www.apache.org/licenses/LICENSE-2.0
88
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -49,7 +49,6 @@ type Deployer struct {
4949
DeployNamespace string
5050
Docker *client.Client
5151
Counter int
52-
networkID string
5352
debugLogging bool
5453
config *config.Complement
5554
}
@@ -93,11 +92,10 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen
9392
if len(images) == 0 {
9493
return nil, fmt.Errorf("Deploy: No images have been built for blueprint %s", blueprintName)
9594
}
96-
networkID, err := createNetworkIfNotExists(d.Docker, d.config.PackageNamespace, blueprintName)
95+
networkName, err := createNetworkIfNotExists(d.Docker, d.config.PackageNamespace, blueprintName)
9796
if err != nil {
9897
return nil, fmt.Errorf("Deploy: %w", err)
9998
}
100-
d.networkID = networkID
10199

102100
// deploy images in parallel
103101
var mu sync.Mutex // protects mutable values like the counter and errors
@@ -116,7 +114,7 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen
116114
// TODO: Make CSAPI port configurable
117115
deployment, err := deployImage(
118116
d.Docker, img.ID, fmt.Sprintf("complement_%s_%s_%s_%d", d.config.PackageNamespace, d.DeployNamespace, contextStr, counter),
119-
d.config.PackageNamespace, blueprintName, hsName, asIDToRegistrationMap, contextStr, networkID, d.config,
117+
d.config.PackageNamespace, blueprintName, hsName, asIDToRegistrationMap, contextStr, networkName, d.config,
120118
)
121119
if err != nil {
122120
if deployment != nil && deployment.ContainerID != "" {
@@ -184,14 +182,6 @@ func (d *Deployer) Restart(hsDep *HomeserverDeployment, cfg *config.Complement)
184182
return fmt.Errorf("Restart: Failed to stop container %s: %s", hsDep.ContainerID, err)
185183
}
186184

187-
// Remove the container from the network. If we don't do this,
188-
// (re)starting the container fails with an error like
189-
// "Error response from daemon: endpoint with name complement_fed_1_fed.alice.hs1_1 already exists in network complement_fed_alice".
190-
err = d.Docker.NetworkDisconnect(ctx, d.networkID, hsDep.ContainerID, false)
191-
if err != nil {
192-
return fmt.Errorf("Restart: Failed to disconnect container %s: %s", hsDep.ContainerID, err)
193-
}
194-
195185
err = d.Docker.ContainerStart(ctx, hsDep.ContainerID, types.ContainerStartOptions{})
196186
if err != nil {
197187
return fmt.Errorf("Restart: Failed to start container %s: %s", hsDep.ContainerID, err)
@@ -216,7 +206,7 @@ func (d *Deployer) Restart(hsDep *HomeserverDeployment, cfg *config.Complement)
216206
// nolint
217207
func deployImage(
218208
docker *client.Client, imageID string, containerName, pkgNamespace, blueprintName, hsName string,
219-
asIDToRegistrationMap map[string]string, contextStr, networkID string, cfg *config.Complement,
209+
asIDToRegistrationMap map[string]string, contextStr, networkName string, cfg *config.Complement,
220210
) (*HomeserverDeployment, error) {
221211
ctx := context.Background()
222212
var extraHosts []string
@@ -283,9 +273,8 @@ func deployImage(
283273
Mounts: mounts,
284274
}, &network.NetworkingConfig{
285275
EndpointsConfig: map[string]*network.EndpointSettings{
286-
contextStr: {
287-
NetworkID: networkID,
288-
Aliases: []string{hsName},
276+
networkName: {
277+
Aliases: []string{hsName},
289278
},
290279
},
291280
}, nil, containerName)
@@ -298,7 +287,7 @@ func deployImage(
298287

299288
containerID := body.ID
300289
if cfg.DebugLoggingEnabled {
301-
log.Printf("%s: Created container '%s' using image '%s' on network '%s'", contextStr, containerID, imageID, networkID)
290+
log.Printf("%s: Created container '%s' using image '%s' on network '%s'", contextStr, containerID, imageID, networkName)
302291
}
303292
stubDeployment := &HomeserverDeployment{
304293
ContainerID: containerID,

internal/docker/labels.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ func asIDToRegistrationFromLabels(labels map[string]string) map[string]string {
3333
asMap := make(map[string]string)
3434
for k, v := range labels {
3535
if strings.HasPrefix(k, "application_service_") {
36-
asMap[strings.TrimPrefix(k, "application_service_")] = v
36+
// cf comment of generateASRegistrationYaml for ReplaceAll explanation
37+
asMap[strings.TrimPrefix(k, "application_service_")] = strings.ReplaceAll(v, "\\n", "\n")
3738
}
3839
}
3940
return asMap

internal/federation/server.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func NewServer(t *testing.T, deployment *docker.Deployment, opts ...func(*Server
6969
mux: mux.NewRouter(),
7070
// The server name will be updated when the caller calls Listen() to include the port number
7171
// of the HTTP server e.g "host.docker.internal:56353"
72-
serverName: docker.HostnameRunningComplement,
72+
serverName: deployment.Config.HostnameRunningComplement,
7373
rooms: make(map[string]*ServerRoom),
7474
aliases: make(map[string]string),
7575
UnexpectedRequestsAreErrors: true,
@@ -476,10 +476,10 @@ func federationServer(cfg *config.Complement, h http.Handler) (*http.Server, str
476476
Locality: []string{"London"},
477477
StreetAddress: []string{"123 Street"},
478478
PostalCode: []string{"12345"},
479-
CommonName: docker.HostnameRunningComplement,
479+
CommonName: cfg.HostnameRunningComplement,
480480
},
481481
}
482-
host := docker.HostnameRunningComplement
482+
host := cfg.HostnameRunningComplement
483483
if ip := net.ParseIP(host); ip != nil {
484484
template.IPAddresses = append(template.IPAddresses, ip)
485485
} else {

internal/federation/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
)
1212

1313
func TestComplementServerIsSigned(t *testing.T) {
14-
docker.HostnameRunningComplement = "localhost"
1514
cfg := config.NewConfigFromEnvVars("test", "unimportant")
15+
cfg.HostnameRunningComplement = "localhost"
1616
srv := NewServer(t, &docker.Deployment{
1717
Config: cfg,
1818
})

tests/federation_room_join_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919

2020
"github.com/matrix-org/complement/internal/b"
2121
"github.com/matrix-org/complement/internal/client"
22-
"github.com/matrix-org/complement/internal/docker"
2322
"github.com/matrix-org/complement/internal/federation"
2423
"github.com/matrix-org/complement/internal/match"
2524
"github.com/matrix-org/complement/internal/must"
@@ -157,7 +156,7 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) {
157156
},
158157
})
159158
newSignaturesBlock := map[string]interface{}{
160-
docker.HostnameRunningComplement: map[string]string{
159+
deployment.Config.HostnameRunningComplement: map[string]string{
161160
string(srv.KeyID): "/3z+pJjiJXWhwfqIEzmNksvBHCoXTktK/y0rRuWJXw6i1+ygRG/suDCKhFuuz6gPapRmEMPVILi2mJqHHXPKAg",
162161
},
163162
}
@@ -186,7 +185,7 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) {
186185
},
187186
})
188187
newSignaturesBlock := map[string]interface{}{
189-
docker.HostnameRunningComplement: map[string]string{
188+
deployment.Config.HostnameRunningComplement: map[string]string{
190189
string(srv.KeyID) + "bogus": "/3z+pJjiJXWhwfqIEzmNksvBHCoXTktK/y0rRuWJXw6i1+ygRG/suDCKhFuuz6gPapRmEMPVILi2mJqHHXPKAg",
191190
},
192191
}
@@ -216,7 +215,7 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) {
216215
},
217216
}).JSON()
218217
rawSig, err := json.Marshal(map[string]interface{}{
219-
docker.HostnameRunningComplement: map[string]string{
218+
deployment.Config.HostnameRunningComplement: map[string]string{
220219
string(srv.KeyID): "/3z+pJjiJXWhwfqIEzmNksvBHCoXTktK/y0rRuWJXw6i1+ygRG/suDCKhFuuz6gPapRmEMPVILi2mJqHHXPKAg",
221220
},
222221
})

tests/federation_room_send_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/matrix-org/gomatrixserverlib"
88

99
"github.com/matrix-org/complement/internal/b"
10-
"github.com/matrix-org/complement/internal/docker"
1110
"github.com/matrix-org/complement/internal/federation"
1211
)
1312

@@ -53,7 +52,7 @@ func TestOutboundFederationSend(t *testing.T) {
5352
roomAlias := srv.MakeAliasMapping("flibble", serverRoom.RoomID)
5453

5554
// the local homeserver joins the room
56-
alice.JoinRoom(t, roomAlias, []string{docker.HostnameRunningComplement})
55+
alice.JoinRoom(t, roomAlias, []string{deployment.Config.HostnameRunningComplement})
5756

5857
// the local homeserver sends an event into the room
5958
alice.SendEventSynced(t, serverRoom.RoomID, b.Event{

0 commit comments

Comments
 (0)