Explorar el Código

Merge branch 'dev' of github.com:porter-dev/porter into dev

jnfrati hace 3 años
padre
commit
24e2fa0be3

+ 115 - 0
api/server/handlers/cluster_integration/aws/get_cluster_info.go

@@ -0,0 +1,115 @@
+package aws
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ec2"
+	"github.com/aws/aws-sdk-go/service/eks"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"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/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type GetClusterInfoHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewGetClusterInfoHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *GetClusterInfoHandler {
+	return &GetClusterInfoHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *GetClusterInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	if cluster.AWSIntegrationID == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("no AWS cluster found with cluster ID: %d", cluster.ID)))
+		return
+	}
+
+	awsInt, err := c.Repo().AWSIntegration().ReadAWSIntegration(proj.ID, cluster.AWSIntegrationID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no AWS integration found with project ID: %d and "+
+				"integration ID: %d", proj.ID, cluster.AWSIntegrationID)))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error fetching AWS integration with project ID: %d and "+
+			"integration ID: %d. Error: %w", proj.ID, cluster.AWSIntegrationID, err)))
+		return
+	}
+
+	awsSession, err := awsInt.GetSession()
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("error fetching new session for AWS with "+
+			"project ID: %d and integration ID: %d. Error: %w", proj.ID, cluster.AWSIntegrationID, err), http.StatusConflict))
+		return
+	}
+
+	clusterName := cluster.Name
+
+	if strings.HasPrefix(clusterName, "arn:aws:eks:") {
+		parts := strings.Split(clusterName, "/")
+		clusterName = parts[len(parts)-1]
+	}
+
+	awsConf := aws.NewConfig()
+
+	if awsInt.AWSRegion != "" {
+		awsConf = awsConf.WithRegion(awsInt.AWSRegion)
+	}
+
+	eksSvc := eks.New(awsSession, awsConf)
+
+	clusterInfo, err := eksSvc.DescribeCluster(&eks.DescribeClusterInput{
+		Name: &clusterName,
+	})
+
+	if err != nil || clusterInfo.Cluster == nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	ec2Svc := ec2.New(awsSession, awsConf)
+
+	subnetsInfo, err := ec2Svc.DescribeSubnets(&ec2.DescribeSubnetsInput{
+		SubnetIds: clusterInfo.Cluster.ResourcesVpcConfig.SubnetIds,
+	})
+
+	if err != nil || len(subnetsInfo.Subnets) == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	res := &types.GetAWSClusterInfoResponse{
+		Name:       clusterName,
+		K8sVersion: *clusterInfo.Cluster.Version,
+		EKSVersion: *clusterInfo.Cluster.PlatformVersion,
+	}
+
+	for _, subnet := range subnetsInfo.Subnets {
+		res.Subnets = append(res.Subnets, &types.AWSSubnet{
+			AvailabilityZone:        *subnet.AvailabilityZone,
+			AvailableIPAddressCount: *subnet.AvailableIpAddressCount,
+		})
+	}
+
+	c.WriteResult(w, r, res)
+}

+ 12 - 0
api/server/handlers/infra/forms.go

@@ -408,6 +408,18 @@ tabs:
       required: true
       placeholder: my-cluster
       variable: cluster_name
+    - type: select
+      label: EKS control plane version
+      variable: cluster_version
+      settings:
+        default: 1.20
+        options:
+        - label: 1.20
+          value: 1.20
+        - label: 1.21
+          value: 1.21
+        - label: 1.22
+          value: 1.22
     - type: number-input
       label: Minimum number of EC2 instances to create in the application autoscaling group.
       variable: min_instances

+ 7 - 7
api/server/handlers/release/update_action_config.go → api/server/handlers/release/update_git_action_config.go

@@ -13,26 +13,26 @@ import (
 	"gorm.io/gorm"
 )
 
