Просмотр исходного кода

Merge branch 'nico/por-409-build-settings-tab' of github.com:porter-dev/porter into nico/por-409-build-settings-tab

jnfrati 4 лет назад
Родитель
Сommit
a9cd400ee0

+ 17 - 0
api/client/deploy.go

@@ -107,3 +107,20 @@ func (c *Client) UpgradeRelease(
 		},
 		},
 	)
 	)
 }
 }
+
+// DeleteRelease deletes a Porter release
+func (c *Client) DeleteRelease(
+	ctx context.Context,
+	projID, clusterID uint,
+	namespace, name string,
+) error {
+	return c.deleteRequest(
+		fmt.Sprintf(
+			"/projects/%d/clusters/%d/namespaces/%s/releases/%s/0",
+			projID, clusterID,
+			namespace, name,
+		),
+		nil,
+		nil,
+	)
+}

+ 15 - 29
api/server/handlers/environment/delete_deployment.go

@@ -10,7 +10,6 @@ import (
 	"github.com/google/go-github/v41/github"
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/handlers/gitinstallation"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config"
@@ -40,33 +39,7 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
 
-	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
-
-	if reqErr != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	// check that the environment belongs to the project and cluster IDs
-	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
-
-	if err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("environment id not found in cluster and project")))
-			return
-		}
-
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
-		return
-	}
-
-	owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
-
-	if !ok {
-		return
-	}
-
-	prNumber, reqErr := requestutils.GetURLParamUint(r, "pr_number")
+	deplID, reqErr := requestutils.GetURLParamUint(r, "deployment_id")
 
 
 	if reqErr != nil {
 	if reqErr != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
@@ -74,7 +47,7 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	}
 	}
 
 
 	// read the deployment
 	// read the deployment
-	depl, err := c.Repo().Environment().ReadDeploymentByGitDetails(envID, owner, name, prNumber)
+	depl, err := c.Repo().Environment().ReadDeploymentByID(project.ID, cluster.ID, deplID)
 
 
 	if err != nil {
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
@@ -99,6 +72,19 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		}
 		}
 	}
 	}
 
 
+	// check that the environment belongs to the project and cluster IDs
+	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, depl.EnvironmentID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("environment id not found in cluster and project")))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(reqErr))
+		return
+	}
+
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
 
 
 	if err != nil {
 	if err != nil {

+ 8 - 13
api/server/handlers/environment/list_deployments_by_cluster.go

@@ -9,6 +9,7 @@ import (
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/commonutils"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models"
@@ -140,23 +141,17 @@ func updateDeploymentWithGithubWorkflowRunStatus(
 	env *models.Environment,
 	env *models.Environment,
 	deployment *types.Deployment,
 	deployment *types.Deployment,
 ) {
 ) {
+	if deployment.Status == types.DeploymentStatusInactive {
+		return
+	}
+
 	client, err := getGithubClientFromEnvironment(config, env)
 	client, err := getGithubClientFromEnvironment(config, env)
 
 
 	if err == nil {
 	if err == nil {
-		workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
-			ctx, deployment.RepoOwner, deployment.RepoName,
-			fmt.Sprintf("porter_%s_env.yml", env.Name), &github.ListWorkflowRunsOptions{
-				Branch: deployment.PRBranchFrom,
-				ListOptions: github.ListOptions{
-					Page:    1,
-					PerPage: 1,
-				},
-			},
-		)
-
-		if err == nil && workflowRuns.GetTotalCount() > 0 {
-			latestWorkflowRun := workflowRuns.WorkflowRuns[0]
+		latestWorkflowRun, err := commonutils.GetLatestWorkflowRun(client, env.GitRepoOwner, env.GitRepoName,
+			fmt.Sprintf("porter_%s_env.yml", env.Name), deployment.PRBranchFrom)
 
 
+		if err == nil {
 			deployment.LastWorkflowRunURL = latestWorkflowRun.GetHTMLURL()
 			deployment.LastWorkflowRunURL = latestWorkflowRun.GetHTMLURL()
 
 
 			if (latestWorkflowRun.GetStatus() == "in_progress" ||
 			if (latestWorkflowRun.GetStatus() == "in_progress" ||

+ 9 - 20
api/server/handlers/environment/trigger_deployment_workflow.go

@@ -1,7 +1,6 @@
 package environment
 package environment
 
 
 import (
 import (
-	"context"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
@@ -11,6 +10,7 @@ import (
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/commonutils"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/api/types"
@@ -69,8 +69,8 @@ func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *h
 		return
 		return
 	}
 	}
 
 
-	latestWorkflowRun, err := getLatestWorkflowRun(client, env.GitRepoOwner, env.GitRepoName,
-		fmt.Sprintf("porter_%s_env.yml", env.Name))
+	latestWorkflowRun, err := commonutils.GetLatestWorkflowRun(client, env.GitRepoOwner, env.GitRepoName,
+		fmt.Sprintf("porter_%s_env.yml", env.Name), depl.PRBranchFrom)
 
 
 	if err != nil && errors.Is(err, ErrNoWorkflowRuns) {
 	if err != nil && errors.Is(err, ErrNoWorkflowRuns) {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, 400))
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, 400))
@@ -107,25 +107,14 @@ func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *h
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 		return
 	}
 	}
-}
 
 
-func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
-	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
-		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
-			ListOptions: github.ListOptions{
-				Page:    1,
-				PerPage: 1,
-			},
-		},
-	)
+	// set the status to updating manually here for the frontend to case on
+	depl.Status = types.DeploymentStatusUpdating
 
 
-	if err != nil {
-		return nil, err
-	}
+	_, err = c.Repo().Environment().UpdateDeployment(depl)
 
 
-	if workflowRuns.GetTotalCount() == 0 {
-		return nil, ErrNoWorkflowRuns
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
 	}
 	}
-
-	return workflowRuns.WorkflowRuns[0], nil
 }
 }

