Skip to content

Commit 241cbb6

Browse files
authored
Refactor volumes and labels (#283)
* Refactor volumes and labels - Move labels to their own file as they just create noise and are very simple - Move volumes to their own file and make an interface for them. We'll expand on this in the future to support user mounts. * goimports * add cgroup * Read cgroups in addition to cpuset to find the container ID * Call right functions * Support volume mounts for /ca
1 parent 4d6f00d commit 241cbb6

File tree

5 files changed

+371
-300
lines changed

5 files changed

+371
-300
lines changed

internal/docker/builder.go

Lines changed: 0 additions & 283 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,15 @@
1414
package docker
1515

1616
import (
17-
"archive/tar"
18-
"bytes"
1917
"context"
20-
"errors"
2118
"fmt"
22-
"io/ioutil"
2319
"log"
24-
"net/http"
25-
"net/url"
2620
"os"
27-
"path"
28-
"runtime"
2921
"strings"
3022
"time"
3123

3224
"github.com/docker/docker/api/types"
3325
"github.com/docker/docker/api/types/container"
34-
"github.com/docker/docker/api/types/filters"
35-
"github.com/docker/docker/api/types/mount"
36-
"github.com/docker/docker/api/types/network"
37-
"github.com/docker/docker/api/types/volume"
3826
client "github.com/docker/docker/client"
3927
"github.com/docker/docker/pkg/stdcopy"
4028
"github.com/docker/go-connections/nat"
@@ -387,94 +375,6 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context
387375
)
388376
}
389377

