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

Merge pull request #2173 from porter-dev/staging

Modify branch/image selector + new cluster info endpoint -> production
abelanger5 3 лет назад
Родитель
Сommit
98a13bc927

+ 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

+ 57 - 0
api/server/handlers/release/update_git_action_config.go

@@ -0,0 +1,57 @@
+package release
+
+import (
+	"net/http"
+
+	"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 UpdateGitActionConfigHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewUpdateGitActionConfigHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UpdateGitActionConfigHandler {
+	return &UpdateGitActionConfigHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *UpdateGitActionConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	release, _ := r.Context().Value(types.ReleaseScope).(*models.Release)
+
+	request := &types.UpdateGitActionConfigRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	actionConfig, err := c.Repo().GitActionConfig().ReadGitActionConfig(release.GitActionConfig.ID)
+
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+	}
+
+	actionConfig.GitBranch = request.GitActionConfig.GitBranch
+
+	if err := c.Repo().GitActionConfig().UpdateGitActionConfig(actionConfig); err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+}

+ 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

+ 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
+}

+ 31 - 0
api/server/router/release.go

@@ -815,5 +815,36 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
+	// PATCH /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/git_action_config -> release.NewUpdateGitActionConfigHandler
+	updateGitActionConfigEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbUpdate,
+			Method: types.HTTPVerbPatch,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: "/releases/{name}/{version}/git_action_config",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+				types.ReleaseScope,
+			},
+		},
+	)
+
+	updateGitActionConfigHandler := release.NewUpdateGitActionConfigHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: updateGitActionConfigEndpoint,
+		Handler:  updateGitActionConfigHandler,
+		Router:   r,
+	})
+
 	return routes, newPath
 }

+ 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"`
+}

+ 10 - 0
api/types/release.go

@@ -193,3 +193,13 @@ type GetReleaseAllPodsResponse []v1.Pod
 type PatchUpdateReleaseTags struct {
 	Tags []string `json:"tags"`
 }
+
+type PartialGitActionConfig struct {
+	// The branch to use for the git repository
+	// required: true
+	GitBranch string `json:"branch" form:"required"`
+}
+
+type UpdateGitActionConfigRequest struct {
+	GitActionConfig *PartialGitActionConfig `json:"git_action_config"`
+}

+ 12 - 14
cli/cmd/apply.go

@@ -405,22 +405,20 @@ func (d *Driver) applyApplication(resource *models.Resource, client *api.Client,
 			Name:      resource.Name,
 		})
 
-		if err != nil {
-			if appConfig.OnlyCreate {
-				err = client.DeleteRelease(
-					context.Background(),
-					d.target.Project,
-					d.target.Cluster,
-					d.target.Namespace,
-					resource.Name,
-				)
+		if err != nil && appConfig.OnlyCreate {
+			deleteJobErr := client.DeleteRelease(
+				context.Background(),
+				d.target.Project,
+				d.target.Cluster,
+				d.target.Namespace,
+				resource.Name,
+			)
 
-				if err != nil {
-					return nil, fmt.Errorf("error deleting job %s with waitForJob and onlyCreate set to true: %w",
-						resource.Name, err)
-				}
+			if deleteJobErr != nil {
+				return nil, fmt.Errorf("error deleting job %s with waitForJob and onlyCreate set to true: %w",
+					resource.Name, deleteJobErr)
 			}
-
+		} else if err != nil {
 			return nil, fmt.Errorf("error waiting for job %s: %w", resource.Name, err)
 		}
 	}

+ 16 - 5
dashboard/src/components/repo-selector/BranchList.tsx

@@ -12,9 +12,14 @@ import SearchBar from "../SearchBar";
 type Props = {
   actionConfig: ActionConfigType;
   setBranch: (x: string) => void;
+  currentBranch?: string;
 };
 
-const BranchList: React.FC<Props> = ({ setBranch, actionConfig }) => {
+const BranchList: React.FC<Props> = ({
+  setBranch,
+  actionConfig,
+  currentBranch,
+}) => {
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(false);
   const [branches, setBranches] = useState<string[]>([]);
@@ -108,6 +113,9 @@ const BranchList: React.FC<Props> = ({ setBranch, actionConfig }) => {
         >
           <img src={branch_icon} alt={"branch icon"} />
           {branch}
+          {currentBranch === branch && (
+            <i className="material-icons-outlined">check</i>
+          )}
         </BranchName>
       );
     });
@@ -144,10 +152,6 @@ const BranchName = styled.div`
   background: #ffffff11;
   :hover {
     background: #ffffff22;
-
-    > i {
-      background: #ffffff22;
-    }
   }
 
   > img {
@@ -156,6 +160,13 @@ const BranchName = styled.div`
     margin-left: 12px;
     margin-right: 12px;
   }
