From ed54884074d254cd9a33f8c512f21635c4affe6c Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Thu, 15 Sep 2022 11:19:16 +0200 Subject: [PATCH 01/10] 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" --- internal/config/config.go | 10 ++++++++++ internal/docker/builder.go | 17 ++++++----------- internal/docker/deployer.go | 4 ++-- internal/federation/server.go | 6 +++--- internal/federation/server_test.go | 2 +- tests/federation_room_join_test.go | 7 +++---- tests/federation_room_send_test.go | 3 +-- 7 files changed, 26 insertions(+), 23 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 698e551c..6c044632 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -78,6 +78,9 @@ type Complement struct { CAPrivateKey *rsa.PrivateKey BestEffort bool + + // The hostname of Complement from the perspective of a Homeserver running inside a container + HostnameRunningComplement string } var hsRegex = regexp.MustCompile(`COMPLEMENT_BASE_IMAGE_(.+)=(.+)$`) @@ -129,6 +132,13 @@ func NewConfigFromEnvVars(pkgNamespace, baseImageURI string) *Complement { panic("package namespace must be set") } + HostnameRunningComplement := os.Getenv("COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT") + if HostnameRunningComplement != "" { + cfg.HostnameRunningComplement = HostnameRunningComplement + } else { + cfg.HostnameRunningComplement = "host.docker.internal" + } + return cfg } diff --git a/internal/docker/builder.go b/internal/docker/builder.go index 90488079..d3b3ccee 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -21,7 +21,6 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/go-connections/nat" @@ -32,8 +31,6 @@ import ( ) var ( - // HostnameRunningComplement is the hostname of Complement from the perspective of a Homeserver. - HostnameRunningComplement = "host.docker.internal" // HostnameRunningDocker is the hostname of the docker daemon from the perspective of Complement. HostnameRunningDocker = "localhost" ) @@ -292,32 +289,32 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { continue } // collect and store access tokens as labels 'access_token_$userid: $token' - labels := make(map[string]string) + changes := make([]string, 0) accessTokens := runner.AccessTokens(res.homeserver.Name) if len(bprint.KeepAccessTokensForUsers) > 0 { // only keep access tokens for specified users for _, userID := range bprint.KeepAccessTokensForUsers { tok, ok := accessTokens[userID] if ok { - labels["access_token_"+userID] = tok + changes = append(changes, fmt.Sprintf("LABEL %s=%s", "access_token_"+userID, tok)) } } } else { // keep all tokens for k, v := range accessTokens { - labels["access_token_"+k] = v + changes = append(changes, fmt.Sprintf("LABEL %s=%s", "access_token_"+k, v)) } } deviceIDs := runner.DeviceIDs(res.homeserver.Name) for userID, deviceID := range deviceIDs { - labels["device_id"+userID] = deviceID + changes = append(changes, fmt.Sprintf("LABEL %s=%s", "device_id"+userID, deviceID)) } // Combine the labels for tokens and application services asLabels := labelsForApplicationServices(res.homeserver) for k, v := range asLabels { - labels[k] = v + changes = append(changes, fmt.Sprintf("LABEL %s=%s", k, v)) } // Stop the container before we commit it. @@ -336,9 +333,7 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { Author: "Complement", Pause: true, Reference: "localhost/complement:" + res.contextStr, - Config: &container.Config{ - Labels: labels, - }, + Changes: changes, }) if err != nil { d.log("%s : failed to ContainerCommit: %s\n", res.contextStr, err) diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index 3cc66deb..0a02dded 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -283,7 +283,7 @@ func deployImage( Mounts: mounts, }, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ - contextStr: { + "complement_" + pkgNamespace + "_" + blueprintName: { NetworkID: networkID, Aliases: []string{hsName}, }, diff --git a/internal/federation/server.go b/internal/federation/server.go index 71b519da..09a37aae 100644 --- a/internal/federation/server.go +++ b/internal/federation/server.go @@ -69,7 +69,7 @@ func NewServer(t *testing.T, deployment *docker.Deployment, opts ...func(*Server mux: mux.NewRouter(), // The server name will be updated when the caller calls Listen() to include the port number // of the HTTP server e.g "host.docker.internal:56353" - serverName: docker.HostnameRunningComplement, + serverName: deployment.Config.HostnameRunningComplement, rooms: make(map[string]*ServerRoom), aliases: make(map[string]string), UnexpectedRequestsAreErrors: true, @@ -476,10 +476,10 @@ func federationServer(cfg *config.Complement, h http.Handler) (*http.Server, str Locality: []string{"London"}, StreetAddress: []string{"123 Street"}, PostalCode: []string{"12345"}, - CommonName: docker.HostnameRunningComplement, + CommonName: cfg.HostnameRunningComplement, }, } - host := docker.HostnameRunningComplement + host := cfg.HostnameRunningComplement if ip := net.ParseIP(host); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { diff --git a/internal/federation/server_test.go b/internal/federation/server_test.go index 5ae81ee0..3a2b5840 100644 --- a/internal/federation/server_test.go +++ b/internal/federation/server_test.go @@ -11,8 +11,8 @@ import ( ) func TestComplementServerIsSigned(t *testing.T) { - docker.HostnameRunningComplement = "localhost" cfg := config.NewConfigFromEnvVars("test", "unimportant") + cfg.HostnameRunningComplement = "localhost" srv := NewServer(t, &docker.Deployment{ Config: cfg, }) diff --git a/tests/federation_room_join_test.go b/tests/federation_room_join_test.go index a4694770..2ecf40f9 100644 --- a/tests/federation_room_join_test.go +++ b/tests/federation_room_join_test.go @@ -19,7 +19,6 @@ import ( "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/client" - "github.com/matrix-org/complement/internal/docker" "github.com/matrix-org/complement/internal/federation" "github.com/matrix-org/complement/internal/match" "github.com/matrix-org/complement/internal/must" @@ -157,7 +156,7 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) { }, }) newSignaturesBlock := map[string]interface{}{ - docker.HostnameRunningComplement: map[string]string{ + deployment.Config.HostnameRunningComplement: map[string]string{ string(srv.KeyID): "/3z+pJjiJXWhwfqIEzmNksvBHCoXTktK/y0rRuWJXw6i1+ygRG/suDCKhFuuz6gPapRmEMPVILi2mJqHHXPKAg", }, } @@ -186,7 +185,7 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) { }, }) newSignaturesBlock := map[string]interface{}{ - docker.HostnameRunningComplement: map[string]string{ + deployment.Config.HostnameRunningComplement: map[string]string{ string(srv.KeyID) + "bogus": "/3z+pJjiJXWhwfqIEzmNksvBHCoXTktK/y0rRuWJXw6i1+ygRG/suDCKhFuuz6gPapRmEMPVILi2mJqHHXPKAg", }, } @@ -216,7 +215,7 @@ func TestJoinFederatedRoomWithUnverifiableEvents(t *testing.T) { }, }).JSON() rawSig, err := json.Marshal(map[string]interface{}{ - docker.HostnameRunningComplement: map[string]string{ + deployment.Config.HostnameRunningComplement: map[string]string{ string(srv.KeyID): "/3z+pJjiJXWhwfqIEzmNksvBHCoXTktK/y0rRuWJXw6i1+ygRG/suDCKhFuuz6gPapRmEMPVILi2mJqHHXPKAg", }, }) diff --git a/tests/federation_room_send_test.go b/tests/federation_room_send_test.go index 13f15ea5..fc4ed33a 100644 --- a/tests/federation_room_send_test.go +++ b/tests/federation_room_send_test.go @@ -7,7 +7,6 @@ import ( "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/complement/internal/b" - "github.com/matrix-org/complement/internal/docker" "github.com/matrix-org/complement/internal/federation" ) @@ -53,7 +52,7 @@ func TestOutboundFederationSend(t *testing.T) { roomAlias := srv.MakeAliasMapping("flibble", serverRoom.RoomID) // the local homeserver joins the room - alice.JoinRoom(t, roomAlias, []string{docker.HostnameRunningComplement}) + alice.JoinRoom(t, roomAlias, []string{deployment.Config.HostnameRunningComplement}) // the local homeserver sends an event into the room alice.SendEventSynced(t, serverRoom.RoomID, b.Event{ From 8a070631efeab4133993423f34e4a7e2933d42d4 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Fri, 16 Sep 2022 10:33:03 +0200 Subject: [PATCH 02/10] Use quotes for labels in changes --- internal/docker/builder.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/docker/builder.go b/internal/docker/builder.go index d3b3ccee..a5389677 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -296,25 +296,25 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { for _, userID := range bprint.KeepAccessTokensForUsers { tok, ok := accessTokens[userID] if ok { - changes = append(changes, fmt.Sprintf("LABEL %s=%s", "access_token_"+userID, tok)) + changes = appendLabelToChanges(changes, "access_token_"+userID, tok) } } } else { // keep all tokens for k, v := range accessTokens { - changes = append(changes, fmt.Sprintf("LABEL %s=%s", "access_token_"+k, v)) + changes = appendLabelToChanges(changes, "access_token_"+k, v) } } deviceIDs := runner.DeviceIDs(res.homeserver.Name) for userID, deviceID := range deviceIDs { - changes = append(changes, fmt.Sprintf("LABEL %s=%s", "device_id"+userID, deviceID)) + changes = appendLabelToChanges(changes, "device_id"+userID, deviceID) } // Combine the labels for tokens and application services asLabels := labelsForApplicationServices(res.homeserver) for k, v := range asLabels { - changes = append(changes, fmt.Sprintf("LABEL %s=%s", k, v)) + changes = appendLabelToChanges(changes, k, v) } // Stop the container before we commit it. @@ -328,6 +328,10 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { // Log again so we can see the timings. d.log("%s: Stopped container: %s", res.contextStr, res.containerID) + for _, c := range changes { + d.log(c) + } + // commit the container commit, err := d.Docker.ContainerCommit(context.Background(), res.containerID, types.ContainerCommitOptions{ Author: "Complement", @@ -346,6 +350,10 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { return errs } +func appendLabelToChanges(changes []string, key string, value string) []string { + return append(changes, fmt.Sprintf("LABEL \"%s\"=\"%s\"", key, value)) +} + // construct this homeserver and execute its instructions, keeping the container alive. func (d *Builder) constructHomeserver(blueprintName string, runner *instruction.Runner, hs b.Homeserver, networkID string) result { contextStr := fmt.Sprintf("%s.%s.%s", d.Config.PackageNamespace, blueprintName, hs.Name) From 9542f6cfa9cd22fb3a7e7a62b7193d012270c2fb Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Mon, 19 Sep 2022 11:16:17 +0200 Subject: [PATCH 03/10] Remove debug + makes AS labels on one line --- internal/docker/builder.go | 28 ++++++++++++---------------- internal/docker/labels.go | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/internal/docker/builder.go b/internal/docker/builder.go index a5389677..877f3504 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -328,10 +328,6 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { // Log again so we can see the timings. d.log("%s: Stopped container: %s", res.contextStr, res.containerID) - for _, c := range changes { - d.log(c) - } - // commit the container commit, err := d.Docker.ContainerCommit(context.Background(), res.containerID, types.ContainerCommitOptions{ Author: "Complement", @@ -407,18 +403,18 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context } func generateASRegistrationYaml(as b.ApplicationService) string { - return fmt.Sprintf("id: %s\n", as.ID) + - fmt.Sprintf("hs_token: %s\n", as.HSToken) + - fmt.Sprintf("as_token: %s\n", as.ASToken) + - fmt.Sprintf("url: '%s'\n", as.URL) + - fmt.Sprintf("sender_localpart: %s\n", as.SenderLocalpart) + - fmt.Sprintf("rate_limited: %v\n", as.RateLimited) + - "namespaces:\n" + - " users:\n" + - " - exclusive: false\n" + - " regex: .*\n" + - " rooms: []\n" + - " aliases: []\n" + return fmt.Sprintf("id: %s\\n", as.ID) + + fmt.Sprintf("hs_token: %s\\n", as.HSToken) + + fmt.Sprintf("as_token: %s\\n", as.ASToken) + + fmt.Sprintf("url: '%s'\\n", as.URL) + + fmt.Sprintf("sender_localpart: %s\\n", as.SenderLocalpart) + + fmt.Sprintf("rate_limited: %v\\n", as.RateLimited) + + "namespaces:\\n" + + " users:\\n" + + " - exclusive: false\\n" + + " regex: .*\\n" + + " rooms: []\\n" + + " aliases: []\\n" } // createNetworkIfNotExists creates a docker network and returns its id. diff --git a/internal/docker/labels.go b/internal/docker/labels.go index 424c1fa0..fa1b0d41 100644 --- a/internal/docker/labels.go +++ b/internal/docker/labels.go @@ -33,7 +33,7 @@ func asIDToRegistrationFromLabels(labels map[string]string) map[string]string { asMap := make(map[string]string) for k, v := range labels { if strings.HasPrefix(k, "application_service_") { - asMap[strings.TrimPrefix(k, "application_service_")] = v + asMap[strings.TrimPrefix(k, "application_service_")] = strings.Replace(v, "\\n", "\n", -1) } } return asMap From 107c3bc13fcbe73a4068949204170ef2a84f0de0 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Mon, 19 Sep 2022 11:50:21 +0200 Subject: [PATCH 04/10] Remove hack, not needed anymore --- internal/docker/deployer.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index 0a02dded..0df13e67 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -184,19 +184,6 @@ func (d *Deployer) Restart(hsDep *HomeserverDeployment, cfg *config.Complement) return fmt.Errorf("Restart: Failed to stop container %s: %s", hsDep.ContainerID, err) } - // Remove the container from the network. If we don't do this, - // (re)starting the container fails with an error like - // "Error response from daemon: endpoint with name complement_fed_1_fed.alice.hs1_1 already exists in network complement_fed_alice". - err = d.Docker.NetworkDisconnect(ctx, d.networkID, hsDep.ContainerID, false) - if err != nil { - return fmt.Errorf("Restart: Failed to disconnect container %s: %s", hsDep.ContainerID, err) - } - - err = d.Docker.ContainerStart(ctx, hsDep.ContainerID, types.ContainerStartOptions{}) - if err != nil { - return fmt.Errorf("Restart: Failed to start container %s: %s", hsDep.ContainerID, err) - } - // Wait for the container to be ready. baseURL, fedBaseURL, err := waitForPorts(ctx, d.Docker, hsDep.ContainerID) if err != nil { From 23bce5113ed366c2b5b04f96ef861d97a087df69 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Mon, 19 Sep 2022 12:23:19 +0200 Subject: [PATCH 05/10] a bit too far... --- internal/docker/deployer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index 0df13e67..da4efbff 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -184,6 +184,11 @@ func (d *Deployer) Restart(hsDep *HomeserverDeployment, cfg *config.Complement) return fmt.Errorf("Restart: Failed to stop container %s: %s", hsDep.ContainerID, err) } + err = d.Docker.ContainerStart(ctx, hsDep.ContainerID, types.ContainerStartOptions{}) + if err != nil { + return fmt.Errorf("Restart: Failed to start container %s: %s", hsDep.ContainerID, err) + } + // Wait for the container to be ready. baseURL, fedBaseURL, err := waitForPorts(ctx, d.Docker, hsDep.ContainerID) if err != nil { From 0162397f36f53d95763c98ce3eaa4b60b2ee0660 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Mon, 19 Sep 2022 14:08:42 +0200 Subject: [PATCH 06/10] Add doc --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 74edbee6..3bd6705c 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,17 @@ If you are using [ufw](https://code.launchpad.net/ufw), this can be done with: sudo ufw allow in on br-+ ``` +### Running using Podman + +It is possible to run the test suite using Podman and the compatibility layer for Docker API. +Rootless mode is also supported. + +To do so you should: +- `systemctl --user start podman.service` to start the rootless API daemon (can also be enabled). +- `DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock BUILDAH_FORMAT=docker COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT=host.containers.internal ...` + +Docker image format is needed because OCI format doesn't support the HEALTHCHECK directive unfortunately. + ### Running against Dendrite For instance, for Dendrite: From a60d5cc56c7ecf32fdf265663e20d6857eb6eaf0 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Tue, 20 Sep 2022 16:07:55 +0200 Subject: [PATCH 07/10] Convert from labels to changes --- internal/docker/builder.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/internal/docker/builder.go b/internal/docker/builder.go index 877f3504..81071e0f 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -289,32 +289,32 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { continue } // collect and store access tokens as labels 'access_token_$userid: $token' - changes := make([]string, 0) + labels := make(map[string]string) accessTokens := runner.AccessTokens(res.homeserver.Name) if len(bprint.KeepAccessTokensForUsers) > 0 { // only keep access tokens for specified users for _, userID := range bprint.KeepAccessTokensForUsers { tok, ok := accessTokens[userID] if ok { - changes = appendLabelToChanges(changes, "access_token_"+userID, tok) + labels["access_token_"+userID] = tok } } } else { // keep all tokens for k, v := range accessTokens { - changes = appendLabelToChanges(changes, "access_token_"+k, v) + labels["access_token_"+k] = v } } deviceIDs := runner.DeviceIDs(res.homeserver.Name) for userID, deviceID := range deviceIDs { - changes = appendLabelToChanges(changes, "device_id"+userID, deviceID) + labels["device_id"+userID] = deviceID } // Combine the labels for tokens and application services asLabels := labelsForApplicationServices(res.homeserver) for k, v := range asLabels { - changes = appendLabelToChanges(changes, k, v) + labels[k] = v } // Stop the container before we commit it. @@ -333,7 +333,7 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { Author: "Complement", Pause: true, Reference: "localhost/complement:" + res.contextStr, - Changes: changes, + Changes: toChanges(labels), }) if err != nil { d.log("%s : failed to ContainerCommit: %s\n", res.contextStr, err) @@ -346,8 +346,12 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { return errs } -func appendLabelToChanges(changes []string, key string, value string) []string { - return append(changes, fmt.Sprintf("LABEL \"%s\"=\"%s\"", key, value)) +func toChanges(labels map[string]string) []string { + changes := make([]string, 0) + for k, v := range labels { + changes = append(changes, fmt.Sprintf("LABEL \"%s\"=\"%s\"", k, v)) + } + return changes } // construct this homeserver and execute its instructions, keeping the container alive. From eb2bc15d560d1a6f452f3a369b93c09840501bcc Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Tue, 20 Sep 2022 16:20:24 +0200 Subject: [PATCH 08/10] Replace networkID by networkName --- internal/docker/builder.go | 21 +++++++++++---------- internal/docker/deployer.go | 15 ++++++--------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/internal/docker/builder.go b/internal/docker/builder.go index 81071e0f..47f2fe36 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -237,7 +237,7 @@ func (d *Builder) ConstructBlueprint(bprint b.Blueprint) error { func (d *Builder) construct(bprint b.Blueprint) (errs []error) { d.log("Constructing blueprint '%s'", bprint.Name) - networkID, err := createNetworkIfNotExists(d.Docker, d.Config.PackageNamespace, bprint.Name) + networkName, err := createNetworkIfNotExists(d.Docker, d.Config.PackageNamespace, bprint.Name) if err != nil { return []error{err} } @@ -245,7 +245,7 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { runner := instruction.NewRunner(bprint.Name, d.Config.BestEffort, d.Config.DebugLoggingEnabled) results := make([]result, len(bprint.Homeservers)) for i, hs := range bprint.Homeservers { - res := d.constructHomeserver(bprint.Name, runner, hs, networkID) + res := d.constructHomeserver(bprint.Name, runner, hs, networkName) if res.err != nil { errs = append(errs, res.err) if res.containerID != "" { @@ -355,10 +355,10 @@ func toChanges(labels map[string]string) []string { } // construct this homeserver and execute its instructions, keeping the container alive. -func (d *Builder) constructHomeserver(blueprintName string, runner *instruction.Runner, hs b.Homeserver, networkID string) result { +func (d *Builder) constructHomeserver(blueprintName string, runner *instruction.Runner, hs b.Homeserver, networkName string) result { contextStr := fmt.Sprintf("%s.%s.%s", d.Config.PackageNamespace, blueprintName, hs.Name) d.log("%s : constructing homeserver...\n", contextStr) - dep, err := d.deployBaseImage(blueprintName, hs, contextStr, networkID) + dep, err := d.deployBaseImage(blueprintName, hs, contextStr, networkName) if err != nil { log.Printf("%s : failed to deployBaseImage: %s\n", contextStr, err) containerID := "" @@ -386,7 +386,7 @@ func (d *Builder) constructHomeserver(blueprintName string, runner *instruction. } // deployBaseImage runs the base image and returns the baseURL, containerID or an error. -func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, contextStr, networkID string) (*HomeserverDeployment, error) { +func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, contextStr, networkName string) (*HomeserverDeployment, error) { asIDToRegistrationMap := asIDToRegistrationFromLabels(labelsForApplicationServices(hs)) var baseImageURI string if hs.BaseImageURI == nil { @@ -402,7 +402,7 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context return deployImage( d.Docker, baseImageURI, fmt.Sprintf("complement_%s", contextStr), d.Config.PackageNamespace, blueprintName, hs.Name, asIDToRegistrationMap, contextStr, - networkID, d.Config, + networkName, d.Config, ) } @@ -423,7 +423,7 @@ func generateASRegistrationYaml(as b.ApplicationService) string { // createNetworkIfNotExists creates a docker network and returns its id. // ID is guaranteed not to be empty when err == nil -func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkID string, err error) { +func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkName string, err error) { // check if a network already exists for this blueprint nws, err := docker.NetworkList(context.Background(), types.NetworkListOptions{ Filters: label( @@ -439,10 +439,11 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName if len(nws) > 1 { log.Printf("WARNING: createNetworkIfNotExists got %d networks for pkg=%s blueprint=%s", len(nws), pkgNamespace, blueprintName) } - return nws[0].ID, nil + return nws[0].Name, nil } + networkName = "complement_" + pkgNamespace + "_" + blueprintName // make a user-defined network so we get DNS based on the container name - nw, err := docker.NetworkCreate(context.Background(), "complement_"+pkgNamespace+"_"+blueprintName, types.NetworkCreate{ + nw, err := docker.NetworkCreate(context.Background(), networkName, types.NetworkCreate{ Labels: map[string]string{ complementLabel: blueprintName, "complement_blueprint": blueprintName, @@ -461,7 +462,7 @@ func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName if nw.ID == "" { return "", fmt.Errorf("%s: unexpected empty ID while creating networkID", blueprintName) } - return nw.ID, nil + return networkName, nil } func printLogs(docker *client.Client, containerID, contextStr string) { diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index da4efbff..52495b66 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -49,7 +49,6 @@ type Deployer struct { DeployNamespace string Docker *client.Client Counter int - networkID string debugLogging bool config *config.Complement } @@ -93,11 +92,10 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen if len(images) == 0 { return nil, fmt.Errorf("Deploy: No images have been built for blueprint %s", blueprintName) } - networkID, err := createNetworkIfNotExists(d.Docker, d.config.PackageNamespace, blueprintName) + networkName, err := createNetworkIfNotExists(d.Docker, d.config.PackageNamespace, blueprintName) if err != nil { return nil, fmt.Errorf("Deploy: %w", err) } - d.networkID = networkID // deploy images in parallel 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 // TODO: Make CSAPI port configurable deployment, err := deployImage( d.Docker, img.ID, fmt.Sprintf("complement_%s_%s_%s_%d", d.config.PackageNamespace, d.DeployNamespace, contextStr, counter), - d.config.PackageNamespace, blueprintName, hsName, asIDToRegistrationMap, contextStr, networkID, d.config, + d.config.PackageNamespace, blueprintName, hsName, asIDToRegistrationMap, contextStr, networkName, d.config, ) if err != nil { if deployment != nil && deployment.ContainerID != "" { @@ -208,7 +206,7 @@ func (d *Deployer) Restart(hsDep *HomeserverDeployment, cfg *config.Complement) // nolint func deployImage( docker *client.Client, imageID string, containerName, pkgNamespace, blueprintName, hsName string, - asIDToRegistrationMap map[string]string, contextStr, networkID string, cfg *config.Complement, + asIDToRegistrationMap map[string]string, contextStr, networkName string, cfg *config.Complement, ) (*HomeserverDeployment, error) { ctx := context.Background() var extraHosts []string @@ -275,9 +273,8 @@ func deployImage( Mounts: mounts, }, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ - "complement_" + pkgNamespace + "_" + blueprintName: { - NetworkID: networkID, - Aliases: []string{hsName}, + networkName: { + Aliases: []string{hsName}, }, }, }, nil, containerName) @@ -290,7 +287,7 @@ func deployImage( containerID := body.ID if cfg.DebugLoggingEnabled { - log.Printf("%s: Created container '%s' using image '%s' on network '%s'", contextStr, containerID, imageID, networkID) + log.Printf("%s: Created container '%s' using image '%s' on network '%s'", contextStr, containerID, imageID, networkName) } stubDeployment := &HomeserverDeployment{ ContainerID: containerID, From 9e88e44e48f2aa3d3a4a6d5c318b20d490decff3 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Tue, 20 Sep 2022 16:27:58 +0200 Subject: [PATCH 09/10] Add/fix some comments --- internal/docker/builder.go | 5 +++-- internal/docker/labels.go | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/docker/builder.go b/internal/docker/builder.go index 47f2fe36..aae9c6ec 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -406,6 +406,7 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context ) } +// Multilines label using Dockefile syntax is unsupported, let's inline \n instead func generateASRegistrationYaml(as b.ApplicationService) string { return fmt.Sprintf("id: %s\\n", as.ID) + fmt.Sprintf("hs_token: %s\\n", as.HSToken) + @@ -421,8 +422,8 @@ func generateASRegistrationYaml(as b.ApplicationService) string { " aliases: []\\n" } -// createNetworkIfNotExists creates a docker network and returns its id. -// ID is guaranteed not to be empty when err == nil +// createNetworkIfNotExists creates a docker network and returns its name. +// Name is guaranteed not to be empty when err == nil func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkName string, err error) { // check if a network already exists for this blueprint nws, err := docker.NetworkList(context.Background(), types.NetworkListOptions{ diff --git a/internal/docker/labels.go b/internal/docker/labels.go index fa1b0d41..9e6a4404 100644 --- a/internal/docker/labels.go +++ b/internal/docker/labels.go @@ -33,7 +33,8 @@ func asIDToRegistrationFromLabels(labels map[string]string) map[string]string { asMap := make(map[string]string) for k, v := range labels { if strings.HasPrefix(k, "application_service_") { - asMap[strings.TrimPrefix(k, "application_service_")] = strings.Replace(v, "\\n", "\n", -1) + // cf comment of generateASRegistrationYaml for ReplaceAll explanation + asMap[strings.TrimPrefix(k, "application_service_")] = strings.ReplaceAll(v, "\\n", "\n") } } return asMap From 7ad4695725830700fa42e9efd82c7eb727dfc32d Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Mon, 26 Sep 2022 14:24:21 +0200 Subject: [PATCH 10/10] Add comments --- internal/config/config.go | 6 +++++- internal/docker/builder.go | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 6c044632..61e908bf 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -79,7 +79,11 @@ type Complement struct { BestEffort bool - // The hostname of Complement from the perspective of a Homeserver running inside a container + // Name: COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT + // Default: host.docker.internal + // Description: The hostname of Complement from the perspective of a Homeserver running inside a container. + // This can be useful for container runtimes using another hostname to access the host from a container, + // like Podman that uses `host.containers.internal` instead. HostnameRunningComplement string } diff --git a/internal/docker/builder.go b/internal/docker/builder.go index aae9c6ec..b86fe58e 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -346,8 +346,11 @@ func (d *Builder) construct(bprint b.Blueprint) (errs []error) { return errs } +// Convert a map of labels to a list of changes directive in Dockerfile format. +// Labels keys and values can't be multiline (eg. can't contain `\n` character) +// neither can they contain unescaped `"` character. func toChanges(labels map[string]string) []string { - changes := make([]string, 0) + var changes []string for k, v := range labels { changes = append(changes, fmt.Sprintf("LABEL \"%s\"=\"%s\"", k, v)) } @@ -406,7 +409,7 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context ) } -// Multilines label using Dockefile syntax is unsupported, let's inline \n instead +// Multilines label using Dockerfile syntax is unsupported, let's inline \n instead func generateASRegistrationYaml(as b.ApplicationService) string { return fmt.Sprintf("id: %s\\n", as.ID) + fmt.Sprintf("hs_token: %s\\n", as.HSToken) +