Ver Fonte

integration testing for cli client

Alexander Belanger há 5 anos atrás
pai
commit
cc91dbebbe

+ 118 - 0
cli/cmd/api/api.go

@@ -0,0 +1,118 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"time"
+)
+
+// Client represents the client for the Porter API
+type Client struct {
+	BaseURL        string
+	HTTPClient     *http.Client
+	Cookie         *http.Cookie
+	CookieFilename string
+}
+
+// HTTPError is the Porter error response returned if a request fails
+type HTTPError struct {
+	Code   uint     `json:"code"`
+	Errors []string `json:"errors"`
+}
+
+// NewClient constructs a new client based on a set of options
+func NewClient(baseURL string, cookieFileName string) *Client {
+	client := &Client{
+		BaseURL:        baseURL,
+		CookieFilename: cookieFileName,
+		HTTPClient: &http.Client{
+			Timeout: time.Minute,
+		},
+	}
+
+	cookie, _ := client.getCookie()
+
+	if cookie != nil {
+		client.Cookie = cookie
+	}
+
+	return client
+}
+
+func (c *Client) sendRequest(req *http.Request, v interface{}) (*HTTPError, error) {
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	req.Header.Set("Accept", "application/json; charset=utf-8")
+
+	if cookie, _ := c.getCookie(); cookie != nil {
+		c.Cookie = cookie
+		req.AddCookie(c.Cookie)
+	}
+
+	res, err := c.HTTPClient.Do(req)
+
+	if err != nil {
+		return nil, err
+	}
+
+	defer res.Body.Close()
+
+	if cookies := res.Cookies(); len(cookies) == 1 {
+		c.saveCookie(cookies[0])
+	}
+
+	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
+		var errRes HTTPError
+		if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil {
+			return &errRes, nil
+		}
+
+		return nil, fmt.Errorf("unknown error, status code: %d", res.StatusCode)
+	}
+
+	if v != nil {
+		if err = json.NewDecoder(res.Body).Decode(v); err != nil {
+			return nil, err
+		}
+	}
+
+	return nil, nil
+}
+
+// CookieStorage for temporary fs-based cookie storage before jwt tokens
+type CookieStorage struct {
+	Cookie *http.Cookie `json:"cookie"`
+}
+
+// saves single cookie to file
+func (c *Client) saveCookie(cookie *http.Cookie) error {
+	data, err := json.Marshal(&CookieStorage{
+		Cookie: cookie,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(c.CookieFilename, data, 0644)
+}
+
+// retrieves single cookie from file
+func (c *Client) getCookie() (*http.Cookie, error) {
+	data, err := ioutil.ReadFile(c.CookieFilename)
+
+	if err != nil {
+		return nil, err
+	}
+
+	cookie := &CookieStorage{}
+
+	err = json.Unmarshal(data, cookie)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return cookie.Cookie, nil
+}

+ 55 - 0
cli/cmd/api/helper_test.go

@@ -0,0 +1,55 @@
+package api_test
+
+import (
+	"github.com/porter-dev/porter/cli/cmd/docker"
+)
+
+type db int
+
+const (
+	pg db = iota
+	sqlite
+)
+
+// Spins up and shuts down the Docker api server with the given options
+func startPorterServerWithDocker(processID string, port int, db docker.PorterDB) error {
+	env := []string{
+		"ADMIN_INIT=false",
+	}
+
+	startOpts := &docker.PorterStartOpts{
+		ProcessID:      processID,
+		ServerImageTag: "testing",
+		ServerPort:     port,
+		DB:             db,
+		KubeconfigPath: "",
+		SkipKubeconfig: true,
+		Env:            env,
+	}
+
+	_, _, err := docker.StartPorter(startOpts)
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func stopPorterServerWithDocker(processID string) error {
+	agent, err := docker.NewAgentFromEnv()
+
+	if err != nil {
+		return err
+	}
+
+	err = agent.StopPorterContainersWithProcessID(processID)
+
+	if err != nil {
+		return err
+	}
+
+	// remove stopped containers and volumes
+
+	return nil
+}

+ 225 - 0
cli/cmd/api/user.go

@@ -0,0 +1,225 @@
+package api
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+)
+
+// AuthCheckResponse is the user model response that is returned if the
+// user is logged in
+type AuthCheckResponse models.UserExternal
+
+// AuthCheck performs a check to ensure that the user is logged in
+func (c *Client) AuthCheck(ctx context.Context) (*AuthCheckResponse, error) {
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/auth/check", c.BaseURL),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+
+	bodyResp := &AuthCheckResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}
+
+// LoginRequest is the email/password associated with a login request
+type LoginRequest struct {
+	Email    string `json:"email"`
+	Password string `json:"password"`
+}
+
+// LoginResponse is the user model response that is returned after successfully
+// logging in
+type LoginResponse models.UserExternal
+
+// Login authorizes the user and grants them a cookie-based session
+func (c *Client) Login(ctx context.Context, loginRequest *LoginRequest) (*LoginResponse, error) {
+	data, err := json.Marshal(loginRequest)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf("%s/login", c.BaseURL),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &LoginResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}
+
+// Logout logs the user out and deauthorizes the cookie-based session
+func (c *Client) Logout(ctx context.Context) error {
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf("%s/logout", c.BaseURL),
+		nil,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req = req.WithContext(ctx)
+
+	if httpErr, err := c.sendRequest(req, nil); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return err
+	}
+
+	return nil
+}
+
+// CreateUserRequest is the email/password associated with creating a user
+type CreateUserRequest struct {
+	Email    string `json:"email"`
+	Password string `json:"password"`
+}
+
+// CreateUserResponse is the user model response that is returned after successfully
+// creating a user
+type CreateUserResponse models.UserExternal
+
+// CreateUser will create the user, authorize the user and grant them a cookie-based session
+func (c *Client) CreateUser(
+	ctx context.Context,
+	createUserRequest *CreateUserRequest,
+) (*CreateUserResponse, error) {
+	data, err := json.Marshal(createUserRequest)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf("%s/users", c.BaseURL),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &CreateUserResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}
+
+// GetUserResponse is the user model response that is returned after successfully
+// getting a user
+type GetUserResponse models.UserExternal
+
+// GetUser retrieves a user given a user id
+func (c *Client) GetUser(ctx context.Context, userID uint) (*GetUserResponse, error) {
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/users/%d", c.BaseURL, userID),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+
+	bodyResp := &GetUserResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}
+
+// DeleteUserRequest is the password needed to verify a user should be deleted
+type DeleteUserRequest struct {
+	Password string `json:"password"`
+}
+
+// DeleteUser deletes a user of a given user id
+func (c *Client) DeleteUser(
+	ctx context.Context,
+	userID uint,
+	deleteUserRequest *DeleteUserRequest,
+) error {
+	data, err := json.Marshal(deleteUserRequest)
+
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest(
+		"DELETE",
+		fmt.Sprintf("%s/users/%d", c.BaseURL, userID),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req = req.WithContext(ctx)
+
+	if httpErr, err := c.sendRequest(req, nil); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return err
+	}
+
+	return nil
+}

+ 143 - 0
cli/cmd/api/user_test.go

@@ -0,0 +1,143 @@
+package api_test
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/porter-dev/porter/cli/cmd/docker"
+
+	"github.com/porter-dev/porter/cli/cmd/api"
+)
+
+const baseURL string = "http://localhost:10000/api"
+
+func TestMain(m *testing.M) {
+	err := startPorterServerWithDocker("user", 10000, docker.SQLite)
+
+	if err != nil {
+		fmt.Printf("%v\n", err)
+		os.Exit(1)
+	}
+
+	code := m.Run()
+	stopPorterServerWithDocker("user")
+
+	os.Exit(code)
+}
+
+func initUser(email string, client *api.Client, t *testing.T) *api.CreateUserResponse {
+	t.Helper()
+
+	resp, err := client.CreateUser(context.Background(), &api.CreateUserRequest{
+		Email:    email,
+		Password: "hello1234",
+	})
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	return resp
+}
+
+func TestLogin(t *testing.T) {
+	email := "login_test@example.com"
+	client := api.NewClient(baseURL, "cookie_login_test.json")
+	user := initUser(email, client, t)
+
+	resp, err := client.Login(context.Background(), &api.LoginRequest{
+		Email:    user.Email,
+		Password: "hello1234",
+	})
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	if resp.Email != user.Email {
+		t.Errorf("incorrect email: expected %s, got %s\n", user.Email, resp.Email)
+	}
+}
+
+func TestLogout(t *testing.T) {
+	email := "logout_test@example.com"
+	client := api.NewClient(baseURL, "cookie_logout_test.json")
+	user := initUser(email, client, t)
+
+	client.Login(context.Background(), &api.LoginRequest{
+		Email:    user.Email,
+		Password: "hello1234",
+	})
+
+	err := client.Logout(context.Background())
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	// try to get the user and ensure 403
+	_, err = client.AuthCheck(context.Background())
+
+	if err != nil && !strings.Contains(err.Error(), "403") {
+		t.Fatalf("%v\n", err)
+	}
+}
+
+func TestAuthCheck(t *testing.T) {
+	email := "auth_check_test@example.com"
+	client := api.NewClient(baseURL, "cookie_auth_check_test.json")
+	user := initUser(email, client, t)
+	client.Login(context.Background(), &api.LoginRequest{
+		Email:    user.Email,
+		Password: "hello1234",
+	})
+
+	resp, err := client.AuthCheck(context.Background())
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	if resp.Email != user.Email {
+		t.Errorf("incorrect email: expected %s, got %s\n", user.Email, resp.Email)
+	}
+}
+
+func TestGetUser(t *testing.T) {
+	email := "get_user_test@example.com"
+	client := api.NewClient(baseURL, "cookie_get_user_test.json")
+	user := initUser(email, client, t)
+
+	resp, err := client.GetUser(context.Background(), user.ID)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	if resp.Email != user.Email {
+		t.Errorf("incorrect email: expected %s, got %s\n", user.Email, resp.Email)
+	}
+}
+
+func TestDeleteUser(t *testing.T) {
+	email := "delete_user_test@example.com"
+	client := api.NewClient(baseURL, "cookie_delete_user_test.json")
+	user := initUser(email, client, t)
+
+	err := client.DeleteUser(context.Background(), user.ID, &api.DeleteUserRequest{
+		Password: "hello1234",
+	})
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	_, err = client.GetUser(context.Background(), user.ID)
+
+	if err != nil && !strings.Contains(err.Error(), "could not find requested object") {
+		t.Fatalf("%v\n", err)
+	}
+}