+
+  > i {
+    margin-left: auto;
+    margin-right: 15px;
+    font-size: 18px;
+    color: #03b503;
+  }
 `;
 
 const LoadingWrapper = styled.div`

+ 54 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/BuildSettingsTab.tsx

@@ -18,6 +18,9 @@ import yaml from "js-yaml";
 import { AxiosError } from "axios";
 import { AddCustomBuildpackForm } from "components/repo-selector/BuildpackSelection";
 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;
@@ -72,6 +75,42 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
     "loading" | "successful" | string
   >("");
 
+  const [currentBranch, setCurrentBranch] = useState(
+    () => chart?.git_action_config?.git_branch
+  );
+
+  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) {
       return;
@@ -206,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) {
@@ -220,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");
@@ -296,6 +337,19 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
           }}
         ></KeyValueArray>
 
+        <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}
+          currentBranch={currentBranch}
+        />
+
         {!chart.git_action_config.dockerfile_path ? (
           <>
             <Heading>Buildpack Settings</Heading>

+ 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(

+ 21 - 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,25 @@ const updateBuildConfig = baseApi<
     `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/buildconfig`
 );
 
+const updateGitActionConfig = baseApi<
+  {
+    git_action_config: {
+      git_branch: string;
+    };
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    release_name: string;
+    revision?: 0; // Always update latest
+  }
+>(
+  "PATCH",
+  ({ project_id, cluster_id, namespace, release_name, revision = 0 }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/${revision}/git_action_config`
+);
+
 const reRunGHWorkflow = baseApi<
   {},
   {
@@ -2182,6 +2200,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 - 0
internal/repository/git_action_config.go

@@ -7,4 +7,5 @@ import "github.com/porter-dev/porter/internal/models"
 type GitActionConfigRepository interface {
 	CreateGitActionConfig(gr *models.GitActionConfig) (*models.GitActionConfig, error)
 	ReadGitActionConfig(id uint) (*models.GitActionConfig, error)
+	UpdateGitActionConfig(gr *models.GitActionConfig) error
 }

+ 4 - 0
internal/repository/gorm/git_action_config.go

@@ -49,3 +49,7 @@ func (repo *GitActionConfigRepository) ReadGitActionConfig(id uint) (*models.Git
 
 	return ga, nil
 }
+
+func (repo *GitActionConfigRepository) UpdateGitActionConfig(ga *models.GitActionConfig) error {
+	return repo.db.Save(ga).Error
+}

+ 14 - 0
internal/repository/test/git_action_config.go

@@ -46,3 +46,17 @@ func (repo *GitActionConfigRepository) ReadGitActionConfig(id uint) (*models.Git
 	index := int(id - 1)
 	return repo.gitActionConfigs[index], nil
 }
+
+func (repo *GitActionConfigRepository) UpdateGitActionConfig(gac *models.GitActionConfig) error {
+	if !repo.canQuery {
+		return errors.New("Cannot write database")
+	}
+
+	if int(gac.ID-1) >= len(repo.gitActionConfigs) || repo.gitActionConfigs[gac.ID-1] == nil {
+		return gorm.ErrRecordNotFound
+	}
+
+	index := int(gac.ID - 1)
+	repo.gitActionConfigs[index] = gac
+	return nil
+}

+ 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
 }