Alexander Belanger 3 лет назад
Родитель
Сommit
c804706876
32 измененных файлов с 118 добавлено и 131 удалено
  1. 4 4
      api/client/api.go
  2. 1 1
      api/server/handlers/cluster/install_agent.go
  3. 0 2
      api/server/handlers/cluster/notify_new_incident.go
  4. 1 1
      api/server/handlers/cluster/upgrade_agent.go
  5. 1 38
      api/server/handlers/gitinstallation/webhook.go
  6. 0 5
      api/server/handlers/infra/stream_logs.go
  7. 1 1
      api/server/handlers/namespace/create_env_group.go
  8. 1 1
      api/server/handlers/release/create.go
  9. 1 1
      api/server/handlers/release/create_addon.go
  10. 1 1
      api/server/handlers/release/update_image_batch.go
  11. 2 1
      api/server/handlers/release/upgrade.go
  12. 1 1
      api/server/handlers/release/upgrade_webhook.go
  13. 3 2
      api/server/handlers/stack/helpers.go
  14. 1 1
      api/server/handlers/v1/env_group/create.go
  15. 1 1
      api/server/handlers/v1/release/upgrade.go
  16. 4 0
      api/server/shared/config/env/envconfs.go
  17. 11 11
      cli/cmd/config.go
  18. 3 3
      cli/cmd/config/config.go
  19. 2 1
      cli/cmd/connect/ecr.go
  20. 7 7
      cli/cmd/connect/kubeconfig.go
  21. 3 2
      cli/cmd/errors.go
  22. 1 1
      cli/cmd/list.go
  23. 28 7
      cli/cmd/run.go
  24. 1 1
      cli/cmd/utils/close.go
  25. 8 3
      dashboard/src/components/Banner.tsx
  26. 1 2
      dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx
  27. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx
  28. 14 24
      dashboard/src/main/home/dashboard/Dashboard.tsx
  29. 8 2
      internal/helm/agent.go
  30. 2 1
      internal/helm/postrenderer.go
  31. 2 2
      internal/templater/helm/values/writer.go
  32. 3 2
      provisioner/client/client.go

+ 4 - 4
api/client/api.go

