Kaynağa Gözat

fix merge conflict

Alexander Belanger 5 yıl önce
ebeveyn
işleme
7ee14897b5

+ 2 - 0
.gitignore

@@ -1,3 +1,5 @@
 .DS_Store
 .env
 app
+*.db
+

+ 4 - 1
cmd/app/main.go

@@ -5,6 +5,7 @@ import (
 	"log"
 	"net/http"
 
+	"github.com/gorilla/sessions"
 	"github.com/porter-dev/porter/internal/repository/gorm"
 
 	"github.com/porter-dev/porter/server/api"
@@ -30,7 +31,9 @@ func main() {
 
 	repo := gorm.NewRepository(db)
 
-	store, _ := sessionstore.NewStore(repo, appConf.Server)
+	// declare as Store interface (methods Get, New, Save)
+	var store sessions.Store
+	store, _ = sessionstore.NewStore(repo, appConf.Server)
 
 	validator := vr.New()
 

+ 1 - 1
dashboard/src/main/Login.tsx

@@ -40,7 +40,7 @@ export default class Login extends Component<PropsType, StateType> {
         password: password
       }, {}, (err: any, res: any) => {
         // TODO: case and set credential error
-        err ? setCurrentError(JSON.stringify(err)) : authenticate();
+        err ? setCurrentError(err.response.data.errors[0]) : authenticate();
       });
     }
   }

+ 1 - 2
dashboard/src/main/Register.tsx

@@ -48,8 +48,7 @@ export default class Register extends Component<PropsType, StateType> {
         email: email,
         password: password
       }, {}, (err: any, res: any) => {
-        console.log('err',err)
-        err ? setCurrentError(JSON.stringify(err)) : authenticate();
+        err ? setCurrentError(err.response.data.errors[0]) : authenticate();
       });
     } 
   };

+ 94 - 0
dashboard/src/main/home/Toolbar.tsx

@@ -0,0 +1,94 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import ReactModal from 'react-modal';
+
+import { Context } from '../../shared/Context';
+
+import Sidebar from './sidebar/Sidebar';
+import ClusterConfigModal from './modals/ClusterConfigModal';
+
+type PropsType = {
+  logOut: () => void
+};
+
+type StateType = {
+};
+
+export default class Home extends Component<PropsType, StateType> {
+  render() {
+    return (
+      <StyledHome>
+        <ReactModal
+          isOpen={this.context.currentModal === 'ClusterConfigModal'}
+          onRequestClose={() => this.context.setCurrentModal(null)}
+          style={MediumModalStyles}
+          ariaHideApp={false}
+        >
+          <ClusterConfigModal />
+        </ReactModal>
+
+        <Sidebar logOut={this.props.logOut} />
+        <DummyDashboard>
+          🏗️🏗️🏗️🏗️🏗️
+        </DummyDashboard>
+      </StyledHome>
+    );
+  }
+}
+
+Home.contextType = Context;
+
+const MediumModalStyles = {
+  overlay: {
+    backgroundColor: 'rgba(0,0,0,0.6)',
+    zIndex: 2,
+  },
+  content: {
+    borderRadius: '7px',
+    border: 0,
+    width: '760px',
+    maxWidth: '80vw',
+    margin: '0 auto',
+    height: '575px',
+    top: 'calc(50% - 289px)',
+    backgroundColor: '#24272a',
+    animation: 'floatInModal 0.5s 0s',
+    overflow: 'visible',
+  },
+};
+
+const DummyDashboard = styled.div`
+  height: 100%;
+  width: 100vw;
+  font-family: 'Work Sans', sans-serif;
+  overflow-y: auto;
+  display: flex;
+  letter-spacing: 10px;
+  flex: 1;
+  justify-content: center;
+  padding-bottom: 30px;
+  align-items: center;
+  background: ${props => props.theme.bg};
+  position: relative;
+`;
+
+const StyledHome = styled.div`
+  width: 100vw;
+  height: 100vh;
+  position: fixed;
+  top: 0;
+  left: 0;
+  margin: 0;
+  user-select: none;
+  display: flex;
+  justify-content: center;
+
+  @keyframes floatInModal {
+    from {
+      opacity: 0; transform: translateY(30px);
+    }
+    to {
+      opacity: 1; transform: translateY(0px);
+    }
+  }
+`;

+ 29 - 42
dashboard/src/main/home/modals/ClusterConfigModal.tsx

@@ -4,7 +4,7 @@ import close from '../../../assets/close.png';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
-import { ClusterConfig } from '../../../shared/types';
+import { KubeContextConfig } from '../../../shared/types';
 
 import YamlEditor from '../../../components/YamlEditor';
 import SaveButton from '../../../components/SaveButton';
