Multiple playgrounds support (#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
This commit is contained in:
Jonathan Leibiusky
2017-11-14 15:50:04 -03:00
committed by GitHub
parent 3dee0d3f0b
commit 3f5b3882dd
24 changed files with 784 additions and 159 deletions

12
api.go
View File

@@ -3,6 +3,7 @@ package main
import ( import (
"log" "log"
"os" "os"
"time"
"github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/docker"
@@ -11,6 +12,7 @@ import (
"github.com/play-with-docker/play-with-docker/id" "github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/provisioner" "github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/pwd" "github.com/play-with-docker/play-with-docker/pwd"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/scheduler" "github.com/play-with-docker/play-with-docker/scheduler"
"github.com/play-with-docker/play-with-docker/scheduler/task" "github.com/play-with-docker/play-with-docker/scheduler/task"
"github.com/play-with-docker/play-with-docker/storage" "github.com/play-with-docker/play-with-docker/storage"
@@ -41,6 +43,16 @@ func main() {
sch.Start() sch.Start()
d, err := time.ParseDuration(config.DefaultSessionDuration)
if err != nil {
log.Fatalf("Cannot parse duration %s. Got: %v", config.DefaultSessionDuration, err)
}
playground := types.Playground{Domain: config.PlaygroundDomain, DefaultDinDInstanceImage: config.DefaultDinDImage, AllowWindowsInstances: config.NoWindows, DefaultSessionDuration: d, AvailableDinDInstanceImages: []string{config.DefaultDinDImage}}
if _, err := core.PlaygroundNew(playground); err != nil {
log.Fatalf("Cannot create default playground. Got: %v", err)
}
handlers.Bootstrap(core, e) handlers.Bootstrap(core, e)
handlers.Register(nil) handlers.Register(nil)
} }

View File

@@ -2,10 +2,7 @@ package config
import ( import (
"flag" "flag"
"fmt"
"os"
"regexp" "regexp"
"time"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
@@ -27,10 +24,9 @@ const (
var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex) var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex)
var AliasFilter = regexp.MustCompile(AliasPortGroupRegex) var AliasFilter = regexp.MustCompile(AliasPortGroupRegex)
var PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, PWDCName, HashKey, SSHKeyPath, L2RouterIP, DindVolumeSize, CookieHashKey, CookieBlockKey string var PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, HashKey, SSHKeyPath, L2RouterIP, DindVolumeSize, CookieHashKey, CookieBlockKey, DefaultDinDImage, DefaultSessionDuration string
var UseLetsEncrypt, ExternalDindVolume, NoWindows bool var UseLetsEncrypt, ExternalDindVolume, NoWindows bool
var LetsEncryptCertsDir string var LetsEncryptCertsDir string
var LetsEncryptDomains stringslice
var MaxLoadAvg float64 var MaxLoadAvg float64
var ForceTLS bool var ForceTLS bool
var Providers map[string]*oauth2.Config var Providers map[string]*oauth2.Config
@@ -40,18 +36,9 @@ var GithubClientID, GithubClientSecret string
var FacebookClientID, FacebookClientSecret string var FacebookClientID, FacebookClientSecret string
var DockerClientID, DockerClientSecret string var DockerClientID, DockerClientSecret string
type stringslice []string var PlaygroundDomain string
func (i *stringslice) String() string {
return fmt.Sprintf("%s", *i)
}
func (i *stringslice) Set(value string) error {
*i = append(*i, value)
return nil
}
func ParseFlags() { func ParseFlags() {
flag.Var(&LetsEncryptDomains, "letsencrypt-domain", "List of domains to validate with let's encrypt")
flag.StringVar(&LetsEncryptCertsDir, "letsencrypt-certs-dir", "/certs", "Path where let's encrypt certs will be stored") flag.StringVar(&LetsEncryptCertsDir, "letsencrypt-certs-dir", "/certs", "Path where let's encrypt certs will be stored")
flag.BoolVar(&UseLetsEncrypt, "letsencrypt-enable", false, "Enabled let's encrypt tls certificates") flag.BoolVar(&UseLetsEncrypt, "letsencrypt-enable", false, "Enabled let's encrypt tls certificates")
flag.BoolVar(&ForceTLS, "tls", false, "Use TLS to connect to docker daemons") flag.BoolVar(&ForceTLS, "tls", false, "Use TLS to connect to docker daemons")
@@ -63,7 +50,6 @@ func ParseFlags() {
flag.StringVar(&L2ContainerName, "l2", "l2", "Container name used to run L2 Router") flag.StringVar(&L2ContainerName, "l2", "l2", "Container name used to run L2 Router")
flag.StringVar(&L2RouterIP, "l2-ip", "", "Host IP address for L2 router ping response") flag.StringVar(&L2RouterIP, "l2-ip", "", "Host IP address for L2 router ping response")
flag.StringVar(&L2Subdomain, "l2-subdomain", "direct", "Subdomain to the L2 Router") flag.StringVar(&L2Subdomain, "l2-subdomain", "direct", "Subdomain to the L2 Router")
flag.StringVar(&PWDCName, "cname", "", "CNAME given to this host")
flag.StringVar(&HashKey, "hash_key", "salmonrosado", "Hash key to use for cookies") flag.StringVar(&HashKey, "hash_key", "salmonrosado", "Hash key to use for cookies")
flag.StringVar(&DindVolumeSize, "dind-volume-size", "5G", "Dind volume folder size") flag.StringVar(&DindVolumeSize, "dind-volume-size", "5G", "Dind volume folder size")
flag.BoolVar(&NoWindows, "win-disable", false, "Disable windows instances") flag.BoolVar(&NoWindows, "win-disable", false, "Disable windows instances")
@@ -72,6 +58,8 @@ func ParseFlags() {
flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use") flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use")
flag.StringVar(&CookieHashKey, "cookie-hash-key", "", "Hash key to use to validate cookies") flag.StringVar(&CookieHashKey, "cookie-hash-key", "", "Hash key to use to validate cookies")
flag.StringVar(&CookieBlockKey, "cookie-block-key", "", "Block key to use to encrypt cookies") flag.StringVar(&CookieBlockKey, "cookie-block-key", "", "Block key to use to encrypt cookies")
flag.StringVar(&DefaultDinDImage, "default-dind-image", "franela/dind", "Default DinD image to use if not specified otherwise")
flag.StringVar(&DefaultSessionDuration, "default-session-duration", "4h", "Default session duration if not specified otherwise")
flag.StringVar(&GithubClientID, "oauth-github-client-id", "", "Github OAuth Client ID") flag.StringVar(&GithubClientID, "oauth-github-client-id", "", "Github OAuth Client ID")
flag.StringVar(&GithubClientSecret, "oauth-github-client-secret", "", "Github OAuth Client Secret") flag.StringVar(&GithubClientSecret, "oauth-github-client-secret", "", "Github OAuth Client Secret")
@@ -82,6 +70,8 @@ func ParseFlags() {
flag.StringVar(&DockerClientID, "oauth-docker-client-id", "", "Docker OAuth Client ID") flag.StringVar(&DockerClientID, "oauth-docker-client-id", "", "Docker OAuth Client ID")
flag.StringVar(&DockerClientSecret, "oauth-docker-client-secret", "", "Docker OAuth Client Secret") flag.StringVar(&DockerClientSecret, "oauth-docker-client-secret", "", "Docker OAuth Client Secret")
flag.StringVar(&PlaygroundDomain, "playground-domain", "localhost", "Domain to use for the playground")
flag.Parse() flag.Parse()
SecureCookie = securecookie.New([]byte(CookieHashKey), []byte(CookieBlockKey)) SecureCookie = securecookie.New([]byte(CookieHashKey), []byte(CookieBlockKey))
@@ -126,38 +116,3 @@ func registerOAuthProviders() {
Providers["docker"] = conf Providers["docker"] = conf
} }
} }
func GetDindImageName() string {
dindImage := os.Getenv("DIND_IMAGE")
defaultDindImageName := "franela/dind"
if len(dindImage) == 0 {
dindImage = defaultDindImageName
}
return dindImage
}
func GetSSHImage() string {
sshImage := os.Getenv("SSH_IMAGE")
defaultSSHImage := "franela/ssh"
if len(sshImage) == 0 {
return defaultSSHImage
}
return sshImage
}
func GetDuration(reqDur string) time.Duration {
var defaultDuration = 4 * time.Hour
if reqDur != "" {
if dur, err := time.ParseDuration(reqDur); err == nil && dur <= defaultDuration {
return dur
}
return defaultDuration
}
envDur := os.Getenv("EXPIRY")
if dur, err := time.ParseDuration(envDur); err == nil {
return dur
}
return defaultDuration
}

View File

@@ -14,16 +14,13 @@ services:
# use the latest golang image # use the latest golang image
image: golang image: golang
# go to the right place and starts the app # go to the right place and starts the app
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' 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'
volumes: volumes:
# since this app creates networks and launches containers, we need to talk to docker daemon # since this app creates networks and launches containers, we need to talk to docker daemon
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
# mount the box mounted shared folder to the container # mount the box mounted shared folder to the container
- $GOPATH/src:/go/src - $GOPATH/src:/go/src
- sessions:/pwd - sessions:/pwd
environment:
GOOGLE_RECAPTCHA_DISABLED: "true"
DIND_IMAGE: "franela/dind:hybrid"
l2: l2:
container_name: l2 container_name: l2
# use the latest golang image # use the latest golang image

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"log" "log"
@@ -11,6 +12,7 @@ import (
gh "github.com/gorilla/handlers" gh "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
lru "github.com/hashicorp/golang-lru"
"github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/event" "github.com/play-with-docker/play-with-docker/event"
"github.com/play-with-docker/play-with-docker/pwd" "github.com/play-with-docker/play-with-docker/pwd"
@@ -92,10 +94,22 @@ func Register(extend HandlerExtender) {
} }
if config.UseLetsEncrypt { if config.UseLetsEncrypt {
domainCache, err := lru.New(5000)
if err != nil {
log.Fatalf("Could not start domain cache. Got: %v", err)
}
certManager := autocert.Manager{ certManager := autocert.Manager{
Prompt: autocert.AcceptTOS, Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(config.LetsEncryptDomains...), HostPolicy: func(ctx context.Context, host string) error {
Cache: autocert.DirCache(config.LetsEncryptCertsDir), if _, found := domainCache.Get(host); !found {
if playground := core.PlaygroundFindByDomain(host); playground == nil {
return fmt.Errorf("Playground for domain %s was not found", host)
}
domainCache.Add(host, true)
}
return nil
},
Cache: autocert.DirCache(config.LetsEncryptCertsDir),
} }
httpServer.TLSConfig = &tls.Config{ httpServer.TLSConfig = &tls.Config{

View File

@@ -2,15 +2,16 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
"github.com/play-with-docker/play-with-docker/config"
) )
func GetInstanceImages(rw http.ResponseWriter, req *http.Request) { func GetInstanceImages(rw http.ResponseWriter, req *http.Request) {
instanceImages := []string{ playground := core.PlaygroundFindByDomain(req.Host)
config.GetDindImageName(), if playground == nil {
"franela/dind:dev", log.Printf("Playground for domain %s was not found!", req.Host)
rw.WriteHeader(http.StatusBadRequest)
return
} }
json.NewEncoder(rw).Encode(instanceImages) json.NewEncoder(rw).Encode(playground.AvailableDinDInstanceImages)
} }

View File

@@ -1,10 +1,10 @@
package handlers package handlers
import ( import (
"log"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/play-with-docker/play-with-docker/config"
) )
func Home(w http.ResponseWriter, r *http.Request) { func Home(w http.ResponseWriter, r *http.Request) {
@@ -21,7 +21,14 @@ func Home(w http.ResponseWriter, r *http.Request) {
go core.SessionDeployStack(s) go core.SessionDeployStack(s)
} }
if config.NoWindows { playground := core.PlaygroundGet(s.PlaygroundId)
if playground == nil {
log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id)
w.WriteHeader(http.StatusBadRequest)
return
}
if !playground.AllowWindowsInstances {
http.ServeFile(w, r, "./www/index-nw.html") http.ServeFile(w, r, "./www/index-nw.html")
} else { } else {
http.ServeFile(w, r, "./www/index.html") http.ServeFile(w, r, "./www/index.html")

View File

@@ -7,7 +7,6 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/provisioner" "github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/pwd" "github.com/play-with-docker/play-with-docker/pwd"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
@@ -23,7 +22,14 @@ func NewInstance(rw http.ResponseWriter, req *http.Request) {
s := core.SessionGet(sessionId) s := core.SessionGet(sessionId)
if body.Type == "windows" && config.NoWindows { playground := core.PlaygroundGet(s.PlaygroundId)
if playground == nil {
log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id)
rw.WriteHeader(http.StatusBadRequest)
return
}
if body.Type == "windows" && !playground.AllowWindowsInstances {
rw.WriteHeader(http.StatusUnauthorized) rw.WriteHeader(http.StatusUnauthorized)
return return
} }

View File

@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"path" "path"
"strings" "strings"
"time"
"github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/provisioner" "github.com/play-with-docker/play-with-docker/provisioner"
@@ -49,8 +50,32 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
} }
} }
duration := config.GetDuration(reqDur)
s, err := core.SessionNew(userId, duration, stack, stackName, imageName) playground := core.PlaygroundFindByDomain(req.Host)
if playground == nil {
log.Printf("Playground for domain %s was not found!", req.Host)
rw.WriteHeader(http.StatusBadRequest)
return
}
var duration time.Duration
if reqDur != "" {
d, err := time.ParseDuration(reqDur)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
return
}
if d > playground.DefaultSessionDuration {
log.Printf("Specified session duration was %s but maximum allowed by this playground is %d\n", d.String(), playground.DefaultSessionDuration.String())
rw.WriteHeader(http.StatusBadRequest)
return
}
duration = d
} else {
duration = playground.DefaultSessionDuration
}
s, err := core.SessionNew(playground, userId, duration, stack, stackName, imageName)
if err != nil { if err != nil {
if provisioner.OutOfCapacity(err) { if provisioner.OutOfCapacity(err) {
http.Redirect(rw, req, "/ooc", http.StatusFound) http.Redirect(rw, req, "/ooc", http.StatusFound)
@@ -62,9 +87,6 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
//TODO: Return some error code //TODO: Return some error code
} else { } else {
hostname := req.Host hostname := req.Host
if config.PWDCName != "" {
hostname = fmt.Sprintf("%s.%s", config.PWDCName, req.Host)
}
// If request is not a form, return sessionId in the body // If request is not a form, return sessionId in the body
if req.Header.Get("X-Requested-With") == "XMLHttpRequest" { if req.Header.Get("X-Requested-With") == "XMLHttpRequest" {
resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname} resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname}

View File

@@ -11,7 +11,6 @@ import (
"strings" "strings"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/docker"
"github.com/play-with-docker/play-with-docker/id" "github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/pwd/types"
@@ -44,7 +43,11 @@ func checkHostnameExists(sessionId, hostname string, instances []*types.Instance
func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) { func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) {
if conf.ImageName == "" { if conf.ImageName == "" {
conf.ImageName = config.GetDindImageName() playground, err := d.storage.PlaygroundGet(session.PlaygroundId)
if err != nil {
return nil, err
}
conf.ImageName = playground.DefaultDinDInstanceImage
} }
log.Printf("NewInstance - using image: [%s]\n", conf.ImageName) log.Printf("NewInstance - using image: [%s]\n", conf.ImageName)
if conf.Hostname == "" { if conf.Hostname == "" {

View File

@@ -43,7 +43,9 @@ func TestClientNew(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf) p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "") playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
client := p.ClientNew("foobar", session) client := p.ClientNew("foobar", session)
@@ -81,8 +83,9 @@ func TestClientCount(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf) p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g p.generator = _g
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew("", time.Hour, "", "", "") session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
p.ClientNew("foobar", session) p.ClientNew("foobar", session)
@@ -122,8 +125,9 @@ func TestClientResizeViewPort(t *testing.T) {
_e.M.On("Emit", event.INSTANCE_VIEWPORT_RESIZE, "aaaabbbbcccc", []interface{}{uint(80), uint(24)}).Return() _e.M.On("Emit", event.INSTANCE_VIEWPORT_RESIZE, "aaaabbbbcccc", []interface{}{uint(80), uint(24)}).Return()
p := NewPWD(_f, _e, _s, sp, ipf) p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g p.generator = _g
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew("", time.Hour, "", "", "") session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
client := p.ClientNew("foobar", session) client := p.ClientNew("foobar", session)
_s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil) _s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil)

View File

@@ -70,7 +70,11 @@ func TestInstanceNew(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf) p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "") playground := &types.Playground{Id: "foobar", DefaultDinDInstanceImage: "franela/dind"}
_s.On("PlaygroundGet", "foobar").Return(playground, nil)
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
expectedInstance := types.Instance{ expectedInstance := types.Instance{
@@ -78,7 +82,7 @@ func TestInstanceNew(t *testing.T) {
Hostname: "node1", Hostname: "node1",
IP: "10.0.0.1", IP: "10.0.0.1",
RoutableIP: "10.0.0.1", RoutableIP: "10.0.0.1",
Image: config.GetDindImageName(), Image: "franela/dind",
SessionId: session.Id, SessionId: session.Id,
SessionHost: session.Host, SessionHost: session.Host,
ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}), ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}),
@@ -138,7 +142,8 @@ func TestInstanceNew_WithNotAllowedImage(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf) p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "") playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
@@ -207,7 +212,8 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf) p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g p.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "") playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err) assert.Nil(t, err)
expectedInstance := types.Instance{ expectedInstance := types.Instance{

View File

@@ -13,7 +13,7 @@ type Mock struct {
mock.Mock mock.Mock
} }
func (m *Mock) SessionNew(userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) { func (m *Mock) SessionNew(playground *types.Playground, userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) {
args := m.Called(duration, stack, stackName, imageName) args := m.Called(duration, stack, stackName, imageName)
return args.Get(0).(*types.Session), args.Error(1) return args.Get(0).(*types.Session), args.Error(1)
} }
@@ -119,7 +119,27 @@ func (m *Mock) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*t
args := m.Called(loginRequest, user) args := m.Called(loginRequest, user)
return args.Get(0).(*types.User), args.Error(1) return args.Get(0).(*types.User), args.Error(1)
} }
func (m *Mock) UserGet(id string) (*types.User, error) { func (m *Mock) UserGet(id string) (*types.User, error) {
args := m.Called(id) args := m.Called(id)
return args.Get(0).(*types.User), args.Error(1) return args.Get(0).(*types.User), args.Error(1)
} }
func (m *Mock) PlaygroundNew(playground types.Playground) (*types.Playground, error) {
args := m.Called(playground)
return args.Get(0).(*types.Playground), args.Error(1)
}
func (m *Mock) PlaygroundGet(id string) *types.Playground {
args := m.Called(id)
return args.Get(0).(*types.Playground)
}
func (m *Mock) PlaygroundFindByDomain(domain string) *types.Playground {
args := m.Called(domain)
return args.Get(0).(*types.Playground)
}
func (m *Mock) PlaygroundList() ([]*types.Playground, error) {
args := m.Called()
return args.Get(0).([]*types.Playground), args.Error(1)
}

36
pwd/playground.go Normal file
View File

@@ -0,0 +1,36 @@
package pwd
import (
"log"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/twinj/uuid"
)
func (p *pwd) PlaygroundNew(playground types.Playground) (*types.Playground, error) {
playground.Id = uuid.NewV5(uuid.NameSpaceURL, uuid.Name(playground.Domain)).String()
if err := p.storage.PlaygroundPut(&playground); err != nil {
log.Printf("Error saving playground %s. Got: %v\n", playground.Id, err)
return nil, err
}
return &playground, nil
}
func (p *pwd) PlaygroundGet(id string) *types.Playground {
if playground, err := p.storage.PlaygroundGet(id); err != nil {
log.Printf("Error retrieving playground %s. Got: %v\n", id, err)
return nil
} else {
return playground
}
}
func (p *pwd) PlaygroundFindByDomain(domain string) *types.Playground {
id := uuid.NewV5(uuid.NameSpaceURL, uuid.Name(domain)).String()
return p.PlaygroundGet(id)
}
func (p *pwd) PlaygroundList() ([]*types.Playground, error) {
return p.storage.PlaygroundGetAll()
}

152
pwd/playground_test.go Normal file
View File

@@ -0,0 +1,152 @@
package pwd
import (
"testing"
"time"
"github.com/play-with-docker/play-with-docker/docker"
"github.com/play-with-docker/play-with-docker/event"
"github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/twinj/uuid"
)
func TestPlaygroundNew(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
playground, e := p.PlaygroundNew(expectedPlayground)
assert.Nil(t, e)
assert.NotNil(t, playground)
expectedPlayground.Id = uuid.NewV5(uuid.NameSpaceURL, uuid.Name("localhost")).String()
assert.Equal(t, expectedPlayground, *playground)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}
func TestPlaygroundGet(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
playground, e := p.PlaygroundNew(expectedPlayground)
assert.Nil(t, e)
assert.NotNil(t, playground)
_s.On("PlaygroundGet", playground.Id).Return(playground, nil)
playground2 := p.PlaygroundGet(playground.Id)
assert.NotNil(t, playground2)
assert.Equal(t, *playground, *playground2)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}
func TestPlaygroundFindByDomain(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
playground, e := p.PlaygroundNew(expectedPlayground)
assert.Nil(t, e)
assert.NotNil(t, playground)
_s.On("PlaygroundGet", playground.Id).Return(playground, nil)
playground2 := p.PlaygroundFindByDomain("localhost")
assert.NotNil(t, playground2)
assert.Equal(t, *playground, *playground2)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}
func TestPlaygroundList(t *testing.T) {
_d := &docker.Mock{}
_f := &docker.FactoryMock{}
_s := &storage.Mock{}
_g := &id.MockGenerator{}
_e := &event.Mock{}
_s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil)
ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s))
sp := provisioner.NewOverlaySessionProvisioner(_f)
p := NewPWD(_f, _e, _s, sp, ipf)
p.generator = _g
pd := types.Playground{Domain: "localhost1", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
p1, e := p.PlaygroundNew(pd)
assert.Nil(t, e)
assert.NotNil(t, p1)
pd = types.Playground{Domain: "localhost2", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}}
p2, e := p.PlaygroundNew(pd)
assert.Nil(t, e)
assert.NotNil(t, p2)
_s.On("PlaygroundGetAll").Return([]*types.Playground{p1, p2}, nil)
received, err := p.PlaygroundList()
assert.Nil(t, err)
assert.NotNil(t, received)
_d.AssertExpectations(t)
_f.AssertExpectations(t)
_s.AssertExpectations(t)
_g.AssertExpectations(t)
_e.M.AssertExpectations(t)
}