@@ -150,9 +150,9 @@ func (c *Client) postRequest(relPath string, data interface{}, response interfac
 
 		if i != int(retryCount)-1 {
 			if httpErr != nil {
-				fmt.Printf("Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code)
+				fmt.Fprintf(os.Stderr, "Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code)
 			} else {
-				fmt.Printf("Error: %v, retrying request...\n", err)
+				fmt.Fprintf(os.Stderr, "Error: %v, retrying request...\n", err)
 			}
 		}
 	}
@@ -205,9 +205,9 @@ func (c *Client) patchRequest(relPath string, data interface{}, response interfa
 
 		if i != int(retryCount)-1 {
 			if httpErr != nil {
-				fmt.Printf("Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code)
+				fmt.Fprintf(os.Stderr, "Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code)
 			} else {
-				fmt.Printf("Error: %v, retrying request...\n", err)
+				fmt.Fprintf(os.Stderr, "Error: %v, retrying request...\n", err)
 			}
 		}
 	}

+ 1 - 1
api/server/handlers/cluster/install_agent.go

@@ -144,7 +144,7 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		Values:    porterAgentValues,
 	}
 
-	_, err = helmAgent.InstallChart(conf, c.Config().DOConf)
+	_, err = helmAgent.InstallChart(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 0 - 2
api/server/handlers/cluster/notify_new_incident.go

@@ -118,8 +118,6 @@ func (c *NotifyNewIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 			)
 		}
 
-		fmt.Println("NOTIFYING NEW:", request.ReleaseName, request.InvolvedObjectKind, request.InvolvedObjectName)
-
 		err := multi.NotifyNew(request, url)
 
 		if err != nil {

+ 1 - 1
api/server/handlers/cluster/upgrade_agent.go

@@ -66,7 +66,7 @@ func (c *UpgradeAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		Cluster:    cluster,
 		Repo:       c.Repo(),
 		Registries: []*models.Registry{},
-	}, c.Config().DOConf)
+	}, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 1 - 38
api/server/handlers/gitinstallation/webhook.go

@@ -1,12 +1,7 @@
 package gitinstallation
 
 import (
-	"crypto/hmac"
-	"crypto/sha256"
-	"encoding/hex"
-	"io/ioutil"
 	"net/http"
-	"strings"
 
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/authz"
@@ -35,21 +30,13 @@ func NewGithubAppWebhookHandler(
 }
 
 func (c *GithubAppWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	payload, err := ioutil.ReadAll(r.Body)
+	payload, err := github.ValidatePayload(r, []byte(c.Config().GithubAppConf.WebhookSecret))
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	// verify webhook secret
-	signature := r.Header.Get("X-Hub-Signature-256")
-
-	if !verifySignature([]byte(c.Config().GithubAppConf.WebhookSecret), signature, payload) {
-		c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
-		return
-	}
-
 	event, err := github.ParseWebHook(github.WebHookType(r), payload)
 
 	if err != nil {
@@ -89,27 +76,3 @@ func (c *GithubAppWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		}
 	}
 }
-
-// verifySignature verifies a signature based on hmac protocal
-// https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks
-func verifySignature(secret []byte, signature string, body []byte) bool {
-	if len(signature) != 71 || !strings.HasPrefix(signature, "sha256=") {
-		return false
-	}
-
-	actual := make([]byte, 32)
-	_, err := hex.Decode(actual, []byte(signature[7:]))
-
-	if err != nil {
-		return false
-	}
-
-	computed := hmac.New(sha256.New, secret)
-	_, err = computed.Write(body)
-
-	if err != nil {
-		return false
-	}
-
-	return hmac.Equal(computed.Sum(nil), actual)
-}

+ 0 - 5
api/server/handlers/infra/stream_logs.go

@@ -3,7 +3,6 @@ package infra
 import (
 	"context"
 	"errors"
-	"fmt"
 	"io"
 	"net/http"
 	"sync"
@@ -68,7 +67,6 @@ func (c *InfraStreamLogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 		for {
 			if _, _, err := safeRW.ReadMessage(); err != nil {
 				errorchan <- nil
-				fmt.Println("closing websocket goroutine")
 				return
 			}
 		}
@@ -87,8 +85,6 @@ func (c *InfraStreamLogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 					errorchan <- err
 				}
 
-				fmt.Println("closing grpc goroutine")
-
 				return
 			}
 
@@ -96,7 +92,6 @@ func (c *InfraStreamLogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 			if err != nil {
 				errorchan <- nil
-				fmt.Println("closing grpc goroutine")
 				return
 			}
 		}

+ 1 - 1
api/server/handlers/namespace/create_env_group.go

@@ -191,7 +191,7 @@ func rolloutApplications(
 				Values:     newConfig,
 			}
 
-			_, err = helmAgent.UpgradeReleaseByValues(conf, config.DOConf)
+			_, err = helmAgent.UpgradeReleaseByValues(conf, config.DOConf, config.ServerConf.DisablePullSecretsInjection)
 
 			if err != nil {
 				mu.Lock()

+ 1 - 1
api/server/handlers/release/create.go

@@ -104,7 +104,7 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		Registries: registries,
 	}
 
-	helmRelease, err := helmAgent.InstallChart(conf, c.Config().DOConf)
+	helmRelease, err := helmAgent.InstallChart(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 1 - 1
api/server/handlers/release/create_addon.go

@@ -94,7 +94,7 @@ func (c *CreateAddonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		Registries: registries,
 	}
 
-	helmRelease, err := helmAgent.InstallChart(conf, c.Config().DOConf)
+	helmRelease, err := helmAgent.InstallChart(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 1 - 1
api/server/handlers/release/update_image_batch.go

@@ -108,7 +108,7 @@ func (c *UpdateImageBatchHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 					Values:     rel.Config,
 				}
 
-				_, err = helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf)
+				_, err = helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 				if err != nil {
 					// if this is a release not found error, just return - the release has likely been deleted from the underlying

+ 2 - 1
api/server/handlers/release/upgrade.go

@@ -160,7 +160,8 @@ func (c *UpgradeReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 		}
 	}
 
-	newHelmRelease, upgradeErr := helmAgent.UpgradeRelease(conf, request.Values, c.Config().DOConf)
+	newHelmRelease, upgradeErr := helmAgent.UpgradeRelease(conf, request.Values, c.Config().DOConf,
+		c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if upgradeErr == nil && newHelmRelease != nil {
 		helmRelease = newHelmRelease

+ 1 - 1
api/server/handlers/release/upgrade_webhook.go

@@ -174,7 +174,7 @@ func (c *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		),
 	}
 
-	rel, err = helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf)
+	rel, err = helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if err != nil {
 		notifyOpts.Status = notifier.StatusHelmFailed

+ 3 - 2
api/server/handlers/stack/helpers.go

@@ -54,7 +54,7 @@ func applyAppResource(opts *applyAppResourceOpts) (*release.Release, error) {
 		"revision": opts.stackRevision,
 	}
 
-	return opts.helmAgent.InstallChart(conf, opts.config.DOConf)
+	return opts.helmAgent.InstallChart(conf, opts.config.DOConf, opts.config.ServerConf.DisablePullSecretsInjection)
 }
 
 type rollbackAppResourceOpts struct {
@@ -106,7 +106,8 @@ func updateAppResourceTag(opts *updateAppResourceTagOpts) error {
 		StackRevision: opts.stackRevision,
 	}
 
-	_, err = opts.helmAgent.UpgradeReleaseByValues(conf, opts.config.DOConf)
+	_, err = opts.helmAgent.UpgradeReleaseByValues(conf, opts.config.DOConf,
+		opts.config.ServerConf.DisablePullSecretsInjection)
 
 	return err
 }

+ 1 - 1
api/server/handlers/v1/env_group/create.go

@@ -207,7 +207,7 @@ func rolloutApplications(
 				Values:     newConfig,
 			}
 
-			_, err = helmAgent.UpgradeReleaseByValues(conf, config.DOConf)
+			_, err = helmAgent.UpgradeReleaseByValues(conf, config.DOConf, config.ServerConf.DisablePullSecretsInjection)
 
 			if err != nil {
 				mu.Lock()

+ 1 - 1
api/server/handlers/v1/release/upgrade.go

@@ -144,7 +144,7 @@ func (c *UpgradeReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 		}
 	}
 
-	newHelmRelease, upgradeErr := helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf)
+	newHelmRelease, upgradeErr := helmAgent.UpgradeReleaseByValues(conf, c.Config().DOConf, c.Config().ServerConf.DisablePullSecretsInjection)
 
 	if upgradeErr == nil && newHelmRelease != nil {
 		helmRelease = newHelmRelease

+ 4 - 0
api/server/shared/config/env/envconfs.go

@@ -106,6 +106,10 @@ type ServerConf struct {
 
 	// Enable gitlab integration
 	EnableGitlab bool `env:"ENABLE_GITLAB,default=false"`
+
+	// DisableRegistrySecretsInjection is used to denote if Porter should not inject
+	// imagePullSecrets into a kubernetes deployment (Porter application)
+	DisablePullSecretsInjection bool `env:"DISABLE_PULL_SECRETS_INJECTION,default=false"`
 }
 
 // DBConf is the database configuration: if generated from environment variables,

+ 11 - 11
cli/cmd/config.go

@@ -26,7 +26,7 @@ var configCmd = &cobra.Command{
 	Short: "Commands that control local configuration settings",
 	Run: func(cmd *cobra.Command, args []string) {
 		if err := printConfig(); err != nil {
-			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 			os.Exit(1)
 		}
 	},
@@ -47,14 +47,14 @@ var configSetProjectCmd = &cobra.Command{
 			projID, err := strconv.ParseUint(args[0], 10, 64)
 
 			if err != nil {
-				color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 				os.Exit(1)
 			}
 
 			err = cliConf.SetProject(uint(projID))
 
 			if err != nil {
-				color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 				os.Exit(1)
 			}
 		}
@@ -76,14 +76,14 @@ var configSetClusterCmd = &cobra.Command{
 			clusterID, err := strconv.ParseUint(args[0], 10, 64)
 
 			if err != nil {
-				color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 				os.Exit(1)
 			}
 
 			err = cliConf.SetCluster(uint(clusterID))
 
 			if err != nil {
-				color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 				os.Exit(1)
 			}
 		}
@@ -105,14 +105,14 @@ var configSetRegistryCmd = &cobra.Command{
 			registryID, err := strconv.ParseUint(args[0], 10, 64)
 
 			if err != nil {
-				color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 				os.Exit(1)
 			}
 
 			err = cliConf.SetRegistry(uint(registryID))
 
 			if err != nil {
-				color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 				os.Exit(1)
 			}
 		}
@@ -127,14 +127,14 @@ var configSetHelmRepoCmd = &cobra.Command{
 		hrID, err := strconv.ParseUint(args[0], 10, 64)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 			os.Exit(1)
 		}
 
 		err = cliConf.SetHelmRepo(uint(hrID))
 
 		if err != nil {
-			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 			os.Exit(1)
 		}
 	},
@@ -148,7 +148,7 @@ var configSetHostCmd = &cobra.Command{
 		err := cliConf.SetHost(args[0])
 
 		if err != nil {
-			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 			os.Exit(1)
 		}
 	},
@@ -162,7 +162,7 @@ var configSetKubeconfigCmd = &cobra.Command{
 		err := cliConf.SetKubeconfig(args[0])
 
 		if err != nil {
-			color.New(color.FgRed).Printf("An error occurred: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
 			os.Exit(1)
 		}
 	},

+ 3 - 3
cli/cmd/config/config.go

@@ -67,7 +67,7 @@ func initAndLoadConfig(_config *CLIConfig) {
 	if _, err := os.Stat(porterDir); os.IsNotExist(err) {
 		os.Mkdir(porterDir, 0700)
 	} else if err != nil {
-		color.New(color.FgRed).Printf("%v\n", err)
+		color.New(color.FgRed).Fprintf(os.Stderr, "%v\n", err)
 		os.Exit(1)
 	}
 
@@ -96,12 +96,12 @@ func initAndLoadConfig(_config *CLIConfig) {
 			err := ioutil.WriteFile(filepath.Join(home, ".porter", "porter.yaml"), []byte{}, 0644)
 
 			if err != nil {
-				color.New(color.FgRed).Printf("%v\n", err)
+				color.New(color.FgRed).Fprintf(os.Stderr, "%v\n", err)
 				os.Exit(1)
 			}
 		} else {
 			// Config file was found but another error was produced
-			color.New(color.FgRed).Printf("%v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "%v\n", err)
 			os.Exit(1)
 		}
 	}

+ 2 - 1
cli/cmd/connect/ecr.go

@@ -3,6 +3,7 @@ package connect
 import (
 	"context"
 	"fmt"
+	"os"
 	"strings"
 	"time"
 
@@ -53,7 +54,7 @@ Would you like to proceed? %s `,
 		creds, err := agent.CreateIAMECRUser(region)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return ecrManual(client, projectID, region)
 		}
 

+ 7 - 7
cli/cmd/connect/kubeconfig.go

@@ -351,14 +351,14 @@ Would you like to proceed? %s `,
 		agent, err := gcpLocal.NewDefaultAgent()
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
 		}
 
 		projID, err := agent.GetProjectIDForGKECluster(endpoint)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
 		}
 
@@ -370,14 +370,14 @@ Would you like to proceed? %s `,
 		resp, err := agent.CreateServiceAccount(name)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
 		}
 
 		err = agent.SetServiceAccountIAMPolicy(resp)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
 		}
 
@@ -385,7 +385,7 @@ Would you like to proceed? %s `,
 		bytes, err := agent.CreateServiceAccountKey(resp)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
 		}
 
@@ -454,14 +454,14 @@ Would you like to proceed? %s `,
 		agent, err := awsLocal.NewDefaultKubernetesAgent(kubeconfigPath, contextName)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess, resolver)
 		}
 
 		creds, err := agent.CreateIAMKubernetesMapping(awsClusterIDGuess)
 
 		if err != nil {
-			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			color.New(color.FgRed).Fprintf(os.Stderr, "Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess, resolver)
 		}
 

+ 3 - 2
cli/cmd/errors.go

@@ -3,6 +3,7 @@ package cmd
 import (
 	"context"
 	"errors"
+	"os"
 	"strings"
 
 	"github.com/fatih/color"
@@ -32,7 +33,7 @@ func checkLoginAndRun(args []string, runner func(user *types.GetAuthenticatedUse
 			return ErrCannotConnect
 		}
 
-		red.Printf("Error: %v\n", err.Error())
+		red.Fprintf(os.Stderr, "Error: %v\n", err.Error())
 		return err
 	}
 
@@ -51,7 +52,7 @@ func checkLoginAndRun(args []string, runner func(user *types.GetAuthenticatedUse
 			return nil
 		}
 
-		red.Printf("Error: %v\n", err.Error())
+		red.Fprintf(os.Stderr, "Error: %v\n", err.Error())
 		return err
 	}
 

+ 1 - 1
cli/cmd/list.go

@@ -27,7 +27,7 @@ var listCmd = &cobra.Command{
 				os.Exit(1)
 			}
 		} else {
-			color.New(color.FgRed).Printf("invalid command: %s\n", args[0])
+			color.New(color.FgRed).Fprintf(os.Stderr, "invalid command: %s\n", args[0])
 		}
 	},
 }

+ 28 - 7
cli/cmd/run.go

@@ -111,12 +111,33 @@ func init() {
 }
 
 func run(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
-	color.New(color.FgGreen).Println("Running", strings.Join(args[1:], " "), "for release", args[0])
+	execArgs := args[1:]
+
+	color.New(color.FgGreen).Println("Running", strings.Join(execArgs, " "), "for release", args[0])
 
 	if nonInteractive {
 		color.New(color.FgBlue).Println("Using non-interactive mode. The first available pod will be used to run the command.")
 	}
 
+	if len(execArgs) > 0 {
+		release, err := client.GetRelease(
+			context.Background(), cliConf.Project, cliConf.Cluster, namespace, args[0],
+		)
+
+		if err != nil {
+			return fmt.Errorf("error fetching release %s: %w", args[0], err)
+		}
+
+		if release.BuildConfig != nil &&
+			(strings.Contains(release.BuildConfig.Builder, "heroku") ||
+				strings.Contains(release.BuildConfig.Builder, "paketo")) &&
+			execArgs[0] != "/cnb/lifecycle/launcher" &&
+			execArgs[0] != "launcher" {
+			// this is a buildpacks release using a heroku builder, prepend the launcher
+			execArgs = append([]string{"/cnb/lifecycle/launcher"}, execArgs...)
+		}
+	}
+
 	podsSimple, err := getPods(client, namespace, args[0])
 
 	if err != nil {
@@ -202,10 +223,10 @@ func run(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []strin
 	}
 
 	if existingPod {
-		return executeRun(config, namespace, selectedPod.Name, selectedContainerName, args[1:])
+		return executeRun(config, namespace, selectedPod.Name, selectedContainerName, execArgs)
 	}
 
-	return executeRunEphemeral(config, namespace, selectedPod.Name, selectedContainerName, args[1:])
+	return executeRunEphemeral(config, namespace, selectedPod.Name, selectedContainerName, execArgs)
 }
 
 func cleanup(_ *types.GetAuthenticatedUserResponse, client *api.Client, _ []string) error {
@@ -815,15 +836,15 @@ func isPodExited(pod *v1.Pod) bool {
 
 func handlePodAttachError(err error, config *PorterRunSharedConfig, namespace, podName, container string) error {
 	if verbose {
-		color.New(color.FgYellow).Printf("Error: %s\n", err)
+		color.New(color.FgYellow).Fprintf(os.Stderr, "Error: %s\n", err)
 	}
-	color.New(color.FgYellow).Println("Could not open a shell to this container. Container logs:")
+	color.New(color.FgYellow).Fprintln(os.Stderr, "Could not open a shell to this container. Container logs:")
 
 	var writtenBytes int64
 	writtenBytes, _ = pipePodLogsToStdout(config, namespace, podName, container, false)
 
 	if verbose || writtenBytes == 0 {
-		color.New(color.FgYellow).Println("Could not get logs. Pod events:")
+		color.New(color.FgYellow).Fprintln(os.Stderr, "Could not get logs. Pod events:")
 		pipeEventsToStdout(config, namespace, podName, container, false)
 	}
 	return err
@@ -892,7 +913,7 @@ func deletePod(config *PorterRunSharedConfig, name, namespace string) error {
 	)
 
 	if err != nil {
-		color.New(color.FgRed).Printf("Could not delete ephemeral pod: %s\n", err.Error())
+		color.New(color.FgRed).Fprintf(os.Stderr, "Could not delete ephemeral pod: %s\n", err.Error())
 		return err
 	}
 

+ 1 - 1
cli/cmd/utils/close.go

@@ -20,7 +20,7 @@ func closeHandler(closer func() error) {
 			os.Exit(0)
 		}
 
-		color.New(color.FgRed).Printf("shutdown unsuccessful: %s\n", err.Error())
+		color.New(color.FgRed).Fprintf(os.Stderr, "shutdown unsuccessful: %s\n", err.Error())
 		os.Exit(1)
 	}()
 }

+ 8 - 3
dashboard/src/components/Banner.tsx

@@ -8,9 +8,10 @@ interface Props {
   type?: string;
   icon?: React.ReactNode;
   children: React.ReactNode;
+  noMargin?: boolean;
 }
 
-const Banner: React.FC<Props> = ({ type, children, icon }) => {
+const Banner: React.FC<Props> = ({ type, icon, children, noMargin }) => {
   const renderIcon = () => {
     if (icon) {
       return icon;
@@ -25,6 +26,7 @@ const Banner: React.FC<Props> = ({ type, children, icon }) => {
   return (
     <StyledBanner
       color={type === "error" ? "#ff385d" : type === "warning" && "#f5cb42"}
+      noMargin={noMargin}
     >
       {renderIcon()}
       {children}
@@ -34,10 +36,13 @@ const Banner: React.FC<Props> = ({ type, children, icon }) => {
 
 export default Banner;
 
-const StyledBanner = styled.div<{ color?: string }>`
+const StyledBanner = styled.div<{
+  color?: string;
+  noMargin?: boolean;
+}>`
   height: 40px;
   width: 100%;
-  margin: 5px 0 10px;
+  margin: ${(props) => (props.noMargin ? "5px 0 10px" : "")};
   font-size: 13px;
   font-family: "Work Sans", sans-serif;
   display: flex;

+ 1 - 2
dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx

@@ -146,7 +146,6 @@ const Label = styled.div`
   display: flex;
   align-items: center;
   margin-right: 12px;
-
   > i {
     margin-right: 8px;
     font-size: 18px;
@@ -157,4 +156,4 @@ const StyledNamespaceSelector = styled.div`
   display: flex;
   align-items: center;
   font-size: 13px;
-`;
+`;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx

@@ -106,7 +106,7 @@ const ButtonEnablePREnvironments = ({ setIsReady }: Props) => {
     <>
       <Container>
         <Button {...getButtonProps()}>
-          <i className="material-icons">add</i> Add Repository
+          <i className="material-icons">add</i> Add repository
         </Button>
       </Container>
     </>

+ 14 - 24
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -13,6 +13,7 @@ import TabRegion from "components/TabRegion";
 import Provisioner from "../provisioner/Provisioner";
 import FormDebugger from "components/porter-form/FormDebugger";
 import TitleSection from "components/TitleSection";
+import Banner from "components/Banner";
 
 import { pushFiltered, pushQueryParams } from "shared/routing";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
@@ -115,24 +116,20 @@ class Dashboard extends Component<PropsType, StateType> {
       );
     } else if (this.currentTab() === "create-cluster") {
       let helperText = "Create a cluster to link to this project";
-      let helperIcon = "info";
-      let helperColor = "white";
+      let helperType = "info";
       if (
-        this.context.hasBillingEnabled &&
-        this.context.usage.current.clusters !== 0 &&
-        this.context.usage.current.clusters >= this.context.usage.limit.clusters
+        true
       ) {
         helperText =
           "You need to update your billing to provision or connect a new cluster";
-        helperIcon = "warning";
-        helperColor = "#f5cb42";
+        helperType = "warning";
       }
       return (
         <>
-          <Banner color={helperColor}>
-            <i className="material-icons">{helperIcon}</i>
+          <Banner type={helperType} noMargin>
             {helperText}
           </Banner>
+          <Br />
           <ProvisionerSettings infras={this.state.infras} provisioner={true} />
         </>
       );
@@ -226,25 +223,18 @@ const Br = styled.div`
   height: 1px;
 `;
 
+const Code = styled.div`
+  font-family: monospace;
+  margin: 0 7px;
+`;
+
 const DashboardWrapper = styled.div`
   padding-bottom: 100px;
 `;
 
-const Banner = styled.div<{ color: string }>`
-  height: 40px;
-  width: 100%;
-  margin: 5px 0 30px;
-  font-size: 13px;
-  display: flex;
-  border-radius: 5px;
-  padding-left: 15px;
-  align-items: center;
-  background: #ffffff11;
-  color: ${(props) => props.color};
-  > i {
-    margin-right: 10px;
-    font-size: 18px;
-  }
+const A = styled.a`
+  margin-left: 10px;
+  color: #8590ff;
 `;
 
 const TopRow = styled.div`

+ 8 - 2
internal/helm/agent.go

@@ -180,6 +180,7 @@ func (a *Agent) UpgradeRelease(
 	conf *UpgradeReleaseConfig,
 	values string,
 	doAuth *oauth2.Config,
+	disablePullSecretsInjection bool,
 ) (*release.Release, error) {
 	valuesYaml, err := chartutil.ReadValues([]byte(values))
 
@@ -189,13 +190,14 @@ func (a *Agent) UpgradeRelease(
 
 	conf.Values = valuesYaml
 
-	return a.UpgradeReleaseByValues(conf, doAuth)
+	return a.UpgradeReleaseByValues(conf, doAuth, disablePullSecretsInjection)
 }
 
 // UpgradeReleaseByValues upgrades a release by unmarshaled yaml values
 func (a *Agent) UpgradeReleaseByValues(
 	conf *UpgradeReleaseConfig,
 	doAuth *oauth2.Config,
+	disablePullSecretsInjection bool,
 ) (*release.Release, error) {
 	// grab the latest release
 	rel, err := a.GetRelease(conf.Name, 0, true)
@@ -220,6 +222,7 @@ func (a *Agent) UpgradeReleaseByValues(
 		rel.Namespace,
 		conf.Registries,
 		doAuth,
+		disablePullSecretsInjection,
 	)
 
 	if err != nil {
@@ -383,6 +386,7 @@ func (a *Agent) InstallChartFromValuesBytes(
 	conf *InstallChartConfig,
 	values []byte,
 	doAuth *oauth2.Config,
+	disablePullSecretsInjection bool,
 ) (*release.Release, error) {
 	valuesYaml, err := chartutil.ReadValues(values)
 
@@ -392,13 +396,14 @@ func (a *Agent) InstallChartFromValuesBytes(
 
 	conf.Values = valuesYaml
 
-	return a.InstallChart(conf, doAuth)
+	return a.InstallChart(conf, doAuth, disablePullSecretsInjection)
 }
 
 // InstallChart installs a new chart
 func (a *Agent) InstallChart(
 	conf *InstallChartConfig,
 	doAuth *oauth2.Config,
+	disablePullSecretsInjection bool,
 ) (*release.Release, error) {
 	cmd := action.NewInstall(a.ActionConfig)
 
@@ -423,6 +428,7 @@ func (a *Agent) InstallChart(
 		conf.Namespace,
 		conf.Registries,
 		doAuth,
+		disablePullSecretsInjection,
 	)
 
 	if err != nil {

+ 2 - 1
internal/helm/postrenderer.go

@@ -32,11 +32,12 @@ func NewPorterPostrenderer(
 	namespace string,
 	regs []*models.Registry,
 	doAuth *oauth2.Config,
+	disablePullSecretsInjection bool,
 ) (postrender.PostRenderer, error) {
 	var dockerSecretsPostrenderer *DockerSecretsPostRenderer
 	var err error
 
-	if cluster != nil && agent != nil && regs != nil && len(regs) > 0 {
+	if !disablePullSecretsInjection && cluster != nil && agent != nil && regs != nil && len(regs) > 0 {
 		dockerSecretsPostrenderer, err = NewDockerSecretsPostRenderer(cluster, repo, agent, namespace, regs, doAuth)
 
 		if err != nil {

+ 2 - 2
internal/templater/helm/values/writer.go

@@ -42,7 +42,7 @@ func (w *TemplateWriter) Create(
 		Values:    vals,
 	}
 
-	_, err := w.Agent.InstallChart(conf, nil)
+	_, err := w.Agent.InstallChart(conf, nil, false)
 
 	if err != nil {
 		return nil, err
@@ -64,7 +64,7 @@ func (w *TemplateWriter) Update(
 		Values: vals,
 	}
 
-	_, err := w.Agent.UpgradeReleaseByValues(conf, nil)
+	_, err := w.Agent.UpgradeReleaseByValues(conf, nil, false)
 
 	if err != nil {
 		return nil, err

+ 3 - 2
provisioner/client/client.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
+	"os"
 	"strings"
 	"time"
 
@@ -155,9 +156,9 @@ func (c *Client) postRequest(relPath string, data interface{}, response interfac
 
 		if i != int(retryCount)-1 {
 			if httpErr != nil {
-				fmt.Printf("Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code)
+				fmt.Fprintf(os.Stderr, "Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code)
 			} else {
-				fmt.Printf("Error: %v, retrying request...\n", err)
+				fmt.Fprintf(os.Stderr, "Error: %v, retrying request...\n", err)
 			}
 		}
 	}