Browse Source

tabs -> spaces

master
forest 3 years ago
parent
commit
2d95328237
  1. 5
      .gitignore
  2. 19
      config.json
  3. 56
      docker_manager.go
  4. 2
      frontend.go
  5. 34
      frontend_vscode.go
  6. 23
      go.mod
  7. 6
      go.sum
  8. 10
      main.go

5
.gitignore vendored

@ -1 +1,4 @@
data
data
dockerbuild
notes.txt
live-server/node_modules

19
config.json

@ -1,17 +1,20 @@
{
"DebugLog": true,
"ListenAddress": "127.0.0.1:3000",
"SelfDomain": "helloworld.cyberia.club",
"SelfURLProtocol": "https",
"VSCodePort": 8080,
"DebugLog": true,
"ListenAddress": "127.0.0.1:3000",
"SelfDomain": "helloworld.cyberia.club",
"SelfURLProtocol": "https",
"VSCodePort": 8080,
"VSCodeUID": 1000,
"VSCodeGID": 1000,
"VSCodeDomain": "code.helloworld.cyberia.club",
"VSCodeDockerImage": "code-server-configured:v2",
"VSCodeDomain": "code.helloworld.cyberia.club",
"VSCodeDockerImage": "code-server-configured:v7",
"LiveServerDomain": "preview.helloworld.cyberia.club",
"LiveServerPort": 2000,
"SelfDockerLabel": "com.docker.compose.service:wod",
"HostMountedVolumeRoot": "/home/cyberian/wod/workspaces",
"ContainerMountedVolumeRoot": "/app/workspaces",
"OAuthProviders": [
"ContainerLiveServerRoot": "/app/live-server",
"OAuthProviders": [
{
"InternalID": "cyberia-gitea",
"ClientID": "70b75072-55a1-467c-b11f-629bdf6e116c",

56
docker_manager.go

@ -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))

2
frontend.go

@ -39,6 +39,7 @@ type FrontendApp struct {
Domain string
SelfURLProtocol string
VSCodeDomain string
LiveServerDomain string
WorkingDirectory string
DB *DB
OAuthProviders map[string]OAuthProvider
@ -63,6 +64,7 @@ func initFrontend(workingDirectory string, config *Config, db *DB, oauthProvider
Domain: config.SelfDomain,
SelfURLProtocol: config.SelfURLProtocol,
VSCodeDomain: config.VSCodeDomain,
LiveServerDomain: config.LiveServerDomain,
WorkingDirectory: workingDirectory,
DB: db,
OAuthProviders: oauthProviders,

34
frontend_vscode.go

@ -19,7 +19,7 @@ type ResponseModifyingTransport struct {
UnderlyingTransport http.RoundTripper
}
func setupVSCodeRoute(config *Config, app *FrontendApp, dockerManager *DockerManager) http.HandlerFunc {
func setupVSCodeRoute(config *Config, app *FrontendApp, dockerManager *DockerManager) func(int) http.HandlerFunc {
reverseProxyInstance := &httputil.ReverseProxy{
Director: func(request *http.Request) {
@ -32,26 +32,28 @@ func setupVSCodeRoute(config *Config, app *FrontendApp, dockerManager *DockerMan
request.URL.Host = backendHost
},
// Transport: &ResponseModifyingTransport{
// //NavHeaderNode: elementNodes[0],
// DebugLog: config.DebugLog,
// UnderlyingTransport: http.DefaultTransport,
// //NavHeaderNode: elementNodes[0],
// DebugLog: config.DebugLog,
// UnderlyingTransport: http.DefaultTransport,
// },
}
return app.handlerFuncWithSession(true, func(response http.ResponseWriter, request *http.Request, session Session) {
ipAddress, err := dockerManager.EnsureContainerExistsForUser(session.GetUserId(), session.Username)
if err != nil {
app.unhandledError(response, request, err)
return
}
request.Header.Set("X-WOD-BackendHost", fmt.Sprintf("%s:%d", ipAddress, config.VSCodePort))
return func(port int) http.HandlerFunc {
return app.handlerFuncWithSession(true, func(response http.ResponseWriter, request *http.Request, session Session) {
ipAddress, err := dockerManager.EnsureContainerExistsForUser(session.GetUserId(), session.Username)
if err != nil {
app.unhandledError(response, request, err)
return
}
request.Header.Set("X-WOD-BackendHost", fmt.Sprintf("%s:%d", ipAddress, port))
if app.DebugLog {
log.Printf("reverse proxying to: %s:%d\n", ipAddress, config.VSCodePort)
}
if app.DebugLog {
log.Printf("reverse proxying to: %s:%d\n", ipAddress, port)
}
reverseProxyInstance.ServeHTTP(response, request)
})
reverseProxyInstance.ServeHTTP(response, request)
})
}
}

23
go.mod

@ -3,18 +3,19 @@ module git.sequentialread.com/forest/workspace-on-demand
go 1.17
require (
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04
git.sequentialread.com/forest/pkg-errors v0.9.2
github.com/shengdoushi/base58 v1.0.0
github.com/syndtr/goleveldb v1.0.0
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04
git.sequentialread.com/forest/pkg-errors v0.9.2
github.com/shengdoushi/base58 v1.0.0
github.com/syndtr/goleveldb v1.0.0
)
require (
github.com/golang/protobuf v1.4.2 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.25.0 // indirect
github.com/golang/protobuf v1.4.2 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.25.0 // indirect
)

6
go.sum

@ -33,6 +33,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04 h1:FmvQmRJzAgbCc/4qfECAluzd+oVBzXNJMjyLQTJ4Wq0=
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04/go.mod h1:jaNfZ5BXx8OsKVZ6FuN0Lr/gIeEwbTNNHSO4RpFz6qo=
git.sequentialread.com/forest/greenhouse-daemon/child-process-service v0.0.0-20211024192223-c563be03d35e h1:JveOQL4LsbNzKCbfgnvr31TLlgQEvMcnY8TL1m7YdrA=
git.sequentialread.com/forest/greenhouse-daemon/child-process-service v0.0.0-20211024192223-c563be03d35e/go.mod h1:Q/NOXcXUvoWtfMHl1x34aL7mEhT2S99Nx013Jjm6pis=
git.sequentialread.com/forest/lumberjack v0.0.0-20210825181050-880244457f2e h1:4Jqvham9nIkxx34Ou0KKEE3AXPwXYJLLw1zWTuqbISY=
git.sequentialread.com/forest/lumberjack v0.0.0-20210825181050-880244457f2e/go.mod h1:KeDUOMaz5z/cNWO4zIRrs+1pcLuKVVtxfWsxIxPpCSY=
git.sequentialread.com/forest/pkg-errors v0.9.2 h1:j6pwbL6E+TmE7TD0tqRtGwuoCbCfO6ZR26Nv5nest9g=
git.sequentialread.com/forest/pkg-errors v0.9.2/go.mod h1:8TkJ/f8xLWFIAid20aoqgDZcCj9QQt+FU+rk415XO1w=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -243,6 +247,8 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

10
main.go

@ -21,9 +21,12 @@ type Config struct {
VSCodeGID int
VSCodeDomain string
VSCodeDockerImage string
LiveServerDomain string
LiveServerPort int
SelfDockerLabel string
HostMountedVolumeRoot string
ContainerMountedVolumeRoot string
ContainerLiveServerRoot string
OAuthProviders []OAuthConfig
}
@ -79,7 +82,10 @@ func main() {
frontend := initFrontend(workingDirectory, config, db, oauthProviders)
setupAuthRoutes(frontend)
vsCodeHandler := setupVSCodeRoute(config, frontend, dockerManager)
reverseProxyFactory := setupVSCodeRoute(config, frontend, dockerManager)
vsCodeHandler := reverseProxyFactory(config.VSCodePort)
liveServerHandler := reverseProxyFactory(config.LiveServerPort)
// before routing by path, we have to route based on the hostname.
hostRouter := http.NewServeMux()
@ -90,6 +96,8 @@ func main() {
if request.Host == frontend.VSCodeDomain {
vsCodeHandler(response, request)
} else if request.Host == frontend.LiveServerDomain {
liveServerHandler(response, request)
} else {
frontend.Router.ServeHTTP(response, request)
}

Loading…
Cancel
Save