+ 29 - 30
api/server/handlers/gitinstallation/rerun_workflow.go

@@ -1,20 +1,18 @@
 package gitinstallation
 package gitinstallation
 
 
 import (
 import (
-	"context"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
+	"strings"
 
 
-	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/commonutils"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config"
 )
 )
 
 
-var ErrNoWorkflowRuns = errors.New("no previous workflow runs found")
-
 type RerunWorkflowHandler struct {
 type RerunWorkflowHandler struct {
 	handlers.PorterHandlerReadWriter
 	handlers.PorterHandlerReadWriter
 }
 }
@@ -37,10 +35,28 @@ func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	}
 	}
 
 
 	filename := r.URL.Query().Get("filename")
 	filename := r.URL.Query().Get("filename")
+	// if branch is empty then the latest workflow run is rerun, meaning that if
+	// there were multiple workflow runs for the same file but for different branches
+	// only the very latest of the workflow runs will be rerun
+	branch := r.URL.Query().Get("branch")
+	releaseName := r.URL.Query().Get("release_name")
+
+	if filename == "" && releaseName == "" {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("filename and release name are both empty")))
+		return
+	}
 
 
 	if filename == "" {
 	if filename == "" {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("filename query param not set")))
-		return
+		if c.Config().ServerConf.InstanceName != "" {
+			filename = fmt.Sprintf("porter_%s_%s.yml", strings.Replace(
+				strings.ToLower(releaseName), "-", "_", -1),
+				strings.ToLower(c.Config().ServerConf.InstanceName),
+			)
+		} else {
+			filename = fmt.Sprintf("porter_%s.yml", strings.Replace(
+				strings.ToLower(releaseName), "-", "_", -1),
+			)
+		}
 	}
 	}
 
 
 	client, err := GetGithubAppClientFromRequest(c.Config(), r)
 	client, err := GetGithubAppClientFromRequest(c.Config(), r)
@@ -50,11 +66,15 @@ func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 		return
 	}
 	}
 
 
-	latestWorkflowRun, err := getLatestWorkflowRun(client, owner, name, filename)
+	latestWorkflowRun, err := commonutils.GetLatestWorkflowRun(client, owner, name, filename, branch)
 
 
-	if err != nil && errors.Is(err, ErrNoWorkflowRuns) {
+	if err != nil && errors.Is(err, commonutils.ErrNoWorkflowRuns) {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, 400))
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, 400))
 		return
 		return
+	} else if err != nil && errors.Is(err, commonutils.ErrWorkflowNotFound) {
+		w.WriteHeader(http.StatusNotFound)
+		c.WriteResult(w, r, filename)
+		return
 	} else if err != nil {
 	} else if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 		return
@@ -73,7 +93,7 @@ func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 		return
 	}
 	}
 
 
-	latestWorkflowRun, err = getLatestWorkflowRun(client, owner, name, filename)
+	latestWorkflowRun, err = commonutils.GetLatestWorkflowRun(client, owner, name, filename, branch)
 
 
 	if err != nil {
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
@@ -82,24 +102,3 @@ func (c *RerunWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 
 	c.WriteResult(w, r, latestWorkflowRun.GetHTMLURL())
 	c.WriteResult(w, r, latestWorkflowRun.GetHTMLURL())
 }
 }
-
-func getLatestWorkflowRun(client *github.Client, owner, repo, filename string) (*github.WorkflowRun, error) {
-	workflowRuns, _, err := client.Actions.ListWorkflowRunsByFileName(
-		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
-			ListOptions: github.ListOptions{
-				Page:    1,
-				PerPage: 1,
-			},
-		},
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	if workflowRuns.GetTotalCount() == 0 {
-		return nil, ErrNoWorkflowRuns
-	}
-
-	return workflowRuns.WorkflowRuns[0], nil
-}

+ 3 - 8
api/server/router/cluster.go

@@ -463,20 +463,15 @@ func getClusterRoutes(
 			Router:   r,
 			Router:   r,
 		})
 		})
 
 