+ 194 - 4
cli/cmd/docker/porter.go

@@ -2,16 +2,179 @@ package docker
 
 import (
 	"fmt"
+	"path/filepath"
+	"strings"
 	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/mount"
 	"github.com/docker/go-connections/nat"
+	"k8s.io/client-go/util/homedir"
 )
 
-// PorterStartOpts are the options for starting the Porter container
+// PorterDB is used for enumerating DB types
+type PorterDB int
+
+// The supported DB types
+const (
+	Postgres PorterDB = iota
+	SQLite
+)
+
+// PorterStartOpts are the options for starting the Porter stack
 type PorterStartOpts struct {
+	ProcessID      string
+	ServerImageTag string
+	ServerPort     int
+	DB             PorterDB
+	KubeconfigPath string
+	SkipKubeconfig bool
+	Env            []string
+}
+
+// StartPorter creates a new Docker agent using the host environment, and creates a
+// new Porter instance
+func StartPorter(opts *PorterStartOpts) (agent *Agent, id string, err error) {
+	home := homedir.HomeDir()
+	outputConfPath := filepath.Join(home, ".porter", "porter.kubeconfig")
+	containerConfPath := "/porter/porter.kubeconfig"
+
+	agent, err = NewAgentFromEnv()
+
+	if err != nil {
+		return nil, "", err
+	}
+
+	// the volume mounts to use
+	mounts := make([]mount.Mount, 0)
+
+	// the volumes passed to the Porter container
+	volumesMap := make(map[string]struct{})
+
+	if !opts.SkipKubeconfig {
+		// add a bind mount with the kubeconfig
+		mount := mount.Mount{
+			Type:        mount.TypeBind,
+			Source:      outputConfPath,
+			Target:      containerConfPath,
+			ReadOnly:    true,
+			Consistency: mount.ConsistencyFull,
+		}
+
+		mounts = append(mounts, mount)
+	}
+
+	netID, err := agent.CreateBridgeNetworkIfNotExist("porter_network_" + opts.ProcessID)
+
+	if err != nil {
+		return nil, "", err
+	}
+
+	switch opts.DB {
+	case SQLite:
+		// check if sqlite volume exists, create it if not
+		vol, err := agent.CreateLocalVolumeIfNotExist("porter_sqlite_" + opts.ProcessID)
+
+		if err != nil {
+			return nil, "", err
+		}
+
+		// create mount
+		mount := mount.Mount{
+			Type:        mount.TypeVolume,
+			Source:      vol.Name,
+			Target:      "/sqlite",
+			ReadOnly:    false,
+			Consistency: mount.ConsistencyFull,
+		}
+
+		mounts = append(mounts, mount)
+		volumesMap[vol.Name] = struct{}{}
+
+		opts.Env = append(opts.Env, []string{
+			"SQL_LITE=true",
+			"SQL_LITE_PATH=/sqlite/porter.db",
+		}...)
+	case Postgres:
+		// check if postgres volume exists, create it if not
+		vol, err := agent.CreateLocalVolumeIfNotExist("porter_postgres_" + opts.ProcessID)
+
+		if err != nil {
+			return nil, "", err
+		}
+
+		// pgMount is mount for postgres container
+		pgMount := []mount.Mount{
+			mount.Mount{
+				Type:        mount.TypeVolume,
+				Source:      vol.Name,
+				Target:      "/var/lib/postgresql/data",
+				ReadOnly:    false,
+				Consistency: mount.ConsistencyFull,
+			},
+		}
+
+		// create postgres container with mount
+		startOpts := PostgresOpts{
+			Name:   "porter_postgres_" + opts.ProcessID,
+			Image:  "postgres:latest",
+			Mounts: pgMount,
+			VolumeMap: map[string]struct{}{
+				"porter_postgres": struct{}{},
+			},
+			NetworkID: netID,
+			Env: []string{
+				"POSTGRES_USER=porter",
+				"POSTGRES_PASSWORD=porter",
+				"POSTGRES_DB=porter",
+			},
+		}
+
+		pgID, err := agent.StartPostgresContainer(startOpts)
+
+		fmt.Println("Waiting for postgres:latest to be healthy...")
+		agent.WaitForContainerHealthy(pgID, 10)
+
+		if err != nil {
+			return nil, "", err
+		}
+
+		opts.Env = append(opts.Env, []string{
+			"SQL_LITE=false",
+			"DB_USER=porter",
+			"DB_PASS=porter",
+			"DB_NAME=porter",
+			"DB_HOST=porter_postgres_" + opts.ProcessID,
+			"DB_PORT=5432",
+		}...)
+
+		defer agent.WaitForContainerStop(pgID)
+	}
+
+	// create Porter container
+	startOpts := PorterServerStartOpts{
+		Name:          "porter_server_" + opts.ProcessID,
+		Image:         "porter1/porter:" + opts.ServerImageTag,
+		HostPort:      uint(opts.ServerPort),
+		ContainerPort: 8080,
+		Mounts:        mounts,
+		VolumeMap:     volumesMap,
+		NetworkID:     netID,
+		Env:           opts.Env,
+	}
+
+	id, err = agent.StartPorterContainer(startOpts)
+
+	if err != nil {
+		return nil, "", err
+	}
+
+	return agent, id, nil
+}
+
+// PorterServerStartOpts are the options for starting the Porter server
+type PorterServerStartOpts struct {
 	Name          string
 	Image         string
 	StartCmd      []string
@@ -25,7 +188,7 @@ type PorterStartOpts struct {
 
 // StartPorterContainer pulls a specific Porter image and starts a container
 // using the Docker engine. It returns the container ID
-func (a *Agent) StartPorterContainer(opts PorterStartOpts) (string, error) {
+func (a *Agent) StartPorterContainer(opts PorterServerStartOpts) (string, error) {
 	id, err := a.upsertPorterContainer(opts)
 
 	if err != nil {
@@ -52,7 +215,7 @@ func (a *Agent) StartPorterContainer(opts PorterStartOpts) (string, error) {
 // if spec has changed, remove and recreate container
 // if container does not exist, create the container
 // otherwise, return stopped container
-func (a *Agent) upsertPorterContainer(opts PorterStartOpts) (id string, err error) {
+func (a *Agent) upsertPorterContainer(opts PorterServerStartOpts) (id string, err error) {
 	containers, err := a.getContainersCreatedByStart()
 
 	// remove the matching container
@@ -78,7 +241,7 @@ func (a *Agent) upsertPorterContainer(opts PorterStartOpts) (id string, err erro
 }
 
 // create the container and return its id
-func (a *Agent) pullAndCreatePorterContainer(opts PorterStartOpts) (id string, err error) {
+func (a *Agent) pullAndCreatePorterContainer(opts PorterServerStartOpts) (id string, err error) {
 	a.PullImage(opts.Image)
 
 	// format the port array for binding to host machine
@@ -250,6 +413,33 @@ func (a *Agent) StopPorterContainers() error {
 	return nil
 }
 
+// StopPorterContainersWithProcessID finds all containers that were started via the CLI
+// and have a given process id and stops them without removal.
+func (a *Agent) StopPorterContainersWithProcessID(processID string) error {
+	fmt.Println("Stopping containers...")
+
+	containers, err := a.getContainersCreatedByStart()
+
+	if err != nil {
+		return err
+	}
+
+	// remove all Porter containers
+	for _, container := range containers {
+		if strings.Contains(container.Names[0], "_"+processID) {
+			timeout, _ := time.ParseDuration("15s")
+
+			err := a.client.ContainerStop(a.ctx, container.ID, &timeout)
+
+			if err != nil {
+				return a.handleDockerClientErr(err, "Could not stop container "+container.ID)
+			}
+		}
+	}
+
+	return nil
+}
+
 // getContainersCreatedByStart gets all containers that were created by the "porter start"
 // command by looking for the label "CreatedByPorterCLI" (or .label of the agent)
 func (a *Agent) getContainersCreatedByStart() ([]types.Container, error) {

+ 17 - 129
cli/cmd/start.go

@@ -14,8 +14,6 @@ import (
 	"github.com/porter-dev/porter/cli/cmd/credstore"
 
 	"github.com/spf13/cobra"
-
-	"github.com/docker/docker/api/types/mount"
 )
 
 type startOps struct {
@@ -113,7 +111,7 @@ func stop() error {
 		return err
 	}
 
-	err = agent.StopPorterContainers()
+	err = agent.StopPorterContainersWithProcessID("main")
 
 	if err != nil {
 		return err
@@ -134,8 +132,6 @@ func start(
 	var err error
 	home := homedir.HomeDir()
 	outputConfPath := filepath.Join(home, ".porter", "porter.kubeconfig")
-	containerConfPath := "/porter/porter.kubeconfig"
-	port := 8080
 
 	// if not insecure, or username/pw set incorrectly, prompt for new username/pw
 	if username, pw, err = credstore.Get(); !insecure && err != nil {
@@ -169,37 +165,6 @@ func start(
 		}
 	}
 
-	agent, err := docker.NewAgentFromEnv()
-
-	if err != nil {
-		return err
-	}
-
-	// the volume mounts to use
-	mounts := make([]mount.Mount, 0)
-
-	// the volumes passed to the Porter container
-	volumesMap := make(map[string]struct{})
-
-	if !skipKubeconfig {
-		// add a bind mount with the kubeconfig
-		mount := mount.Mount{
-			Type:        mount.TypeBind,
-			Source:      outputConfPath,
-			Target:      containerConfPath,
-			ReadOnly:    true,
-			Consistency: mount.ConsistencyFull,
-		}
-
-		mounts = append(mounts, mount)
-	}
-
-	netID, err := agent.CreateBridgeNetworkIfNotExist("porter_network")
-
-	if err != nil {
-		return err
-	}
-
 	env := make([]string, 0)
 
 	env = append(env, []string{
@@ -208,106 +173,29 @@ func start(
 		"ADMIN_PASSWORD=" + pw,
 	}...)
 
-	switch db {
-	case "sqlite":
-		// check if sqlite volume exists, create it if not
-		vol, err := agent.CreateLocalVolumeIfNotExist("porter_sqlite")
-
-		if err != nil {
-			return err
-		}
+	var porterDB docker.PorterDB
 
-		// create mount
-		mount := mount.Mount{
-			Type:        mount.TypeVolume,
-			Source:      vol.Name,
-			Target:      "/sqlite",
-			ReadOnly:    false,
-			Consistency: mount.ConsistencyFull,
-		}
-
-		mounts = append(mounts, mount)
-		volumesMap[vol.Name] = struct{}{}
-
-		env = append(env, []string{
-			"SQL_LITE=true",
-			"SQL_LITE_PATH=/sqlite/porter.db",
-		}...)
+	switch db {
 	case "postgres":
-		// check if postgres volume exists, create it if not
-		vol, err := agent.CreateLocalVolumeIfNotExist("porter_postgres")
-
-		if err != nil {
-			return err
-		}
-
-		// pgMount is mount for postgres container
-		pgMount := []mount.Mount{
-			mount.Mount{
-				Type:        mount.TypeVolume,
-				Source:      vol.Name,
-				Target:      "/var/lib/postgresql/data",
-				ReadOnly:    false,
-				Consistency: mount.ConsistencyFull,
-			},
-		}
-
-		// create postgres container with mount
-		startOpts := docker.PostgresOpts{
-			Name:   "porter_postgres",
-			Image:  "postgres:latest",
-			Mounts: pgMount,
-			VolumeMap: map[string]struct{}{
-				"porter_postgres": struct{}{},
-			},
-			NetworkID: netID,
-			Env: []string{
-				"POSTGRES_USER=porter",
-				"POSTGRES_PASSWORD=porter",
-				"POSTGRES_DB=porter",
-			},
-		}
-
-		pgID, err := agent.StartPostgresContainer(startOpts)
-
-		fmt.Println("Waiting for postgres:latest to be healthy...")
-		agent.WaitForContainerHealthy(pgID, 10)
-
-		if err != nil {
-			return err
-		}
-
-		env = append(env, []string{
-			"SQL_LITE=false",
-			"DB_USER=porter",
-			"DB_PASS=porter",
-			"DB_NAME=porter",
-			"DB_HOST=porter_postgres",
-			"DB_PORT=5432",
-		}...)
-
-		defer agent.WaitForContainerStop(pgID)
-	}
-
-	// create Porter container
-	// TODO -- look for unused port
-	startOpts := docker.PorterStartOpts{
-		Name:          "porter_server",
-		Image:         "porter1/porter:" + imageTag,
-		HostPort:      uint(port),
-		ContainerPort: 8080,
-		Mounts:        mounts,
-		VolumeMap:     volumesMap,
-		NetworkID:     netID,
-		Env:           env,
+		porterDB = docker.Postgres
+	case "sqlite":
+		porterDB = docker.SQLite
 	}
 
-	id, err := agent.StartPorterContainer(startOpts)
+	port := 8080
 
-	if err != nil {
-		return err
+	startOpts := &docker.PorterStartOpts{
+		ProcessID:      "main",
+		ServerImageTag: imageTag,
+		ServerPort:     port,
+		DB:             porterDB,
+		KubeconfigPath: kubeconfigPath,
+		SkipKubeconfig: skipKubeconfig,
+		Env:            env,
 	}
 
+	agent, id, err := docker.StartPorter(startOpts)
+
 	fmt.Println("Spinning up the server...")
 	time.Sleep(7 * time.Second)
 	openBrowser(fmt.Sprintf("http://localhost:%d/login?email=%s", port, username))

+ 7 - 1
cmd/app/main.go

@@ -29,7 +29,13 @@ func main() {
 		return
 	}
 
-	repo := gorm.NewRepository(db)
+	var key [32]byte
+
+	for i, b := range []byte(appConf.Db.EncryptionKey) {
+		key[i] = b
+	}
+
+	repo := gorm.NewRepository(db, &key)
 
 	// upsert admin if config requires
 	// if appConf.Db.AdminInit {

+ 1 - 1
internal/repository/gorm/user.go

@@ -55,7 +55,7 @@ func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error)
 
 // DeleteUser deletes a single user using their unique id
 func (repo *UserRepository) DeleteUser(user *models.User) (*models.User, error) {
-	if err := repo.db.First(&models.User{}, user.ID).Delete(&user).Error; err != nil {
+	if err := repo.db.Delete(&user).Error; err != nil {
 		return nil, err
 	}
 	return user, nil