|
|
|
@ -54,7 +54,8 @@ type DockerContainerNetwork struct {
|
|
|
|
|
|
|
|
|
|
type CreateContainer struct { |
|
|
|
|
Hostname string |
|
|
|
|
Domainname string |
|
|
|
|
Entrypoint string |
|
|
|
|
Cmd []string |
|
|
|
|
User string |
|
|
|
|
Env []string |
|
|
|
|
Image string |
|
|
|
@ -98,6 +99,7 @@ type DockerManager struct {
|
|
|
|
|
selfNetworkName string |
|
|
|
|
client *http.Client |
|
|
|
|
ipAddressByUserId map[string]string |
|
|
|
|
containerIdByUserId map[string]string |
|
|
|
|
pendingCreationByUserId map[string]bool |
|
|
|
|
vscodeDockerImage string |
|
|
|
|
dockerSocket string |
|
|
|
@ -123,19 +125,21 @@ func initDockerManager(config *Config) (*DockerManager, error) {
|
|
|
|
|
Timeout: time.Second * time.Duration(5), |
|
|
|
|
}, |
|
|
|
|
ipAddressByUserId: map[string]string{}, |
|
|
|
|
containerIdByUserId: map[string]string{}, |
|
|
|
|
pendingCreationByUserId: map[string]bool{}, |
|
|
|
|
vscodeDockerImage: config.VSCodeDockerImage, |
|
|
|
|
dockerSocket: "/var/run/docker.sock", |
|
|
|
|
dockerAPIVersion: "1.40", |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ipAddressByUserId, selfNetworkName, err := manager.pollContainers() |
|
|
|
|
ipAddressByUserId, containerIdByUserId, selfNetworkName, err := manager.pollContainers() |
|
|
|
|
if err != nil { |
|
|
|
|
//log.Println(err)
|
|
|
|
|
return nil, err |
|
|
|
|
} else { |
|
|
|
|
manager.selfNetworkName = selfNetworkName |
|
|
|
|
manager.ipAddressByUserId = ipAddressByUserId |
|
|
|
|
manager.containerIdByUserId = containerIdByUserId |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
@ -158,7 +162,7 @@ func initDockerManager(config *Config) (*DockerManager, error) {
|
|
|
|
|
|
|
|
|
|
lastLongInterval = time.Now() |
|
|
|
|
|
|
|
|
|
ipAddressByUserId, selfNetworkName, err := manager.pollContainers() |
|
|
|
|
ipAddressByUserId, containerIdByUserId, selfNetworkName, err := manager.pollContainers() |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
log.Printf("DockerManager.pollContainers() failed: %s\n", err) |
|
|
|
@ -166,6 +170,7 @@ func initDockerManager(config *Config) (*DockerManager, error) {
|
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
manager.ipAddressByUserId = ipAddressByUserId |
|
|
|
|
manager.containerIdByUserId = containerIdByUserId |
|
|
|
|
manager.selfNetworkName = selfNetworkName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -178,13 +183,19 @@ func initDockerManager(config *Config) (*DockerManager, error) {
|
|
|
|
|
func (manager *DockerManager) EnsureContainerExistsForUser(userId string, username string) (string, error) { |
|
|
|
|
|
|
|
|
|
ipAddress := manager.ipAddressByUserId[userId] |
|
|
|
|
containerId := manager.containerIdByUserId[userId] |
|
|
|
|
|
|
|
|
|
if ipAddress == "" { |
|
|
|
|
if containerId == "" { |
|
|
|
|
manager.pendingCreationByUserId[userId] = true |
|
|
|
|
containerId, err := manager.createDockerContainer(userId, username) |
|
|
|
|
var err error |
|
|
|
|
containerId, err = manager.createDockerContainer(userId, username) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if ipAddress == "" { |
|
|
|
|
manager.pendingCreationByUserId[userId] = true |
|
|
|
|
|
|
|
|
|
for attempts := 0; attempts < 30; attempts++ { |
|
|
|
|
time.Sleep(time.Millisecond * 500) |
|
|
|
|
statusCode, err := manager.startDockerContainer(containerId) |
|
|
|
@ -199,7 +210,6 @@ func (manager *DockerManager) EnsureContainerExistsForUser(userId string, userna
|
|
|
|
|
if manager.DebugLog { |
|
|
|
|
log.Printf("start polling for container %s ip...\n", containerId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
return ipAddress, nil |
|
|
|
|
} |
|
|
|
@ -255,20 +265,21 @@ func (manager *DockerManager) EnsureContainerExistsForUser(userId string, userna
|
|
|
|
|
return "", fmt.Errorf("Timed out waiting for container for %s (%s)", userId, username) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (manager *DockerManager) pollContainers() (map[string]string, string, error) { |
|
|
|
|
func (manager *DockerManager) pollContainers() (map[string]string, map[string]string, string, error) { |
|
|
|
|
|
|
|
|
|
containers, err := manager.listDockerContainers() |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, "", errors.Wrap(err, "can't list docker containers") |
|
|
|
|
return nil, nil, "", errors.Wrap(err, "can't list docker containers") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ipAddressByUserId := map[string]string{} |
|
|
|
|
containerIdByUserId := map[string]string{} |
|
|
|
|
|
|
|
|
|
selfContainersNetwork := map[string]bool{} |
|
|
|
|
|
|
|
|
|
labelSplit := strings.Split(manager.SelfDockerLabel, ":") |
|
|
|
|
if len(labelSplit) != 2 { |
|
|
|
|
return nil, "", errors.New("SelfDockerLabel must contain exactly 1 colon ") |
|
|
|
|
return nil, nil, "", errors.New("SelfDockerLabel must contain exactly 1 colon ") |
|
|
|
|
} |
|
|
|
|
labelKey := strings.TrimSpace(labelSplit[0]) |
|
|
|
|
labelValue := strings.TrimSpace(labelSplit[1]) |
|
|
|
@ -282,6 +293,7 @@ func (manager *DockerManager) pollContainers() (map[string]string, string, error
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if container.Labels != nil && container.Labels["workspace-on-demand-userid"] != "" { |
|
|
|
|
containerIdByUserId[container.Labels["workspace-on-demand-userid"]] = container.Id |
|
|
|
|
if container.NetworkSettings.Networks != nil { |
|
|
|
|
for _, network := range container.NetworkSettings.Networks { |
|
|
|
|
// TODO what about multiple networks? this is not needed yet we just use defualt network
|
|
|
|
@ -292,20 +304,20 @@ func (manager *DockerManager) pollContainers() (map[string]string, string, error
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(selfContainersNetwork) == 0 { |
|
|
|
|
return nil, "", errors.New("can't figure out which docker network i'm using: none found") |
|
|
|
|
return nil, nil, "", errors.New("can't figure out which docker network i'm using: none found") |
|
|
|
|
} |
|
|
|
|
if len(selfContainersNetwork) > 1 { |
|
|
|
|
return nil, "", fmt.Errorf( |
|
|
|
|
return nil, nil, "", fmt.Errorf( |
|
|
|
|
"can't figure out which docker network: more than one network found on a container with a label containing '%s'", |
|
|
|
|
manager.SelfDockerLabel, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for networkName := range selfContainersNetwork { |
|
|
|
|
return ipAddressByUserId, networkName, nil |
|
|
|
|
return ipAddressByUserId, containerIdByUserId, networkName, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil, "", errors.New("thats unpossible") |
|
|
|
|
return nil, nil, "", errors.New("thats unpossible") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//https://docs.docker.com/engine/api/v1.40/#tag/Container
|
|
|
|
@ -336,12 +348,12 @@ func (manager *DockerManager) listDockerContainers() ([]DockerContainer, error)
|
|
|
|
|
|
|
|
|
|
func (manager *DockerManager) createDockerContainer(userId string, username string) (string, error) { |
|
|
|
|
|
|
|
|
|
containerName := fmt.Sprintf("vscode-%s-%s", userId, username) |
|
|
|
|
containerName := fmt.Sprintf("code-server-%s-%s", userId, username) |
|
|
|
|
endpoint := fmt.Sprintf("containers/create?name=%s", containerName) |
|
|
|
|
|
|
|
|
|
hostnameBuffer := make([]byte, 4) |
|
|
|
|
hostnameBuffer := make([]byte, 2) |
|
|
|
|
rand.Read(hostnameBuffer) |
|
|
|
|
hostname := fmt.Sprintf("vscode-%s", base58.Encode(hostnameBuffer, base58.BitcoinAlphabet)) |
|
|
|
|
hostname := fmt.Sprintf("linux-%s", base58.Encode(hostnameBuffer, base58.BitcoinAlphabet)) |
|
|
|
|
|
|
|
|
|
containerConfigDir := filepath.Join(manager.ContainerMountedVolumeRoot, fmt.Sprintf("%s/config/code-server", userId)) |
|
|
|
|
containerProjectDir := filepath.Join(manager.ContainerMountedVolumeRoot, fmt.Sprintf("%s/project", userId)) |
|
|
|
@ -374,10 +386,12 @@ func (manager *DockerManager) createDockerContainer(userId string, username stri
|
|
|
|
|
networkConfig[manager.selfNetworkName] = CreateDockerNetworkConfig{} |
|
|
|
|
|
|
|
|
|
createRequest := CreateContainer{ |
|
|
|
|
Hostname: hostname, |
|
|
|
|
Image: manager.vscodeDockerImage, |
|
|
|
|
User: "1000:1000", |
|
|
|
|
Env: []string{fmt.Sprintf("DOCKER_USER=%s", username)}, |
|
|
|
|
Hostname: hostname, |
|
|
|
|
Image: manager.vscodeDockerImage, |
|
|
|
|
Entrypoint: "/usr/bin/entrypoint.sh", |
|
|
|
|
Cmd: []string{"--bind-addr", "0.0.0.0:8080"}, |
|
|
|
|
User: "1000:1000", |
|
|
|
|
Env: []string{fmt.Sprintf("DOCKER_USER=%s", username)}, |
|
|
|
|
Volumes: map[string]CreateDockerVolume{ |
|
|
|
|
"/home/coder/.config": {}, |
|
|
|
|
"/home/coder/project": {}, |
|
|
|
@ -456,7 +470,7 @@ func (manager *DockerManager) startDockerContainer(containerId string) (int, err
|
|
|
|
|
return 0, errors.Wrap(err, "can't talk to docker api") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if response.StatusCode > 299 { |
|
|
|
|
if response.StatusCode > 304 { |
|
|
|
|
return response.StatusCode, fmt.Errorf( |
|
|
|
|
"docker api (%s) returned HTTP %d: %s", |
|
|
|
|
endpoint, response.StatusCode, string(bytes)) |
|
|
|
|