From 3f5b3882dd44ece3c6cac56d32b4abc53b09c47d Mon Sep 17 00:00:00 2001 From: Jonathan Leibiusky Date: Tue, 14 Nov 2017 15:50:04 -0300 Subject: [PATCH] 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 --- api.go | 12 ++ config/config.go | 57 +------ docker-compose.yml | 5 +- handlers/bootstrap.go | 20 ++- handlers/get_instance_images.go | 13 +- handlers/home.go | 11 +- handlers/new_instance.go | 10 +- handlers/new_session.go | 32 +++- provisioner/dind.go | 7 +- pwd/client_test.go | 10 +- pwd/instance_test.go | 14 +- pwd/mock.go | 22 ++- pwd/playground.go | 36 +++++ pwd/playground_test.go | 152 +++++++++++++++++++ pwd/pwd.go | 7 +- pwd/session.go | 3 +- pwd/session_test.go | 7 +- pwd/types/playground.go | 83 ++++++++++ pwd/types/playground_test.go | 119 +++++++++++++++ pwd/types/session.go | 1 + storage/file.go | 46 +++++- storage/file_test.go | 260 ++++++++++++++++++++++++-------- storage/mock.go | 12 ++ storage/storage.go | 4 + 24 files changed, 784 insertions(+), 159 deletions(-) create mode 100644 pwd/playground.go create mode 100644 pwd/playground_test.go create mode 100644 pwd/types/playground.go create mode 100644 pwd/types/playground_test.go diff --git a/api.go b/api.go index 0d13baf..09d6b47 100644 --- a/api.go +++ b/api.go @@ -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) } diff --git a/config/config.go b/config/config.go index 56bbb33..5d830c9 100644 --- a/config/config.go +++ b/config/config.go @@ -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 -} diff --git a/docker-compose.yml b/docker-compose.yml index bc9777f..0163a65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/handlers/bootstrap.go b/handlers/bootstrap.go index 24c7301..f4827d4 100644 --- a/handlers/bootstrap.go +++ b/handlers/bootstrap.go @@ -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{ diff --git a/handlers/get_instance_images.go b/handlers/get_instance_images.go index f85bfd2..a2e42cd 100644 --- a/handlers/get_instance_images.go +++ b/handlers/get_instance_images.go @@ -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) } diff --git a/handlers/home.go b/handlers/home.go index cce588b..887aa28 100644 --- a/handlers/home.go +++ b/handlers/home.go @@ -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") diff --git a/handlers/new_instance.go b/handlers/new_instance.go index c9853ba..7ce24ae 100644 --- a/handlers/new_instance.go +++ b/handlers/new_instance.go @@ -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 } diff --git a/handlers/new_session.go b/handlers/new_session.go index 8afbe31..a6ae265 100644 --- a/handlers/new_session.go +++ b/handlers/new_session.go @@ -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} diff --git a/provisioner/dind.go b/provisioner/dind.go index 40d6159..a840650 100644 --- a/provisioner/dind.go +++ b/provisioner/dind.go @@ -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 == "" { diff --git a/pwd/client_test.go b/pwd/client_test.go index 88ed666..e7984bd 100644 --- a/pwd/client_test.go +++ b/pwd/client_test.go @@ -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) diff --git a/pwd/instance_test.go b/pwd/instance_test.go index 2fa3bb0..cdd38e5 100644 --- a/pwd/instance_test.go +++ b/pwd/instance_test.go @@ -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{ diff --git a/pwd/mock.go b/pwd/mock.go index 459d997..53b6a88 100644 --- a/pwd/mock.go +++ b/pwd/mock.go @@ -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) +} diff --git a/pwd/playground.go b/pwd/playground.go new file mode 100644 index 0000000..812374f --- /dev/null +++ b/pwd/playground.go @@ -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() +} diff --git a/pwd/playground_test.go b/pwd/playground_test.go new file mode 100644 index 0000000..f3bf5e9 --- /dev/null +++ b/pwd/playground_test.go @@ -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) +} diff --git a/pwd/pwd.go b/pwd/pwd.go index 630c64f..558c79b 100644 --- a/pwd/pwd.go +++ b/pwd/pwd.go @@ -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 { diff --git a/pwd/session.go b/pwd/session.go index 75bbf01..eed8f3d 100644 --- a/pwd/session.go +++ b/pwd/session.go @@ -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 diff --git a/pwd/session_test.go b/pwd/session_test.go index ffec01a..7a2d2c2 100644 --- a/pwd/session_test.go +++ b/pwd/session_test.go @@ -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) diff --git a/pwd/types/playground.go b/pwd/types/playground.go new file mode 100644 index 0000000..c0959f5 --- /dev/null +++ b/pwd/types/playground.go @@ -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"` +} diff --git a/pwd/types/playground_test.go b/pwd/types/playground_test.go new file mode 100644 index 0000000..42a2e94 --- /dev/null +++ b/pwd/types/playground_test.go @@ -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) +} diff --git a/pwd/types/session.go b/pwd/types/session.go index c93ce7f..67ce933 100644 --- a/pwd/types/session.go +++ b/pwd/types/session.go @@ -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"` } diff --git a/storage/file.go b/storage/file.go index ea84c9d..aac4cf2 100644 --- a/storage/file.go +++ b/storage/file.go @@ -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) diff --git a/storage/file_test.go b/storage/file_test.go index 6bcae2a..fa77499 100644 --- a/storage/file_test.go +++ b/storage/file_test.go @@ -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) +} diff --git a/storage/mock.go b/storage/mock.go index 1a2c062..3d6d21a 100644 --- a/storage/mock.go +++ b/storage/mock.go @@ -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) +} diff --git a/storage/storage.go b/storage/storage.go index 62f649a..bc40d56 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -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) }