Procházet zdrojové kódy

Merge branch 'use-porter-action' into dev

merge
Alexander Belanger před 4 roky
rodič
revize
c0549ff246

+ 35 - 0
.github/workflows/release.yaml

@@ -347,3 +347,38 @@ jobs:
           asset_path: ./release/static/static_${{steps.tag_name.outputs.tag}}.zip
           asset_name: static_${{steps.tag_name.outputs.tag}}.zip
           asset_content_type: application/zip
+  build-push-docker-cli:
+    name: Build a new porter-cli docker image
+    runs-on: ubuntu-latest
+    needs: release
+    steps:
+      - name: Get tag name
+        id: tag_name
+        run: |
+          tag=${GITHUB_TAG/refs\/tags\//}
+          echo ::set-output name=tag::$tag
+        env:
+          GITHUB_TAG: ${{ github.ref }}
+      - name: Checkout
+        uses: actions/checkout@v2.3.4
+      - name: Configure AWS credentials
+        uses: aws-actions/configure-aws-credentials@v1
+        with:
+          aws-access-key-id: ${{ secrets.ECR_AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.ECR_AWS_SECRET_ACCESS_KEY }}
+          aws-region: us-east-2
+      - name: Login to ECR public
+        id: login-ecr
+        run: |
+          aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/o1j4x7p4
+      - name: Build
+        run: |
+          docker build ./services/porter_cli_container \
+            -t public.ecr.aws/o1j4x7p4/porter-cli:${{steps.tag_name.outputs.tag}} \
+            -t public.ecr.aws/o1j4x7p4/porter-cli:latest \
+            -f ./services/porter_cli_container/Dockerfile \
+            --build-arg VERSION=${{steps.tag_name.outputs.tag}}
+      - name: Push
+        run: |
+          docker push public.ecr.aws/o1j4x7p4/porter-cli:${{steps.tag_name.outputs.tag}}
+          docker push public.ecr.aws/o1j4x7p4/porter-cli:latest

+ 8 - 9
cli/cmd/api/github_action.go

@@ -11,15 +11,14 @@ import (
 // CreateGithubActionRequest represents the accepted fields for creating
 // a Github action
 type CreateGithubActionRequest struct {
-	ReleaseID      uint              `json:"release_id" form:"required"`
-	GitRepo        string            `json:"git_repo" form:"required"`
-	GitBranch      string            `json:"git_branch"`
-	ImageRepoURI   string            `json:"image_repo_uri" form:"required"`
-	DockerfilePath string            `json:"dockerfile_path"`
-	FolderPath     string            `json:"folder_path"`
-	GitRepoID      uint              `json:"git_repo_id" form:"required"`
-	BuildEnv       map[string]string `json:"env"`
-	RegistryID     uint              `json:"registry_id"`
+	ReleaseID      uint   `json:"release_id" form:"required"`
+	GitRepo        string `json:"git_repo" form:"required"`
+	GitBranch      string `json:"git_branch"`
+	ImageRepoURI   string `json:"image_repo_uri" form:"required"`
+	DockerfilePath string `json:"dockerfile_path"`
+	FolderPath     string `json:"folder_path"`
+	GitRepoID      uint   `json:"git_repo_id" form:"required"`
+	RegistryID     uint   `json:"registry_id"`
 }
 
 // CreateGithubAction creates a Github action with basic authentication

+ 6 - 2
cli/cmd/errors.go

@@ -2,12 +2,16 @@ package cmd
 
 import (
 	"context"
+	"errors"
 	"strings"
 
 	"github.com/fatih/color"
 	"github.com/porter-dev/porter/cli/cmd/api"
 )
 
+var ErrNotLoggedIn error = errors.New("You are not logged in.")
+var ErrCannotConnect error = errors.New("Unable to connect to the Porter server.")
+
 func checkLoginAndRun(args []string, runner func(user *api.AuthCheckResponse, client *api.Client, args []string) error) error {
 	client := GetAPIClient(config)
 
@@ -18,12 +22,12 @@ func checkLoginAndRun(args []string, runner func(user *api.AuthCheckResponse, cl
 
 		if strings.Contains(err.Error(), "403") {
 			red.Print("You are not logged in. Log in using \"porter auth login\"\n")
-			return nil
+			return ErrNotLoggedIn
 		} else if strings.Contains(err.Error(), "connection refused") {
 			red.Printf("Unable to connect to the Porter server at %s\n", config.Host)
 			red.Print("To set a different host, run \"porter config set-host [HOST]\"\n")
 			red.Print("To start a local server, run \"porter server start\"\n")
-			return nil
+			return ErrCannotConnect
 		}
 
 		red.Printf("Error: %v\n", err.Error())

+ 1 - 0
cli/cmd/logs.go

@@ -13,6 +13,7 @@ import (
 // without any subcommands
 var logsCmd = &cobra.Command{
 	Use:   "logs [release]",
+	Args:  cobra.ExactArgs(1),
 	Short: "Logs the output from a given application.",
 	Run: func(cmd *cobra.Command, args []string) {
 		err := checkLoginAndRun(args, logs)

+ 26 - 9
cli/cmd/run.go

@@ -25,6 +25,7 @@ import (
 )
 
 var namespace string
+var verbose bool
 
 // runCmd represents the "porter run" base command when called
 // without any subcommands
@@ -60,6 +61,14 @@ func init() {
 		false,
 		"whether to connect to an existing pod",
 	)
+
+	runCmd.PersistentFlags().BoolVarP(
+		&verbose,
+		"verbose",
+		"v",
+		false,
+		"whether to print verbose output",
+	)
 }
 
 func run(_ *api.AuthCheckResponse, client *api.Client, args []string) error {
@@ -326,20 +335,25 @@ func executeRunEphemeral(config *PorterRunSharedConfig, namespace, name, contain
 
 		time.Sleep(2 * time.Second)
 
-		// ugly way to catch no TTY errors, such as when running command "echo \"hello\""
-		if i == 4 && err != nil {
-			color.New(color.FgYellow).Println("Could not open a shell to this container. Container logs:\n")
+	}
+
+	// ugly way to catch no TTY errors, such as when running command "echo \"hello\""
+	if err != nil {
+		color.New(color.FgYellow).Println("Could not open a shell to this container. Container logs:\n")
 
-			var writtenBytes int64
+		var writtenBytes int64
 
-			writtenBytes, err = pipePodLogsToStdout(config, namespace, podName, container, false)
+		writtenBytes, err = pipePodLogsToStdout(config, namespace, podName, container, false)
 
-			if writtenBytes == 0 {
-				color.New(color.FgYellow).Println("Could not get logs. Pod events:\n")
+		if verbose || writtenBytes == 0 {
+			color.New(color.FgYellow).Println("Could not get logs. Pod events:\n")
 
-				err = pipeEventsToStdout(config, namespace, podName, container, false)
-			}
+			err = pipeEventsToStdout(config, namespace, podName, container, false)
 		}
+	} else if verbose {
+		color.New(color.FgYellow).Println("Pod events:\n")
+
+		pipeEventsToStdout(config, namespace, podName, container, false)
 	}
 
 	// delete the ephemeral pod
@@ -370,6 +384,9 @@ func pipePodLogsToStdout(config *PorterRunSharedConfig, namespace, name, contain
 }
 
 func pipeEventsToStdout(config *PorterRunSharedConfig, namespace, name, container string, follow bool) error {
+	// update the config in case the operation has taken longer than token expiry time
+	config.setSharedConfig()
+
 	// creates the clientset
 	resp, err := config.Clientset.CoreV1().Events(namespace).List(
 		context.TODO(),

+ 8 - 0
dashboard/package-lock.json

@@ -6244,6 +6244,14 @@
         "scheduler": "^0.19.1"
       }
     },
+    "react-error-boundary": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.3.tgz",
+      "integrity": "sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA==",
+      "requires": {
+        "@babel/runtime": "^7.12.5"
+      }
+    },
     "react-is": {
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

+ 2 - 1
dashboard/package.json

@@ -45,7 +45,8 @@
     "react-router-dom": "^5.2.0",
     "react-table": "^7.7.0",
     "semver": "^7.3.5",
-    "styled-components": "^5.2.0"
+    "styled-components": "^5.2.0",
+    "react-error-boundary": "^3.1.3"
   },
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",

+ 41 - 3
dashboard/src/App.tsx

@@ -1,14 +1,52 @@
 import React, { Component } from "react";
 import { BrowserRouter } from "react-router-dom";
+import PorterErrorBoundary from "shared/PorterErrorBoundary";
+import styled, { createGlobalStyle } from "styled-components";
 
 import MainWrapper from "./main/MainWrapper";
 
 export default class App extends Component {
   render() {
     return (
-      <BrowserRouter>
-        <MainWrapper />
-      </BrowserRouter>
+      <StyledMain>
+        <GlobalStyle />
+        <PorterErrorBoundary errorBoundaryLocation="globalErrorBoundary">
+          <BrowserRouter>
+            <MainWrapper />
+          </BrowserRouter>
+        </PorterErrorBoundary>
+      </StyledMain>
     );
   }
 }
+
+const GlobalStyle = createGlobalStyle`
+  * {
+    box-sizing: border-box;
+    font-family: 'Work Sans', sans-serif;
+  }
+  
+  body {
+    background: #202227;
+    overscroll-behavior-x: none;
+  }
+
+  a {
+    color: #949eff;
+    text-decoration: none;
+  }
+
+  img {
+    max-width: 100%;
+  }
+`;
+
+const StyledMain = styled.div`
+  height: 100vh;
+  width: 100vw;
+  position: fixed;
+  top: 0;
+  left: 0;
+  background: #202227;
+  color: white;
+`;

+ 117 - 0
dashboard/src/components/UnexpectedErrorPage.tsx

@@ -0,0 +1,117 @@
+import React from "react";
+import styled from "styled-components";
+
+const UnexpectedErrorPage: React.FC = ({ error, resetErrorBoundary }: any) => (
+  <>
+    <StyledPageNotFound>
+      <Mega>
+        Unknwown
+        <Inside>Unknown Error</Inside>
+      </Mega>
+      <Flex>
+        <BackButton width="140px" onClick={() => resetErrorBoundary(error)}>
+          <i className="material-icons">arrow_back</i>
+          Reload page
+        </BackButton>
+        <Splitter>|</Splitter>
+        <Helper>
+          Sorry for the inconvinience! The Porter team has been notified
+        </Helper>
+      </Flex>
+    </StyledPageNotFound>
+  </>
+);
+
+export default UnexpectedErrorPage;
+
+const Splitter = styled.div`
+  margin: 0 20px;
+  font-size: 27px;
+  font-weight: 200;
+  color: #ffffff15;
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const Helper = styled.div`
+  font-size: 15px;
+  max-width: 550px;
+  margin-right: -50px;
+`;
+
+const BackButton = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  cursor: pointer;
+  font-size: 13px;
+  height: 35px;
+  padding: 5px 16px;
+  padding-right: 15px;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  width: ${(props: { width: string }) => props.width};
+  color: white;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: white;
+    font-size: 16px;
+    margin-right: 6px;
+    margin-left: -2px;
+  }
+`;
+
+const StyledPageNotFound = styled.div`
+  font-family: "Work Sans", sans-serif;
+  color: #6f6f6f;
+  font-size: 16px;
+  user-select: none;
+  margin-top: -80px;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+`;
+
+const Mega = styled.div`
+  font-size: 200px;
+  color: #ffffff06;
+  position: relative;
+  font-weight: bold;
+  text-align: center;
+
+  > i {
+    font-size: 23px;
+    margin-right: 12px;
+  }
+`;
+
+const Inside = styled.div`
+  position: absolute;
+  color: #6f6f6f;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 400;
+  font-size: 20px;
+
+  > i {
+    font-size: 23px;
+    margin-right: 12px;
+  }
+`;

+ 1 - 2
dashboard/src/index.html

@@ -59,14 +59,13 @@
               n.parentNode.insertBefore(t, n);
               analytics._loadOptions = e;
             };
-            analytics._writeKey = "ZKKaKBrAw9BGE8aF8XDoupd7Fi6ZyN5b";
+            analytics._writeKey = "J6sN7XaMPOGIkA1ZGYMBU4UX37aPZ1Yb";
             analytics.SNIPPET_VERSION = "4.13.2";
             analytics.load("<%= htmlWebpackPlugin.options.segmentKey %>");
             analytics.page();
           }
       })();
     </script>
-
     <link rel="icon" href="https://i.ibb.co/HnSk02f/ptr.png" />
     <meta
       name="description"

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

@@ -1,6 +1,5 @@
 import React, { Component } from "react";
-import styled, { createGlobalStyle } from "styled-components";
-import { Redirect, Route, Switch } from "react-router-dom";
+import { Route, Redirect, Switch } from "react-router-dom";
 
 import api from "shared/api";
 import { Context } from "shared/Context";
@@ -208,44 +207,12 @@ export default class Main extends Component<PropsType, StateType> {
 
   render() {
     return (
-      <StyledMain>
-        <GlobalStyle />
+      <>
         {this.renderMain()}
         <CurrentError currentError={this.context.currentError} />
-      </StyledMain>
+      </>
     );
   }
 }
 
 Main.contextType = Context;
-
-const GlobalStyle = createGlobalStyle`
-  * {
-    box-sizing: border-box;
-    font-family: 'Work Sans', sans-serif;
-  }
-  
-  body {
-    background: #202227;
-    overscroll-behavior-x: none;
-  }
-
-  a {
-    color: #949eff;
-    text-decoration: none;
-  }
-
-  img {
-    max-width: 100%;
-  }
-`;
-
-const StyledMain = styled.div`
-  height: 100vh;
-  width: 100vw;
-  position: fixed;
-  top: 0;
-  left: 0;
-  background: #202227;
-  color: white;
-`;

+ 2 - 4
dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx

@@ -73,7 +73,7 @@ class LaunchFlow extends Component<PropsType, StateType> {
     selectedRegistry: null as any,
   };
 
-  createGHAction = (chartName: string, chartNamespace: string, env?: any) => {
+  createGHAction = (chartName: string, chartNamespace: string) => {
     let { currentProject, currentCluster, setCurrentError } = this.context;
     let {
       actionConfig,
@@ -100,7 +100,6 @@ class LaunchFlow extends Component<PropsType, StateType> {
           folder_path: folderPath,
           image_repo_uri: imageRepoUri,
           git_repo_id: actionConfig.git_repo_id,
-          env: env,
         },
         {
           project_id: currentProject.id,
@@ -314,8 +313,7 @@ class LaunchFlow extends Component<PropsType, StateType> {
       )
       .then((res: any) => {
         if (sourceType === "repo") {
-          let env = rawValues["container.env.normal"];
-          this.createGHAction(name, selectedNamespace, env);
+          this.createGHAction(name, selectedNamespace);
         }
         // this.props.setCurrentView('cluster-dashboard');
         this.setState({ saveValuesStatus: "successful" }, () => {

+ 39 - 0
dashboard/src/shared/PorterErrorBoundary.tsx

@@ -0,0 +1,39 @@
+import UnexpectedErrorPage from "components/UnexpectedErrorPage";
+import React from "react";
+import { ErrorBoundary } from "react-error-boundary";
+
+export type PorterErrorBoundaryProps<OnResetProps = {}> = {
+  errorBoundaryLocation: string;
+  onReset?: (props: OnResetProps) => unknown;
+};
+
+const PorterErrorBoundary: React.FC<PorterErrorBoundaryProps> = ({
+  errorBoundaryLocation,
+  onReset,
+  children,
+}) => {
+  const handleError = (error: Error, info: { componentStack: string }) => {
+    window?.analytics?.track("React Error", {
+      location: errorBoundaryLocation,
+      error: error.message,
+      componentStack: info?.componentStack,
+      url: window.location.toString(),
+    });
+  };
+
+  const handleOnReset = (props: unknown) => {
+    typeof onReset === "function" ? onReset(props) : window.location.reload();
+  };
+
+  return (
+    <ErrorBoundary
+      onError={handleError}
+      FallbackComponent={UnexpectedErrorPage}
+      onReset={handleOnReset}
+    >
+      {children}
+    </ErrorBoundary>
+  );
+};
+
+export default PorterErrorBoundary;

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

@@ -122,7 +122,6 @@ const createGHAction = baseApi<
     dockerfile_path: string;
     folder_path: string;
     git_repo_id: number;
-    env: any;
   },
   {
     project_id: number;

+ 10 - 10
internal/forms/git_action.go

@@ -7,19 +7,18 @@ import (
 // CreateGitAction represents the accepted values for creating a
 // github action integration
 type CreateGitAction struct {
-	ReleaseID      uint              `json:"release_id" form:"required"`
-	GitRepo        string            `json:"git_repo" form:"required"`
-	GitBranch      string            `json:"git_branch"`
-	ImageRepoURI   string            `json:"image_repo_uri" form:"required"`
-	DockerfilePath string            `json:"dockerfile_path"`
-	FolderPath     string            `json:"folder_path"`
-	GitRepoID      uint              `json:"git_repo_id" form:"required"`
-	BuildEnv       map[string]string `json:"env"`
-	RegistryID     uint              `json:"registry_id"`
+	ReleaseID      uint   `json:"release_id" form:"required"`
+	GitRepo        string `json:"git_repo" form:"required"`
+	GitBranch      string `json:"git_branch"`
+	ImageRepoURI   string `json:"image_repo_uri" form:"required"`
+	DockerfilePath string `json:"dockerfile_path"`
+	FolderPath     string `json:"folder_path"`
+	GitRepoID      uint   `json:"git_repo_id" form:"required"`
+	RegistryID     uint   `json:"registry_id"`
 }
 
 // ToGitActionConfig converts the form to a gorm git action config model
-func (ca *CreateGitAction) ToGitActionConfig() (*models.GitActionConfig, error) {
+func (ca *CreateGitAction) ToGitActionConfig(version string) (*models.GitActionConfig, error) {
 	return &models.GitActionConfig{
 		ReleaseID:            ca.ReleaseID,
 		GitRepo:              ca.GitRepo,
@@ -29,6 +28,7 @@ func (ca *CreateGitAction) ToGitActionConfig() (*models.GitActionConfig, error)
 		FolderPath:           ca.FolderPath,
 		GithubInstallationID: ca.GitRepoID,
 		IsInstallation:       true,
+		Version:              version,
 	}, nil
 }
 

+ 10 - 26
internal/integrations/ci/actions/actions.go

@@ -32,12 +32,11 @@ type GithubActions struct {
 	GithubAppSecretPath  string
 	GithubInstallationID uint
 
-	WebhookToken string
-	PorterToken  string
-	BuildEnv     map[string]string
-	ProjectID    uint
-	ClusterID    uint
-	ReleaseName  string
+	PorterToken string
+	BuildEnv    map[string]string
+	ProjectID   uint
+	ClusterID   uint
+	ReleaseName string
 
 	GitBranch      string
 	DockerFilePath string
@@ -45,6 +44,7 @@ type GithubActions struct {
 	ImageRepoURL   string
 
 	defaultBranch string
+	Version       string
 }
 
 func (g *GithubActions) Setup() (string, error) {
@@ -67,24 +67,8 @@ func (g *GithubActions) Setup() (string, error) {
 
 	g.defaultBranch = repo.GetDefaultBranch()
 
-	// create a new secret with a webhook token
-	err = g.createGithubSecret(client, g.getWebhookSecretName(), g.WebhookToken)
-
-	if err != nil {
-		return "", err
-	}
-
-	// create new secrets porter token, project id, and cluster id
-	err = g.createGithubSecret(client, g.getPorterTokenSecretName(), g.PorterToken)
-
-	if err != nil {
-		return "", err
-	}
-
-	// create a new secret with the build variables
-	err = g.createEnvSecret(client)
-
-	if err != nil {
+	// create porter token secret
+	if err := g.createGithubSecret(client, g.getPorterTokenSecretName(), g.PorterToken); err != nil {
 		return "", err
 	}
 
@@ -140,6 +124,7 @@ type GithubActionYAMLStep struct {
 	Timeout uint64            `yaml:"timeout-minutes,omitempty"`
 	Uses    string            `yaml:"uses,omitempty"`
 	Run     string            `yaml:"run,omitempty"`
+	With    map[string]string `yaml:"with,omitempty"`
 	Env     map[string]string `yaml:"env,omitempty"`
 }
 
@@ -167,8 +152,7 @@ type GithubActionYAML struct {
 func (g *GithubActions) GetGithubActionYAML() ([]byte, error) {
 	gaSteps := []GithubActionYAMLStep{
 		getCheckoutCodeStep(),
-		getDownloadPorterStep(),
-		getConfigurePorterStep(g.ServerURL, g.getPorterTokenSecretName(), g.ProjectID, g.ClusterID, g.ReleaseName),
+		getUpdateAppStep(g.ServerURL, g.getPorterTokenSecretName(), g.ProjectID, g.ClusterID, g.ReleaseName, g.Version),
 	}
 
 	branch := g.GitBranch

+ 12 - 29
internal/integrations/ci/actions/steps.go

@@ -4,6 +4,8 @@ import (
 	"fmt"
 )
 
+const updateAppActionName = "porter-dev/porter-update-action"
+
 func getCheckoutCodeStep() GithubActionYAMLStep {
 	return GithubActionYAMLStep{
 		Name: "Checkout code",
@@ -11,36 +13,17 @@ func getCheckoutCodeStep() GithubActionYAMLStep {
 	}
 }
 
-const download string = `name=$(curl -s https://api.github.com/repos/porter-dev/porter/releases/latest | grep "browser_download_url.*/porter_.*_Linux_x86_64\.zip" | cut -d ":" -f 2,3 | tr -d \")
-name=$(basename $name)
-curl -L https://github.com/porter-dev/porter/releases/latest/download/$name --output $name
-unzip -a $name
-rm $name
-chmod +x ./porter
-sudo mv ./porter /usr/local/bin/porter
-`
-
-func getDownloadPorterStep() GithubActionYAMLStep {
+func getUpdateAppStep(serverURL, porterTokenSecretName string, projectID uint, clusterID uint, appName string, actionVersion string) GithubActionYAMLStep {
 	return GithubActionYAMLStep{
-		Name: "Download Porter",
-		ID:   "download_porter",
-		Run:  download,
-	}
-}
-
-const configure string = `porter update --app %s`
-
-func getConfigurePorterStep(serverURL, porterTokenSecretName string, projectID uint, clusterID uint, appName string) GithubActionYAMLStep {
-	return GithubActionYAMLStep{
-		Name:    "Update Porter App",
-		ID:      "update_porter",
-		Run:     fmt.Sprintf(configure, appName),
-		Timeout: 20,
-		Env: map[string]string{
-			"PORTER_TOKEN":   fmt.Sprintf("${{ secrets.%s }}", porterTokenSecretName),
-			"PORTER_HOST":    serverURL,
-			"PORTER_PROJECT": fmt.Sprintf("%d", projectID),
-			"PORTER_CLUSTER": fmt.Sprintf("%d", clusterID),
+		Name: "Update Porter App",
+		Uses: fmt.Sprintf("%s@%s", updateAppActionName, actionVersion),
+		With: map[string]string{
+			"app":     appName,
+			"cluster": fmt.Sprintf("%d", clusterID),
+			"host":    serverURL,
+			"project": fmt.Sprintf("%d", projectID),
+			"token":   fmt.Sprintf("${{ secrets.%s }}", porterTokenSecretName),
 		},
+		Timeout: 20,
 	}
 }

+ 2 - 0
internal/models/gitrepo.go

@@ -75,6 +75,8 @@ type GitActionConfig struct {
 
 	// Determines on how authentication is performed on this action
 	IsInstallation bool `json:"is_installation"`
+
+	Version string `json:"version" gorm:"default:v0.0.1"`
 }
 
 // GitActionConfigExternal is an external GitActionConfig to be shared over REST

+ 0 - 2
server/api/deploy_handler.go

@@ -167,7 +167,6 @@ func (app *App) HandleDeployTemplate(w http.ResponseWriter, r *http.Request) {
 			ImageRepoURI:   form.GithubActionConfig.ImageRepoURI,
 			DockerfilePath: form.GithubActionConfig.DockerfilePath,
 			GitRepoID:      form.GithubActionConfig.GitRepoID,
-			BuildEnv:       form.GithubActionConfig.BuildEnv,
 			RegistryID:     form.GithubActionConfig.RegistryID,
 		}
 
@@ -381,7 +380,6 @@ func (app *App) HandleUninstallTemplate(w http.ResponseWriter, r *http.Request)
 					GitRepoOwner:           repoSplit[0],
 					Repo:                   *app.Repo,
 					GithubConf:             app.GithubProjectConf,
-					WebhookToken:           release.WebhookToken,
 					ProjectID:              uint(projID),
 					ReleaseName:            name,
 					GitBranch:              gitAction.GitBranch,

+ 6 - 3
server/api/git_action_handler.go

@@ -16,6 +16,10 @@ import (
 	"github.com/porter-dev/porter/internal/registry"
 )
 
+const (
+	updateAppActionVersion = "v0.1.0"
+)
+
 // HandleCreateGitAction creates a new Github action in a repository for a given
 // release
 func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
@@ -108,7 +112,7 @@ func (app *App) createGitActionFromForm(
 	}
 
 	// convert the form to a git action config
-	gitAction, err := form.ToGitActionConfig()
+	gitAction, err := form.ToGitActionConfig(updateAppActionVersion)
 
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
@@ -165,7 +169,6 @@ func (app *App) createGitActionFromForm(
 		GitRepoOwner:           repoSplit[0],
 		Repo:                   *app.Repo,
 		GithubConf:             app.GithubProjectConf,
-		WebhookToken:           release.WebhookToken,
 		ProjectID:              uint(projID),
 		ReleaseName:            name,
 		GitBranch:              gitAction.GitBranch,
@@ -173,8 +176,8 @@ func (app *App) createGitActionFromForm(
 		FolderPath:             gitAction.FolderPath,
 		ImageRepoURL:           gitAction.ImageRepoURI,
 		PorterToken:            encoded,
-		BuildEnv:               form.BuildEnv,
 		ClusterID:              release.ClusterID,
+		Version:                gitAction.Version,
 	}
 
 	_, err = gaRunner.Setup()

+ 38 - 14
server/api/release_handler.go

@@ -40,6 +40,10 @@ const (
 	ErrReleaseDeploy
 )
 
+var (
+	createEnvSecretConstraint, _ = semver.NewConstraint(" < 0.1.0")
+)
+
 // HandleListReleases retrieves a list of releases for a cluster
 // with various filter options
 func (app *App) HandleListReleases(w http.ResponseWriter, r *http.Request) {
@@ -777,6 +781,8 @@ func (app *App) HandleGetReleaseToken(w http.ResponseWriter, r *http.Request) {
 			Code:   ErrReleaseReadData,
 			Errors: []string{"release not found"},
 		}, w)
+
+		return
 	}
 
 	release, err := app.Repo.Release.ReadRelease(uint(clusterID), name, namespace)
@@ -786,6 +792,8 @@ func (app *App) HandleGetReleaseToken(w http.ResponseWriter, r *http.Request) {
 			Code:   ErrReleaseReadData,
 			Errors: []string{"release not found"},
 		}, w)
+
+		return
 	}
 
 	releaseExt := release.Externalize()
@@ -807,6 +815,8 @@ func (app *App) HandleCreateWebhookToken(w http.ResponseWriter, r *http.Request)
 			Code:   ErrReleaseReadData,
 			Errors: []string{"release not found"},
 		}, w)
+
+		return
 	}
 
 	// read the release from the target cluster
@@ -1056,6 +1066,8 @@ func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
 				Code:   ErrReleaseReadData,
 				Errors: []string{"release not found"},
 			}, w)
+
+			return
 		}
 
 		release, err := app.Repo.Release.ReadRelease(uint(clusterID), name, rel.Namespace)
@@ -1109,7 +1121,6 @@ func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
 					GitRepoOwner:           repoSplit[0],
 					Repo:                   *app.Repo,
 					GithubConf:             app.GithubProjectConf,
-					WebhookToken:           release.WebhookToken,
 					ProjectID:              uint(projID),
 					ReleaseName:            name,
 					GitBranch:              gitAction.GitBranch,
@@ -1118,15 +1129,21 @@ func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
 					ImageRepoURL:           gitAction.ImageRepoURI,
 					BuildEnv:               cEnv.Container.Env.Normal,
 					ClusterID:              release.ClusterID,
+					Version:                gitAction.Version,
 				}
 
-				err = gaRunner.CreateEnvSecret()
-
+				actionVersion, err := semver.NewVersion(gaRunner.Version)
 				if err != nil {
-					app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-						Code:   ErrReleaseReadData,
-						Errors: []string{"could not update github secret"},
-					}, w)
+					app.handleErrorInternal(err, w)
+				}
+
+				if createEnvSecretConstraint.Check(actionVersion) {
+					if err := gaRunner.CreateEnvSecret(); err != nil {
+						app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+							Code:   ErrReleaseReadData,
+							Errors: []string{"could not update github secret"},
+						}, w)
+					}
 				}
 			}
 		}
@@ -1458,6 +1475,8 @@ func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
 				Code:   ErrReleaseReadData,
 				Errors: []string{"release not found"},
 			}, w)
+
+			return
 		}
 
 		release, err := app.Repo.Release.ReadRelease(uint(clusterID), name, rel.Namespace)
@@ -1526,7 +1545,6 @@ func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
 					GitRepoOwner:           repoSplit[0],
 					Repo:                   *app.Repo,
 					GithubConf:             app.GithubProjectConf,
-					WebhookToken:           release.WebhookToken,
 					ProjectID:              uint(projID),
 					ReleaseName:            name,
 					GitBranch:              gitAction.GitBranch,
@@ -1535,15 +1553,21 @@ func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
 					ImageRepoURL:           gitAction.ImageRepoURI,
 					BuildEnv:               cEnv.Container.Env.Normal,
 					ClusterID:              release.ClusterID,
+					Version:                gitAction.Version,
 				}
 
-				err = gaRunner.CreateEnvSecret()
-
+				actionVersion, err := semver.NewVersion(gaRunner.Version)
 				if err != nil {
-					app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
-						Code:   ErrReleaseReadData,
-						Errors: []string{"could not update github secret"},
-					}, w)
+					app.handleErrorInternal(err, w)
+				}
+
+				if createEnvSecretConstraint.Check(actionVersion) {
+					if err := gaRunner.CreateEnvSecret(); err != nil {
+						app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+							Code:   ErrReleaseReadData,
+							Errors: []string{"could not update github secret"},
+						}, w)
+					}
 				}
 			}
 		}

