package main import ( "context" "fmt" "log" "os" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" ) const ( fixedDockerVersion = "1.38" ) var ( dockerCtx context.Context dockerCLI *client.Client currentVpnRemptePort = 1194 ) func setupContext() { if dockerCtx == nil { dockerCtx = context.Background() } } func setupDockerCLI() (err error) { os.Setenv("DOCKER_API_VERSION", "1.27") if dockerCLI == nil { dockerCLI, err = client.NewEnvClient() } return err } func spawnCompanion(username string, accesscode string) { log.Println("Spwaning a new companion container") // setup the context and the docker cli connection setupContext() setupDockerCLI() sessionSalt := generateSessionSalt() vpnRemoteAddress := os.Getenv("HOSTNAME") // get the vpnRemoteport and increment it vpnRemotePort := currentVpnRemptePort log.Println("vpnRemptePort ", currentVpnRemptePort) currentVpnRemptePort++ log.Println("Generating the container config") Config := &container.Config{ Image: "circus-companion:latest", Cmd: []string{"-username", username, "-accessCode", accesscode, "-sessionSalt", sessionSalt, "-vpnRemoteAddress", vpnRemoteAddress, "-vpnRemotePort", fmt.Sprintf("%d", vpnRemotePort)}, ExposedPorts: nat.PortSet{ "8080/tcp": struct{}{}, }, // Container labels used by traefik Labels: map[string]string{ "traefik.enable": "true", fmt.Sprintf("traefik.http.routers.%s.entrypoints", username): "web", fmt.Sprintf("traefik.http.routers.%s.rule", username): fmt.Sprintf("Host(`%s.%s`)", username, os.Getenv("HOSTNAME")), fmt.Sprintf("traefik.http.middlewares.%s-https-redirect.redirectscheme.scheme", username): "https", fmt.Sprintf("traefik.http.routers.%s.middlewares", username): fmt.Sprintf("%s-https-redirect", username), fmt.Sprintf("traefik.http.routers.%s-secure.entrypoints", username): "websecure", fmt.Sprintf("traefik.http.routers.%s-secure.rule", username): fmt.Sprintf("Host(`%s.%s`)", username, os.Getenv("HOSTNAME")), fmt.Sprintf("traefik.http.routers.%s-secure.tls", username): "true", fmt.Sprintf("traefik.http.routers.%s-secure.tls.certresolver", username): "mytlschallenge", fmt.Sprintf("traefik.http.routers.%s-secure.service", username): fmt.Sprintf("%s", username), fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port", username): "8080", "traefik.docker.network": "circus", }, } // configure the host // this mounts the docker socket and the companion seed file into the // container HostConfig := &container.HostConfig{ Mounts: []mount.Mount{ { Type: mount.TypeBind, Source: "/var/run/docker.sock", Target: "/var/run/docker.sock", }, { Type: mount.TypeBind, Source: "/etc/companion.json", Target: "/etc/companion.json", }, }, } circusNetworkID, err := getCircusNetworkID(dockerCtx, dockerCLI) NetworkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ "circus": { NetworkID: circusNetworkID, }, }, } log.Println("Creating the container") // create the docker container resp, err := dockerCLI.ContainerCreate(dockerCtx, Config, HostConfig, NetworkingConfig, "") if err != nil { panic(err) } log.Println("Container created!") // id of the created container containerID := resp.ID // start the docker container err = dockerCLI.ContainerStart(dockerCtx, containerID, types.ContainerStartOptions{}) if err != nil { fmt.Println(err) } log.Printf("Adding the container (%s) to the circus network", containerID) // get the circus network id circusNetworkID, err = getCircusNetworkID(dockerCtx, dockerCLI) if err != nil { log.Println("error: could not get CircusNetworkID", err) } log.Printf("circus network ID: %s", circusNetworkID) // connect the companion container to the circus network connectContainerToNetwork(containerID, circusNetworkID) log.Println("Container added to the circus network") // Get IP Address of that container inspectJSON, err := dockerCLI.ContainerInspect(dockerCtx, containerID) if err != nil { log.Println("inspectJsonErr: ", err) } fmt.Println("---") fmt.Printf("%#v", inspectJSON.NetworkSettings.Networks) fmt.Printf("companion IP: %#v", inspectJSON.NetworkSettings.Networks["circus"].IPAddress) } func generateSessionSalt() string { return fmt.Sprintf("%d", time.Now().UnixNano()) } // listDockerNetworks lists the docker networks func listDockerNetworks(dockerCtx context.Context, dockerCLI *client.Client) ([]types.NetworkResource, error) { // list the docker networks networks, err := dockerCLI.NetworkList(dockerCtx, types.NetworkListOptions{}) if err != nil { return nil, fmt.Errorf("network list err: %s", err) } return networks, nil } // searchDockerNetworkIDFromName returns the docker network ID using the given name // to find it. func searchDockerNetworkIDFromName(networks []types.NetworkResource, NetworkNameToFind string) (string, error) { for _, network := range networks { if network.Name == NetworkNameToFind { return network.ID, nil } } return "", fmt.Errorf("could not find the network id for the network %s: %s", NetworkNameToFind) } // connectContainerToNetwork inserts the container with the given ID into the // network with the given ID func connectContainerToNetwork(containerID string, networkID string) { err := dockerCLI.NetworkConnect(dockerCtx, networkID, containerID, nil) if err != nil { log.Printf("Could not connect container %s to network %s: %s", containerID[:10], networkID[:10], err) } log.Println("connectContainerToNetwork err: ", err) } func getCircusNetworkID(dockerCtx context.Context, dockerCLI *client.Client) (string, error) { networks, err := listDockerNetworks(dockerCtx, dockerCLI) if err != nil { return "", err } circusNetworkID, err := searchDockerNetworkIDFromName(networks, "circus") if err != nil { return "", err } return circusNetworkID, nil }