jusrhee 5 tahun lalu
induk
melakukan
d60bcbb919
31 mengubah file dengan 672 tambahan dan 157 penghapusan
  1. 13 0
      dashboard/package-lock.json
  2. 1 1
      dashboard/package.json
  3. TEMPAT SAMPAH
      dashboard/src/assets/My Health Connection - Appointment Details.pdf
  4. 2 1
      dashboard/src/components/ResourceTab.tsx
  5. 14 0
      dashboard/src/components/values-form/ValuesForm.tsx
  6. 3 0
      dashboard/src/components/values-form/ValuesWrapper.tsx
  7. 10 0
      dashboard/src/index.html
  8. 3 2
      dashboard/src/main/Main.tsx
  9. 7 0
      dashboard/src/main/home/Home.tsx
  10. 9 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  11. 9 5
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx
  12. 120 28
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx
  13. 1 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx
  14. 3 0
      dashboard/src/main/home/sidebar/ClusterSection.tsx
  15. 4 2
      dashboard/src/main/home/sidebar/Sidebar.tsx
  16. 1 0
      dashboard/src/main/home/templates/Templates.tsx
  17. 52 47
      internal/kubernetes/agent.go
  18. 1 1
      internal/kubernetes/provisioner/global_stream.go
  19. 23 0
      internal/kubernetes/provisioner/input/docr.go
  20. 23 0
      internal/kubernetes/provisioner/input/doks.go
  21. 24 0
      internal/kubernetes/provisioner/input/ecr.go
  22. 24 0
      internal/kubernetes/provisioner/input/eks.go
  23. 23 0
      internal/kubernetes/provisioner/input/gcr.go
  24. 24 0
      internal/kubernetes/provisioner/input/gke.go
  25. 178 14
      internal/kubernetes/provisioner/provisioner.go
  26. 10 3
      internal/models/infra.go
  27. 80 3
      internal/repository/gorm/infra.go
  28. 1 1
      internal/repository/gorm/repository.go
  29. 1 1
      server/api/oauth_github_handler.go
  30. 8 35
      server/api/provision_handler.go
  31. 0 10
      server/router/router.go

+ 13 - 0
dashboard/package-lock.json

@@ -2944,6 +2944,11 @@
         "websocket-driver": ">=0.5.1"
       }
     },
+    "fflate": {
+      "version": "0.4.8",
+      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
+      "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+    },
     "figgy-pudding": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -5356,6 +5361,14 @@
       "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
       "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
     },
+    "posthog-js": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.8.5.tgz",
+      "integrity": "sha512-YwKejadn4GAjqbA3cvpmou2As3P0nMSt76kySRmfH/XmM00YDs8o4WFrorWHJvDAbln46CXIzAzfgS71sZ+gXw==",
+      "requires": {
+        "fflate": "^0.4.1"
+      }
+    },
     "posthog-node": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-1.0.6.tgz",

+ 1 - 1
dashboard/package.json

@@ -17,7 +17,7 @@
     "js-yaml": "^3.14.0",
     "lodash": "^4.17.20",
     "markdown-to-jsx": "^7.0.1",
-    "posthog-node": "^1.0.6",
+    "posthog-js": "^1.8.5",
     "qs": "^6.9.4",
     "random-words": "^1.1.1",
     "react": "^16.13.1",

TEMPAT SAMPAH
dashboard/src/assets/My Health Connection - Appointment Details.pdf


+ 2 - 1
dashboard/src/components/ResourceTab.tsx

@@ -15,6 +15,7 @@ type PropsType = {
     available?: number,
     total?: number,
   } | null