@@ -14,8 +14,7 @@ type PropsType = {
 
 type StateType = {
   currentTab: string,
-  clusters: ClusterConfig[],
-  selected: boolean[],
+  kubeContexts: KubeContextConfig[],
   rawKubeconfig: string,
   saveKubeconfigStatus: string | null,
   saveSelectedStatus: string | null
@@ -24,8 +23,7 @@ type StateType = {
 export default class ClusterConfigModal extends Component<PropsType, StateType> {
   state = {
     currentTab: 'kubeconfig',
-    clusters: [] as ClusterConfig[],
-    selected: [] as boolean[],
+    kubeContexts: [] as KubeContextConfig[],
     rawKubeconfig: '# If you are using certificate files, include those explicitly',
     saveKubeconfigStatus: null as (string | null),
     saveSelectedStatus: null as (string | null),
@@ -35,26 +33,11 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
     let { setCurrentError, userId } = this.context;
 
     // Parse kubeconfig to retrieve all possible clusters
-    api.getAllClusters('<token>', {}, { id: userId }, (err: any, res: any) => {
+    api.getContexts('<token>', {}, { id: userId }, (err: any, res: any) => {
       if (err) {
         setCurrentError('getAllClusters: ' + JSON.stringify(err));
       } else {
-        let clusters = res.data;
-        this.setState({ clusters });
-
-        if (clusters && clusters.length > 0) {
-          
-          // Check against list of connected clusters
-          api.getClusters('<token>', {}, { id: userId }, (err: any, res: any) => {
-            if (err) {
-              setCurrentError('getClusters: ' + JSON.stringify(err));
-            } else {
-              console.log(res)
-              let selected = clusters.map((x: ClusterConfig) => res.data.includes(x));
-              this.setState({ selected });
-            }
-          });
-        }
+        this.setState({ kubeContexts: res.data });
       }
     });
   }
@@ -64,7 +47,7 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
 
     api.getUser('<token>', {}, { id: userId }, (err: any, res: any) => {
       if (err) {
-        setCurrentError('getUser: ' + JSON.stringify(err));
+        setCurrentError(JSON.stringify(err));
       } else if (res.data.rawKubeConfig !== '') {
         this.setState({ rawKubeconfig: res.data.rawKubeConfig });
       }
@@ -80,22 +63,22 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
   };
 
   toggleCluster = (i: number): void => {
-    let newSelected = this.state.selected;
-    newSelected[i] = !this.state.selected[i];
-    this.setState({ selected: newSelected });
+    let newKubeContexts = this.state.kubeContexts;
+    newKubeContexts[i].selected = !newKubeContexts[i].selected;
+    this.setState({ kubeContexts: newKubeContexts });
   };
 
   renderClusterList = (): JSX.Element[] | JSX.Element => {
-    let { clusters, selected } = this.state;
+    let { kubeContexts } = this.state;
 
-    if (clusters && clusters.length > 0) {
-      return clusters.map((cluster: ClusterConfig, i) => {
+    if (kubeContexts && kubeContexts.length > 0) {
+      return kubeContexts.map((kubeContext: KubeContextConfig, i) => {
         return (
           <Row key={i} onClick={() => this.toggleCluster(i)}>
-            <Checkbox checked={selected[i]}>
+            <Checkbox checked={kubeContext.selected}>
               <i className="material-icons">done</i>
             </Checkbox>
-            {cluster.name}
+            {kubeContext.name}
           </Row>
         );
       })
@@ -131,34 +114,35 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
           });
 
           this.updateChecklist();
+          this.context.currentModalData.updateClusters();
         }
       }
     );
   }
 
   handleSaveSelected = () => {
-    let { clusters, selected } = this.state;
+    let { kubeContexts } = this.state;
     let { userId } = this.context;
 
     this.setState({ saveSelectedStatus: 'loading' });
-    let allowedClusters: string[] = [];
-    clusters.forEach((x, i) => {
-      if (selected[i]) {
-        allowedClusters.push(x.name);
+    let allowedContexts: string[] = [];
+    kubeContexts.forEach((x, i) => {
+      if (x.selected) {
+        allowedContexts.push(x.name);
       }
     });
-
-    console.log(allowedClusters);
     
     api.updateUser(
       '<token>',
-      { allowedClusters },
+      { allowedContexts },
       { id: userId },
       (err: any, res: any) => {
         if (err) {
           this.setState({ saveSelectedStatus: 'error' });
         } else {
           this.setState({ saveSelectedStatus: 'successful' });
+          this.updateChecklist();
+          this.context.currentModalData.updateClusters();
         }
       }
     );
@@ -184,13 +168,13 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
 
     return (
       <div>
-        <Subtitle>Select the clusters you want Porter to connect to</Subtitle>
+        <Subtitle>Select the contexts you want Porter to use</Subtitle>
         <ClusterList>
           {this.renderClusterList()}
         </ClusterList>
         <SaveButton
           text='Save Selected'
-          disabled={this.state.clusters.length === 0}
+          disabled={this.state.kubeContexts.length === 0}
           onClick={this.handleSaveSelected}
           status={this.state.saveSelectedStatus}
         />
@@ -201,7 +185,10 @@ export default class ClusterConfigModal extends Component<PropsType, StateType>
   render() {
     return (
       <StyledClusterConfigModal>
-        <CloseButton onClick={() => { this.context.setCurrentModal(null) }}>
+        <CloseButton onClick={() => {
+          this.context.setCurrentModal(null);
+          this.context.setCurrentModalData(null);
+        }}>
           <CloseButtonImg src={close} />
         </CloseButton>
 

+ 26 - 33
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -4,7 +4,7 @@ import drawerBg from '../../../assets/drawer-bg.png';
 
 import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
-import { ClusterConfig } from '../../../shared/types';
+import { KubeContextConfig } from '../../../shared/types';
 
 import Drawer from './Drawer';
 
@@ -17,31 +17,10 @@ type StateType = {
   configExists: boolean,
   showDrawer: boolean,
   initializedDrawer: boolean,
-  clusters: any[],
+  kubeContexts: KubeContextConfig[],
   activeIndex: number,
 };
 
-const dummyClusters: ClusterConfig[]  = [
-  { 
-    name: 'happy-ol-trees', 
-    server: 'idc',
-    context: 'idk',
-    user: 'jusrhee'
-  },
-  { 
-    name: 'joyous-petite-rocks', 
-    server: 'idc',
-    context: 'idk',
-    user: 'jusrhee'
-  },
-  { 
-    name: 'friendly-small-bush', 
-    server: 'idc',
-    context: 'idk',
-    user: 'jusrhee'
-  }
-];
-
 export default class ClusterSection extends Component<PropsType, StateType> {
 
   // Need to track initialized for animation mounting
@@ -49,22 +28,30 @@ export default class ClusterSection extends Component<PropsType, StateType> {
     configExists: true,
     showDrawer: false,
     initializedDrawer: false,
-    clusters: [] as ClusterConfig[],
+    kubeContexts: [] as KubeContextConfig[],
     activeIndex: 0,
   };
 
-  componentDidMount() {
+  updateClusters = () => {
     let { setCurrentError, userId } = this.context;
 
-    api.getClusters('<token>', {}, { id: userId }, (err: any, res: any) => {      
+    // TODO: query with selected filter once implemented
+    api.getContexts('<token>', {}, { id: userId }, (err: any, res: any) => {
       if (err) {
-        setCurrentError(JSON.stringify(err));
+        setCurrentError('getContexts: ' + JSON.stringify(err));
       } else {
-        this.setState({ clusters: res.data });
+
+        // Filter selected (temporary)
+        let kubeContexts = res.data.filter((x: KubeContextConfig) => x.selected);
+        this.setState({ kubeContexts });
       }
     });
   }
 
+  componentDidMount() {
+    this.updateClusters();
+  }
+
   // Need to override showDrawer when the sidebar is closed
   componentDidUpdate(prevProps: PropsType) {
     if (prevProps !== this.props) {
@@ -86,9 +73,10 @@ export default class ClusterSection extends Component<PropsType, StateType> {
     if (this.state.initializedDrawer) {
       return (
         <Drawer
+          updateClusters={this.updateClusters}
           toggleDrawer={this.toggleDrawer}
           showDrawer={this.state.showDrawer}
-          clusters={this.state.clusters}
+          kubeContexts={this.state.kubeContexts}
           activeIndex={this.state.activeIndex}
           setActiveIndex={(i: number): void => this.setState({ activeIndex: i })}
         />
@@ -96,15 +84,20 @@ export default class ClusterSection extends Component<PropsType, StateType> {
     }
   };
 
+  showClusterConfigModal = () => {
+    this.context.setCurrentModal('ClusterConfigModal');
+    this.context.setCurrentModalData({ updateClusters: this.updateClusters });
+  }
+
   renderContents = (): JSX.Element => {
-    let { clusters, activeIndex, showDrawer } = this.state;
+    let { kubeContexts, activeIndex, showDrawer } = this.state;
 
-    if (clusters.length > 0) {
+    if (kubeContexts.length > 0) {
       return (
         <ClusterSelector showDrawer={showDrawer}>
           <LinkWrapper>
             <ClusterIcon><i className="material-icons">polymer</i></ClusterIcon>
-            <ClusterName>{clusters[activeIndex].name}</ClusterName>
+            <ClusterName>{kubeContexts[activeIndex].name}</ClusterName>
           </LinkWrapper>
           <DrawerButton onClick={this.toggleDrawer}>
             <BgAccent src={drawerBg} />
@@ -117,7 +110,7 @@ export default class ClusterSection extends Component<PropsType, StateType> {
     }
 
     return (
-      <InitializeButton onClick={() => this.context.setCurrentModal('ClusterConfigModal')}>
+      <InitializeButton onClick={this.showClusterConfigModal}>
         <Plus>+</Plus> Add a Cluster
       </InitializeButton>
     )

+ 45 - 26
dashboard/src/main/home/sidebar/Drawer.tsx

@@ -3,14 +3,15 @@ import styled from 'styled-components';
 import close from '../../../assets/close.png';
 
 import { Context } from '../../../shared/Context';
-import { ClusterConfig } from '../../../shared/types';
+import { KubeContextConfig } from '../../../shared/types';
 
 type PropsType = {
   toggleDrawer: () => void,
   showDrawer: boolean,
-  clusters: ClusterConfig[],
+  kubeContexts: KubeContextConfig[],
   activeIndex: number,
-  setActiveIndex: (i: number) => void
+  setActiveIndex: (i: number) => void,
+  updateClusters: () => void
 };
 
 type StateType = {
@@ -18,24 +19,29 @@ type StateType = {
 
 export default class Drawer extends Component<PropsType, StateType> {
 
-  renderClusterList = (): JSX.Element[] => {
-    return this.props.clusters.map((cluster, i) => {
-      /*
-      let active = this.context.activeProject &&
-        this.context.activeProject.namespace == val.namespace; 
-      */
+  renderClusterList = (): JSX.Element[] | JSX.Element => {
+    let { kubeContexts, activeIndex, setActiveIndex } = this.props;
+    if (kubeContexts.length > 0) {
+      return kubeContexts.map((kubeContext: KubeContextConfig, i: number) => {
+        /*
+        let active = this.context.activeProject &&
+          this.context.activeProject.namespace == val.namespace; 
+        */
+
+        return (
+          <ClusterOption
+            key={i}
+            active={i === activeIndex}
+            onClick={() => setActiveIndex(i)}
+          >
+            <ClusterIcon><i className="material-icons">polymer</i></ClusterIcon>
+            <ClusterName>{kubeContext.name}</ClusterName>
+          </ClusterOption>
+        );
+      });
+    }
 
-      return (
-        <ClusterOption 
-          key={i}
-          active={i === this.props.activeIndex}
-          onClick={() => this.props.setActiveIndex(i)}
-        >
-          <ClusterIcon><i className="material-icons">polymer</i></ClusterIcon>
-          <ClusterName>{cluster.name}</ClusterName>
-        </ClusterOption>
-      );
-    });
+    return <Placeholder>No clusters selected</Placeholder>
   };
 
   renderCloseOverlay = (): JSX.Element | undefined => {
@@ -57,7 +63,10 @@ export default class Drawer extends Component<PropsType, StateType> {
 
           {this.renderClusterList()}
 
-          <InitializeButton onClick={() => this.context.setCurrentModal('ClusterConfigModal')}>
+          <InitializeButton onClick={() => {
+            this.context.setCurrentModal('ClusterConfigModal');
+            this.context.setCurrentModalData({ updateClusters: this.props.updateClusters });
+          }}>
             <Plus>+</Plus> Manage Clusters
           </InitializeButton>
         </StyledDrawer>
@@ -88,7 +97,7 @@ const InitializeButton = styled.div`
   justify-content: center;
   width: calc(100% - 30px);
   height: 38px;
-  margin: 20px 15px 12px;
+  margin: 45px 15px 12px;
   font-size: 13px;
   font-weight: 500;
   border-radius: 3px;
@@ -108,7 +117,7 @@ const ClusterOption = styled.div`
   padding-right: 30px;
   display: flex;
   align-items: center;
-  height: 50px;
+  height: 42px;
   text-decoration: none;
   color: white;
   font-size: 14px;
@@ -116,9 +125,19 @@ const ClusterOption = styled.div`
   overflow: hidden;
   text-overflow: ellipsis;
   cursor: pointer;
-  background: ${(props: { active: boolean }) => props.active ? '#ffffff22' : ''};
+  background: ${(props: { active?: boolean }) => props.active ? '#ffffff18' : ''};
   :hover {
-    background: #ffffff33;
+    background: #ffffff22;
+  }
+`;
+
+const Placeholder = styled(ClusterOption)`
+  color: #ffffff99;
+  justify-content: center;
+  padding: 0;
+  cursor: default;
+  :hover {
+    background: none;
   }
 `;
 
@@ -167,7 +186,7 @@ const ClusterIcon = styled.div`
 const StyledDrawer = styled.div`
   position: absolute;
   height: 100%;
-  padding-top: 36px;
+  padding-top: 41px;
   width: 230px;
   overflow-y: auto;
   padding-bottom: 40px;

+ 13 - 7
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -84,8 +84,8 @@ export default class Sidebar extends Component<PropsType, StateType> {
     api.logOutUser('<token>', {}, {}, (err: any, res: any) => {
       // TODO: case and set logout error
       
-      err ? setCurrentError(JSON.stringify(err)) : logOut();
-    });
+      err ? setCurrentError(err.response.data.errors[0]) : logOut();
+    }); 
   }
 
   // SidebarBg is separate to cover retracted drawer
@@ -117,9 +117,11 @@ export default class Sidebar extends Component<PropsType, StateType> {
             releaseDrawer={() => this.setState({ forceCloseDrawer: false })}
           />
 
-          <LogOutButton onClick={this.handleLogout}>
-            Log Out <i className="material-icons">keyboard_return</i>
-          </LogOutButton>
+          <BottomSection>
+            <LogOutButton onClick={this.handleLogout}>
+              Log Out <i className="material-icons">keyboard_return</i>
+            </LogOutButton>
+          </BottomSection>
         </StyledSidebar>
       </div>
     );
@@ -160,10 +162,14 @@ const NavButton = styled.div`
   }
 `;
 
-const LogOutButton = styled(NavButton)`
+const BottomSection = styled.div`
   position: absolute;
-  width: calc(100% - 55px); 
+  width: 100%;
   bottom: 12px;
+`;
+
+const LogOutButton = styled(NavButton)`
+  width: calc(100% - 55px); 
   border-top-right-radius: 3px;
   border-bottom-right-radius: 3px;
   margin-left: -1px;

+ 9 - 2
dashboard/src/shared/Context.tsx

@@ -28,9 +28,12 @@ class ContextProvider extends Component {
     setCurrentModal: (currentModal: string): void => {
       this.setState({ currentModal });
     },
+    currentModalData: null as any,
+    setCurrentModalData: (currentModalData: any): void => {
+      this.setState({ currentModalData });
+    },
     currentError: null as string | null,
     setCurrentError: (currentError: string): void => {
-      console.log('setting err', currentError)
       this.setState({ currentError });
     },
     currentCluster: null as string | null,
@@ -41,10 +44,14 @@ class ContextProvider extends Component {
     setUserId: (userId: number): void => {
       this.setState({ userId });
     },
+    devOpsMode: true,
+    setDevOpsMode: (devOpsMode: boolean): void => {
+      this.setState({ devOpsMode });
+    }
   };
 
   componentDidMount() {
-    this.setState({ userId: 3 });
+    this.setState({ userId: 1 });
   }
 
   render() {

+ 4 - 9
dashboard/src/shared/api.tsx

@@ -29,17 +29,13 @@ const getUser = baseApi<{}, { id: number }>('GET', pathParams => {
 
 const updateUser = baseApi<{
   rawKubeConfig?: string,
-  allowedClusters?: string[]
+  allowedContexts?: string[]
 }, { id: number }>('PUT', pathParams => {
   return `/api/users/${pathParams.id}`;
 });
 
-const getClusters = baseApi<{}, { id: number }>('GET', pathParams => {
-  return `/api/users/${pathParams.id}/clusters`;
-});
-
-const getAllClusters = baseApi<{}, { id: number }>('GET', pathParams => {
-  return `/api/users/${pathParams.id}/clusters/all`;
+const getContexts = baseApi<{}, { id: number }>('GET', pathParams => {
+  return `/api/users/${pathParams.id}/contexts`;
 });
 
 // Bundle export to allow default api import (api.<method> is more readable)
@@ -50,6 +46,5 @@ export default {
   logOutUser,
   getUser,
   updateUser,
-  getClusters,
-  getAllClusters
+  getContexts,
 }

+ 0 - 1
dashboard/src/shared/baseApi.tsx

@@ -39,7 +39,6 @@ export const baseApi = <T extends {}, S = {}>(requestType: string, endpoint: ((p
       });
     } else {
       axios.get(endpointString, {
-        withCredentials: true,
         params
       })
       .then(res => {

+ 3 - 2
dashboard/src/shared/types.tsx

@@ -1,6 +1,7 @@
-export interface ClusterConfig {
+export interface KubeContextConfig {
+  cluster: string,
   name: string,
+  selected?: boolean,
   server: string,
-  context: string,
   user: string
 }

+ 3 - 1
docker/.env

@@ -10,4 +10,6 @@ DB_PORT=5432
 DB_USER=porter
 DB_PASS=porter
 DB_NAME=porter
-COOKIE_SECRETS=secret
+COOKIE_SECRETS=secret
+
+QUICK_START=true

+ 1 - 1
docker/dev.Dockerfile

@@ -16,4 +16,4 @@ RUN go build -ldflags '-w -s' -a -o ./bin/migrate ./cmd/migrate \
 # for live reloading of go container
 RUN go get github.com/cosmtrek/air
 
-CMD air -c .air.toml
+CMD /porter/bin/migrate; air -c .air.toml

+ 1 - 0
go.mod

@@ -40,6 +40,7 @@ require (
 	gopkg.in/go-playground/validator.v9 v9.31.0
 	gopkg.in/yaml.v2 v2.3.0
 	gorm.io/driver/postgres v1.0.2
+	gorm.io/driver/sqlite v1.1.3
 	gorm.io/gorm v1.20.2
 	helm.sh/helm v2.16.12+incompatible
 	helm.sh/helm/v3 v3.3.4

+ 4 - 104
go.sum

@@ -625,6 +625,8 @@ github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
+github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA=
+github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -703,9 +705,7 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
 github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
@@ -781,7 +781,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@@ -815,12 +814,10 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -895,7 +892,6 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -1041,18 +1037,13 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8=
 golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d h1:L/IKR6COd7ubZrs2oTnTi73IhgqJ71c9s80WsQnh0Es=
-golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1153,7 +1144,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -1228,7 +1218,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
-gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
 gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
 gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
@@ -1250,18 +1239,14 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/postgres v1.0.1 h1:jRfDNUxpxNrea/97kbcscAQGmiks4UCKAYXsvh4rhOQ=
-gorm.io/driver/postgres v1.0.1/go.mod h1:pv4dVhHvEVrP7k/UYqdBIllbdbpB5VTz89X1O0uOrCA=
 gorm.io/driver/postgres v1.0.2 h1:mB5JjD4QglbCTdMT1aZDxQzHr87XDK1qh0MKIU3P96g=
 gorm.io/driver/postgres v1.0.2/go.mod h1:FvRSYfBI9jEp6ZSjlpS9qNcSjxwYxFc03UOTrHdvvYA=
-gorm.io/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
-gorm.io/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
-gorm.io/gorm v1.20.1 h1:+hOwlHDqvqmBIMflemMVPLJH7tZYK4RxFDBHEfJTup0=
+gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc=
+gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
 gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.20.2 h1:bZzSEnq7NDGsrd+n3evOOedDrY5oLM5QPlCjZJUK2ro=
 gorm.io/gorm v1.20.2/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
-helm.sh/helm v1.2.1 h1:Jrn7kKQqQ/hnFWZEX+9pMFvYqFexkzrBnGqYBmIph7c=
 helm.sh/helm v2.16.12+incompatible h1:nQfifk10KcpAGD1RJaNZVW/fWiqluV0JMuuDwdba4rw=
 helm.sh/helm v2.16.12+incompatible/go.mod h1:0Xbc6ErzwWH9qC55X1+hE3ZwhM3atbhCm/NbFZw5i+4=
 helm.sh/helm/v3 v3.3.4 h1:tbad6WQVMxEw1HlVBvI2rQqOblmI5lgXOrWAMwJ198M=
@@ -1274,121 +1259,38 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.0.0-20200914174313-52bf62410745 h1:hEgy7kHg2J1GrSDvkf+IlbZWFBi536OTioyP1Cqujro=
-k8s.io/api v0.0.0-20200914174313-52bf62410745/go.mod h1:UT3vnXQcd48N6K0IuGGYk1ufh1lolzq+pC4aE2BPvWA=
 k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4=
 k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
-k8s.io/api v0.18.9-rc.0/go.mod h1:32vFAuLAX89RZVkp4GtXk81UEzc/lDGxAYghfu8qFEI=
-k8s.io/api v0.18.9/go.mod h1:9u/h6sUh6FxfErv7QqetX1EB3yBMIYOBXzdcf0Gf0rc=
-k8s.io/api v0.18.10-rc.0/go.mod h1:+/nAEryKg7rREk9NaRCGstia/6qH7sR6epIIIDu04O0=
-k8s.io/api v0.19.0-alpha.0/go.mod h1:gwFXFTdIdFlWazixRQwbq3p9mI191fabextJ/aOC1k8=
-k8s.io/api v0.19.0-alpha.1/go.mod h1:fNr5XDW1/+18jRxdpIGIdL8AeDhv/8y7jmMGTpIdAoA=
-k8s.io/api v0.19.0-alpha.2/go.mod h1:ujOZQ0qV79Ae02qAditrRXodlKq4GMe7khc3uS2hhEw=
-k8s.io/api v0.19.0-alpha.3/go.mod h1:W/NfI00K9E3/pXwbly0FrdxxtjIjUs/v4m3Li5pT3J8=
-k8s.io/api v0.19.0-beta.0/go.mod h1:6NPoy+1qu2rrljjwWauQMCxWpN1/VfhYnwCB6EB8Mn4=
-k8s.io/api v0.19.0-beta.1/go.mod h1:aZcY31HnWwA9OGlx1K9jTTG0gD7hXXLwzpIVn1Td1gw=
-k8s.io/api v0.19.0-beta.2/go.mod h1:LgaR0+wwwUQzSn968ds/S5cabkwptRrQB1VKZYB1zZg=
-k8s.io/api v0.19.0-rc.0/go.mod h1:WBGMHEmngOdQBAvJiYUgP5mGDdCWXM52yDm1gtos8C0=
-k8s.io/api v0.19.0-rc.1/go.mod h1:NdiA9gl+9BuvhHU0WfqE5A7SrA4iO5TrmRE0caDteuA=
-k8s.io/api v0.19.0-rc.2/go.mod h1:9nHeM2gbqeaL7yN6UFvOxKzLG5gZ4v+DJ6bpavDetZo=
-k8s.io/api v0.19.0-rc.3/go.mod h1:hfDN4tL/yqfs4aVrlvx2vNMhKACfoiTyGSvVnygqSd4=
-k8s.io/api v0.19.0-rc.4/go.mod h1:1xlMhKahfl3bVAn1T1PhMriUVYwRNJ7D8YMDnUz/yGw=
-k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw=
-k8s.io/api v0.19.1-rc.0/go.mod h1:T5DSfVHz1QXbjy7ezqThvoKt7j+goH0ltauR0oz4BGM=
-k8s.io/api v0.19.1/go.mod h1:+u/k4/K/7vp4vsfdT7dyl8Oxk1F26Md4g5F26Tu85PU=
-k8s.io/api v0.19.2-rc.0/go.mod h1:GOfZBLBzYQ228kgdElkfOSQ0eM8jeInXsVLF1PY0h70=
-k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
-k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
 k8s.io/apiextensions-apiserver v0.18.8 h1:pkqYPKTHa0/3lYwH7201RpF9eFm0lmZDFBNzhN+k/sA=
 k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM=
-k8s.io/apimachinery v0.0.0-20200910171558-1173d23fd476/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
-k8s.io/apimachinery v0.0.0-20200916235632-714f1137f89b/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
 k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0=
 k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig=
-k8s.io/apimachinery v0.18.9-rc.0/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig=
-k8s.io/apimachinery v0.18.9/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk=
-k8s.io/apimachinery v0.18.10-rc.0/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk=
-k8s.io/apimachinery v0.19.0-alpha.0/go.mod h1:5X8oEhnd931nEg6/Nkumo00nT6ZsCLp2h7Xwd7Ym6P4=
-k8s.io/apimachinery v0.19.0-alpha.1/go.mod h1:MwmRUlFgPZZjQ9mmX205Ve0gth+HzXB7tiAFmJilVME=
-k8s.io/apimachinery v0.19.0-alpha.2/go.mod h1:imoz42hIYwpLTRWXU8pdJ9IE8DbxUsnU9lyVN8Y1SNo=
-k8s.io/apimachinery v0.19.0-alpha.3/go.mod h1:imoz42hIYwpLTRWXU8pdJ9IE8DbxUsnU9lyVN8Y1SNo=
-k8s.io/apimachinery v0.19.0-beta.0/go.mod h1:x4z2+k1N0YTBvV8PmaVs4/hSmKVVENZmTqI8gBygpLA=
-k8s.io/apimachinery v0.19.0-beta.1/go.mod h1:x4z2+k1N0YTBvV8PmaVs4/hSmKVVENZmTqI8gBygpLA=
-k8s.io/apimachinery v0.19.0-beta.2/go.mod h1:diAekxQB6O2LunkgrS6bHwK4dfE2K8KIxK3GeFjrgBU=
-k8s.io/apimachinery v0.19.0-rc.0/go.mod h1:EjWiYOPi+BZennZ5pGa3JLkQ+znhEOodGy/+umjiLDU=
-k8s.io/apimachinery v0.19.0-rc.1/go.mod h1:eHbWZVMaaewmYBAUuRYnAmTTMtDhvpPNZuh8/6Yl7v0=
-k8s.io/apimachinery v0.19.0-rc.2/go.mod h1:eHbWZVMaaewmYBAUuRYnAmTTMtDhvpPNZuh8/6Yl7v0=
-k8s.io/apimachinery v0.19.0-rc.3/go.mod h1:oE8UQU9DqIIc9PyIEYxTj/oJECzZLymCEU9dL0H4F+o=
-k8s.io/apimachinery v0.19.0-rc.4/go.mod h1:oE8UQU9DqIIc9PyIEYxTj/oJECzZLymCEU9dL0H4F+o=
-k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
-k8s.io/apimachinery v0.19.1-rc.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
-k8s.io/apimachinery v0.19.1/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
-k8s.io/apimachinery v0.19.2-rc.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
-k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
-k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
 k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM=
 k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k=
 k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M=
-k8s.io/cli-runtime v0.18.9-rc.0/go.mod h1:CAw3/xS/Kb7ERMha5eu2F/KGUDGecQT+8ghA74GHBms=
-k8s.io/cli-runtime v0.18.9/go.mod h1:Pw7UPmZd/wIlGd7DWGTUWA7qn92jCeybNeiS5WYJI6A=
-k8s.io/cli-runtime v0.18.10-rc.0/go.mod h1:+ilfZmUD5Be0SsqhUP1n/HfwBVFO5sJzOjxX2fYYDeA=
-k8s.io/cli-runtime v0.19.0-alpha.0/go.mod h1:UG9FPBN/kIdVGF8Iz6EsCqIHR3FJNOaKGbSRPgcNg8E=
-k8s.io/cli-runtime v0.19.0-alpha.1/go.mod h1:vTsSzQZWQfN4IXDBSWqIgWyICm7i6TAQG4Mlw3b4JcI=
-k8s.io/cli-runtime v0.19.0-alpha.2/go.mod h1:k2LnE5XW0j4nuDrfdkiXNOxLyNRAtN8Zd5udvGL/FyI=
-k8s.io/cli-runtime v0.19.0-alpha.3/go.mod h1:9Rd1gtyri8aAR3k13nZ3/iLv7fFTk2nvCEM/AIl2PAA=
-k8s.io/cli-runtime v0.19.0-beta.0/go.mod h1:CekH2Cm+SCrZXL28ylmfPcngWaTCqOovWylKSq6juyc=
-k8s.io/cli-runtime v0.19.0-beta.1/go.mod h1:WdBehFU2wQa+cT53trT/BZsRlAv/oQAwlNq43B53BoI=
-k8s.io/cli-runtime v0.19.0-beta.2/go.mod h1:xV5hDb8a5U9ep1Qu8YbSeqY63FL5m307+WP8mZ6fNR8=
-k8s.io/cli-runtime v0.19.0-rc.0/go.mod h1:91S0K5jlSkD5spOMdlcGuJp4nNO0Etdk/XvEcX90xws=
-k8s.io/cli-runtime v0.19.0-rc.1/go.mod h1:ygoCd1Hj7nwjxJkt2VJMGLiRNxC8BewCX41kwhkyGAs=
-k8s.io/cli-runtime v0.19.0-rc.2/go.mod h1:AeERvBzh6NuhN5I3D3gJhjzMBjmZKfuyV4cIB0SY5+w=
-k8s.io/cli-runtime v0.19.0-rc.3/go.mod h1:9Gx6aAV0meCZhUk20b+o8oSROGrIeuT1CofCKgnqXhA=
-k8s.io/cli-runtime v0.19.0-rc.4/go.mod h1:t3T2QwNf8DoGLBVbTO24lvpXShK65xbrTOnM4grRPyI=
-k8s.io/cli-runtime v0.19.0/go.mod h1:tun9l0eUklT8IHIM0jors17KmUjcrAxn0myoBYwuNuo=
-k8s.io/cli-runtime v0.19.1-rc.0/go.mod h1:ENz5EYxOnrf6IOu0usywgCha4Ltl7a4w5Sbz6sR51sM=
-k8s.io/cli-runtime v0.19.1/go.mod h1:X6g8e4NBiG8GMsKewXsRpo36MO6xrvXa+0wCg7zO4aU=
-k8s.io/cli-runtime v0.19.2-rc.0/go.mod h1:kR2WVmAw0WDUYxF9TVlkfmLEoq8k760JWeks3pWZ2kU=
-k8s.io/cli-runtime v0.19.2 h1:d4uOtKhy3ImdaKqZJ8yQgLrdtUwsJLfP4Dw7L/kVPOo=
-k8s.io/cli-runtime v0.19.2/go.mod h1:CMynmJM4Yf02TlkbhKxoSzi4Zf518PukJ5xep/NaNeY=
-k8s.io/client-go v0.0.0-20200917000235-cba7285b7f29 h1:2lPJxQtfK6wMW+gN6WrQFCZ5dYiFpgDphXneGsE3mZU=
-k8s.io/client-go v0.0.0-20200917000235-cba7285b7f29/go.mod h1:Plj2rfLmeMYfAMuMgA/1EGuUaDxt78tvB9yfdi6fg6A=
 k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM=
 k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU=
-k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc=
-k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
-k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
-k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
-k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
 k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
 k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8=
 k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/helm v1.2.1 h1:Ny4wgW4p7X3tFXR34PziNkUxw2pV0G1DIFmI1QRDdo0=
-k8s.io/helm v2.16.12+incompatible h1:K2zhF8+B85Ya1n7n3eH34xwwp5qNUM42TBFENDZJT7w=
 k8s.io/helm v2.16.12+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.1.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
 k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
-k8s.io/kube-openapi v0.0.0-20200403204345-e1beb1bd0f35/go.mod h1:NwPpO8COeh/j9Q9ModsqBxwHcWDo/PmrJOPyquZCC1A=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
-k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
-k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ=
-k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
 k8s.io/kubectl v0.18.8 h1:qTkHCz21YmK0+S0oE6TtjtxmjeDP42gJcZJyRKsIenA=
 k8s.io/kubectl v0.18.8/go.mod h1:PlEgIAjOMua4hDFTEkVf+W5M0asHUKfE4y7VDZkpLHM=
 k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 k8s.io/metrics v0.18.8/go.mod h1:j7JzZdiyhLP2BsJm/Fzjs+j5Lb1Y7TySjhPWqBPwRXA=
 k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
-k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 k8s.io/utils v0.0.0-20200912215256-4140de9c8800 h1:9ZNvfPvVIEsp/T1ez4GQuzCcCTEQWhovSofhqR73A6g=
 k8s.io/utils v0.0.0-20200912215256-4140de9c8800/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
@@ -1398,10 +1300,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT
 sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
 sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200207200219-5e70324e7c1c/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
 sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=

+ 9 - 1
internal/adapter/gorm.go

@@ -2,9 +2,12 @@ package gorm
 
 import (
 	"fmt"
+	"os"
+	"strconv"
 
 	"github.com/porter-dev/porter/internal/config"
 	"gorm.io/driver/postgres"
+	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
 )
 
@@ -18,5 +21,10 @@ func New(conf *config.DBConf) (*gorm.DB, error) {
 		conf.Host,
 	)
 
-	return gorm.Open(postgres.Open(dsn), &gorm.Config{})
+	if quickstart, _ := strconv.ParseBool(os.Getenv("QUICK_START")); quickstart {
+		return gorm.Open(sqlite.Open("./internal/porter.db"), &gorm.Config{})
+	} else {
+		return gorm.Open(postgres.Open(dsn), &gorm.Config{})
+	}
+
 }

+ 9 - 0
internal/auth/sessionstore.go

@@ -127,6 +127,15 @@ func NewStore(repo *repository.Repository, conf config.ServerConf) (*PGStore, er
 	return dbStore, nil
 }
 
+// NewFilesystemStore takes session key pairs to create a session-store in the local fs without using a db.
+func NewFilesystemStore(conf config.ServerConf) *sessions.FilesystemStore {
+
+	// Defaults to os.TempDir() when first argument (path) isn't specified.
+	store := sessions.NewFilesystemStore("", conf.CookieSecret)
+
+	return store
+}
+
 // Get Fetches a session for a given name after it has been added to the
 // registry.
 func (store *PGStore) Get(r *http.Request, name string) (*sessions.Session, error) {

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

@@ -46,7 +46,7 @@ func (repo *UserRepository) ReadUserByEmail(email string) (*models.User, error)
 
 // UpdateUser modifies an existing User in the database
 func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
-	if err := repo.db.First(&models.User{}, user.ID).Updates(user).Error; err != nil {
+	if err := repo.db.Save(user).Error; err != nil {
 		return nil, err
 	}
 

+ 4 - 3
server/api/api.go

@@ -4,7 +4,8 @@ import (
 	"github.com/go-playground/locales/en"
 	ut "github.com/go-playground/universal-translator"
 	"github.com/go-playground/validator/v10"
-	sessionstore "github.com/porter-dev/porter/internal/auth"
+
+	"github.com/gorilla/sessions"
 	"github.com/porter-dev/porter/internal/helm"
 	"github.com/porter-dev/porter/internal/kubernetes"
 	lr "github.com/porter-dev/porter/internal/logger"
@@ -25,7 +26,7 @@ type App struct {
 	logger     *lr.Logger
 	repo       *repository.Repository
 	validator  *validator.Validate
-	store      *sessionstore.PGStore
+	store      sessions.Store
 	translator *ut.Translator
 	cookieName string
 	testing    bool
@@ -37,7 +38,7 @@ func New(
 	logger *lr.Logger,
 	repo *repository.Repository,
 	validator *validator.Validate,
-	store *sessionstore.PGStore,
+	store sessions.Store,
 	cookieName string,
 	testing bool,
 ) *App {

+ 1 - 3
server/api/user_handler.go

@@ -3,7 +3,6 @@ package api
 import (
 	"encoding/json"
 	"errors"
-	"fmt"
 	"net/http"
 	"strconv"
 	"strings"
@@ -57,8 +56,7 @@ func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
 // HandleAuthCheck checks whether current session is authenticated.
 func (app *App) HandleAuthCheck(w http.ResponseWriter, r *http.Request) {
 	session, err := app.store.Get(r, app.cookieName)
-	cook, _ := r.Cookie("porter")
-	fmt.Println("cooki", cook)
+
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 	}

+ 27 - 4
server/api/user_handler_test.go

@@ -322,12 +322,12 @@ func TestHandleReadUser(t *testing.T) {
 	testUserRequests(t, readUserTests, true)
 }
 
-var readUserClustersTests = []*userTest{
+var readUserContextsTests = []*userTest{
 	&userTest{
 		initializers: []func(tester *tester){
 			initUserWithContexts,
 		},
-		msg:       "Read user successful",
+		msg:       "Read user context selected successful",
 		method:    "GET",
 		endpoint:  "/api/users/1/contexts",
 		body:      "",
@@ -338,10 +338,33 @@ var readUserClustersTests = []*userTest{
 			userContextBodyValidator,
 		},
 	},
+	&userTest{
+		initializers: []func(tester *tester){
+			func(tester *tester) {
+				initUserDefault(tester)
+
+				user, _ := tester.repo.User.ReadUserByEmail("belanger@getporter.dev")
+				user.Contexts = ""
+				user.RawKubeConfig = []byte("apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin")
+
+				tester.repo.User.UpdateUser(user)
+			},
+		},
+		msg:       "Read user context not selected successful",
+		method:    "GET",
+		endpoint:  "/api/users/1/contexts",
+		body:      "",
+		expStatus: http.StatusOK,
+		useCookie: true,
+		expBody:   `[{"name":"context-test","server":"https://localhost","cluster":"cluster-test","user":"test-admin","selected":false}]`,
+		validators: []func(c *userTest, tester *tester, t *testing.T){
+			userContextBodyValidator,
+		},
+	},
 }
 
-func TestHandleReadUserClusters(t *testing.T) {
-	testUserRequests(t, readUserClustersTests, true)
+func TestHandleReadUserContexts(t *testing.T) {
+	testUserRequests(t, readUserContextsTests, true)
 }
 
 var updateUserTests = []*userTest{

+ 3 - 3
server/router/middleware/auth.go

@@ -8,18 +8,18 @@ import (
 	"strconv"
 
 	"github.com/go-chi/chi"
-	sessionstore "github.com/porter-dev/porter/internal/auth"
+	"github.com/gorilla/sessions"
 )
 
 // Auth implements the authorization functions
 type Auth struct {
-	store      *sessionstore.PGStore
+	store      sessions.Store
 	cookieName string
 }
 
 // NewAuth returns a new Auth instance
 func NewAuth(
-	store *sessionstore.PGStore,
+	store sessions.Store,
 	cookieName string,
 ) *Auth {
 	return &Auth{store, cookieName}

+ 2 - 3
server/router/router.go

@@ -2,15 +2,14 @@ package router
 
 import (
 	"github.com/go-chi/chi"
+	"github.com/gorilla/sessions"
 	"github.com/porter-dev/porter/server/api"
 	"github.com/porter-dev/porter/server/requestlog"
 	mw "github.com/porter-dev/porter/server/router/middleware"
-
-	sessionstore "github.com/porter-dev/porter/internal/auth"
 )
 
 // New creates a new Chi router instance
-func New(a *api.App, store *sessionstore.PGStore, cookieName string) *chi.Mux {
+func New(a *api.App, store sessions.Store, cookieName string) *chi.Mux {
 	l := a.Logger()
 	r := chi.NewRouter()
 	auth := mw.NewAuth(store, cookieName)