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 (
"log"
"os"
"time"
"github.com/play-with-docker/play-with-docker/config"
"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/provisioner"
"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/task"
"github.com/play-with-docker/play-with-docker/storage"
@@ -41,6 +43,16 @@ func main() {
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.Register(nil)
}

View File

@@ -2,10 +2,7 @@ package config
import (
"flag"
"fmt"
"os"
"regexp"
"time"
"github.com/gorilla/securecookie"
@@ -27,10 +24,9 @@ const (
var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex)
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 LetsEncryptCertsDir string
var LetsEncryptDomains stringslice
var MaxLoadAvg float64
var ForceTLS bool
var Providers map[string]*oauth2.Config
@@ -40,18 +36,9 @@ var GithubClientID, GithubClientSecret string
var FacebookClientID, FacebookClientSecret string
var DockerClientID, DockerClientSecret string
type stringslice []string
func (i *stringslice) String() string {
return fmt.Sprintf("%s", *i)
}
func (i *stringslice) Set(value string) error {
*i = append(*i, value)
return nil
}
var PlaygroundDomain string
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.BoolVar(&UseLetsEncrypt, "letsencrypt-enable", false, "Enabled let's encrypt tls certificates")
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(&L2RouterIP, "l2-ip", "", "Host IP address for L2 router ping response")
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(&DindVolumeSize, "dind-volume-size", "5G", "Dind volume folder size")
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(&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(&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(&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(&DockerClientSecret, "oauth-docker-client-secret", "", "Docker OAuth Client Secret")
flag.StringVar(&PlaygroundDomain, "playground-domain", "localhost", "Domain to use for the playground")
flag.Parse()
SecureCookie = securecookie.New([]byte(CookieHashKey), []byte(CookieBlockKey))
@@ -126,38 +116,3 @@ func registerOAuthProviders() {
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
image: golang
# 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:
# since this app creates networks and launches containers, we need to talk to docker daemon
- /var/run/docker.sock:/var/run/docker.sock
# mount the box mounted shared folder to the container
- $GOPATH/src:/go/src
- sessions:/pwd
environment:
GOOGLE_RECAPTCHA_DISABLED: "true"
DIND_IMAGE: "franela/dind:hybrid"
l2:
container_name: l2
# use the latest golang image

View File

@@ -1,6 +1,7 @@
package handlers
import (
"context"
"crypto/tls"
"fmt"
"log"
@@ -11,6 +12,7 @@ import (
gh "github.com/gorilla/handlers"
"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/event"
"github.com/play-with-docker/play-with-docker/pwd"
@@ -92,10 +94,22 @@ func Register(extend HandlerExtender) {
}
if config.UseLetsEncrypt {
domainCache, err := lru.New(5000)
if err != nil {
log.Fatalf("Could not start domain cache. Got: %v", err)
}
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(config.LetsEncryptDomains...),
Cache: autocert.DirCache(config.LetsEncryptCertsDir),
Prompt: autocert.AcceptTOS,
HostPolicy: func(ctx context.Context, host string) error {
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{

View File

@@ -2,15 +2,16 @@ package handlers
import (
"encoding/json"
"log"
"net/http"
"github.com/play-with-docker/play-with-docker/config"
)
func GetInstanceImages(rw http.ResponseWriter, req *http.Request) {
instanceImages := []string{
config.GetDindImageName(),
"franela/dind:dev",
playground := core.PlaygroundFindByDomain(req.Host)
if playground == nil {
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
import (
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/play-with-docker/play-with-docker/config"
)
func Home(w http.ResponseWriter, r *http.Request) {
@@ -21,7 +21,14 @@ func Home(w http.ResponseWriter, r *http.Request) {
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")
} else {
http.ServeFile(w, r, "./www/index.html")

View File

@@ -7,7 +7,6 @@ import (
"net/http"
"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/pwd"
"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)
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)
return
}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"path"
"strings"
"time"
"github.com/play-with-docker/play-with-docker/config"
"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 provisioner.OutOfCapacity(err) {
http.Redirect(rw, req, "/ooc", http.StatusFound)
@@ -62,9 +87,6 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
//TODO: Return some error code
} else {
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 req.Header.Get("X-Requested-With") == "XMLHttpRequest" {
resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname}

View File

@@ -11,7 +11,6 @@ import (
"strings"
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/id"
"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) {
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)
if conf.Hostname == "" {

View File

@@ -43,7 +43,9 @@ func TestClientNew(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
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)
client := p.ClientNew("foobar", session)
@@ -81,8 +83,9 @@ func TestClientCount(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
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)
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()
p := NewPWD(_f, _e, _s, sp, ipf)
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)
client := p.ClientNew("foobar", session)
_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.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)
expectedInstance := types.Instance{
@@ -78,7 +82,7 @@ func TestInstanceNew(t *testing.T) {
Hostname: "node1",
IP: "10.0.0.1",
RoutableIP: "10.0.0.1",
Image: config.GetDindImageName(),
Image: "franela/dind",
SessionId: session.Id,
SessionHost: session.Host,
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.generator = _g
session, err := p.SessionNew("", time.Hour, "", "", "")
playground := &types.Playground{Id: "foobar"}
session, err := p.SessionNew(playground, "", time.Hour, "", "", "")
assert.Nil(t, err)
@@ -207,7 +212,8 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) {
p := NewPWD(_f, _e, _s, sp, ipf)
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)
expectedInstance := types.Instance{

View File

@@ -13,7 +13,7 @@ type Mock struct {
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)
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)
return args.Get(0).(*types.User), args.Error(1)
}
func (m *Mock) UserGet(id string) (*types.User, error) {
args := m.Called(id)
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 {
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
SessionGetSmallestViewPort(sessionId string) types.ViewPort
SessionDeployStack(session *types.Session) error
@@ -98,6 +98,11 @@ type PWDApi interface {
UserGetLoginRequest(id string) (*types.LoginRequest, error)
UserLogin(loginRequest *types.LoginRequest, user *types.User) (*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 {

View File

@@ -44,7 +44,7 @@ type SessionSetupInstanceConf struct {
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())
s := &types.Session{}
@@ -54,6 +54,7 @@ func (p *pwd) SessionNew(userId string, duration time.Duration, stack, stackName
s.Ready = true
s.Stack = stack
s.UserId = userId
s.PlaygroundId = playground.Id
if s.Stack != "" {
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/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"
@@ -45,7 +46,8 @@ func TestSessionNew(t *testing.T) {
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.NotNil(t, s)
@@ -56,12 +58,13 @@ func TestSessionNew(t *testing.T) {
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
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, "stackName", s.StackName)
assert.Equal(t, "imageName", s.ImageName)
assert.Equal(t, "localhost", s.Host)
assert.Equal(t, playground.Id, s.PlaygroundId)
assert.False(t, s.Ready)
_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"`
Host string `json:"host" bson:"host"`
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"`
LoginRequests map[string]*types.LoginRequest `json:"login_requests"`
Users map[string]*types.User `json:"user"`
Playgrounds map[string]*types.Playground `json:"playgrounds"`
WindowsInstancesBySessionId map[string][]string `json:"windows_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 {
file, err := os.Open(store.path)
@@ -376,12 +396,13 @@ func (store *storage) load() error {
}
} else {
store.db = &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{},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
@@ -392,6 +413,19 @@ func (store *storage) load() error {
file.Close()
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 {
file, err := os.Create(store.path)

View File

@@ -30,12 +30,13 @@ func TestSessionPut(t *testing.T) {
assert.Nil(t, err)
expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s},
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{},
Sessions: map[string]*types.Session{s.Id: s},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
@@ -59,12 +60,13 @@ func TestSessionPut(t *testing.T) {
func TestSessionGet(t *testing.T) {
expectedSession := &types.Session{Id: "aaabbbccc"}
expectedDB := &DB{
Sessions: map[string]*types.Session{expectedSession.Id: expectedSession},
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{},
Sessions: map[string]*types.Session{expectedSession.Id: expectedSession},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
@@ -98,12 +100,13 @@ func TestSessionGetAll(t *testing.T) {
s1 := &types.Session{Id: "aaabbbccc"}
s2 := &types.Session{Id: "dddeeefff"}
expectedDB := &DB{
Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2},
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{},
Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
@@ -163,12 +166,13 @@ func TestSessionDelete(t *testing.T) {
func TestInstanceGet(t *testing.T) {
expectedInstance := &types.Instance{SessionId: "aaabbbccc", Name: "i1", IP: "10.0.0.1"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{expectedInstance.SessionId: []string{expectedInstance.Name}},
ClientsBySessionId: map[string][]string{},
@@ -217,12 +221,13 @@ func TestInstancePut(t *testing.T) {
assert.Nil(t, err)
expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{i.Name: i},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{i.Name: i},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{i.SessionId: []string{i.Name}},
ClientsBySessionId: map[string][]string{},
@@ -280,12 +285,13 @@ func TestInstanceFindBySessionId(t *testing.T) {
i1 := &types.Instance{SessionId: "aaabbbccc", Name: "c1"}
i2 := &types.Instance{SessionId: "aaabbbccc", Name: "c2"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2},
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{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Name, i2.Name}},
ClientsBySessionId: map[string][]string{},
@@ -316,12 +322,13 @@ func TestWindowsInstanceGetAll(t *testing.T) {
i1 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i1"}
i2 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i2"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Id, i2.Id}},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
@@ -371,12 +378,13 @@ func TestWindowsInstancePut(t *testing.T) {
assert.Nil(t, err)
expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{i.Id: i},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{},
WindowsInstances: map[string]*types.WindowsInstance{i.Id: i},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{i.SessionId: []string{i.Id}},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{},
@@ -433,12 +441,13 @@ func TestWindowsInstanceDelete(t *testing.T) {
func TestClientGet(t *testing.T) {
c := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c.Id: c},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c.Id: c},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
@@ -487,12 +496,13 @@ func TestClientPut(t *testing.T) {
assert.Nil(t, err)
expectedDB := &DB{
Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c.Id: c},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{s.Id: s},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c.Id: c},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
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"}
c2 := &types.Client{SessionId: "aaabbbccc", Id: "c2"}
expectedDB := &DB{
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Sessions: map[string]*types.Session{},
Instances: map[string]*types.Instance{},
Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2},
WindowsInstances: map[string]*types.WindowsInstance{},
LoginRequests: map[string]*types.LoginRequest{},
Users: map[string]*types.User{},
Playgrounds: map[string]*types.Playground{},
WindowsInstancesBySessionId: map[string][]string{},
InstancesBySessionId: map[string][]string{},
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.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)
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)
UserPut(user *types.User) error
UserGet(id string) (*types.User, error)
PlaygroundPut(playground *types.Playground) error
PlaygroundGet(id string) (*types.Playground, error)
PlaygroundGetAll() ([]*types.Playground, error)
}