+  expanded?: boolean,
 };
 
 type StateType = {
@@ -24,7 +25,7 @@ type StateType = {
 
 export default class ResourceTab extends Component<PropsType, StateType> {
   state = {
-    expanded: false,
+    expanded: this.props.expanded || false,
     showTooltip: false,
   }
 

+ 14 - 0
dashboard/src/components/values-form/ValuesForm.tsx

@@ -92,6 +92,20 @@ export default class ValuesForm extends Component<PropsType, StateType> {
               label={item.label}
             />
           );
+        case 'array-input':
+          return (
+            <InputRow
+              key={i}
+              isRequired={item.required}
+              type='text'
+              value={this.getInputValue(item)}
+              setValue={(x: string) => {
+                this.props.setMetaState({ [key]: [x] });
+              }}
+              label={item.label}
+              unit={item.settings ? item.settings.unit : null}
+            />
+          );
         case 'string-input':
           return (
             <InputRow

+ 3 - 0
dashboard/src/components/values-form/ValuesWrapper.tsx

@@ -47,6 +47,9 @@ export default class ValuesWrapper extends Component<PropsType, StateType> {
               case 'string-input':
                 metaState[key] = def ? def : '';
                 break;
+              case 'array-input':
+                metaState[key] = def ? def : [];
+                break;
               case 'number-input':
                 metaState[key] = def.toString() ? def : '';
                 break;

+ 10 - 0
dashboard/src/index.html

@@ -19,5 +19,15 @@
   </head>
 <body>
   <div id="output"></div>
+  <script>
+    window.intercomSettings = {
+      app_id: "gq56g49i"
+    };
+  </script>
+
+  <script>
+  // We pre-filled your app ID in the widget URL: 'https://widget.intercom.io/widget/gq56g49i'
+  (function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/gq56g49i';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);};if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
+  </script>
 </body>
 </html>

+ 3 - 2
dashboard/src/main/Main.tsx

@@ -31,8 +31,9 @@ export default class Main extends Component<PropsType, StateType> {
 
   componentDidMount() {
     let { setUser } = this.context;
-    api.checkAuth('', {}, {}, (err: any, res: any) => {      
-      if (err && err.response.status == 403) {
+    api.checkAuth('', {}, {}, (err: any, res: any) => {    
+      console.log(err)  
+      if (err && err.response?.status == 403) {
         this.setState({ isLoggedIn: false, loading: false })
       }
 

+ 7 - 0
dashboard/src/main/home/Home.tsx

@@ -20,6 +20,7 @@ import IntegrationsInstructionsModal from './modals/IntegrationsInstructionsModa
 import NewProject from './new-project/NewProject';
 import Navbar from './navbar/Navbar';
 import Provisioner from './new-project/Provisioner';
+import posthog from 'posthog-js';
 
 type PropsType = {
   logOut: () => void
@@ -91,6 +92,12 @@ export default class Home extends Component<PropsType, StateType> {
   }
 
   componentDidMount() {
+    let { user } = this.context;
+    window.location.href.indexOf('127.0.0.1') === -1 && posthog.init(process.env.POSTHOG_API_KEY, {
+      api_host: process.env.POSTHOG_HOST,
+      loaded: function(posthog) { posthog.identify(user.email) }
+    })
+
     this.getProjects();
   }
 

+ 9 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -474,9 +474,15 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
         console.log(err);
         return
       }
-      
+
+      if (res.data?.spec?.rules && res.data?.spec?.rules[0]?.host) {
+        this.setState({url: `https://${res.data?.spec?.rules[0]?.host}` })
+        return;
+      }
+
       if (res.data?.status?.loadBalancer?.ingress) {
-        this.setState({url: `http://${res.data?.status?.loadBalancer?.ingress[0]?.hostname}` })
+        this.setState({ url: `http://${res.data?.status?.loadBalancer?.ingress[0]?.hostname}` })
+        return;
       }
     });
 
@@ -520,7 +526,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
       return (
         <Url>
           <Bolded>Internal URI:</Bolded>
-          {`${serviceName}.${serviceNamespace}.namespace.svc.cluster.local`}
+          {`${serviceName}.${serviceNamespace}.svc.cluster.local`}
         </Url>
       );
     }

+ 9 - 5
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx

@@ -10,6 +10,7 @@ type PropsType = {
   selectedPod: any,
   selectPod: Function,
   isLast?: boolean,
+  isFirst?: boolean,
 };
 
 type StateType = {
@@ -26,7 +27,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
 
   componentDidMount() {
     let { currentCluster, currentProject, setCurrentError } = this.context;
-    let { controller } = this.props;
+    let { controller, selectPod, isFirst } = this.props;
 
     let selectors = [] as string[];
     let ml = controller?.spec?.selector?.matchLabels || controller?.spec?.selector;
@@ -61,6 +62,10 @@ export default class ControllerTab extends Component<PropsType, StateType> {
       });
       
       this.setState({ pods, raw: res.data });
+      
+      if (isFirst) {
+        selectPod(res.data[0])
+      }
     })
   }
 
@@ -80,7 +85,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
   }
 
   getPodStatus = (status: any) => {
-    if (status?.phase == 'Pending' && status?.containerStatuses) {
+    if (status?.phase == 'Pending' && status?.containerStatuses !== undefined) {
       return status.containerStatuses[0].state.waiting.reason
       // return 'waiting'
     }
@@ -104,17 +109,16 @@ export default class ControllerTab extends Component<PropsType, StateType> {
   }
 
   render() {
-    let { controller, selectedPod, isLast, selectPod } = this.props;
-    console.log(controller)
+    let { controller, selectedPod, isLast, selectPod, isFirst } = this.props;
     let [available, total] = this.getAvailability(controller.kind, controller);
     let status = (available == total) ? 'running' : 'waiting'
-    console.log('state', this.state)
     return (
       <ResourceTab
         label={controller.kind}
         name={controller.metadata.name}
         status={{ label: status, available, total }}
         isLast={isLast}
+        expanded={isFirst}
       >
         {
           this.state.raw.map((pod, i) => {

+ 120 - 28
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -8,20 +8,28 @@ type PropsType = {
 
 type StateType = {
   logs: string[],
-  ws: any
+  ws: any,
+  scroll: boolean,
 };
 
 export default class Logs extends Component<PropsType, StateType> {
   
   state = {
     logs: [] as string[],
-    ws : null as any
+    ws : null as any,
+    scroll: true,
   }
 
+  ws = null as any;
   scrollRef = React.createRef<HTMLDivElement>()
+  parentRef = React.createRef<HTMLDivElement>()
 
-  scrollToBottom = () => {
-    this.scrollRef.current.scrollTop = this.scrollRef.current.scrollHeight
+  scrollToBottom = (smooth: boolean) => {
+    if (smooth) {
+      this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "smooth" })
+    } else {
+      this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "auto" })
+    }
   }
 
   renderLogs = () => {
@@ -37,44 +45,79 @@ export default class Logs extends Component<PropsType, StateType> {
     })
   }
 
-  componentDidMount() {
+  setupWebsocket = () => {  
     let { currentCluster, currentProject } = this.context;
     let { selectedPod } = this.props;
     if (!selectedPod.metadata?.name) return
+
     let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws'
-    let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?cluster_id=${currentCluster.id}&service_account_id=${currentCluster.service_account_id}`)
-    
-    this.setState({ ws }, () => {
-      if (!this.state.ws) return;
-  
-      this.state.ws.onopen = () => {
-        console.log('connected to websocket')
-      }
-  
-      this.state.ws.onmessage = (evt: MessageEvent) => {
-        this.setState({ logs: [...this.state.logs, evt.data] }, () => {
-          this.scrollToBottom()
-        })
-      }
-  
-      this.state.ws.onerror = (err: ErrorEvent) => {
-        console.log(err)
-      }
-    })
+    this.ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?cluster_id=${currentCluster.id}&service_account_id=${currentCluster.service_account_id}`)
+
+    this.ws.onopen = () => {
+      console.log('connected to websocket')
+    }
+
+    this.ws.onmessage = (evt: MessageEvent) => {
+      this.setState({ logs: [...this.state.logs, evt.data] }, () => {
+        if (this.state.scroll && this.state.logs.length >50) {
+          this.scrollToBottom(false)
+        }
+      })
+    }
+
+    this.ws.onerror = (err: ErrorEvent) => {
+      console.log("websocket error:", err)
+    }
+
+    this.ws.onclose = () => {
+      console.log("closing pod logs")
+    }
+  }
+
+  refreshLogs = () => {
+    if (this.ws) {
+      this.ws.close();
+      this.ws = null;
+      this.setState({logs: []})
+      this.setupWebsocket();
+    }
+  }
+
+  componentDidMount() {
+    this.setupWebsocket()
+    this.scrollToBottom(false);
   }
 
   componentWillUnmount() {
-    if (this.state.ws) {
-      this.state.ws.close()
+    console.log('log unmount')
+    if (this.ws) {
+      this.ws.close()
     }
   }
 
   render() {
     return (
-      <LogStream ref={this.scrollRef}>
-        <Wrapper>
+      <LogStream>
+        <Wrapper ref={this.parentRef}>
           {this.renderLogs()}
+          <div ref={this.scrollRef} />
         </Wrapper>
+        <Options>
+          <Scroll onClick={()=> {
+            this.setState({scroll: !this.state.scroll}, () => {
+              if (this.state.scroll) {
+                this.scrollToBottom(true)
+              }
+            }); 
+          }}>
+            <input type="checkbox" checked={this.state.scroll} onChange={() => {}}/>
+            Scroll to Bottom
+          </Scroll>
+          <Refresh onClick={() => {this.refreshLogs()}}>
+            <i className="material-icons">autorenew</i>
+            Refresh
+          </Refresh>
+        </Options>
       </LogStream>
     );
   }
@@ -82,6 +125,54 @@ export default class Logs extends Component<PropsType, StateType> {
 
 Logs.contextType = Context;
 
+const Scroll = styled.div`
+  align-items: center;
+  display: flex;
+  cursor: pointer;
+  width: 145px;
+  height: 100%;
+
+  :hover {
+    background: #2468d6;
+  }
+
+  > input {
+    width; 18px;
+    margin-left: 10px;
+    margin-right: 6px;
+    pointer-events: none;
+  }
+`
+
+const Refresh = styled.div`
+  display: flex;
+  align-items: center;
+  width: 87px;
+  user-select: none;
+  cursor: pointer;
+  height: 100%;
+
+  > i {
+    margin-left: 6px;
+    font-size: 17px;
+    margin-right: 6px;
+  }
+
+  :hover {
+    background: #2468d6;
+  }
+`
+
+const Options = styled.div`
+  width: 100%;
+  height: 25px;
+  background: #397ae3;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+`
+
 const Wrapper = styled.div`
   width: 100%;
   height: 100%;
@@ -91,6 +182,7 @@ const Wrapper = styled.div`
 
 const LogStream = styled.div`
   display: flex;
+  flex-direction: column;
   flex: 1;
   float: right;
   height: 100%;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -51,6 +51,7 @@ export default class StatusSection extends Component<PropsType, StateType> {
           selectPod={this.selectPod.bind(this)}
           controller={c}
           isLast={i === this.state.controllers.length - 1}
+          isFirst={i === 0}
         />
       )
     })

+ 3 - 0
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -56,6 +56,8 @@ export default class ClusterSection extends Component<PropsType, StateType> {
           clusters.sort((a: any, b: any) => a.id - b.id);
           if (clusters.length > 0) {
             this.setState({ clusters });
+            setCurrentCluster(clusters[0]);
+            /*
             try {
               setCurrentCluster((localStorage.getItem('currentCluster')) ? 
                 JSON.parse(localStorage.getItem('currentCluster')) : clusters[0]
@@ -63,6 +65,7 @@ export default class ClusterSection extends Component<PropsType, StateType> {
             } catch(err) {
               setCurrentCluster(clusters[0]);
             }
+            */
           } else if (this.props.currentView !== 'provisioner') {
             this.setState({ clusters: [] });
             setCurrentCluster(null);

+ 4 - 2
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -9,6 +9,7 @@ import { Context } from '../../../shared/Context';
 import ClusterSection from './ClusterSection';
 import ProjectSectionContainer from './ProjectSectionContainer';
 import loading from '../../../assets/loading.gif';
+import posthog from 'posthog-js';
 
 type PropsType = {
   forceSidebar: boolean,
@@ -117,9 +118,10 @@ export default class Sidebar extends Component<PropsType, StateType> {
             Templates
           </NavButton>
           <NavButton
-            // onClick={() => this.props.setCurrentView('integrations')}
             selected={this.props.currentView === 'integrations'}
-            onClick={() => this.context.setCurrentModal('IntegrationsInstructionsModal', {})}
+            onClick={() => {
+              this.context.setCurrentModal('IntegrationsInstructionsModal', {})
+            }}
           >
             <img src={integrations} />
             Integrations

+ 1 - 0
dashboard/src/main/home/templates/Templates.tsx

@@ -17,6 +17,7 @@ const tabOptions = [
 const hardcodedNames: any = {
   'postgresql': 'PostgreSQL',
   'docker': 'Docker',
+  'https-issuer': 'HTTPS Issuer'
 };
 
 type PropsType = {

+ 52 - 47
internal/kubernetes/agent.go

@@ -123,8 +123,9 @@ func (a *Agent) GetPodsByLabel(selector string) (*v1.PodList, error) {
 
 // GetPodLogs streams real-time logs from a given pod.
 func (a *Agent) GetPodLogs(namespace string, name string, conn *websocket.Conn) error {
+	tails := int64(400)
+
 	// follow logs
-	tails := int64(30)
 	podLogOpts := v1.PodLogOptions{
 		Follow:    true,
 		TailLines: &tails,
@@ -250,13 +251,14 @@ func (a *Agent) ProvisionECR(
 	projectID uint,
 	awsConf *integrations.AWSIntegration,
 	ecrName string,
+	repo repository.Repository,
 	infra *models.Infra,
 	operation provisioner.ProvisionerOperation,
 	pgConf *config.DBConf,
 	redisConf *config.RedisConf,
 	provImageTag string,
 ) (*batchv1.Job, error) {
-	id := infra.GetID()
+	id := infra.GetUniqueName()
 	prov := &provisioner.Conf{
 		ID:                  id,
 		Name:                fmt.Sprintf("prov-%s-%s", id, string(operation)),
@@ -265,6 +267,7 @@ func (a *Agent) ProvisionECR(
 		Redis:               redisConf,
 		Postgres:            pgConf,
 		ProvisionerImageTag: provImageTag,
+		LastApplied:         infra.LastApplied,
 		AWS: &aws.Conf{
 			AWSRegion:          awsConf.AWSRegion,
 			AWSAccessKeyID:     string(awsConf.AWSAccessKeyID),
@@ -275,7 +278,7 @@ func (a *Agent) ProvisionECR(
 		},
 	}
 
-	return a.provision(prov)
+	return a.provision(prov, infra, repo)
 }
 
 // ProvisionEKS spawns a new provisioning pod that creates an EKS instance
@@ -283,13 +286,14 @@ func (a *Agent) ProvisionEKS(
 	projectID uint,
 	awsConf *integrations.AWSIntegration,
 	eksName string,
+	repo repository.Repository,
 	infra *models.Infra,
 	operation provisioner.ProvisionerOperation,
 	pgConf *config.DBConf,
 	redisConf *config.RedisConf,
 	provImageTag string,
 ) (*batchv1.Job, error) {
-	id := infra.GetID()
+	id := infra.GetUniqueName()
 	prov := &provisioner.Conf{
 		ID:                  id,
 		Name:                fmt.Sprintf("prov-%s-%s", id, string(operation)),
@@ -298,6 +302,7 @@ func (a *Agent) ProvisionEKS(
 		Redis:               redisConf,
 		Postgres:            pgConf,
 		ProvisionerImageTag: provImageTag,
+		LastApplied:         infra.LastApplied,
 		AWS: &aws.Conf{
 			AWSRegion:          awsConf.AWSRegion,
 			AWSAccessKeyID:     string(awsConf.AWSAccessKeyID),
@@ -308,20 +313,21 @@ func (a *Agent) ProvisionEKS(
 		},
 	}
 
-	return a.provision(prov)
+	return a.provision(prov, infra, repo)
 }
 
 // ProvisionGCR spawns a new provisioning pod that creates a GCR instance
 func (a *Agent) ProvisionGCR(
 	projectID uint,
 	gcpConf *integrations.GCPIntegration,
+	repo repository.Repository,
 	infra *models.Infra,
 	operation provisioner.ProvisionerOperation,
 	pgConf *config.DBConf,
 	redisConf *config.RedisConf,
 	provImageTag string,
 ) (*batchv1.Job, error) {
-	id := infra.GetID()
+	id := infra.GetUniqueName()
 	prov := &provisioner.Conf{
 		ID:                  id,
 		Name:                fmt.Sprintf("prov-%s-%s", id, string(operation)),
@@ -330,6 +336,7 @@ func (a *Agent) ProvisionGCR(
 		Redis:               redisConf,
 		Postgres:            pgConf,
 		ProvisionerImageTag: provImageTag,
+		LastApplied:         infra.LastApplied,
 		GCP: &gcp.Conf{
 			GCPRegion:    gcpConf.GCPRegion,
 			GCPProjectID: gcpConf.GCPProjectID,
@@ -337,7 +344,7 @@ func (a *Agent) ProvisionGCR(
 		},
 	}
 
-	return a.provision(prov)
+	return a.provision(prov, infra, repo)
 }
 
 // ProvisionGKE spawns a new provisioning pod that creates a GKE instance
@@ -345,13 +352,14 @@ func (a *Agent) ProvisionGKE(
 	projectID uint,
 	gcpConf *integrations.GCPIntegration,
 	gkeName string,
+	repo repository.Repository,
 	infra *models.Infra,
 	operation provisioner.ProvisionerOperation,
 	pgConf *config.DBConf,
 	redisConf *config.RedisConf,
 	provImageTag string,
 ) (*batchv1.Job, error) {
-	id := infra.GetID()
+	id := infra.GetUniqueName()
 	prov := &provisioner.Conf{
 		ID:                  id,
 		Name:                fmt.Sprintf("prov-%s-%s", id, string(operation)),
@@ -360,6 +368,7 @@ func (a *Agent) ProvisionGKE(
 		Redis:               redisConf,
 		Postgres:            pgConf,
 		ProvisionerImageTag: provImageTag,
+		LastApplied:         infra.LastApplied,
 		GCP: &gcp.Conf{
 			GCPRegion:    gcpConf.GCPRegion,
 			GCPProjectID: gcpConf.GCPProjectID,
@@ -370,7 +379,7 @@ func (a *Agent) ProvisionGKE(
 		},
 	}
 
-	return a.provision(prov)
+	return a.provision(prov, infra, repo)
 }
 
 // ProvisionDOCR spawns a new provisioning pod that creates a DOCR instance
@@ -400,14 +409,15 @@ func (a *Agent) ProvisionDOCR(
 		return nil, err
 	}
 
-	id := infra.GetID()
+	id := infra.GetUniqueName()
 	prov := &provisioner.Conf{
-		ID:        id,
-		Name:      fmt.Sprintf("prov-%s-%s", id, string(operation)),
-		Kind:      provisioner.DOCR,
-		Operation: operation,
-		Redis:     redisConf,
-		Postgres:  pgConf,
+		ID:          id,
+		Name:        fmt.Sprintf("prov-%s-%s", id, string(operation)),
+		Kind:        provisioner.DOCR,
+		Operation:   operation,
+		Redis:       redisConf,
+		Postgres:    pgConf,
+		LastApplied: infra.LastApplied,
 		DO: &do.Conf{
 			DOToken: tok,
 		},
@@ -417,7 +427,7 @@ func (a *Agent) ProvisionDOCR(
 		},
 	}
 
-	return a.provision(prov)
+	return a.provision(prov, infra, repo)
 }
 
 // ProvisionDOKS spawns a new provisioning pod that creates a DOKS instance
@@ -447,14 +457,15 @@ func (a *Agent) ProvisionDOKS(
 		return nil, err
 	}
 
-	id := infra.GetID()
+	id := infra.GetUniqueName()
 	prov := &provisioner.Conf{
-		ID:        id,
-		Name:      fmt.Sprintf("prov-%s-%s", id, string(operation)),
-		Kind:      provisioner.DOKS,
-		Operation: operation,
-		Redis:     redisConf,
-		Postgres:  pgConf,
+		ID:          id,
+		Name:        fmt.Sprintf("prov-%s-%s", id, string(operation)),
+		Kind:        provisioner.DOKS,
+		Operation:   operation,
+		Redis:       redisConf,
+		Postgres:    pgConf,
+		LastApplied: infra.LastApplied,
 		DO: &do.Conf{
 			DOToken: tok,
 		},
@@ -464,32 +475,13 @@ func (a *Agent) ProvisionDOKS(
 		},
 	}
 
-	return a.provision(prov)
-}
-
-// ProvisionTest spawns a new provisioning pod that tests provisioning
-func (a *Agent) ProvisionTest(
-	projectID uint,
-	operation provisioner.ProvisionerOperation,
-	pgConf *config.DBConf,
-	redisConf *config.RedisConf,
-	provImageTag string,
-) (*batchv1.Job, error) {
-	prov := &provisioner.Conf{
-		ID:                  fmt.Sprintf("%s-%d", "testing", projectID),
-		Name:                fmt.Sprintf("prov-%s-%d-%s", "testing", projectID, string(operation)),
-		Operation:           operation,
-		Kind:                provisioner.Test,
-		Redis:               redisConf,
-		Postgres:            pgConf,
-		ProvisionerImageTag: provImageTag,
-	}
-
-	return a.provision(prov)
+	return a.provision(prov, infra, repo)
 }
 
 func (a *Agent) provision(
 	prov *provisioner.Conf,
+	infra *models.Infra,
+	repo repository.Repository,
 ) (*batchv1.Job, error) {
 	prov.Namespace = "default"
 
@@ -499,11 +491,24 @@ func (a *Agent) provision(
 		return nil, err
 	}
 
-	return a.Clientset.BatchV1().Jobs(prov.Namespace).Create(
+	job, err = a.Clientset.BatchV1().Jobs(prov.Namespace).Create(
 		context.TODO(),
 		job,
 		metav1.CreateOptions{},
 	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	infra.LastApplied = prov.LastApplied
+	infra, err = repo.Infra.UpdateInfra(infra)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return job, nil
 }
 
 // CreateImagePullSecrets will create the required image pull secrets and

+ 1 - 1
internal/kubernetes/provisioner/global_stream.go

@@ -111,7 +111,7 @@ func GlobalStreamListener(
 		// parse messages from the global stream
 		for _, msg := range xstreams[0].Messages {
 			// parse the id to identify the infra
-			kind, projID, infraID, err := models.ParseWorkspaceID(fmt.Sprintf("%v", msg.Values["id"]))
+			kind, projID, infraID, err := models.ParseUniqueName(fmt.Sprintf("%v", msg.Values["id"]))
 
 			if fmt.Sprintf("%v", msg.Values["status"]) == "created" {
 				infra, err := repo.Infra.ReadInfra(infraID)

+ 23 - 0
internal/kubernetes/provisioner/input/docr.go

@@ -0,0 +1,23 @@
+package input
+
+import (
+	"encoding/json"
+)
+
+type DOCR struct {
+	DOToken              string `json:"do_token"`
+	DOCRName             string `json:"docr_name"`
+	DOCRSubscriptionTier string `json:"docr_subscription_tier"`
+}
+
+func (docr *DOCR) GetInput() ([]byte, error) {
+	return json.Marshal(docr)
+}
+
+func GetDOCRInput(bytes []byte) (*DOCR, error) {
+	res := &DOCR{}
+
+	err := json.Unmarshal(bytes, res)
+
+	return res, err
+}

+ 23 - 0
internal/kubernetes/provisioner/input/doks.go

@@ -0,0 +1,23 @@
+package input
+
+import (
+	"encoding/json"
+)
+
+type DOKS struct {
+	DORegion    string `json:"do_region"`
+	DOToken     string `json:"do_token"`
+	ClusterName string `json:"cluster_name"`
+}
+
+func (doks *DOKS) GetInput() ([]byte, error) {
+	return json.Marshal(doks)
+}
+
+func GetDOKSInput(bytes []byte) (*DOKS, error) {
+	res := &DOKS{}
+
+	err := json.Unmarshal(bytes, res)
+
+	return res, err
+}

+ 24 - 0
internal/kubernetes/provisioner/input/ecr.go

@@ -0,0 +1,24 @@
+package input
+
+import (
+	"encoding/json"
+)
+
+type ECR struct {
+	AWSRegion    string `json:"aws_region"`
+	AWSAccessKey string `json:"aws_access_key"`
+	AWSSecretKey string `json:"aws_secret_key"`
+	ECRName      string `json:"ecr_name"`
+}
+
+func (ecr *ECR) GetInput() ([]byte, error) {
+	return json.Marshal(ecr)
+}
+
+func GetECRInput(bytes []byte) (*ECR, error) {
+	res := &ECR{}
+
+	err := json.Unmarshal(bytes, res)
+
+	return res, err
+}

+ 24 - 0
internal/kubernetes/provisioner/input/eks.go

@@ -0,0 +1,24 @@
+package input
+
+import (
+	"encoding/json"
+)
+
+type EKS struct {
+	AWSRegion    string `json:"aws_region"`
+	AWSAccessKey string `json:"aws_access_key"`
+	AWSSecretKey string `json:"aws_secret_key"`
+	ClusterName  string `json:"cluster_name"`
+}
+
+func (eks *EKS) GetInput() ([]byte, error) {
+	return json.Marshal(eks)
+}
+
+func GetEKSInput(bytes []byte) (*EKS, error) {
+	res := &EKS{}
+
+	err := json.Unmarshal(bytes, res)
+
+	return res, err
+}

+ 23 - 0
internal/kubernetes/provisioner/input/gcr.go

@@ -0,0 +1,23 @@
+package input
+
+import (
+	"encoding/json"
+)
+
+type GCR struct {
+	GCPCredentials string `json:"gcp_credentials"`
+	GCPRegion      string `json:"gcp_region"`
+	GCPProjectID   string `json:"gcp_project_id"`
+}
+
+func (gcr *GCR) GetInput() ([]byte, error) {
+	return json.Marshal(gcr)
+}
+
+func GetGCRInput(bytes []byte) (*GCR, error) {
+	res := &GCR{}
+
+	err := json.Unmarshal(bytes, res)
+
+	return res, err
+}

+ 24 - 0
internal/kubernetes/provisioner/input/gke.go

@@ -0,0 +1,24 @@
+package input
+
+import (
+	"encoding/json"
+)
+
+type GKE struct {
+	GCPCredentials string `json:"gcp_credentials"`
+	GCPRegion      string `json:"gcp_region"`
+	GCPProjectID   string `json:"gcp_project_id"`
+	ClusterName    string `json:"cluster_name"`
+}
+
+func (gke *GKE) GetInput() ([]byte, error) {
+	return json.Marshal(gke)
+}
+
+func GetGKEInput(bytes []byte) (*GKE, error) {
+	res := &GKE{}
+
+	err := json.Unmarshal(bytes, res)
+
+	return res, err
+}

+ 178 - 14
internal/kubernetes/provisioner/provisioner.go

@@ -13,6 +13,7 @@ import (
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/do"
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/do/docr"
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/do/doks"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/input"
 
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp"
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp/gke"
@@ -25,7 +26,6 @@ type InfraOption string
 
 // The list of infra options
 const (
-	Test InfraOption = "test"
 	ECR  InfraOption = "ecr"
 	EKS  InfraOption = "eks"
 	GCR  InfraOption = "gcr"
@@ -44,6 +44,7 @@ type Conf struct {
 	Postgres            *config.DBConf
 	Operation           ProvisionerOperation
 	ProvisionerImageTag string
+	LastApplied         []byte
 
 	// provider-specific configurations
 
@@ -84,11 +85,7 @@ func (conf *Conf) GetProvisionerJobTemplate() (*batchv1.Job, error) {
 
 	ttl := int32(3600)
 
-	backoffLimit := int32(5)
-
-	if operation == string(Apply) {
-		backoffLimit = int32(1)
-	}
+	backoffLimit := int32(1)
 
 	labels := map[string]string{
 		"app": "provisioner",
@@ -96,29 +93,196 @@ func (conf *Conf) GetProvisionerJobTemplate() (*batchv1.Job, error) {
 
 	args := make([]string, 0)
 
-	if conf.Kind == Test {
-		args = []string{operation, "test", "hello"}
-	} else if conf.Kind == ECR {
+	switch conf.Kind {
+	case ECR:
 		args = []string{operation, "ecr"}
+
+		if len(conf.LastApplied) > 0 {
+			inputConf, err := input.GetECRInput(conf.LastApplied)
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.AWS.AWSAccessKeyID = inputConf.AWSAccessKey
+			conf.AWS.AWSSecretAccessKey = inputConf.AWSSecretKey
+			conf.AWS.AWSRegion = inputConf.AWSRegion
+			conf.ECR.ECRName = inputConf.ECRName
+		} else {
+			inputConf := &input.ECR{
+				AWSRegion:    conf.AWS.AWSRegion,
+				AWSAccessKey: conf.AWS.AWSAccessKeyID,
+				AWSSecretKey: conf.AWS.AWSSecretAccessKey,
+				ECRName:      conf.ECR.ECRName,
+			}
+
+			lastApplied, err := inputConf.GetInput()
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.LastApplied = lastApplied
+		}
+
 		env = conf.AWS.AttachAWSEnv(env)
 		env = conf.ECR.AttachECREnv(env)
-	} else if conf.Kind == EKS {
+	case EKS:
 		args = []string{operation, "eks"}
+
+		if len(conf.LastApplied) > 0 {
+			inputConf, err := input.GetEKSInput(conf.LastApplied)
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.AWS.AWSAccessKeyID = inputConf.AWSAccessKey
+			conf.AWS.AWSSecretAccessKey = inputConf.AWSSecretKey
+			conf.AWS.AWSRegion = inputConf.AWSRegion
+			conf.EKS.ClusterName = inputConf.ClusterName
+		} else {
+			inputConf := &input.EKS{
+				AWSRegion:    conf.AWS.AWSRegion,
+				AWSAccessKey: conf.AWS.AWSAccessKeyID,
+				AWSSecretKey: conf.AWS.AWSSecretAccessKey,
+				ClusterName:  conf.EKS.ClusterName,
+			}
+
+			lastApplied, err := inputConf.GetInput()
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.LastApplied = lastApplied
+		}
+
 		env = conf.AWS.AttachAWSEnv(env)
 		env = conf.EKS.AttachEKSEnv(env)
-	} else if conf.Kind == GCR {
+	case GCR:
 		args = []string{operation, "gcr"}
+
+		if len(conf.LastApplied) > 0 {
+			inputConf, err := input.GetGCRInput(conf.LastApplied)
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.GCP.GCPKeyData = inputConf.GCPCredentials
+			conf.GCP.GCPRegion = inputConf.GCPRegion
+			conf.GCP.GCPProjectID = inputConf.GCPProjectID
+		} else {
+			inputConf := &input.GCR{
+				GCPCredentials: conf.GCP.GCPKeyData,
+				GCPRegion:      conf.GCP.GCPRegion,
+				GCPProjectID:   conf.GCP.GCPProjectID,
+			}
+
+			lastApplied, err := inputConf.GetInput()
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.LastApplied = lastApplied
+		}
+
 		env = conf.GCP.AttachGCPEnv(env)
-	} else if conf.Kind == GKE {
+	case GKE:
 		args = []string{operation, "gke"}
+
+		if len(conf.LastApplied) > 0 {
+			inputConf, err := input.GetGKEInput(conf.LastApplied)
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.GCP.GCPKeyData = inputConf.GCPCredentials
+			conf.GCP.GCPRegion = inputConf.GCPRegion
+			conf.GCP.GCPProjectID = inputConf.GCPProjectID
+			conf.GKE.ClusterName = inputConf.ClusterName
+		} else {
+			inputConf := &input.GKE{
+				GCPCredentials: conf.GCP.GCPKeyData,
+				GCPRegion:      conf.GCP.GCPRegion,
+				GCPProjectID:   conf.GCP.GCPProjectID,
+				ClusterName:    conf.GKE.ClusterName,
+			}
+
+			lastApplied, err := inputConf.GetInput()
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.LastApplied = lastApplied
+		}
+
 		env = conf.GCP.AttachGCPEnv(env)
 		env = conf.GKE.AttachGKEEnv(env)
-	} else if conf.Kind == DOCR {
+	case DOCR:
 		args = []string{operation, "docr"}
+
+		if len(conf.LastApplied) > 0 {
+			inputConf, err := input.GetDOCRInput(conf.LastApplied)
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.DO.DOToken = inputConf.DOToken
+			conf.DOCR.DOCRSubscriptionTier = inputConf.DOCRSubscriptionTier
+			conf.DOCR.DOCRName = inputConf.DOCRName
+		} else {
+			inputConf := &input.DOCR{
+				DOToken:              conf.DO.DOToken,
+				DOCRSubscriptionTier: conf.DOCR.DOCRSubscriptionTier,
+				DOCRName:             conf.DOCR.DOCRName,
+			}
+
+			lastApplied, err := inputConf.GetInput()
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.LastApplied = lastApplied
+		}
+
 		env = conf.DO.AttachDOEnv(env)
 		env = conf.DOCR.AttachDOCREnv(env)
-	} else if conf.Kind == GKE {
+	case DOKS:
 		args = []string{operation, "doks"}
+
+		if len(conf.LastApplied) > 0 {
+			inputConf, err := input.GetDOKSInput(conf.LastApplied)
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.DO.DOToken = inputConf.DOToken
+			conf.DOKS.DORegion = inputConf.DORegion
+			conf.DOKS.DOKSClusterName = inputConf.ClusterName
+		} else {
+			inputConf := &input.DOKS{
+				DOToken:     conf.DO.DOToken,
+				DORegion:    conf.DOKS.DORegion,
+				ClusterName: conf.DOKS.DOKSClusterName,
+			}
+
+			lastApplied, err := inputConf.GetInput()
+
+			if err != nil {
+				return nil, err
+			}
+
+			conf.LastApplied = lastApplied
+		}
+
 		env = conf.DO.AttachDOEnv(env)
 		env = conf.DOKS.AttachDOKSEnv(env)
 	}

+ 10 - 3
internal/models/infra.go

@@ -59,6 +59,13 @@ type Infra struct {
 	// The DO integration that was used to create the infra:
 	// this points to an OAuthIntegrationID
 	DOIntegrationID uint
+
+	// ------------------------------------------------------------------
+	// All fields below this line are encrypted before storage
+	// ------------------------------------------------------------------
+
+	// The last-applied input variables to the provisioner
+	LastApplied []byte
 }
 
 // InfraExternal is an external Infra to be shared over REST
@@ -86,12 +93,12 @@ func (i *Infra) Externalize() *InfraExternal {
 }
 
 // GetID returns the unique id for this infra
-func (i *Infra) GetID() string {
+func (i *Infra) GetUniqueName() string {
 	return fmt.Sprintf("%s-%d-%d-%s", i.Kind, i.ProjectID, i.ID, i.Suffix)
 }
 
-// ParseWorkspaceID returns the (kind, projectID, infraID)
-func ParseWorkspaceID(workspaceID string) (string, uint, uint, error) {
+// ParseUniqueName returns the (kind, projectID, infraID, suffix)
+func ParseUniqueName(workspaceID string) (string, uint, uint, error) {
 	strArr := strings.Split(workspaceID, "-")
 
 	if len(strArr) < 3 {

+ 80 - 3
internal/repository/gorm/infra.go

@@ -1,6 +1,8 @@
 package gorm
 
 import (
+	"fmt"
+
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/repository"
 	"gorm.io/gorm"
@@ -8,17 +10,24 @@ import (
 
 // InfraRepository uses gorm.DB for querying the database
 type InfraRepository struct {
-	db *gorm.DB
+	db  *gorm.DB
+	key *[32]byte
 }
 
 // NewInfraRepository returns a InfraRepository which uses
 // gorm.DB for querying the database
-func NewInfraRepository(db *gorm.DB) repository.InfraRepository {
-	return &InfraRepository{db}
+func NewInfraRepository(db *gorm.DB, key *[32]byte) repository.InfraRepository {
+	return &InfraRepository{db, key}
 }
 
 // CreateInfra creates a new aws infra
 func (repo *InfraRepository) CreateInfra(infra *models.Infra) (*models.Infra, error) {
+	err := repo.EncryptInfraData(infra, repo.key)
+
+	if err != nil {
+		return nil, err
+	}
+
 	project := &models.Project{}
 
 	if err := repo.db.Where("id = ?", infra.ProjectID).First(&project).Error; err != nil {
@@ -35,6 +44,12 @@ func (repo *InfraRepository) CreateInfra(infra *models.Infra) (*models.Infra, er
 		return nil, err
 	}
 
+	err = repo.DecryptInfraData(infra, repo.key)
+
+	if err != nil {
+		return nil, err
+	}
+
 	return infra, nil
 }
 
@@ -46,6 +61,14 @@ func (repo *InfraRepository) ReadInfra(id uint) (*models.Infra, error) {
 		return nil, err
 	}
 
+	fmt.Println("INNFRA LAST APPLIED", string(infra.LastApplied))
+
+	err := repo.DecryptInfraData(infra, repo.key)
+
+	if err != nil {
+		return nil, err
+	}
+
 	return infra, nil
 }
 
@@ -60,6 +83,10 @@ func (repo *InfraRepository) ListInfrasByProjectID(
 		return nil, err
 	}
 
+	for _, infra := range infras {
+		repo.DecryptInfraData(infra, repo.key)
+	}
+
 	return infras, nil
 }
 
@@ -67,9 +94,59 @@ func (repo *InfraRepository) ListInfrasByProjectID(
 func (repo *InfraRepository) UpdateInfra(
 	ai *models.Infra,
 ) (*models.Infra, error) {
+	err := repo.EncryptInfraData(ai, repo.key)
+
+	if err != nil {
+		return nil, err
+	}
+
 	if err := repo.db.Save(ai).Error; err != nil {
 		return nil, err
 	}
 
+	err = repo.DecryptInfraData(ai, repo.key)
+
+	if err != nil {
+		return nil, err
+	}
+
 	return ai, nil
 }
+
+// EncryptInfraData will encrypt the infra data before
+// writing to the DB
+func (repo *InfraRepository) EncryptInfraData(
+	infra *models.Infra,
+	key *[32]byte,
+) error {
+	if len(infra.LastApplied) > 0 {
+		cipherData, err := repository.Encrypt(infra.LastApplied, key)
+
+		if err != nil {
+			return err
+		}
+
+		infra.LastApplied = cipherData
+	}
+
+	return nil
+}
+
+// DecryptInfraData will decrypt the user's infra data before
+// returning it from the DB
+func (repo *InfraRepository) DecryptInfraData(
+	infra *models.Infra,
+	key *[32]byte,
+) error {
+	if len(infra.LastApplied) > 0 {
+		plaintext, err := repository.Decrypt(infra.LastApplied, key)
+
+		if err != nil {
+			return err
+		}
+
+		infra.LastApplied = plaintext
+	}
+
+	return nil
+}

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

@@ -17,7 +17,7 @@ func NewRepository(db *gorm.DB, key *[32]byte) *repository.Repository {
 		Cluster:          NewClusterRepository(db, key),
 		HelmRepo:         NewHelmRepoRepository(db, key),
 		Registry:         NewRegistryRepository(db, key),
-		Infra:         NewInfraRepository(db),
+		Infra:            NewInfraRepository(db, key),
 		KubeIntegration:  NewKubeIntegrationRepository(db, key),
 		BasicIntegration: NewBasicIntegrationRepository(db, key),
 		OIDCIntegration:  NewOIDCIntegrationRepository(db, key),

+ 1 - 1
server/api/oauth_github_handler.go

@@ -183,7 +183,7 @@ func (app *App) updateProjectFromToken(projectID uint, userID uint, tok *oauth2.
 	// create the git repo
 	gr := &models.GitRepo{
 		ProjectID:          projectID,
-		RepoEntity:         *user.Name,
+		RepoEntity:         *user.Login,
 		OAuthIntegrationID: oauthInt.ID,
 	}
 

+ 8 - 35
server/api/provision_handler.go

@@ -15,40 +15,6 @@ import (
 	"github.com/porter-dev/porter/internal/adapter"
 )
 
-// HandleProvisionTest will create a test resource by deploying a provisioner
-// container pod
-func (app *App) HandleProvisionTest(w http.ResponseWriter, r *http.Request) {
-	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
-
-	if err != nil || projID == 0 {
-		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
-		return
-	}
-
-	// create a new agent
-	agent, err := kubernetes.GetAgentInClusterConfig()
-
-	if err != nil {
-		app.handleErrorDataRead(err, w)
-		return
-	}
-
-	_, err = agent.ProvisionTest(
-		uint(projID),
-		provisioner.Apply,
-		&app.DBConf,
-		app.RedisConf,
-		app.ServerConf.ProvisionerImageTag,
-	)
-
-	if err != nil {
-		app.handleErrorInternal(err, w)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
 // HandleProvisionAWSECRInfra provisions a new aws ECR instance for a project
 func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Request) {
 	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
@@ -115,6 +81,7 @@ func (app *App) HandleProvisionAWSECRInfra(w http.ResponseWriter, r *http.Reques
 		uint(projID),
 		awsInt,
 		form.ECRName,
+		*app.Repo,
 		infra,
 		provisioner.Apply,
 		&app.DBConf,
@@ -206,6 +173,7 @@ func (app *App) HandleDestroyAWSECRInfra(w http.ResponseWriter, r *http.Request)
 		infra.ProjectID,
 		awsInt,
 		form.ECRName,
+		*app.Repo,
 		infra,
 		provisioner.Destroy,
 		&app.DBConf,
@@ -289,6 +257,7 @@ func (app *App) HandleProvisionAWSEKSInfra(w http.ResponseWriter, r *http.Reques
 		uint(projID),
 		awsInt,
 		form.EKSName,
+		*app.Repo,
 		infra,
 		provisioner.Apply,
 		&app.DBConf,
@@ -380,6 +349,7 @@ func (app *App) HandleDestroyAWSEKSInfra(w http.ResponseWriter, r *http.Request)
 		infra.ProjectID,
 		awsInt,
 		form.EKSName,
+		*app.Repo,
 		infra,
 		provisioner.Destroy,
 		&app.DBConf,
@@ -462,6 +432,7 @@ func (app *App) HandleProvisionGCPGCRInfra(w http.ResponseWriter, r *http.Reques
 	_, err = agent.ProvisionGCR(
 		uint(projID),
 		gcpInt,
+		*app.Repo,
 		infra,
 		provisioner.Apply,
 		&app.DBConf,
@@ -555,6 +526,7 @@ func (app *App) HandleProvisionGCPGKEInfra(w http.ResponseWriter, r *http.Reques
 		uint(projID),
 		gcpInt,
 		form.GKEName,
+		*app.Repo,
 		infra,
 		provisioner.Apply,
 		&app.DBConf,
@@ -646,6 +618,7 @@ func (app *App) HandleDestroyGCPGKEInfra(w http.ResponseWriter, r *http.Request)
 		infra.ProjectID,
 		gcpInt,
 		form.GKEName,
+		*app.Repo,
 		infra,
 		provisioner.Destroy,
 		&app.DBConf,
@@ -681,7 +654,7 @@ func (app *App) HandleGetProvisioningLogs(w http.ResponseWriter, r *http.Request
 		return
 	}
 
-	streamName := infra.GetID()
+	streamName := infra.GetUniqueName()
 
 	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
 

+ 0 - 10
server/router/router.go

@@ -205,16 +205,6 @@ func New(a *api.App) *chi.Mux {
 		)
 
 		// /api/projects/{project_id}/provision routes
-		r.Method(
-			"POST",
-			"/projects/{project_id}/provision/test",
-			auth.DoesUserHaveProjectAccess(
-				requestlog.NewHandler(a.HandleProvisionTest, l),
-				mw.URLParam,
-				mw.ReadAccess,
-			),
-		)
-
 		r.Method(
 			"POST",
 			"/projects/{project_id}/provision/ecr",