390-
// getCaVolume returns the correct volume mount for providing a CA to homeserver containers.
391-
// If running CI, returns an error if it's unable to find a volume that has /ca
392-
// Otherwise, returns an error if we're unable to find the <cwd>/ca directory on the local host
393-
func getCaVolume(ctx context.Context, docker *client.Client) (caMount mount.Mount, err error) {
394-
// TODO: wrap in a lockfile
395-
if os.Getenv("CI") == "true" {
396-
// When in CI, Complement itself is a container with the CA volume mounted at /ca.
397-
// We need to mount this volume to all homeserver containers to synchronize the CA cert.
398-
// This is needed to establish trust among all containers.
399-
400-
// Get volume mounted at /ca. First we get the container ID
401-
// /proc/1/cpuset should be /docker/<containerID>
402-
cpuset, err := ioutil.ReadFile("/proc/1/cpuset")
403-
if err != nil {
404-
return caMount, err
405-
}
406-
if !strings.Contains(string(cpuset), "docker") {
407-
return caMount, errors.New("Could not identify container ID using /proc/1/cpuset")
408-
}
409-
cpusetList := strings.Split(strings.TrimSpace(string(cpuset)), "/")
410-
containerID := cpusetList[len(cpusetList)-1]
411-
container, err := docker.ContainerInspect(ctx, containerID)
412-
if err != nil {
413-
return caMount, err
414-
}
415-
// Get the volume that matches the destination in our complement container
416-
var volumeName string
417-
for i := range container.Mounts {
418-
if container.Mounts[i].Destination == "/ca" {
419-
volumeName = container.Mounts[i].Name
420-
}
421-
}
422-
if volumeName == "" {
423-
// We did not find a volume. This container might be created without a volume,
424-
// or CI=true is passed but we are not running in a container.
425-
// todo: log that we do not provide a CA volume mount?
426-
return caMount, nil
427-
}
428-
429-
caMount = mount.Mount{
430-
Type: mount.TypeVolume,
431-
Source: volumeName,
432-
Target: "/ca",
433-
}
434-
} else {
435-
// When not in CI, our CA cert is placed in the current working dir.
436-
// We bind mount this directory to all homeserver containers.
437-
cwd, err := os.Getwd()
438-
if err != nil {
439-
return caMount, err
440-
}
441-
caCertificateDirHost := path.Join(cwd, "ca")
442-
if _, err := os.Stat(caCertificateDirHost); os.IsNotExist(err) {
443-
err = os.Mkdir(caCertificateDirHost, 0770)
444-
if err != nil {
445-
return caMount, err
446-
}
447-
}
448-
449-
caMount = mount.Mount{
450-
Type: mount.TypeBind,
451-
Source: path.Join(cwd, "ca"),
452-
Target: "/ca",
453-
}
454-
}
455-
return caMount, nil
456-
}
457-
458-
// getAppServiceVolume returns a volume mount for providing the `/appservice` directory to homeserver containers.
459-
// This directory will contain application service registration config files.
460-
// Returns an error if the volume failed to create
461-
func getAppServiceVolume(ctx context.Context, docker *client.Client) (asMount mount.Mount, err error) {
462-
asVolume, err := docker.VolumeCreate(context.Background(), volume.VolumesCreateBody{
463-
Name: "appservices",
464-
})
465-
if err != nil {
466-
return asMount, err
467-
}
468-
469-
asMount = mount.Mount{
470-
Type: mount.TypeVolume,
471-
Source: asVolume.Name,
472-
Target: "/appservices",
473-
}
474-
475-
return asMount, err
476-
}
477-
478378
func generateASRegistrationYaml(as b.ApplicationService) string {
479379
return fmt.Sprintf("id: %s\n", as.ID) +
480380
fmt.Sprintf("hs_token: %s\n", as.HSToken) +
@@ -490,146 +390,6 @@ func generateASRegistrationYaml(as b.ApplicationService) string {
490390
" aliases: []\n"
491391
}
492392

493-
func deployImage(
494-
docker *client.Client, imageID string, csPort int, containerName, pkgNamespace, blueprintName, hsName string,
495-
asIDToRegistrationMap map[string]string, contextStr, networkID string, spawnHSTimeout time.Duration,
496-
) (*HomeserverDeployment, error) {
497-
ctx := context.Background()
498-
var extraHosts []string
499-
var mounts []mount.Mount
500-
var err error
501-
502-
if runtime.GOOS == "linux" {
503-
// By default docker for linux does not expose this, so do it now.
504-
// When https://github.com/moby/moby/pull/40007 lands in Docker 20, we should
505-
// change this to be `host.docker.internal:host-gateway`
506-
extraHosts = []string{HostnameRunningComplement + ":172.17.0.1"}
507-
}
508-
509-
if os.Getenv("COMPLEMENT_CA") == "true" {
510-
var caMount mount.Mount
511-
caMount, err = getCaVolume(ctx, docker)
512-
if err != nil {
513-
return nil, err
514-
}
515-
516-
mounts = append(mounts, caMount)
517-
}
518-
519-
asMount, err := getAppServiceVolume(ctx, docker)
520-
if err != nil {
521-
return nil, err
522-
}
523-
mounts = append(mounts, asMount)
524-
525-
env := []string{
526-
"SERVER_NAME=" + hsName,
527-
"COMPLEMENT_CA=" + os.Getenv("COMPLEMENT_CA"),
528-
}
529-
530-
body, err := docker.ContainerCreate(ctx, &container.Config{
531-
Image: imageID,
532-
Env: env,
533-
//Cmd: d.ImageArgs,
534-
Labels: map[string]string{
535-
complementLabel: contextStr,
536-
"complement_blueprint": blueprintName,
537-
"complement_pkg": pkgNamespace,
538-
"complement_hs_name": hsName,
539-
},
540-
}, &container.HostConfig{
541-
PublishAllPorts: true,
542-
ExtraHosts: extraHosts,
543-
Mounts: mounts,
544-
}, &network.NetworkingConfig{
545-
EndpointsConfig: map[string]*network.EndpointSettings{
546-
hsName: {
547-
NetworkID: networkID,
548-
Aliases: []string{hsName},
549-
},
550-
},
551-
}, containerName)
552-
if err != nil {
553-
return nil, err
554-
}
555-
556-
containerID := body.ID
557-
558-
// Create the application service files
559-
for asID, registration := range asIDToRegistrationMap {
560-
// Create a fake/virtual file in memory that we can copy to the container
561-
// via https://stackoverflow.com/a/52131297/796832
562-
var buf bytes.Buffer
563-
tw := tar.NewWriter(&buf)
564-
err = tw.WriteHeader(&tar.Header{
565-
Name: fmt.Sprintf("/appservices/%s.yaml", url.PathEscape(asID)),
566-
Mode: 0777,
567-
Size: int64(len(registration)),
568-
})
569-
if err != nil {
570-
return nil, fmt.Errorf("Failed to copy regstration to container: %v", err)
571-
}
572-
tw.Write([]byte(registration))
573-
tw.Close()
574-
575-
// Put our new fake file in the container volume
576-
err = docker.CopyToContainer(context.Background(), containerID, "/", &buf, types.CopyToContainerOptions{
577-
AllowOverwriteDirWithFile: false,
578-
})
579-
if err != nil {
580-
return nil, err
581-
}
582-
}
583-
584-
err = docker.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
585-
if err != nil {
586-
return nil, err
587-
}
588-
inspect, err := docker.ContainerInspect(ctx, containerID)
589-
if err != nil {
590-
return nil, err
591-
}
592-
baseURL, fedBaseURL, err := endpoints(inspect.NetworkSettings.Ports, 8008, 8448)
593-
if err != nil {
594-
return nil, fmt.Errorf("%s : image %s : %w", contextStr, imageID, err)
595-
}
596-
versionsURL := fmt.Sprintf("%s/_matrix/client/versions", baseURL)
597-
// hit /versions to check it is up
598-
var lastErr error
599-
stopTime := time.Now().Add(spawnHSTimeout)
600-
for {
601-
if time.Now().After(stopTime) {
602-
lastErr = fmt.Errorf("timed out checking for homeserver to be up: %s", lastErr)
603-
break
604-
}
605-
res, err := http.Get(versionsURL)
606-
if err != nil {
607-
lastErr = fmt.Errorf("GET %s => error: %s", versionsURL, err)
608-
time.Sleep(50 * time.Millisecond)
609-
continue
610-
}
611-
if res.StatusCode != 200 {
612-
lastErr = fmt.Errorf("GET %s => HTTP %s", versionsURL, res.Status)
613-
time.Sleep(50 * time.Millisecond)
614-
continue
615-
}
616-
lastErr = nil
617-
break
618-
}
619-
620-
d := &HomeserverDeployment{
621-
BaseURL: baseURL,
622-
FedBaseURL: fedBaseURL,
623-
ContainerID: containerID,
624-
AccessTokens: tokensFromLabels(inspect.Config.Labels),
625-
ApplicationServices: asIDToRegistrationFromLabels(inspect.Config.Labels),
626-
}
627-
if lastErr != nil {
628-
return d, fmt.Errorf("%s: failed to check server is up. %w", contextStr, lastErr)
629-
}
630-
return d, nil
631-
}
632-
633393
// createNetworkIfNotExists creates a docker network and returns its id.
634394
// ID is guaranteed not to be empty when err == nil
635395
func createNetworkIfNotExists(docker *client.Client, pkgNamespace, blueprintName string) (networkID string, err error) {
@@ -686,49 +446,6 @@ func printLogs(docker *client.Client, containerID, contextStr string) {
686446
log.Printf("============== %s : END LOGS ==============\n\n\n", contextStr)
687447
}
688448

689-
// label returns a filter for the presence of certain labels ("complement_context") or a match of
690-
// labels ("complement_blueprint=foo").
691-
func label(labelFilters ...string) filters.Args {
692-
f := filters.NewArgs()
693-
// label=<key> or label=<key>=<value>
694-
for _, in := range labelFilters {
695-
f.Add("label", in)
696-
}
697-
return f
698-
}
699-
700-
func tokensFromLabels(labels map[string]string) map[string]string {
701-
userIDToToken := make(map[string]string)
702-
for k, v := range labels {
703-
if strings.HasPrefix(k, "access_token_") {
704-
userIDToToken[strings.TrimPrefix(k, "access_token_")] = v
705-
}
706-
}
707-
return userIDToToken
708-
}
709-
710-
func asIDToRegistrationFromLabels(labels map[string]string) map[string]string {
711-
asMap := make(map[string]string)
712-
for k, v := range labels {
713-
if strings.HasPrefix(k, "application_service_") {
714-
asMap[strings.TrimPrefix(k, "application_service_")] = v
715-
}
716-
}
717-
return asMap
718-
}
719-
720-
func labelsForApplicationServices(hs b.Homeserver) map[string]string {
721-
labels := make(map[string]string)
722-
// collect and store app service registrations as labels 'application_service_$as_id: $registration'
723-
// collect and store app service access tokens as labels 'access_token_$sender_localpart: $as_token'
724-
for _, as := range hs.ApplicationServices {
725-
labels["application_service_"+as.ID] = generateASRegistrationYaml(as)
726-
727-
labels["access_token_@"+as.SenderLocalpart+":"+hs.Name] = as.ASToken
728-
}
729-
return labels
730-
}
731-
732449
func endpoints(p nat.PortMap, csPort, ssPort int) (baseURL, fedBaseURL string, err error) {
733450
csapiPort := fmt.Sprintf("%d/tcp", csPort)
734451
csapiPortInfo, ok := p[nat.Port(csapiPort)]

0 commit comments

Comments
 (0)