1414package  docker
1515
1616import  (
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- 
478378func  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 
635395func  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- 
732449func  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