Skip to content

Commit 3f5b388

Browse files
authored
Multiple playgrounds support (play-with-docker#215)
* Add Playground struct and basic support for creating it and retrieving it * Add missing functions in pwd mock * Get playground from request domain and validate it exists. If valid set it on the newly created session. * Move playground specific configurations to the playground struct and use it everytime we need that conf. * Don't allow to specify a duration bigger that the allowed in the playground
1 parent 3dee0d3 commit 3f5b388

24 files changed

+784
-159
lines changed

api.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"log"
55
"os"
6+
"time"
67

78
"github.com/play-with-docker/play-with-docker/config"
89
"github.com/play-with-docker/play-with-docker/docker"
@@ -11,6 +12,7 @@ import (
1112
"github.com/play-with-docker/play-with-docker/id"
1213
"github.com/play-with-docker/play-with-docker/provisioner"
1314
"github.com/play-with-docker/play-with-docker/pwd"
15+
"github.com/play-with-docker/play-with-docker/pwd/types"
1416
"github.com/play-with-docker/play-with-docker/scheduler"
1517
"github.com/play-with-docker/play-with-docker/scheduler/task"
1618
"github.com/play-with-docker/play-with-docker/storage"
@@ -41,6 +43,16 @@ func main() {
4143

4244
sch.Start()
4345

46+
d, err := time.ParseDuration(config.DefaultSessionDuration)
47+
if err != nil {
48+
log.Fatalf("Cannot parse duration %s. Got: %v", config.DefaultSessionDuration, err)
49+
}
50+
51+
playground := types.Playground{Domain: config.PlaygroundDomain, DefaultDinDInstanceImage: config.DefaultDinDImage, AllowWindowsInstances: config.NoWindows, DefaultSessionDuration: d, AvailableDinDInstanceImages: []string{config.DefaultDinDImage}}
52+
if _, err := core.PlaygroundNew(playground); err != nil {
53+
log.Fatalf("Cannot create default playground. Got: %v", err)
54+
}
55+
4456
handlers.Bootstrap(core, e)
4557
handlers.Register(nil)
4658
}

config/config.go

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ package config
22

33
import (
44
"flag"
5-
"fmt"
6-
"os"
75
"regexp"
8-
"time"
96

107
"github.com/gorilla/securecookie"
118

@@ -27,10 +24,9 @@ const (
2724
var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex)
2825
var AliasFilter = regexp.MustCompile(AliasPortGroupRegex)
2926

30-
var PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, PWDCName, HashKey, SSHKeyPath, L2RouterIP, DindVolumeSize, CookieHashKey, CookieBlockKey string
27+
var PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, HashKey, SSHKeyPath, L2RouterIP, DindVolumeSize, CookieHashKey, CookieBlockKey, DefaultDinDImage, DefaultSessionDuration string
3128
var UseLetsEncrypt, ExternalDindVolume, NoWindows bool
3229
var LetsEncryptCertsDir string
33-
var LetsEncryptDomains stringslice
3430
var MaxLoadAvg float64
3531
var ForceTLS bool
3632
var Providers map[string]*oauth2.Config
@@ -40,18 +36,9 @@ var GithubClientID, GithubClientSecret string
4036
var FacebookClientID, FacebookClientSecret string
4137
var DockerClientID, DockerClientSecret string
4238

43-
type stringslice []string
44-
45-
func (i *stringslice) String() string {
46-
return fmt.Sprintf("%s", *i)
47-
}
48-
func (i *stringslice) Set(value string) error {
49-
*i = append(*i, value)
50-
return nil
51-
}
39+
var PlaygroundDomain string
5240

5341
func ParseFlags() {
54-
flag.Var(&LetsEncryptDomains, "letsencrypt-domain", "List of domains to validate with let's encrypt")
5542
flag.StringVar(&LetsEncryptCertsDir, "letsencrypt-certs-dir", "/certs", "Path where let's encrypt certs will be stored")
5643
flag.BoolVar(&UseLetsEncrypt, "letsencrypt-enable", false, "Enabled let's encrypt tls certificates")
5744
flag.BoolVar(&ForceTLS, "tls", false, "Use TLS to connect to docker daemons")
@@ -63,7 +50,6 @@ func ParseFlags() {
6350
flag.StringVar(&L2ContainerName, "l2", "l2", "Container name used to run L2 Router")
6451
flag.StringVar(&L2RouterIP, "l2-ip", "", "Host IP address for L2 router ping response")
6552
flag.StringVar(&L2Subdomain, "l2-subdomain", "direct", "Subdomain to the L2 Router")
66-
flag.StringVar(&PWDCName, "cname", "", "CNAME given to this host")
6753
flag.StringVar(&HashKey, "hash_key", "salmonrosado", "Hash key to use for cookies")
6854
flag.StringVar(&DindVolumeSize, "dind-volume-size", "5G", "Dind volume folder size")
6955
flag.BoolVar(&NoWindows, "win-disable", false, "Disable windows instances")
@@ -72,6 +58,8 @@ func ParseFlags() {
7258
flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use")
7359
flag.StringVar(&CookieHashKey, "cookie-hash-key", "", "Hash key to use to validate cookies")
7460
flag.StringVar(&CookieBlockKey, "cookie-block-key", "", "Block key to use to encrypt cookies")
61+
flag.StringVar(&DefaultDinDImage, "default-dind-image", "franela/dind", "Default DinD image to use if not specified otherwise")
62+
flag.StringVar(&DefaultSessionDuration, "default-session-duration", "4h", "Default session duration if not specified otherwise")
7563

7664
flag.StringVar(&GithubClientID, "oauth-github-client-id", "", "Github OAuth Client ID")
7765
flag.StringVar(&GithubClientSecret, "oauth-github-client-secret", "", "Github OAuth Client Secret")
@@ -82,6 +70,8 @@ func ParseFlags() {
8270
flag.StringVar(&DockerClientID, "oauth-docker-client-id", "", "Docker OAuth Client ID")
8371
flag.StringVar(&DockerClientSecret, "oauth-docker-client-secret", "", "Docker OAuth Client Secret")
8472

73+
flag.StringVar(&PlaygroundDomain, "playground-domain", "localhost", "Domain to use for the playground")
74+
8575
flag.Parse()
8676

8777
SecureCookie = securecookie.New([]byte(CookieHashKey), []byte(CookieBlockKey))
@@ -126,38 +116,3 @@ func registerOAuthProviders() {
126116
Providers["docker"] = conf
127117
}
128118
}
129-
130-
func GetDindImageName() string {
131-
dindImage := os.Getenv("DIND_IMAGE")
132-
defaultDindImageName := "franela/dind"
133-
if len(dindImage) == 0 {
134-
dindImage = defaultDindImageName
135-
}
136-
return dindImage
137-
}
138-
139-
func GetSSHImage() string {
140-
sshImage := os.Getenv("SSH_IMAGE")
141-
defaultSSHImage := "franela/ssh"
142-
if len(sshImage) == 0 {
143-
return defaultSSHImage
144-
}
145-
return sshImage
146-
}
147-
148-
func GetDuration(reqDur string) time.Duration {
149-
var defaultDuration = 4 * time.Hour
150-
if reqDur != "" {
151-
if dur, err := time.ParseDuration(reqDur); err == nil && dur <= defaultDuration {
152-
return dur
153-
}
154-
return defaultDuration
155-
}
156-
157-
envDur := os.Getenv("EXPIRY")
158-
if dur, err := time.ParseDuration(envDur); err == nil {
159-
return dur
160-
}
161-
162-
return defaultDuration
163-
}

docker-compose.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@ services:
1414
# use the latest golang image
1515
image: golang
1616
# go to the right place and starts the app
17-
command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/github.com/play-with-docker/play-with-docker; go run api.go -save /pwd/sessions -name l2'
17+
command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/github.com/play-with-docker/play-with-docker; go run api.go -save /pwd/sessions -name l2 -default-dind-image franela/dind:hybrid'
1818
volumes:
1919
# since this app creates networks and launches containers, we need to talk to docker daemon
2020
- /var/run/docker.sock:/var/run/docker.sock
2121
# mount the box mounted shared folder to the container
2222
- $GOPATH/src:/go/src
2323
- sessions:/pwd
24-
environment:
25-
GOOGLE_RECAPTCHA_DISABLED: "true"
26-
DIND_IMAGE: "franela/dind:hybrid"
2724
l2:
2825
container_name: l2
2926
# use the latest golang image

handlers/bootstrap.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handlers
22

33
import (
4+
"context"
45
"crypto/tls"
56
"fmt"
67
"log"
@@ -11,6 +12,7 @@ import (
1112

1213
gh "github.com/gorilla/handlers"
1314
"github.com/gorilla/mux"
15+
lru "github.com/hashicorp/golang-lru"
1416
"github.com/play-with-docker/play-with-docker/config"
1517
"github.com/play-with-docker/play-with-docker/event"
1618
"github.com/play-with-docker/play-with-docker/pwd"
@@ -92,10 +94,22 @@ func Register(extend HandlerExtender) {
9294
}
9395

9496
if config.UseLetsEncrypt {
97+
domainCache, err := lru.New(5000)
98+
if err != nil {
99+
log.Fatalf("Could not start domain cache. Got: %v", err)
100+
}
95101
certManager := autocert.Manager{
96-
Prompt: autocert.AcceptTOS,
97-
HostPolicy: autocert.HostWhitelist(config.LetsEncryptDomains...),
98-
Cache: autocert.DirCache(config.LetsEncryptCertsDir),
102+
Prompt: autocert.AcceptTOS,
103+
HostPolicy: func(ctx context.Context, host string) error {
104+
if _, found := domainCache.Get(host); !found {
105+
if playground := core.PlaygroundFindByDomain(host); playground == nil {
106+
return fmt.Errorf("Playground for domain %s was not found", host)
107+
}
108+
domainCache.Add(host, true)
109+
}
110+
return nil
111+
},
112+
Cache: autocert.DirCache(config.LetsEncryptCertsDir),
99113
}
100114

101115
httpServer.TLSConfig = &tls.Config{

handlers/get_instance_images.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ package handlers
22

33
import (
44
"encoding/json"
5+
"log"
56
"net/http"
6-
7-
"github.com/play-with-docker/play-with-docker/config"
87
)
98

109
func GetInstanceImages(rw http.ResponseWriter, req *http.Request) {
11-
instanceImages := []string{
12-
config.GetDindImageName(),
13-
"franela/dind:dev",
10+
playground := core.PlaygroundFindByDomain(req.Host)
11+
if playground == nil {
12+
log.Printf("Playground for domain %s was not found!", req.Host)
13+
rw.WriteHeader(http.StatusBadRequest)
14+
return
1415
}
15-
json.NewEncoder(rw).Encode(instanceImages)
16+
json.NewEncoder(rw).Encode(playground.AvailableDinDInstanceImages)
1617
}

handlers/home.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package handlers
22

33
import (
4+
"log"
45
"net/http"
56

67
"github.com/gorilla/mux"
7-
"github.com/play-with-docker/play-with-docker/config"
88
)
99

1010
func Home(w http.ResponseWriter, r *http.Request) {
@@ -21,7 +21,14 @@ func Home(w http.ResponseWriter, r *http.Request) {
2121
go core.SessionDeployStack(s)
2222
}
2323

24-
if config.NoWindows {
24+
playground := core.PlaygroundGet(s.PlaygroundId)
25+
if playground == nil {
26+
log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id)
27+
w.WriteHeader(http.StatusBadRequest)
28+
return
29+
}
30+
31+
if !playground.AllowWindowsInstances {
2532
http.ServeFile(w, r, "./www/index-nw.html")
2633
} else {
2734
http.ServeFile(w, r, "./www/index.html")

handlers/new_instance.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"net/http"
88

99
"github.com/gorilla/mux"
10-
"github.com/play-with-docker/play-with-docker/config"
1110
"github.com/play-with-docker/play-with-docker/provisioner"
1211
"github.com/play-with-docker/play-with-docker/pwd"
1312
"github.com/play-with-docker/play-with-docker/pwd/types"
@@ -23,7 +22,14 @@ func NewInstance(rw http.ResponseWriter, req *http.Request) {
2322

2423
s := core.SessionGet(sessionId)
2524

26-
if body.Type == "windows" && config.NoWindows {
25+
playground := core.PlaygroundGet(s.PlaygroundId)
26+
if playground == nil {
27+
log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id)
28+
rw.WriteHeader(http.StatusBadRequest)
29+
return
30+
}
31+
32+
if body.Type == "windows" && !playground.AllowWindowsInstances {
2733
rw.WriteHeader(http.StatusUnauthorized)
2834
return
2935
}

handlers/new_session.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"path"
99
"strings"
10+
"time"
1011

1112
"github.com/play-with-docker/play-with-docker/config"
1213
"github.com/play-with-docker/play-with-docker/provisioner"
@@ -49,8 +50,32 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
4950
}
5051

5152
}
52-
duration := config.GetDuration(reqDur)
53-
s, err := core.SessionNew(userId, duration, stack, stackName, imageName)
53+
54+
playground := core.PlaygroundFindByDomain(req.Host)
55+
if playground == nil {
56+
log.Printf("Playground for domain %s was not found!", req.Host)
57+
rw.WriteHeader(http.StatusBadRequest)
58+
return
59+
}
60+
61+
var duration time.Duration
62+
if reqDur != "" {
63+
d, err := time.ParseDuration(reqDur)
64+
if err != nil {
65+
rw.WriteHeader(http.StatusBadRequest)
66+
return
67+
}
68+
if d > playground.DefaultSessionDuration {
69+
log.Printf("Specified session duration was %s but maximum allowed by this playground is %d\n", d.String(), playground.DefaultSessionDuration.String())
70+
rw.WriteHeader(http.StatusBadRequest)
71+
return
72+
}
73+
duration = d
74+
} else {
75+
duration = playground.DefaultSessionDuration
76+
}
77+
78+
s, err := core.SessionNew(playground, userId, duration, stack, stackName, imageName)
5479
if err != nil {
5580
if provisioner.OutOfCapacity(err) {
5681
http.Redirect(rw, req, "/ooc", http.StatusFound)
@@ -62,9 +87,6 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
6287
//TODO: Return some error code
6388
} else {
6489
hostname := req.Host
65-
if config.PWDCName != "" {
66-
hostname = fmt.Sprintf("%s.%s", config.PWDCName, req.Host)
67-
}
6890
// If request is not a form, return sessionId in the body
6991
if req.Header.Get("X-Requested-With") == "XMLHttpRequest" {
7092
resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname}

provisioner/dind.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212

1313
lru "github.com/hashicorp/golang-lru"
14-
"github.com/play-with-docker/play-with-docker/config"
1514
"github.com/play-with-docker/play-with-docker/docker"
1615
"github.com/play-with-docker/play-with-docker/id"
1716
"github.com/play-with-docker/play-with-docker/pwd/types"
@@ -44,7 +43,11 @@ func checkHostnameExists(sessionId, hostname string, instances []*types.Instance
4443

4544
func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) {
4645
if conf.ImageName == "" {
47-
conf.ImageName = config.GetDindImageName()
46+
playground, err := d.storage.PlaygroundGet(session.PlaygroundId)
47+
if err != nil {
48+
return nil, err
49+
}
50+
conf.ImageName = playground.DefaultDinDInstanceImage
4851
}
4952
log.Printf("NewInstance - using image: [%s]\n", conf.ImageName)
5053
if conf.Hostname == "" {

pwd/client_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ func TestClientNew(t *testing.T) {
4343
p := NewPWD(_f, _e, _s, sp, ipf)
4444
p.generator = _g
4545

46-
session, err := p.SessionNew("", time.Hour, "", "", "")
46+
playground := &types.Playground{Id: "foobar"}
47+
48+
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
4749
assert.Nil(t, err)
4850

4951
client := p.ClientNew("foobar", session)
@@ -81,8 +83,9 @@ func TestClientCount(t *testing.T) {
8183

8284
p := NewPWD(_f, _e, _s, sp, ipf)
8385
p.generator = _g
86+
playground := &types.Playground{Id: "foobar"}
8487

85-
session, err := p.SessionNew("", time.Hour, "", "", "")
88+
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
8689
assert.Nil(t, err)
8790

8891
p.ClientNew("foobar", session)
@@ -122,8 +125,9 @@ func TestClientResizeViewPort(t *testing.T) {
122125
_e.M.On("Emit", event.INSTANCE_VIEWPORT_RESIZE, "aaaabbbbcccc", []interface{}{uint(80), uint(24)}).Return()
123126
p := NewPWD(_f, _e, _s, sp, ipf)
124127
p.generator = _g
128+
playground := &types.Playground{Id: "foobar"}
125129

126-
session, err := p.SessionNew("", time.Hour, "", "", "")
130+
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
127131
assert.Nil(t, err)
128132
client := p.ClientNew("foobar", session)
129133
_s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil)

0 commit comments

Comments
 (0)