-		// DELETE /api/projects/{project_id}/clusters/{cluster_id}/deployments/{environment_id}/{owner}/{name}/{pr_number} ->
+		// DELETE /api/projects/{project_id}/clusters/{cluster_id}/deployments/{deployment_id} ->
 		// environment.NewDeleteDeploymentHandler
 		// environment.NewDeleteDeploymentHandler
 		deleteDeploymentEndpoint := factory.NewAPIEndpoint(
 		deleteDeploymentEndpoint := factory.NewAPIEndpoint(
 			&types.APIRequestMetadata{
 			&types.APIRequestMetadata{
 				Verb:   types.APIVerbDelete,
 				Verb:   types.APIVerbDelete,
 				Method: types.HTTPVerbDelete,
 				Method: types.HTTPVerbDelete,
 				Path: &types.Path{
 				Path: &types.Path{
-					Parent: basePath,
-					RelativePath: fmt.Sprintf(
-						"%s/deployments/{environment_id}/{%s}/{%s}/{pr_number}",
-						relPath,
-						types.URLParamGitRepoOwner,
-						types.URLParamGitRepoName,
-					),
+					Parent:       basePath,
+					RelativePath: relPath + "/deployments/{deployment_id}",
 				},
 				},
 				Scopes: []types.PermissionScope{
 				Scopes: []types.PermissionScope{
 					types.UserScope,
 					types.UserScope,

+ 38 - 0
api/server/shared/commonutils/git_utils.go

@@ -0,0 +1,38 @@
+package commonutils
+
+import (
+	"context"
+	"errors"
+	"net/http"
+
+	"github.com/google/go-github/v41/github"
+)
+
+var ErrNoWorkflowRuns = errors.New("no previous workflow runs found")
+var ErrWorkflowNotFound = errors.New("no workflow found, file missing")
+
+func GetLatestWorkflowRun(client *github.Client, owner, repo, filename, branch string) (*github.WorkflowRun, error) {
+	workflowRuns, ghResponse, err := client.Actions.ListWorkflowRunsByFileName(
+		context.Background(), owner, repo, filename, &github.ListWorkflowRunsOptions{
+			Branch: branch,
+			ListOptions: github.ListOptions{
+				Page:    1,
+				PerPage: 1,
+			},
+		},
+	)
+
+	if ghResponse.StatusCode == http.StatusNotFound {
+		return nil, ErrWorkflowNotFound
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	if workflowRuns.GetTotalCount() == 0 {
+		return nil, ErrNoWorkflowRuns
+	}
+
+	return workflowRuns.WorkflowRuns[0], nil
+}

+ 20 - 15
cli/cmd/apply.go

@@ -398,26 +398,31 @@ func (d *Driver) applyApplication(resource *models.Resource, client *api.Client,
 	if d.source.Name == "job" && appConfig.WaitForJob && (shouldCreate || !appConfig.OnlyCreate) {
 	if d.source.Name == "job" && appConfig.WaitForJob && (shouldCreate || !appConfig.OnlyCreate) {
 		color.New(color.FgYellow).Printf("Waiting for job '%s' to finish\n", resource.Name)
 		color.New(color.FgYellow).Printf("Waiting for job '%s' to finish\n", resource.Name)
 
 
-		prevProject := cliConf.Project
-		prevCluster := cliConf.Cluster
-		name = resource.Name
-		namespace = d.target.Namespace
-		cliConf.Project = d.target.Project
-		cliConf.Cluster = d.target.Cluster
-
 		err = wait.WaitForJob(client, &wait.WaitOpts{
 		err = wait.WaitForJob(client, &wait.WaitOpts{
-			ProjectID: cliConf.Project,
-			ClusterID: cliConf.Cluster,
-			Namespace: namespace,
-			Name:      name,
+			ProjectID: d.target.Project,
+			ClusterID: d.target.Cluster,
+			Namespace: d.target.Namespace,
+			Name:      resource.Name,
 		})
 		})
 
 
 		if err != nil {
 		if err != nil {
-			return nil, err
-		}
+			if appConfig.OnlyCreate {
+				err = client.DeleteRelease(
+					context.Background(),
+					d.target.Project,
+					d.target.Cluster,
+					d.target.Namespace,
+					resource.Name,
+				)
 
 
-		cliConf.Project = prevProject
-		cliConf.Cluster = prevCluster
+				if err != nil {
+					return nil, fmt.Errorf("error deleting job %s with waitForJob and onlyCreate set to true: %w",
+						resource.Name, err)
+				}
+			}
+
+			return nil, fmt.Errorf("error waiting for job %s: %w", resource.Name, err)
+		}
 	}
 	}
 
 
 	return resource, err
 	return resource, err

+ 13 - 0
cli/cmd/deploy.go

@@ -12,6 +12,7 @@ import (
 	"github.com/porter-dev/porter/cli/cmd/config"
 	"github.com/porter-dev/porter/cli/cmd/config"
 	"github.com/porter-dev/porter/cli/cmd/deploy"
 	"github.com/porter-dev/porter/cli/cmd/deploy"
 	"github.com/porter-dev/porter/cli/cmd/utils"
 	"github.com/porter-dev/porter/cli/cmd/utils"
+	templaterUtils "github.com/porter-dev/porter/internal/templater/utils"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"k8s.io/client-go/util/homedir"
 	"k8s.io/client-go/util/homedir"
 )
 )
@@ -637,6 +638,18 @@ func updateUpgradeWithAgent(updateAgent *deploy.DeployAgent) error {
 		return err
 		return err
 	}
 	}
 
 
+	env, err := updateAgent.GetBuildEnv(&deploy.GetBuildEnvOpts{UseNewConfig: false})
+
+	if err == nil && len(env) > 0 {
+		valuesObj = templaterUtils.CoalesceValues(valuesObj, map[string]interface{}{
+			"container": map[string]interface{}{
+				"env": map[string]interface{}{
+					"normal": env,
+				},
+			},
+		})
+	}
+
 	err = updateAgent.UpdateImageAndValues(valuesObj)
 	err = updateAgent.UpdateImageAndValues(valuesObj)
 
 
 	if err != nil {
 	if err != nil {

+ 11 - 1
cli/cmd/deploy/create.go

@@ -284,6 +284,16 @@ func (c *CreateAgent) CreateFromDocker(
 			env = map[string]string{}
 			env = map[string]string{}
 		}
 		}
 
 
+		buildEnv, err := GetNestedMap(mergedValues, "container", "env", "build")
+
+		if err == nil {
+			for key, val := range buildEnv {
+				if valStr, ok := val.(string); ok {
+					env[key] = valStr
+				}
+			}
+		}
+
 		// add additional env based on options
 		// add additional env based on options
 		for key, val := range opts.SharedOpts.AdditionalEnv {
 		for key, val := range opts.SharedOpts.AdditionalEnv {
 			env[key] = val
 			env[key] = val
@@ -506,7 +516,7 @@ func (c *CreateAgent) CreateSubdomainIfRequired(mergedValues map[string]interfac
 	// check for automatic subdomain creation if web kind
 	// check for automatic subdomain creation if web kind
 	if c.CreateOpts.Kind == "web" {
 	if c.CreateOpts.Kind == "web" {
 		// look for ingress.enabled and no custom domains set
 		// look for ingress.enabled and no custom domains set
-		ingressMap, err := getNestedMap(mergedValues, "ingress")
+		ingressMap, err := GetNestedMap(mergedValues, "ingress")
 
 
 		if err == nil {
 		if err == nil {
 			enabledVal, enabledExists := ingressMap["enabled"]
 			enabledVal, enabledExists := ingressMap["enabled"]

+ 15 - 5
cli/cmd/deploy/deploy.go

@@ -166,6 +166,16 @@ func (d *DeployAgent) GetBuildEnv(opts *GetBuildEnvOpts) (map[string]string, err
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	buildEnv, err := GetNestedMap(conf, "container", "env", "build")
+
+	if err == nil {
+		for key, val := range buildEnv {
+			if valStr, ok := val.(string); ok {
+				env[key] = valStr
+			}
+		}
+	}
+
 	// add additional env based on options
 	// add additional env based on options
 	for key, val := range d.opts.SharedOpts.AdditionalEnv {
 	for key, val := range d.opts.SharedOpts.AdditionalEnv {
 		env[key] = val
 		env[key] = val
@@ -412,7 +422,7 @@ func GetEnvForRelease(client *client.Client, config map[string]interface{}, proj
 	res := make(map[string]string)
 	res := make(map[string]string)
 
 
 	// first, get the env vars from "container.env.normal"
 	// first, get the env vars from "container.env.normal"
-	envConfig, err := getNestedMap(config, "container", "env", "normal")
+	envConfig, err := GetNestedMap(config, "container", "env", "normal")
 
 
 	// if the field is not found, set envConfig to an empty map; this release has no env set
 	// if the field is not found, set envConfig to an empty map; this release has no env set
 	if err != nil {
 	if err != nil {
@@ -435,7 +445,7 @@ func GetEnvForRelease(client *client.Client, config map[string]interface{}, proj
 
 
 	// next, get the env vars specified by "container.env.synced"
 	// next, get the env vars specified by "container.env.synced"
 	// look for container.env.synced
 	// look for container.env.synced
-	envConf, err := getNestedMap(config, "container", "env")
+	envConf, err := GetNestedMap(config, "container", "env")
 
 
 	// if error, just return the env detected from above
 	// if error, just return the env detected from above
 	if err != nil {
 	if err != nil {
@@ -558,7 +568,7 @@ func (d *DeployAgent) getReleaseImage() (string, error) {
 	}
 	}
 
 
 	// get the image from the conig
 	// get the image from the conig
-	imageConfig, err := getNestedMap(d.release.Config, "image")
+	imageConfig, err := GetNestedMap(d.release.Config, "image")
 
 
 	if err != nil {
 	if err != nil {
 		return "", fmt.Errorf("could not get image config from release: %s", err.Error())
 		return "", fmt.Errorf("could not get image config from release: %s", err.Error())
@@ -581,7 +591,7 @@ func (d *DeployAgent) getReleaseImage() (string, error) {
 
 
 func (d *DeployAgent) pullCurrentReleaseImage() (string, error) {
 func (d *DeployAgent) pullCurrentReleaseImage() (string, error) {
 	// pull the currently deployed image to use cache, if possible
 	// pull the currently deployed image to use cache, if possible
-	imageConfig, err := getNestedMap(d.release.Config, "image")
+	imageConfig, err := GetNestedMap(d.release.Config, "image")
 
 
 	if err != nil {
 	if err != nil {
 		return "", fmt.Errorf("could not get image config from release: %s", err.Error())
 		return "", fmt.Errorf("could not get image config from release: %s", err.Error())
@@ -668,7 +678,7 @@ func (e *NestedMapFieldNotFoundError) Error() string {
 	return fmt.Sprintf("could not find field %s in configuration", e.Field)
 	return fmt.Sprintf("could not find field %s in configuration", e.Field)
 }
 }
 
 
-func getNestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, error) {
+func GetNestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, error) {
 	var res map[string]interface{}
 	var res map[string]interface{}
 	curr := obj
 	curr := obj
 
 

+ 1 - 1
cli/cmd/deploy/shared.go

@@ -49,7 +49,7 @@ func coalesceEnvGroups(
 			return err
 			return err
 		}
 		}
 
 
-		envConfig, err := getNestedMap(config, "container", "env", "normal")
+		envConfig, err := GetNestedMap(config, "container", "env", "normal")
 
 
 		if err != nil || envConfig == nil {
 		if err != nil || envConfig == nil {
 			envConfig = make(map[string]interface{})
 			envConfig = make(map[string]interface{})

+ 10 - 0
cli/cmd/preview/build_image_driver.go

@@ -254,6 +254,16 @@ func (d *BuildDriver) Apply(resource *models.Resource) (*models.Resource, error)
 			env = map[string]string{}
 			env = map[string]string{}
 		}
 		}
 
 
+		buildEnv, err := deploy.GetNestedMap(mergedValues, "container", "env", "build")
+
+		if err == nil {
+			for key, val := range buildEnv {
+				if valStr, ok := val.(string); ok {
+					env[key] = valStr
+				}
+			}
+		}
+
 		buildAgent := &deploy.BuildAgent{
 		buildAgent := &deploy.BuildAgent{
 			SharedOpts:  createAgent.CreateOpts.SharedOpts,
 			SharedOpts:  createAgent.CreateOpts.SharedOpts,
 			APIClient:   client,
 			APIClient:   client,

+ 9 - 4
dashboard/src/components/form-components/KeyValueArray.tsx

@@ -130,7 +130,9 @@ export default class KeyValueArray extends Component<PropsType, StateType> {
                   let obj = this.valuesToObject();
                   let obj = this.valuesToObject();
                   this.props.setValues(obj);
                   this.props.setValues(obj);
                 }}
                 }}
-                disabled={this.props.disabled || value.includes("PORTERSECRET")}
+                disabled={
+                  this.props.disabled || value?.includes("PORTERSECRET")
+                }
                 spellCheck={false}
                 spellCheck={false}
               />
               />
               <Spacer />
               <Spacer />
@@ -145,12 +147,14 @@ export default class KeyValueArray extends Component<PropsType, StateType> {
                   let obj = this.valuesToObject();
                   let obj = this.valuesToObject();
                   this.props.setValues(obj);
                   this.props.setValues(obj);
                 }}
                 }}
-                disabled={this.props.disabled || value.includes("PORTERSECRET")}
-                type={value.includes("PORTERSECRET") ? "password" : "text"}
+                disabled={
+                  this.props.disabled || value?.includes("PORTERSECRET")
+                }
+                type={value?.includes("PORTERSECRET") ? "password" : "text"}
                 spellCheck={false}
                 spellCheck={false}
               />
               />
               {this.renderDeleteButton(i)}
               {this.renderDeleteButton(i)}
-              {this.renderHiddenOption(value.includes("PORTERSECRET"), i)}
+              {this.renderHiddenOption(value?.includes("PORTERSECRET"), i)}
             </InputWrapper>
             </InputWrapper>
           );
           );
         })}
         })}
@@ -176,6 +180,7 @@ export default class KeyValueArray extends Component<PropsType, StateType> {
               this.props.setValues(newValues);
               this.props.setValues(newValues);
               this.setState({ values: this.objectToValues(newValues) });
               this.setState({ values: this.objectToValues(newValues) });
             }}
             }}
+            normalEnvVarsOnly
           />
           />
         </Modal>
         </Modal>
       );
       );

+ 1 - 1
dashboard/src/components/porter-form/field-components/KeyValueArray.tsx

@@ -31,7 +31,7 @@ const KeyValueArray: React.FC<Props> = (props) => {
       initState: () => {
       initState: () => {
         let values = props.value[0];
         let values = props.value[0];
         const normalValues = Object.entries(values?.normal || {});
         const normalValues = Object.entries(values?.normal || {});
-        values = omit(values, ["normal", "synced"]);
+        values = omit(values, ["normal", "synced", "build"]);
         return {
         return {
           values: hasSetValue(props)
           values: hasSetValue(props)
             ? ([...Object.entries(values), ...normalValues]?.map(([k, v]) => {
             ? ([...Object.entries(values), ...normalValues]?.map(([k, v]) => {

+ 151 - 121
dashboard/src/main/home/cluster-dashboard/expanded-chart/BuildSettingsTab.tsx

@@ -1,4 +1,3 @@
-import DynamicLink from "components/DynamicLink";
 import Heading from "components/form-components/Heading";
 import Heading from "components/form-components/Heading";
 import Helper from "components/form-components/Helper";
 import Helper from "components/form-components/Helper";
 import KeyValueArray from "components/form-components/KeyValueArray";
 import KeyValueArray from "components/form-components/KeyValueArray";
@@ -16,6 +15,8 @@ import {
 } from "shared/types";
 } from "shared/types";
 import styled, { keyframes } from "styled-components";
 import styled, { keyframes } from "styled-components";
 import yaml from "js-yaml";
 import yaml from "js-yaml";
+import DynamicLink from "components/DynamicLink";
+import { AxiosError } from "axios";
 
 
 const DEFAULT_PAKETO_STACK = "paketobuildpacks/builder:full";
 const DEFAULT_PAKETO_STACK = "paketobuildpacks/builder:full";
 const DEFAULT_HEROKU_STACK = "heroku/buildpacks:20";
 const DEFAULT_HEROKU_STACK = "heroku/buildpacks:20";
@@ -62,8 +63,13 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
 
 
   const [buildConfig, setBuildConfig] = useState<BuildConfig>(null);
   const [buildConfig, setBuildConfig] = useState<BuildConfig>(null);
   const [envVariables, setEnvVariables] = useState(
   const [envVariables, setEnvVariables] = useState(
-    chart.config?.container?.env?.normal || null
+    chart.config?.container?.env?.build || null
   );
   );
+  const [runningWorkflowURL, setRunningWorkflowURL] = useState("");
+  const [reRunError, setReRunError] = useState<{
+    title: string;
+    description: string;
+  }>(null);
   const [buttonStatus, setButtonStatus] = useState<
   const [buttonStatus, setButtonStatus] = useState<
     "loading" | "successful" | string
     "loading" | "successful" | string
   >("");
   >("");
@@ -99,7 +105,7 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
       return;
       return;
     }
     }
 
 
-    values.container.env.normal = envs;
+    values.container.env.build = { ...envs };
     const valuesYaml = yaml.dump({ ...values });
     const valuesYaml = yaml.dump({ ...values });
     try {
     try {
       await api.upgradeChartValues(
       await api.upgradeChartValues(
@@ -128,12 +134,71 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
           project_id: currentProject.id,
           project_id: currentProject.id,
           cluster_id: currentCluster.id,
           cluster_id: currentCluster.id,
           git_installation_id: chart.git_action_config?.git_repo_id,
           git_installation_id: chart.git_action_config?.git_repo_id,
-          owner: chart.git_action_config.repo?.split("/")[0],
-          name: chart.git_action_config.repo?.split("/")[1],
-          filename: `porter_${chart.name.replaceAll("-", "_")}.yaml`,
+          owner: chart.git_action_config?.git_repo?.split("/")[0],
+          name: chart.git_action_config?.git_repo?.split("/")[1],
+          branch: chart.git_action_config?.git_branch,
+          release_name: chart.name,
         }
         }
       );
       );
     } catch (error) {
     } catch (error) {
+      if (!error?.response) {
+        throw error;
+      }
+
+      let tmpError: AxiosError = error;
+
+      /**
+       * @smell
+       * Currently the expanded chart is clearing all the state when a chart update is triggered (saveEnvVariables).
+       * Temporary usage of setCurrentError until a context is applied to keep the state of the ReRunError during re renders.
+       */
+
+      if (tmpError.response.status === 400) {
+        // setReRunError({
+        //   title: "No previous run found",
+        //   description:
+        //     "There are no previous runs for this workflow, please trigger manually a run before changing the build settings.",
+        // });
+        setCurrentError(
+          "There are no previous runs for this workflow, please trigger manually a run before changing the build settings."
+        );
+        return;
+      }
+
+      if (tmpError.response.status === 409) {
+        // setReRunError({
+        //   title: "The workflow is still running",
+        //   description:
+        //     'If you want to make more changes, please choose the option "Save" until the workflow finishes.',
+        // });
+
+        if (typeof tmpError.response.data === "string") {
+          setRunningWorkflowURL(tmpError.response.data);
+        }
+        setCurrentError(
+          'The workflow is still running. If you want to make more changes, please choose the option "Save" until the workflow finishes. You can check the current status of the workflow here ' +
+            tmpError.response.data
+        );
+        return;
+      }
+
+      if (tmpError.response.status === 404) {
+        let description =
+          "Apparently there's no action file that corresponds to this deployment. ";
+        if (typeof tmpError.response.data === "string") {
+          const filename = tmpError.response.data;
+          description = description.concat(
+            `Please check that the file ${filename} exists on your repository.`
+          );
+        }
+        // setReRunError({
+        //   title: "The action doesn't seem to exist",
+        //   description,
+        // });
+
+        setCurrentError(description);
+        return;
+      }
       throw error;
       throw error;
     }
     }
   };
   };
@@ -182,6 +247,34 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
         </DisabledOverlay>
         </DisabledOverlay>
       ) : null}
       ) : null}
       <StyledSettingsSection blurContent={isPreviousVersion}>
       <StyledSettingsSection blurContent={isPreviousVersion}>
+        {/* {reRunError !== null ? (
+        <AlertCard>
+          <AlertCardIcon className="material-icons">error</AlertCardIcon>
+          <AlertCardContent className="content">
+            <AlertCardTitle className="title">
+              {reRunError.title}
+            </AlertCardTitle>
+            {reRunError.description}
+            {runningWorkflowURL.length ? (
+              <>
+                {" "}
+                To go to the workflow{" "}
+                <DynamicLink to={runningWorkflowURL} target="_blank">
+                  click here
+                </DynamicLink>
+              </>
+            ) : null}
+          </AlertCardContent>
+          <AlertCardAction
+            onClick={() => {
+              setReRunError(null);
+              setRunningWorkflowURL("");
+            }}
+          >
+            <span className="material-icons">close</span>
+          </AlertCardAction>
+        </AlertCard>
+      ) : null} */}
         <Heading isAtTop>Build step environment variables:</Heading>
         <Heading isAtTop>Build step environment variables:</Heading>
         <KeyValueArray
         <KeyValueArray
           values={envVariables}
           values={envVariables}
@@ -191,17 +284,20 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
             clusterId: currentCluster.id,
             clusterId: currentCluster.id,
           }}
           }}
           setValues={(values) => {
           setValues={(values) => {
-            console.log(values);
             setEnvVariables(values);
             setEnvVariables(values);
           }}
           }}
         ></KeyValueArray>
         ></KeyValueArray>
 
 
-        <Heading>Buildpack settings</Heading>
-        <BuildpackConfigSection
-          currentChart={chart}
-          actionConfig={chart.git_action_config}
-          onChange={(buildConfig) => setBuildConfig(buildConfig)}
-        />
+        {!chart.git_action_config.dockerfile_path ? (
+          <>
+            <Heading>Buildpack settings</Heading>
+            <BuildpackConfigSection
+              currentChart={chart}
+              actionConfig={chart.git_action_config}
+              onChange={(buildConfig) => setBuildConfig(buildConfig)}
+            />
+          </>
+        ) : null}
         <SaveButtonWrapper>
         <SaveButtonWrapper>
           <MultiSaveButton
           <MultiSaveButton
             options={[
             options={[
@@ -252,8 +348,6 @@ const BuildpackConfigSection: React.FC<{
     []
     []
   );
   );
 
 
-  const [runningWorkflowURL, setRunningWorkflowURL] = useState<string>(null);
-
   useEffect(() => {
   useEffect(() => {
     const currentBuildConfig = currentChart?.build_config;
     const currentBuildConfig = currentChart?.build_config;
 
 
@@ -473,28 +567,6 @@ const BuildpackConfigSection: React.FC<{
 
 
   return (
   return (
     <BuildpackConfigurationContainer>
     <BuildpackConfigurationContainer>
-      {runningWorkflowURL && (
-        <AlertCard>
-          <AlertCardIcon className="material-icons">error</AlertCardIcon>
-          <AlertCardContent className="content">
-            <AlertCardTitle className="title">
-              The workflow is still running
-            </AlertCardTitle>
-            Please wait until it finishes before changing the buildpack
-            configuration. To go to the workflow{" "}
-            <DynamicLink to={runningWorkflowURL} target="_blank">
-              click here
-            </DynamicLink>
-          </AlertCardContent>
-          <AlertCardAction
-            onClick={() => {
-              setRunningWorkflowURL("");
-            }}
-          >
-            <span className="material-icons">close</span>
-          </AlertCardAction>
-        </AlertCard>
-      )}
       <>
       <>
         <SelectRow
         <SelectRow
           value={selectedBuilder}
           value={selectedBuilder}
@@ -527,17 +599,6 @@ const BuildpackConfigSection: React.FC<{
         )}
         )}
       </>
       </>
     </BuildpackConfigurationContainer>
     </BuildpackConfigurationContainer>
-    // <SaveButtonWrapper>
-    //   <SaveButton
-    //     onClick={() => {
-    //       handleSubmit();
-    //     }}
-    //     status={buttonStatus}
-    //     text="Save build config"
-    //     makeFlush
-    //     clearPosition
-    //   />
-    // </SaveButtonWrapper>
   );
   );
 };
 };
 
 
@@ -563,55 +624,6 @@ const fadeIn = keyframes`
   }
   }
 `;
 `;
 
 
-const AlertCard = styled.div`
-  transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
-  border-radius: 4px;
-  box-shadow: none;
-  font-weight: 400;
-  font-size: 0.875rem;
-  line-height: 1.43;
-  letter-spacing: 0.01071em;
-  border: 1px solid rgb(229, 115, 115);
-  display: flex;
-  padding: 6px 16px;
-  color: rgb(244, 199, 199);
-  margin-top: 20px;
-  position: relative;
-`;
-
-const AlertCardIcon = styled.span`
-  color: rgb(239, 83, 80);
-  margin-right: 12px;
-  padding: 7px 0px;
-  display: flex;
-  font-size: 22px;
-  opacity: 0.9;
-`;
-
-const AlertCardTitle = styled.div`
-  margin: -2px 0px 0.35em;
-  font-size: 1rem;
-  line-height: 1.5;
-  letter-spacing: 0.00938em;
-  font-weight: 500;
-`;
-
-const AlertCardContent = styled.div`
-  padding: 8px 0px;
-`;
-
-const AlertCardAction = styled.button`
-  position: absolute;
-  right: 5px;
-  top: 5px;
-  border: none;
-  background-color: unset;
-  color: white;
-  :hover {
-    cursor: pointer;
-  }
-`;
-
 const SaveButtonWrapper = styled.div`
 const SaveButtonWrapper = styled.div`
   width: 100%;
   width: 100%;
   margin-top: 30px;
   margin-top: 30px;
@@ -683,12 +695,6 @@ const EventName = styled.div`
   color: #ffffff;
   color: #ffffff;
 `;
 `;
 
 
-const EventReason = styled.div`
-  font-family: "Work Sans", sans-serif;
-  color: #aaaabb;
-  margin-top: 5px;
-`;
-
 const ActionContainer = styled.div`
 const ActionContainer = styled.div`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
@@ -719,27 +725,51 @@ const DeleteButton = styled.button`
   }
   }
 `;
 `;
 
 
-const SubmitButton = styled.button`
-  height: 35px;
-  font-size: 13px;
+const AlertCard = styled.div`
+  transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+  border-radius: 4px;
+  box-shadow: none;
+  font-weight: 400;
+  font-size: 0.875rem;
+  line-height: 1.43;
+  letter-spacing: 0.01071em;
+  border: 1px solid rgb(229, 115, 115);
+  display: flex;
+  padding: 6px 16px;
+  color: rgb(244, 199, 199);
   margin-top: 20px;
   margin-top: 20px;
-  margin-bottom: 30px;
+  position: relative;
+`;
+
+const AlertCardIcon = styled.span`
+  color: rgb(239, 83, 80);
+  margin-right: 12px;
+  padding: 7px 0px;
+  display: flex;
+  font-size: 22px;
+  opacity: 0.9;
+`;
+
+const AlertCardTitle = styled.div`
+  margin: -2px 0px 0.35em;
+  font-size: 1rem;
+  line-height: 1.5;
+  letter-spacing: 0.00938em;
   font-weight: 500;
   font-weight: 500;
-  font-family: "Work Sans", sans-serif;
+`;
+
+const AlertCardContent = styled.div`
+  padding: 8px 0px;
+`;
+
+const AlertCardAction = styled.button`
+  position: absolute;
+  right: 5px;
+  top: 5px;
+  border: none;
+  background-color: unset;
   color: white;
   color: white;
-  padding: 6px 20px 7px 20px;
-  text-align: left;
-  border: 0;
-  border-radius: 5px;
-  background: ${(props) => (!props.disabled ? props.color : "#aaaabb")};
-  box-shadow: ${(props) =>
-    !props.disabled ? "0 2px 5px 0 #00000030" : "none"};
-  cursor: ${(props) => (!props.disabled ? "pointer" : "default")};
-  user-select: none;
-  :focus {
-    outline: 0;
-  }
   :hover {
   :hover {
-    filter: ${(props) => (!props.disabled ? "brightness(120%)" : "")};
+    cursor: pointer;
   }
   }
 `;
 `;

+ 6 - 7
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -542,12 +542,12 @@ const ExpandedChart: React.FC<Props> = (props) => {
       );
       );
     }
     }
 
 
-    // if (currentChart?.git_action_config?.git_repo) {
-    rightTabOptions.push({
-      label: "Build Settings",
-      value: "build-settings",
-    });
-    // }
+    if (currentChart?.git_action_config?.git_repo) {
+      rightTabOptions.push({
+        label: "Build Settings",
+        value: "build-settings",
+      });
+    }
 
 
     // Settings tab is always last
     // Settings tab is always last
     if (isAuthorized("application", "", ["get", "delete"])) {
     if (isAuthorized("application", "", ["get", "delete"])) {
@@ -948,7 +948,6 @@ const LineBreak = styled.div`
 
 
 const BodyWrapper = styled.div`
 const BodyWrapper = styled.div`
   position: relative;
   position: relative;
-  overflow: hidden;
   margin-bottom: 120px;
   margin-bottom: 120px;
 `;
 `;
 
 

+ 12 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -27,6 +27,7 @@ import ConnectToJobInstructionsModal from "./jobs/ConnectToJobInstructionsModal"
 import CommandLineIcon from "assets/command-line-icon";
 import CommandLineIcon from "assets/command-line-icon";
 import CronParser from "cron-parser";
 import CronParser from "cron-parser";
 import CronPrettifier from "cronstrue";
 import CronPrettifier from "cronstrue";
+import BuildSettingsTab from "./BuildSettingsTab";
 
 
 const readableDate = (s: string) => {
 const readableDate = (s: string) => {
   let ts = new Date(s);
   let ts = new Date(s);
@@ -84,6 +85,13 @@ export const ExpandedJobChartFC: React.FC<{
     rightTabOptions.push({ label: "Settings", value: "settings" });
     rightTabOptions.push({ label: "Settings", value: "settings" });
   }
   }
 
 
+  if (chart?.git_action_config?.git_repo) {
+    rightTabOptions.push({
+      label: "Build Settings",
+      value: "build-settings",
+    });
+  }
+
   const leftTabOptions = [{ label: "Jobs", value: "jobs" }];
   const leftTabOptions = [{ label: "Jobs", value: "jobs" }];
 
 
   const processValuesToUpdateChart = (newConfig?: any) => (
   const processValuesToUpdateChart = (newConfig?: any) => (
@@ -234,6 +242,10 @@ export const ExpandedJobChartFC: React.FC<{
       );
       );
     }
     }
 
 
+    if (currentTab === "build-settings") {
+      return <BuildSettingsTab chart={chart} />;
+    }
+
     if (
     if (
       currentTab === "settings" &&
       currentTab === "settings" &&
       isAuthorized("job", "", ["get", "delete"])
       isAuthorized("job", "", ["get", "delete"])

+ 1 - 4
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentCard.tsx

@@ -41,10 +41,7 @@ const DeploymentCard: React.FC<{
         {
         {
           cluster_id: currentCluster.id,
           cluster_id: currentCluster.id,
           project_id: currentProject.id,
           project_id: currentProject.id,
-          environment_id: deployment.environment_id,
-          repo_owner: deployment.gh_repo_owner,
-          repo_name: deployment.gh_repo_name,
-          pr_number: deployment.pull_request_id,
+          deployment_id: deployment.id,
         }
         }
       )
       )
       .then(() => {
       .then(() => {

+ 6 - 2
dashboard/src/main/home/modals/LoadEnvGroupModal.tsx

@@ -30,6 +30,7 @@ type PropsType = {
   enableSyncedEnvGroups?: boolean;
   enableSyncedEnvGroups?: boolean;
   syncedEnvGroups?: PopulatedEnvGroup[];
   syncedEnvGroups?: PopulatedEnvGroup[];
   setSyncedEnvGroups?: (values: PopulatedEnvGroup) => void;
   setSyncedEnvGroups?: (values: PopulatedEnvGroup) => void;
+  normalEnvVarsOnly?: boolean;
 };
 };
 
 
 type StateType = {
 type StateType = {
@@ -132,6 +133,9 @@ export default class LoadEnvGroupModal extends Component<PropsType, StateType> {
     } else {
     } else {
       return this.state.envGroups
       return this.state.envGroups
         .filter((envGroup) => {
         .filter((envGroup) => {
+          if (!Array.isArray(this.props.syncedEnvGroups)) {
+            return true;
+          }
           return !this.props.syncedEnvGroups.find(
           return !this.props.syncedEnvGroups.find(
             (syncedEnvGroup) => syncedEnvGroup.name === envGroup.name
             (syncedEnvGroup) => syncedEnvGroup.name === envGroup.name
           );
           );
@@ -265,11 +269,11 @@ export default class LoadEnvGroupModal extends Component<PropsType, StateType> {
                   />
                   />
                 </IconWrapper>
                 </IconWrapper>
               </>
               </>
-            ) : (
+            ) : !this.props.normalEnvVarsOnly ? (
               <Helper color="#f5cb42">
               <Helper color="#f5cb42">
                 Upgrade the job template to enable sync env groups
                 Upgrade the job template to enable sync env groups
               </Helper>
               </Helper>
-            )}
+            ) : null}
           </AbsoluteWrapper>
           </AbsoluteWrapper>
         </GroupModalSections>
         </GroupModalSections>
 
 

+ 29 - 12
dashboard/src/shared/api.tsx

@@ -367,21 +367,15 @@ const deletePRDeployment = baseApi<
   {
   {
     cluster_id: number;
     cluster_id: number;
     project_id: number;
     project_id: number;
-    environment_id: number;
-    repo_owner: string;
-    repo_name: string;
-    pr_number: number;
+    deployment_id: number;
   }
   }
 >("DELETE", (pathParams) => {
 >("DELETE", (pathParams) => {
   const {
   const {
     cluster_id,
     cluster_id,
     project_id,
     project_id,
-    environment_id,
-    repo_owner,
-    repo_name,
-    pr_number,
+    deployment_id,
   } = pathParams;
   } = pathParams;
-  return `/api/projects/${project_id}/clusters/${cluster_id}/deployments/${environment_id}/${repo_owner}/${repo_name}/${pr_number}`;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/deployments/${deployment_id}`;
 });
 });
 
 
 const getNotificationConfig = baseApi<
 const getNotificationConfig = baseApi<
@@ -1721,12 +1715,35 @@ const reRunGHWorkflow = baseApi<
     git_installation_id: number;
     git_installation_id: number;
     owner: string;
     owner: string;
     name: string;
     name: string;
-    filename: string;
+    branch: string;
+    filename?: string;
+    release_name?: string;
   }
   }
 >(
 >(
   "POST",
   "POST",
-  ({ project_id, git_installation_id, owner, name, cluster_id, filename }) =>
-    `/api/projects/${project_id}/gitrepos/${git_installation_id}/${owner}/${name}/clusters/${cluster_id}/rerun_workflow?filename=${filename}`
+  ({
+    project_id,
+    git_installation_id,
+    owner,
+    name,
+    cluster_id,
+    filename,
+    release_name,
+    branch,
+  }) => {
+    const queryParams = new URLSearchParams();
+
+    queryParams.set("branch", branch);
+
+    if (release_name) {
+      queryParams.set("release_name", release_name);
+    }
+    if (filename) {
+      queryParams.set("filename", filename);
+    }
+
+    return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${owner}/${name}/clusters/${cluster_id}/rerun_workflow?${queryParams.toString()}`;
+  }
 );
 );
 
 
 const triggerPreviewEnvWorkflow = baseApi<
 const triggerPreviewEnvWorkflow = baseApi<

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

@@ -73,6 +73,9 @@ export interface ChartTypeWithExtendedConfig extends ChartType {
         normal: {
         normal: {
           [key: string]: string;
           [key: string]: string;
         };
         };
+        build: {
+          [key: string]: string;
+        };
         synced: any;
         synced: any;
       };
       };
       lifecycle: { postStart: string; preStop: string };
       lifecycle: { postStart: string; preStop: string };