View File

@@ -72,7 +72,7 @@ func SessionNotEmpty(e error) bool {
} }
type PWDApi interface { type PWDApi interface {
SessionNew(userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) SessionNew(playground *types.Playground, userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error)
SessionClose(session *types.Session) error SessionClose(session *types.Session) error
SessionGetSmallestViewPort(sessionId string) types.ViewPort SessionGetSmallestViewPort(sessionId string) types.ViewPort
SessionDeployStack(session *types.Session) error SessionDeployStack(session *types.Session) error
@@ -98,6 +98,11 @@ type PWDApi interface {
UserGetLoginRequest(id string) (*types.LoginRequest, error) UserGetLoginRequest(id string) (*types.LoginRequest, error)
UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error)
UserGet(id string) (*types.User, error) UserGet(id string) (*types.User, error)
PlaygroundNew(playground types.Playground) (*types.Playground, error)
PlaygroundGet(id string) *types.Playground
PlaygroundFindByDomain(domain string) *types.Playground
PlaygroundList() ([]*types.Playground, error)
} }
func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi, sp provisioner.SessionProvisionerApi, ipf provisioner.InstanceProvisionerFactoryApi) *pwd { func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi, sp provisioner.SessionProvisionerApi, ipf provisioner.InstanceProvisionerFactoryApi) *pwd {

View File

@@ -44,7 +44,7 @@ type SessionSetupInstanceConf struct {
Tls bool `json:"tls"` Tls bool `json:"tls"`
} }
func (p *pwd) SessionNew(userId string, duration time.Duration, stack, stackName, imageName string) (*types.Session, error) { func (p *pwd) SessionNew(playground *types.Playground, userId string, duration time.Duration, stack, stackName, imageName string) (*types.Session, error) {
defer observeAction("SessionNew", time.Now()) defer observeAction("SessionNew", time.Now())
s := &types.Session{} s := &types.Session{}
@@ -54,6 +54,7 @@ func (p *pwd) SessionNew(userId string, duration time.Duration, stack, stackName
s.Ready = true s.Ready = true
s.Stack = stack s.Stack = stack
s.UserId = userId s.UserId = userId
s.PlaygroundId = playground.Id
if s.Stack != "" { if s.Stack != "" {
s.Ready = false s.Ready = false

View File

@@ -10,6 +10,7 @@ import (
"github.com/play-with-docker/play-with-docker/event" "github.com/play-with-docker/play-with-docker/event"
"github.com/play-with-docker/play-with-docker/id" "github.com/play-with-docker/play-with-docker/id"
"github.com/play-with-docker/play-with-docker/provisioner" "github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/storage" "github.com/play-with-docker/play-with-docker/storage"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
@@ -45,7 +46,8 @@ func TestSessionNew(t *testing.T) {
before := time.Now() before := time.Now()
s, e := p.SessionNew("", time.Hour, "", "", "") playground := &types.Playground{Id: "foobar"}
s, e := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, e) assert.Nil(t, e)
assert.NotNil(t, s) assert.NotNil(t, s)
@@ -56,12 +58,13 @@ func TestSessionNew(t *testing.T) {
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second) assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
assert.True(t, s.Ready) assert.True(t, s.Ready)
s, _ = p.SessionNew("", time.Hour, "stackPath", "stackName", "imageName") s, _ = p.SessionNew(playground, "", time.Hour, "stackPath", "stackName", "imageName")
assert.Equal(t, "stackPath", s.Stack) assert.Equal(t, "stackPath", s.Stack)
assert.Equal(t, "stackName", s.StackName) assert.Equal(t, "stackName", s.StackName)
assert.Equal(t, "imageName", s.ImageName) assert.Equal(t, "imageName", s.ImageName)
assert.Equal(t, "localhost", s.Host) assert.Equal(t, "localhost", s.Host)
assert.Equal(t, playground.Id, s.PlaygroundId)
assert.False(t, s.Ready) assert.False(t, s.Ready)
_d.AssertExpectations(t) _d.AssertExpectations(t)

83
pwd/types/playground.go Normal file
View File

@@ -0,0 +1,83 @@
package types
import (
"strconv"
"time"
)
type PlaygroundExtras map[string]interface{}
func (e PlaygroundExtras) Get(name string) (interface{}, bool) {
v, f := e[name]
return v, f
}
func (e PlaygroundExtras) GetInt(name string) (int, bool) {
v, f := e[name]
if f {
if r, ok := v.(int); ok {
return r, ok
} else if r, ok := v.(float64); ok {
return int(r), ok
} else if r, ok := v.(string); ok {
if v, err := strconv.Atoi(r); err != nil {
return 0, false
} else {
return v, true
}
}
return v.(int), f
} else {
return 0, f
}
}
func (e PlaygroundExtras) GetString(name string) (string, bool) {
v, f := e[name]
if f {
if r, ok := v.(int); ok {
return strconv.Itoa(r), ok
} else if r, ok := v.(float64); ok {
return strconv.FormatFloat(r, 'g', -1, 64), ok
} else if r, ok := v.(bool); ok {
return strconv.FormatBool(r), ok
} else if r, ok := v.(string); ok {
return r, ok
} else {
return "", false
}
} else {
return "", f
}
}
func (e PlaygroundExtras) GetDuration(name string) (time.Duration, bool) {
v, f := e[name]
if f {
if r, ok := v.(int); ok {
return time.Duration(r), ok
} else if r, ok := v.(float64); ok {
return time.Duration(r), ok
} else if r, ok := v.(string); ok {
if d, err := time.ParseDuration(r); err != nil {
return time.Duration(0), false
} else {
return d, true
}
} else {
return time.Duration(0), false
}
} else {
return time.Duration(0), f
}
}
type Playground struct {
Id string `json:"id" bson:"id"`
Domain string `json:"domain" bson:"domain"`
DefaultDinDInstanceImage string `json:"default_dind_instance_image" bson:"default_dind_instance_image"`
AvailableDinDInstanceImages []string `json:"available_dind_instance_images" bson:"available_dind_instance_images"`
AllowWindowsInstances bool `json:"allow_windows_instances" bson:"allow_windows_instances"`
DefaultSessionDuration time.Duration `json:"default_session_duration" bson:"default_session_duration"`
L2RouterIP string `json:"l2_router_ip" bson:"l2_router_ip"`
Extras PlaygroundExtras `json:"extras" bson:"extras"`
}

View File

@@ -0,0 +1,119 @@
package types
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/twinj/uuid"
)
func TestPlayground_Extras_GetInt(t *testing.T) {
p := Playground{
Id: uuid.NewV4().String(),
Domain: "localhost",
DefaultDinDInstanceImage: "franel/dind",
AllowWindowsInstances: false,
DefaultSessionDuration: time.Hour * 4,
Extras: PlaygroundExtras{
"intFromInt": 10,
"intFromFloat": 32.0,
"intFromString": "15",
},
}
b, err := json.Marshal(p)
assert.Nil(t, err)
var p2 Playground
json.Unmarshal(b, &p2)
v, found := p2.Extras.GetInt("intFromInt")
assert.True(t, found)
assert.Equal(t, 10, v)
v, found = p2.Extras.GetInt("intFromFloat")
assert.True(t, found)
assert.Equal(t, 32, v)
v, found = p2.Extras.GetInt("intFromString")
assert.True(t, found)
assert.Equal(t, 15, v)
}
func TestPlayground_Extras_GetString(t *testing.T) {
p := Playground{
Id: uuid.NewV4().String(),
Domain: "localhost",
DefaultDinDInstanceImage: "franel/dind",
AllowWindowsInstances: false,
DefaultSessionDuration: time.Hour * 4,
Extras: PlaygroundExtras{
"stringFromInt": 10,
"stringFromFloat": 32.3,
"stringFromString": "15",
"stringFromBool": false,
},
}
b, err := json.Marshal(p)
assert.Nil(t, err)
var p2 Playground
json.Unmarshal(b, &p2)
v, found := p2.Extras.GetString("stringFromInt")
assert.True(t, found)
assert.Equal(t, "10", v)
v, found = p2.Extras.GetString("stringFromFloat")
assert.True(t, found)
assert.Equal(t, "32.3", v)
v, found = p2.Extras.GetString("stringFromString")
assert.True(t, found)
assert.Equal(t, "15", v)
v, found = p2.Extras.GetString("stringFromBool")
assert.True(t, found)
assert.Equal(t, "false", v)
}
func TestPlayground_Extras_GetDuration(t *testing.T) {
p := Playground{
Id: uuid.NewV4().String(),
Domain: "localhost",
DefaultDinDInstanceImage: "franel/dind",
AllowWindowsInstances: false,
DefaultSessionDuration: time.Hour * 4,
Extras: PlaygroundExtras{
"durationFromInt": 10,
"durationFromFloat": 32.3,
"durationFromString": "4h",
"durationFromDuration": time.Hour * 3,
},
}
b, err := json.Marshal(p)
assert.Nil(t, err)
var p2 Playground
json.Unmarshal(b, &p2)
v, found := p2.Extras.GetDuration("durationFromInt")
assert.True(t, found)
assert.Equal(t, time.Duration(10), v)
v, found = p2.Extras.GetDuration("durationFromFloat")
assert.True(t, found)
assert.Equal(t, time.Duration(32), v)
v, found = p2.Extras.GetDuration("durationFromString")
assert.True(t, found)
assert.Equal(t, time.Hour*4, v)
v, found = p2.Extras.GetDuration("durationFromDuration")
assert.True(t, found)
assert.Equal(t, time.Hour*3, v)
}

View File

@@ -15,4 +15,5 @@ type Session struct {
ImageName string `json:"image_name" bson:"image_name"` ImageName string `json:"image_name" bson:"image_name"`
Host string `json:"host" bson:"host"` Host string `json:"host" bson:"host"`
UserId string `json:"user_id" bson:"user_id"` UserId string `json:"user_id" bson:"user_id"`
PlaygroundId string `json:"playground_id" bson:"playground_id"`
} }

View File

@@ -22,6 +22,7 @@ type DB struct {
WindowsInstances map[string]*types.WindowsInstance `json:"windows_instances"` WindowsInstances map[string]*types.WindowsInstance `json:"windows_instances"`
LoginRequests map[string]*types.LoginRequest `json:"login_requests"` LoginRequests map[string]*types.LoginRequest `json:"login_requests"`
Users map[string]*types.User `json:"user"` Users map[string]*types.User `json:"user"`
Playgrounds map[string]*types.Playground `json:"playgrounds"`
WindowsInstancesBySessionId map[string][]string `json:"windows_instances_by_session_id"` WindowsInstancesBySessionId map[string][]string `json:"windows_instances_by_session_id"`
InstancesBySessionId map[string][]string `json:"instances_by_session_id"` InstancesBySessionId map[string][]string `json:"instances_by_session_id"`
@@ -364,6 +365,25 @@ func (store *storage) UserGet(id string) (*types.User, error) {
} }
} }
func (store *storage) PlaygroundPut(playground *types.Playground) error {
store.rw.Lock()
defer store.rw.Unlock()
store.db.Playgrounds[playground.Id] = playground
return store.save()
}
func (store *storage) PlaygroundGet(id string) (*types.Playground, error) {
store.rw.Lock()
defer store.rw.Unlock()
if playground, found := store.db.Playgrounds[id]; !found {
return nil, NotFoundError
} else {
return playground, nil
}
return nil, NotFoundError
}
func (store *storage) load() error { func (store *storage) load() error {
file, err := os.Open(store.path) file, err := os.Open(store.path)
@@ -376,12 +396,13 @@ func (store *storage) load() error {
} }
} else { } else {
store.db = &DB{ store.db = &DB{
Sessions: map[string]*types.Session{}, Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -392,6 +413,19 @@ func (store *storage) load() error {
file.Close() file.Close()
return nil return nil
} }
func (store *storage) PlaygroundGetAll() ([]*types.Playground, error) {
store.rw.Lock()
defer store.rw.Unlock()
playgrounds := make([]*types.Playground, len(store.db.Playgrounds))
i := 0
for _, p := range store.db.Playgrounds {
playgrounds[i] = p
i++
}
return playgrounds, nil
}
func (store *storage) save() error { func (store *storage) save() error {
file, err := os.Create(store.path) file, err := os.Create(store.path)

View File

@@ -30,12 +30,13 @@ func TestSessionPut(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s}, Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -59,12 +60,13 @@ func TestSessionPut(t *testing.T) {
func TestSessionGet(t *testing.T) { func TestSessionGet(t *testing.T) {
expectedSession := &types.Session{Id: "aaabbbccc"} expectedSession := &types.Session{Id: "aaabbbccc"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{expectedSession.Id: expectedSession}, Sessions: map[string]*types.Session{expectedSession.Id: expectedSession},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -98,12 +100,13 @@ func TestSessionGetAll(t *testing.T) {
s1 := &types.Session{Id: "aaabbbccc"} s1 := &types.Session{Id: "aaabbbccc"}
s2 := &types.Session{Id: "dddeeefff"} s2 := &types.Session{Id: "dddeeefff"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2}, Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -163,12 +166,13 @@ func TestSessionDelete(t *testing.T) {
func TestInstanceGet(t *testing.T) { func TestInstanceGet(t *testing.T) {
expectedInstance := &types.Instance{SessionId: "aaabbbccc", Name: "i1", IP: "10.0.0.1"} expectedInstance := &types.Instance{SessionId: "aaabbbccc", Name: "i1", IP: "10.0.0.1"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{}, Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance}, Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{expectedInstance.SessionId: []string{expectedInstance.Name}}, InstancesBySessionId: map[string][]string{expectedInstance.SessionId: []string{expectedInstance.Name}},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -217,12 +221,13 @@ func TestInstancePut(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s}, Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{i.Name: i}, Instances: map[string]*types.Instance{i.Name: i},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{i.SessionId: []string{i.Name}}, InstancesBySessionId: map[string][]string{i.SessionId: []string{i.Name}},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -280,12 +285,13 @@ func TestInstanceFindBySessionId(t *testing.T) {
i1 := &types.Instance{SessionId: "aaabbbccc", Name: "c1"} i1 := &types.Instance{SessionId: "aaabbbccc", Name: "c1"}
i2 := &types.Instance{SessionId: "aaabbbccc", Name: "c2"} i2 := &types.Instance{SessionId: "aaabbbccc", Name: "c2"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{}, Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2}, Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Name, i2.Name}}, InstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Name, i2.Name}},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -316,12 +322,13 @@ func TestWindowsInstanceGetAll(t *testing.T) {
i1 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i1"} i1 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i1"}
i2 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i2"} i2 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i2"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{}, Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2}, WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Id, i2.Id}}, WindowsInstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Id, i2.Id}},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -371,12 +378,13 @@ func TestWindowsInstancePut(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s}, Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{}, Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{i.Id: i}, WindowsInstances: map[string]*types.WindowsInstance{i.Id: i},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{i.SessionId: []string{i.Id}}, WindowsInstancesBySessionId: map[string][]string{i.SessionId: []string{i.Id}},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{}, ClientsBySessionId: map[string][]string{},
@@ -433,12 +441,13 @@ func TestWindowsInstanceDelete(t *testing.T) {
func TestClientGet(t *testing.T) { func TestClientGet(t *testing.T) {
c := &types.Client{SessionId: "aaabbbccc", Id: "c1"} c := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{}, Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c.Id: c}, Clients: map[string]*types.Client{c.Id: c},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}}, ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
@@ -487,12 +496,13 @@ func TestClientPut(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s}, Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c.Id: c}, Clients: map[string]*types.Client{c.Id: c},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}}, ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
@@ -550,12 +560,13 @@ func TestClientFindBySessionId(t *testing.T) {
c1 := &types.Client{SessionId: "aaabbbccc", Id: "c1"} c1 := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
c2 := &types.Client{SessionId: "aaabbbccc", Id: "c2"} c2 := &types.Client{SessionId: "aaabbbccc", Id: "c2"}
expectedDB := &DB{ expectedDB := &DB{
Sessions: map[string]*types.Session{}, Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{}, Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2}, Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2},
WindowsInstances: map[string]*types.WindowsInstance{}, WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{}, LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{}, Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{}, WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{}, InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{c1.SessionId: []string{c1.Id, c2.Id}}, ClientsBySessionId: map[string][]string{c1.SessionId: []string{c1.Id, c2.Id}},
@@ -581,3 +592,120 @@ func TestClientFindBySessionId(t *testing.T) {
assert.Subset(t, clients, []*types.Client{c1, c2}) assert.Subset(t, clients, []*types.Client{c1, c2})
assert.Len(t, clients, 2) assert.Len(t, clients, 2)
} }
func TestPlaygroundGet(t *testing.T) {
p := &types.Playground{Id: "aaabbbccc"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{p.Id: p},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
UsersByProvider: map[string]string{},
}
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
encoder := json.NewEncoder(tmpfile)
err = encoder.Encode(&expectedDB)
assert.Nil(t, err)
tmpfile.Close()
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
found, err := storage.PlaygroundGet("aaabbbccc")
assert.Nil(t, err)
assert.Equal(t, p, found)
}
func TestPlaygroundPut(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
tmpfile.Close()
os.Remove(tmpfile.Name())
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
p := &types.Playground{Id: "aaabbbccc"}
err = storage.PlaygroundPut(p)
assert.Nil(t, err)
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{p.Id: p},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
UsersByProvider: map[string]string{},
}
var loadedDB *DB
file, err := os.Open(tmpfile.Name())
assert.Nil(t, err)
defer file.Close()
decoder := json.NewDecoder(file)
err = decoder.Decode(&loadedDB)
assert.Nil(t, err)
assert.EqualValues(t, expectedDB, loadedDB)
}
func TestPlaygroundGetAll(t *testing.T) {
p1 := &types.Playground{Id: "aaabbbccc"}
p2 := &types.Playground{Id: "dddeeefff"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{p1.Id: p1, p2.Id: p2},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
UsersByProvider: map[string]string{},
}
tmpfile, err := ioutil.TempFile("", "pwd")
if err != nil {
log.Fatal(err)
}
encoder := json.NewEncoder(tmpfile)
err = encoder.Encode(&expectedDB)
assert.Nil(t, err)
tmpfile.Close()
defer os.Remove(tmpfile.Name())
storage, err := NewFileStorage(tmpfile.Name())
assert.Nil(t, err)
found, err := storage.PlaygroundGetAll()
assert.Nil(t, err)
assert.Equal(t, []*types.Playground{p1, p2}, found)
}

View File

@@ -106,3 +106,15 @@ func (m *Mock) UserGet(id string) (*types.User, error) {
args := m.Called(id) args := m.Called(id)
return args.Get(0).(*types.User), args.Error(1) return args.Get(0).(*types.User), args.Error(1)
} }
func (m *Mock) PlaygroundPut(playground *types.Playground) error {
args := m.Called(playground)
return args.Error(0)
}
func (m *Mock) PlaygroundGet(id string) (*types.Playground, error) {
args := m.Called(id)
return args.Get(0).(*types.Playground), args.Error(1)
}
func (m *Mock) PlaygroundGetAll() ([]*types.Playground, error) {
args := m.Called()
return args.Get(0).([]*types.Playground), args.Error(1)
}

View File

@@ -42,4 +42,8 @@ type StorageApi interface {
UserFindByProvider(providerName, providerUserId string) (*types.User, error) UserFindByProvider(providerName, providerUserId string) (*types.User, error)
UserPut(user *types.User) error UserPut(user *types.User) error
UserGet(id string) (*types.User, error) UserGet(id string) (*types.User, error)
PlaygroundPut(playground *types.Playground) error
PlaygroundGet(id string) (*types.Playground, error)
PlaygroundGetAll() ([]*types.Playground, error)
} }