-type UpdateActionConfigHandler struct {
+type UpdateGitActionConfigHandler struct {
 	handlers.PorterHandlerReadWriter
 }
 
-func NewUpdateActionConfigHandler(
+func NewUpdateGitActionConfigHandler(
 	config *config.Config,
 	decoderValidator shared.RequestDecoderValidator,
 	writer shared.ResultWriter,
-) *UpdateActionConfigHandler {
-	return &UpdateActionConfigHandler{
+) *UpdateGitActionConfigHandler {
+	return &UpdateGitActionConfigHandler{
 		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
 	}
 }
 
-func (c *UpdateActionConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (c *UpdateGitActionConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
 	namespace := r.Context().Value(types.NamespaceScope).(string)
 
-	request := &types.UpdateActionConfigRequest{}
+	request := &types.UpdateGitActionConfigRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, request); !ok {
 		return
@@ -49,7 +49,7 @@ func (c *UpdateActionConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 	}
 
-	actionConfig, err := c.Repo().GitActionConfig().ReadGitActionConfig(release.ID)
+	actionConfig, err := c.Repo().GitActionConfig().ReadGitActionConfig(release.GitActionConfig.ID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {

+ 17 - 0
api/server/handlers/release/update_rollback.go

@@ -127,6 +127,23 @@ func UpdateReleaseRepo(config *config.Config, release *models.Release, helmRelea
 		if err != nil {
 			return err
 		}
+
+		// determine if the git action config is set, and propagate update to that as well
+		if release.GitActionConfig != nil && release.GitActionConfig.ID != 0 {
+			gitActionConfig, err := config.Repo.GitActionConfig().ReadGitActionConfig(release.GitActionConfig.ID)
+
+			if err != nil {
+				return err
+			}
+
+			gitActionConfig.ImageRepoURI = repoStr
+
+			err = config.Repo.GitActionConfig().UpdateGitActionConfig(gitActionConfig)
+
+			if err != nil {
+				return err
+			}
+		}
 	}
 
 	return nil

+ 45 - 36
api/server/handlers/webhook/github_incoming.go

@@ -77,31 +77,36 @@ func (c *GithubIncomingWebhookHandler) processPullRequestEvent(event *github.Pul
 		return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s] error reading environment: %w", webhookID, owner, repo, err)
 	}
 
+	if event.GetPullRequest() == nil {
+		return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s] incoming webhook does not have pull request information: %w",
+			webhookID, owner, repo, err)
+	}
+
 	// create deployment on GitHub API
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
 
 	if err != nil {
-		return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d] error getting github client: %w",
-			webhookID, owner, repo, env.ID, err)
+		return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, prNumber: %d] "+
+			"error getting github client: %w", webhookID, owner, repo, env.ID, event.GetPullRequest().GetNumber(), err)
 	}
 
 	if env.Mode == "auto" && event.GetAction() == "opened" {
 		_, err := client.Actions.CreateWorkflowDispatchEventByFileName(
 			r.Context(), owner, repo, fmt.Sprintf("porter_%s_env.yml", env.Name),
 			github.CreateWorkflowDispatchEventRequest{
-				Ref: event.PullRequest.GetHead().GetRef(),
+				Ref: event.GetPullRequest().GetHead().GetRef(),
 				Inputs: map[string]interface{}{
-					"pr_number":      strconv.FormatUint(uint64(event.PullRequest.GetNumber()), 10),
-					"pr_title":       event.PullRequest.GetTitle(),
-					"pr_branch_from": event.PullRequest.GetHead().GetRef(),
-					"pr_branch_into": event.PullRequest.GetBase().GetRef(),
+					"pr_number":      strconv.FormatUint(uint64(event.GetPullRequest().GetNumber()), 10),
+					"pr_title":       event.GetPullRequest().GetTitle(),
+					"pr_branch_from": event.GetPullRequest().GetHead().GetRef(),
+					"pr_branch_into": event.GetPullRequest().GetBase().GetRef(),
 				},
 			},
 		)
 
 		if err != nil {
-			return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d] error creating workflow dispatch event: %w",
-				webhookID, owner, repo, env.ID, err)
+			return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, prNumber: %d] "+
+				"error creating workflow dispatch event: %w", webhookID, owner, repo, env.ID, event.GetPullRequest().GetNumber(), err)
 		}
 	} else if event.GetAction() == "synchronize" || event.GetAction() == "closed" {
 		depl, err := c.Repo().Environment().ReadDeploymentByGitDetails(
@@ -109,36 +114,40 @@ func (c *GithubIncomingWebhookHandler) processPullRequestEvent(event *github.Pul
 		)
 
 		if err != nil {
-			return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d] error reading deployment: %w",
-				webhookID, owner, repo, env.ID, err)
+			return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, prNumber: %d] "+
+				"error reading deployment: %w", webhookID, owner, repo, env.ID, event.GetPullRequest().GetNumber(), err)
+		}
+
+		if depl.Status == types.DeploymentStatusInactive {
+			return nil
 		}
 