+ 21 - 3
server/middleware/auth.go

@@ -210,7 +210,13 @@ func (auth *Auth) DoesUserHaveProjectAccess(
 			}
 
 			sessionUserID, ok := session.Values["user_id"]
-			userID = sessionUserID.(uint)
+
+			if !ok {
+				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+				return
+			}
+
+			userID, ok = sessionUserID.(uint)
 
 			if !ok {
 				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
@@ -440,7 +446,13 @@ func (auth *Auth) DoesUserHaveGitInstallationAccess(
 			}
 
 			sessionUserID, ok := session.Values["user_id"]
-			userID = sessionUserID.(uint)
+
+			if !ok {
+				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+				return
+			}
+
+			userID, ok = sessionUserID.(uint)
 
 			if !ok {
 				http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
@@ -741,7 +753,13 @@ func (auth *Auth) DoesUserHaveDOIntegrationAccess(
 func (auth *Auth) doesSessionMatchID(r *http.Request, id uint) bool {
 	session, _ := auth.store.Get(r, auth.cookieName)
 
-	if sessID, ok := session.Values["user_id"].(uint); !ok || sessID != id {
+	userID, ok := session.Values["user_id"]
+
+	if !ok {
+		return false
+	}
+
+	if sessID, ok := userID.(uint); !ok || sessID != id {
 		return false
 	}
 

+ 11 - 0
services/porter_cli_container/Dockerfile

@@ -0,0 +1,11 @@
+FROM ubuntu:latest
+
+COPY get-porter-cli.sh /scratch/
+
+RUN apt-get update && apt-get install -y curl unzip
+
+ARG VERSION
+
+RUN /scratch/get-porter-cli.sh
+
+ENTRYPOINT ["porter"]

+ 15 - 0
services/porter_cli_container/get-porter-cli.sh

@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+if [[ -z $VERSION ]]; then
+  name=$(curl -s https://api.github.com/repos/porter-dev/porter/releases/latest | grep "browser_download_url.*/porter_.*_Linux_x86_64\.zip" | cut -d ":" -f 2,3 | tr -d \")
+  name=$(basename "$name")
+  curl -L https://github.com/porter-dev/porter/releases/latest/download/"$name" --output "$name"
+else
+  name=porter-$VERSION.zip
+  curl -L https://github.com/porter-dev/porter/releases/download/"$VERSION"/porter_"$VERSION"_Linux_x86_64.zip --output "$name"
+fi
+
+unzip -a "$name"
+rm "$name"
+chmod +x ./porter
+mv ./porter /usr/local/bin/