-		if depl.Status != types.DeploymentStatusInactive {
-			if event.GetAction() == "synchronize" {
-				_, err := client.Actions.CreateWorkflowDispatchEventByFileName(
-					r.Context(), owner, repo, fmt.Sprintf("porter_%s_env.yml", env.Name),
-					github.CreateWorkflowDispatchEventRequest{
-						Ref: event.PullRequest.GetHead().GetRef(),
-						Inputs: map[string]interface{}{
-							"pr_number":      strconv.FormatUint(uint64(event.PullRequest.GetNumber()), 10),
-							"pr_title":       event.PullRequest.GetTitle(),
-							"pr_branch_from": event.PullRequest.GetHead().GetRef(),
-							"pr_branch_into": event.PullRequest.GetBase().GetRef(),
-						},
+		if event.GetAction() == "synchronize" {
+			_, err := client.Actions.CreateWorkflowDispatchEventByFileName(
+				r.Context(), owner, repo, fmt.Sprintf("porter_%s_env.yml", env.Name),
+				github.CreateWorkflowDispatchEventRequest{
+					Ref: event.GetPullRequest().GetHead().GetRef(),
+					Inputs: map[string]interface{}{
+						"pr_number":      strconv.FormatUint(uint64(event.GetPullRequest().GetNumber()), 10),
+						"pr_title":       event.GetPullRequest().GetTitle(),
+						"pr_branch_from": event.GetPullRequest().GetHead().GetRef(),
+						"pr_branch_into": event.GetPullRequest().GetBase().GetRef(),
 					},
-				)
-
-				if err != nil {
-					return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, deploymentID: %d] error creating workflow dispatch event: %w",
-						webhookID, owner, repo, env.ID, depl.ID, err)
-				}
-			} else {
-				err = c.deleteDeployment(r, depl, env, client)
-
-				if err != nil {
-					return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, deploymentID: %d] error deleting deployment: %w",
-						webhookID, owner, repo, env.ID, depl.ID, err)
-				}
+				},
+			)
+
+			if err != nil {
+				return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, deploymentID: %d, prNumber: %d] "+
+					"error creating workflow dispatch event: %w", webhookID, owner, repo, env.ID, depl.ID,
+					event.GetPullRequest().GetNumber(), err)
+			}
+		} else {
+			err = c.deleteDeployment(r, depl, env, client)
+
+			if err != nil {
+				return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s, environmentID: %d, deploymentID: %d, prNumber: %d] "+
+					"error deleting deployment: %w", webhookID, owner, repo, env.ID, depl.ID,
+					event.GetPullRequest().GetNumber(), err)
 			}
 		}
 	}

+ 86 - 0
api/server/router/cluster_integration.go

@@ -0,0 +1,86 @@
+package router
+
+import (
+	"github.com/go-chi/chi"
+	awsClusterInt "github.com/porter-dev/porter/api/server/handlers/cluster_integration/aws"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/router"
+	"github.com/porter-dev/porter/api/types"
+)
+
+func NewClusterIntegrationScopedRegisterer(children ...*router.Registerer) *router.Registerer {
+	return &router.Registerer{
+		GetRoutes: GetClusterIntegrationScopedRoutes,
+		Children:  children,
+	}
+}
+
+func GetClusterIntegrationScopedRoutes(
+	r chi.Router,
+	config *config.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+	children ...*router.Registerer,
+) []*router.Route {
+	routes, projPath := getClusterIntegrationRoutes(r, config, basePath, factory)
+
+	if len(children) > 0 {
+		r.Route(projPath.RelativePath, func(r chi.Router) {
+			for _, child := range children {
+				childRoutes := child.GetRoutes(r, config, basePath, factory, child.Children...)
+
+				routes = append(routes, childRoutes...)
+			}
+		})
+	}
+
+	return routes
+}
+
+func getClusterIntegrationRoutes(
+	r chi.Router,
+	config *config.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+) ([]*router.Route, *types.Path) {
+	relPath := "/integrations"
+
+	newPath := &types.Path{
+		Parent:       basePath,
+		RelativePath: relPath,
+	}
+
+	routes := make([]*router.Route, 0)
+
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/integrations/aws/info -> awsClusterInt.NewGetClusterInfoHandler
+	getAWSClusterInfoEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/aws/info",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	getAWSClusterInfoHandler := awsClusterInt.NewGetClusterInfoHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: getAWSClusterInfoEndpoint,
+		Handler:  getAWSClusterInfoHandler,
+		Router:   r,
+	})
+
+	return routes, newPath
+}

+ 1 - 1
api/server/router/release.go

@@ -833,7 +833,7 @@ func getReleaseRoutes(
 		},
 	)
 
-	updateGitActionConfigHandler := release.NewUpdateBuildConfigHandler(
+	updateGitActionConfigHandler := release.NewUpdateGitActionConfigHandler(
 		config,
 		factory.GetDecoderValidator(),
 		factory.GetResultWriter(),

+ 2 - 1
api/server/router/router.go

@@ -29,7 +29,8 @@ func NewAPIRouter(config *config.Config) *chi.Mux {
 
 	releaseRegisterer := NewReleaseScopedRegisterer()
 	namespaceRegisterer := NewNamespaceScopedRegisterer(releaseRegisterer)
-	clusterRegisterer := NewClusterScopedRegisterer(namespaceRegisterer)
+	clusterIntegrationRegisterer := NewClusterIntegrationScopedRegisterer()
+	clusterRegisterer := NewClusterScopedRegisterer(namespaceRegisterer, clusterIntegrationRegisterer)
 	infraRegisterer := NewInfraScopedRegisterer()
 	gitInstallationRegisterer := NewGitInstallationScopedRegisterer()
 	registryRegisterer := NewRegistryScopedRegisterer()

+ 13 - 0
api/types/cluster_integration.go

@@ -0,0 +1,13 @@
+package types
+
+type AWSSubnet struct {
+	AvailabilityZone        string `json:"availability_zone"`
+	AvailableIPAddressCount int64  `json:"available_ip_address_count"`
+}
+
+type GetAWSClusterInfoResponse struct {
+	Name       string       `json:"name"`
+	K8sVersion string       `json:"kubernetes_server_version"`
+	EKSVersion string       `json:"eks_version"`
+	Subnets    []*AWSSubnet `json:"subnets"`
+}

+ 1 - 1
api/types/release.go

@@ -194,6 +194,6 @@ type PatchUpdateReleaseTags struct {
 	Tags []string `json:"tags"`
 }
 
-type UpdateActionConfigRequest struct {
+type UpdateGitActionConfigRequest struct {
 	GitActionConfig *GitActionConfig `json:"git_action_config"`
 }

+ 38 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/BuildSettingsTab.tsx

@@ -20,6 +20,7 @@ import { AddCustomBuildpackForm } from "components/repo-selector/BuildpackSelect
 import { DeviconsNameList } from "assets/devicons-name-list";
 import Selector from "components/Selector";
 import BranchList from "components/repo-selector/BranchList";
+import Banner from "components/Banner";
 
 type Buildpack = {
   name: string;
@@ -78,7 +79,37 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
     () => chart?.git_action_config?.git_branch
   );
 
-  const saveNewBranch = async (newBranch: string) => {};
+  const saveNewBranch = async (newBranch: string) => {
+    if (!newBranch?.length) {
+      return;
+    }
+
+    if (newBranch === chart?.git_action_config?.git_branch) {
+      return;
+    }
+
+    const newGitActionConfig: FullActionConfigType = {
+      ...chart.git_action_config,
+      git_branch: newBranch,
+    };
+
+    try {
+      api.updateGitActionConfig(
+        "<token>",
+        {
+          git_action_config: newGitActionConfig,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          release_name: chart.name,
+          namespace: chart.namespace,
+        }
+      );
+    } catch (error) {
+      throw error;
+    }
+  };
 
   const saveBuildConfig = async (config: BuildConfig) => {
     if (config === null) {
@@ -214,6 +245,7 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
     setButtonStatus("loading");
     try {
       await saveBuildConfig(buildConfig);
+      await saveNewBranch(currentBranch);
       await saveEnvVariables(envVariables);
       setButtonStatus("successful");
     } catch (error) {
@@ -228,6 +260,7 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
     setButtonStatus("loading");
     try {
       await saveBuildConfig(buildConfig);
+      await saveNewBranch(currentBranch);
       await saveEnvVariables(envVariables);
       await triggerWorkflow();
       setButtonStatus("successful");
@@ -304,10 +337,13 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
           }}
         ></KeyValueArray>
 
-        <Heading>Select default branch</Heading>
+        <Heading>Select Default Branch</Heading>
         <Helper>
           Change the default branch the deployments will be made from.
         </Helper>
+        <Banner type="warning">
+          You must also update the deploy branch in your GitHub Action file.
+        </Banner>
         <BranchList
           actionConfig={currentActionConfig}
           setBranch={setCurrentBranch}

+ 5 - 12
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -18,6 +18,7 @@ import NotificationSettingsSection from "./NotificationSettingsSection";
 import { Link } from "react-router-dom";
 import { isDeployedFromGithub } from "shared/release/utils";
 import TagSelector from "./TagSelector";
+import { PORTER_IMAGE_TEMPLATES } from "shared/common";
 
 type PropsType = {
   currentChart: ChartType;
@@ -45,13 +46,6 @@ const SettingsSection: React.FC<PropsType> = ({
   ] = useState<string>("");
   const [loadingWebhookToken, setLoadingWebhookToken] = useState<boolean>(true);
 
-  const [action, setAction] = useState<ActionConfigType>({
-    git_repo: "",
-    image_repo_uri: "",
-    git_repo_id: 0,
-    git_branch: "",
-  });
-
   const { currentCluster, currentProject, setCurrentError } = useContext(
     Context
   );
@@ -81,7 +75,6 @@ const SettingsSection: React.FC<PropsType> = ({
           return;
         }
 
-        setAction(res.data.git_action_config);
         setWebhookToken(res.data.webhook_token);
       })
       .catch(console.log)
@@ -162,7 +155,6 @@ const SettingsSection: React.FC<PropsType> = ({
       );
       setCreateWebhookButtonStatus("successful");
       setTimeout(() => {
-        setAction(res.data.git_action_config);
         setWebhookToken(res.data.webhook_token);
       }, 500);
     } catch (err) {
@@ -214,10 +206,11 @@ const SettingsSection: React.FC<PropsType> = ({
     if (!isAuthorizedToCreateWebhook) {
       buttonStatus = "Unauthorized to create webhook token";
     }
-
+    console.log(PORTER_IMAGE_TEMPLATES.includes(selectedImageUrl));
     return (
       <>
-        {!currentChart.is_stack ? (
+        {!currentChart.is_stack &&
+        !PORTER_IMAGE_TEMPLATES.includes(selectedImageUrl) ? (
           <>
             <Heading>Source Settings</Heading>
             <Helper>Specify an image tag to use.</Helper>
@@ -227,7 +220,7 @@ const SettingsSection: React.FC<PropsType> = ({
               setSelectedImageUrl={(x: string) => setSelectedImageUrl(x)}
               setSelectedTag={(x: string) => setSelectedTag(x)}
               forceExpanded={true}
-              disableImageSelect={isDeployedFromGithub(currentChart)}
+              disableImageSelect={false}
             />
             {!loadingWebhookToken && (
               <>

+ 1 - 7
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/useJobs.ts

@@ -7,13 +7,7 @@ import { ChartType, ChartTypeWithExtendedConfig } from "shared/types";
 import yaml from "js-yaml";
 import { usePrevious } from "shared/hooks/usePrevious";
 import { useRouting } from "shared/routing";
-
-const PORTER_IMAGE_TEMPLATES = [
-  "porterdev/hello-porter-job",
-  "porterdev/hello-porter-job:latest",
-  "public.ecr.aws/o1j4x7p4/hello-porter-job",
-  "public.ecr.aws/o1j4x7p4/hello-porter-job:latest",
-];
+import { PORTER_IMAGE_TEMPLATES } from "shared/common";
 
 export const useJobs = (chart: ChartType) => {
   const { currentProject, currentCluster, setCurrentError } = useContext(

+ 18 - 2
dashboard/src/shared/api.tsx

@@ -1,9 +1,8 @@
 import { PolicyDocType } from "./auth/types";
 import { PullRequest } from "main/home/cluster-dashboard/preview-environments/types";
-import { release } from "process";
 import { baseApi } from "./baseApi";
 
-import { BuildConfig, FullActionConfigType, StorageType } from "./types";
+import { BuildConfig, FullActionConfigType } from "./types";
 import { CreateStackBody } from "main/home/cluster-dashboard/stacks/types";
 
 /**
@@ -1807,6 +1806,22 @@ const updateBuildConfig = baseApi<
     `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/buildconfig`
 );
 
+const updateGitActionConfig = baseApi<
+  {
+    git_action_config: FullActionConfigType;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    release_name: string;
+  }
+>(
+  "PATCH",
+  ({ project_id, cluster_id, namespace, release_name }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/git_action_config`
+);
+
 const reRunGHWorkflow = baseApi<
   {},
   {
@@ -2184,6 +2199,7 @@ export default {
   upgradePorterAgent,
   deletePRDeployment,
   updateBuildConfig,
+  updateGitActionConfig,
   reRunGHWorkflow,
   triggerPreviewEnvWorkflow,
   getTagsByProjectId,

+ 9 - 0
dashboard/src/shared/common.tsx

@@ -135,3 +135,12 @@ export const getIgnoreCase = (object: any, key: string) => {
     Object.keys(object).find((k) => k.toLowerCase() === key.toLowerCase())
   ];
 };
+
+export const PORTER_IMAGE_TEMPLATES = [
+  "porterdev/hello-porter-job",
+  "porterdev/hello-porter-job:latest",
+  "public.ecr.aws/o1j4x7p4/hello-porter-job",
+  "public.ecr.aws/o1j4x7p4/hello-porter-job:latest",
+  "public.ecr.aws/o1j4x7p4/hello-porter",
+  "public.ecr.aws/o1j4x7p4/hello-porter:latest",
+];

+ 17 - 12
internal/helm/agent.go

@@ -112,21 +112,26 @@ func (a *Agent) GetRelease(
 
 	if getDeps && release.Chart != nil && release.Chart.Metadata != nil {
 		for _, dep := range release.Chart.Metadata.Dependencies {
-			depExists := false
-
-			for _, currDep := range release.Chart.Dependencies() {
-				// we just case on name for now -- there might be edge cases we're missing
-				// but this will cover 99% of cases
-				if dep != nil && currDep != nil && dep.Name == currDep.Name() {
-					depExists = true
-					break
+			// only search for dependency if it passes the condition specified in Chart.yaml
+			if dep.Enabled {
+				depExists := false
+
+				for _, currDep := range release.Chart.Dependencies() {
+					// we just case on name for now -- there might be edge cases we're missing
+					// but this will cover 99% of cases
+					if dep != nil && currDep != nil && dep.Name == currDep.Name() {
+						depExists = true
+						break
+					}
 				}
-			}
 
-			if !depExists {
-				depChart, err := loader.LoadChartPublic(dep.Repository, dep.Name, dep.Version)
+				if !depExists {
+					depChart, err := loader.LoadChartPublic(dep.Repository, dep.Name, dep.Version)
+
+					if err != nil {
+						return nil, fmt.Errorf("Error retrieving chart dependency %s/%s-%s: %s", dep.Repository, dep.Name, dep.Version, err.Error())
+					}
 
-				if err == nil {
 					release.Chart.AddDependency(depChart)
 				}
 			}

+ 1 - 1
internal/helm/config.go

@@ -81,7 +81,7 @@ func GetAgentFromK8sAgent(stg string, ns string, l *logger.Logger, k8sAgent *kub
 // the underlying kubernetes.GetAgentInClusterConfig method
 func GetAgentInClusterConfig(form *Form, l *logger.Logger) (*Agent, error) {
 	// create a kubernetes agent
-	k8sAgent, err := kubernetes.GetAgentInClusterConfig()
+	k8sAgent, err := kubernetes.GetAgentInClusterConfig(form.Namespace)
 
 	if err != nil {
 		return nil, err

+ 8 - 4
internal/kubernetes/config.go

@@ -59,7 +59,7 @@ func GetDynamicClientOutOfClusterConfig(conf *OutOfClusterConfig) (dynamic.Inter
 // GetAgentOutOfClusterConfig creates a new Agent using the OutOfClusterConfig
 func GetAgentOutOfClusterConfig(conf *OutOfClusterConfig) (*Agent, error) {
 	if conf.AllowInClusterConnections && conf.Cluster.AuthMechanism == models.InCluster {
-		return GetAgentInClusterConfig()
+		return GetAgentInClusterConfig(conf.DefaultNamespace)
 	}
 
 	restConf, err := conf.ToRESTConfig()
@@ -89,14 +89,14 @@ func IsInCluster() bool {
 
 // GetAgentInClusterConfig uses the service account that kubernetes
 // gives to pods to connect
-func GetAgentInClusterConfig() (*Agent, error) {
+func GetAgentInClusterConfig(namespace string) (*Agent, error) {
 	conf, err := rest.InClusterConfig()
 
 	if err != nil {
 		return nil, err
 	}
 
-	restClientGetter := NewRESTClientGetterFromInClusterConfig(conf)
+	restClientGetter := NewRESTClientGetterFromInClusterConfig(conf, namespace)
 	clientset, err := kubernetes.NewForConfig(conf)
 
 	return &Agent{restClientGetter, clientset}, nil
@@ -419,9 +419,13 @@ func (conf *OutOfClusterConfig) setTokenCache(token string, expiry time.Time) er
 
 // NewRESTClientGetterFromInClusterConfig returns a RESTClientGetter using
 // default values set from the *rest.Config
-func NewRESTClientGetterFromInClusterConfig(conf *rest.Config) genericclioptions.RESTClientGetter {
+func NewRESTClientGetterFromInClusterConfig(conf *rest.Config, namespace string) genericclioptions.RESTClientGetter {
 	cfs := genericclioptions.NewConfigFlags(false)
 
+	if namespace != "" {
+		cfs.Namespace = &namespace
+	}
+
 	cfs.ClusterName = &conf.ServerName
 	cfs.Insecure = &conf.Insecure
 	cfs.APIServer = &conf.Host

+ 8 - 1
internal/kubernetes/local/kubeconfig.go

@@ -80,7 +80,14 @@ func GetSelfAgentFromFileConfig(kubeconfigPath string) (*kubernetes.Agent, error
 		return nil, err
 	}
 
-	restClientGetter := kubernetes.NewRESTClientGetterFromInClusterConfig(restConf)
+	var namespace string
+	cmdConfNamespace, _, err := cmdConf.Namespace()
+
+	if err == nil && cmdConfNamespace != "" {
+		namespace = cmdConfNamespace
+	}
+
+	restClientGetter := kubernetes.NewRESTClientGetterFromInClusterConfig(restConf, namespace)
 	clientset, err := k8s.NewForConfig(restConf)
 
 	return &kubernetes.Agent{

+ 1 - 1
provisioner/server/config/config.go

@@ -248,7 +248,7 @@ func getProvisionerAgent(conf *ProvisionerConf) (*kubernetes.Agent, error) {
 		return nil, fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
 	}
 
-	agent, _ := kubernetes.GetAgentInClusterConfig()
+	agent, _ := kubernetes.GetAgentInClusterConfig(conf.ProvisionerJobNamespace)
 
 	return agent, nil
 }