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

Merge branch 'master' into nafees/porter-agent-v2-endpoints

Nicolas Frati 4 лет назад
Родитель
Сommit
d8a1e58d25

+ 37 - 23
dashboard/src/components/repo-selector/RepoList.tsx

@@ -20,6 +20,7 @@ type Props = {
   setActionConfig: (x: ActionConfigType) => void;
   userId?: number;
   readOnly: boolean;
+  filteredRepos?: string[];
 };
 
 const RepoList: React.FC<Props> = ({
@@ -27,6 +28,7 @@ const RepoList: React.FC<Props> = ({
   setActionConfig,
   userId,
   readOnly,
+  filteredRepos,
 }) => {
   const [repos, setRepos] = useState<RepoType[]>([]);
   const [repoLoading, setRepoLoading] = useState(true);
@@ -123,7 +125,6 @@ const RepoList: React.FC<Props> = ({
       });
   }, []);
 
-
   // clear out actionConfig and SelectedRepository if new search is performed
   useEffect(() => {
     setActionConfig({
@@ -132,15 +133,15 @@ const RepoList: React.FC<Props> = ({
       git_branch: null,
       git_repo_id: 0,
     });
-    setSelectedRepo(null)
-  }, [searchFilter])
+    setSelectedRepo(null);
+  }, [searchFilter]);
 
   const setRepo = (x: RepoType) => {
     let updatedConfig = actionConfig;
     updatedConfig.git_repo = x.FullName;
     updatedConfig.git_repo_id = x.GHRepoID;
     setActionConfig(updatedConfig);
-    setSelectedRepo(x.FullName)
+    setSelectedRepo(x.FullName);
   };
 
   const renderRepoList = () => {
@@ -191,6 +192,9 @@ const RepoList: React.FC<Props> = ({
       return <LoadingWrapper>No matching Github repos found.</LoadingWrapper>;
     } else {
       return results.map((repo: RepoType, i: number) => {
+        const shouldDisable = !!filteredRepos?.find(
+          (filteredRepo) => repo.FullName === filteredRepo
+        );
         return (
           <RepoName
             key={i}
@@ -198,9 +202,11 @@ const RepoList: React.FC<Props> = ({
             lastItem={i === repos.length - 1}
             onClick={() => setRepo(repo)}
             readOnly={readOnly}
+            disabled={shouldDisable}
           >
             <img src={github} alt={"github icon"} />
             {repo.FullName}
+            {shouldDisable && ` - This repo was already added`}
           </RepoName>
         );
       });
@@ -237,32 +243,40 @@ const RepoListWrapper = styled.div`
   overflow-y: auto;
 `;
 
-const RepoName = styled.div`
+type RepoNameProps = {
+  lastItem: boolean;
+  isSelected: boolean;
+  readOnly: boolean;
+  disabled: boolean;
+};
+
+const RepoName = styled.div<RepoNameProps>`
   display: flex;
   width: 100%;
   font-size: 13px;
   border-bottom: 1px solid
-    ${(props: { lastItem: boolean; isSelected: boolean; readOnly: boolean }) =>
-      props.lastItem ? "#00000000" : "#606166"};
-  color: #ffffff;
+    ${(props) => (props.lastItem ? "#00000000" : "#606166")};
+  color: ${(props) => (props.disabled ? "#ffffff88" : "#ffffff")};
   user-select: none;
   align-items: center;
   padding: 10px 0px;
-  cursor: ${(props: {
-    lastItem: boolean;
-    isSelected: boolean;
-    readOnly: boolean;
-  }) => (props.readOnly ? "default" : "pointer")};
-  pointer-events: ${(props: {
-    lastItem: boolean;
-    isSelected: boolean;
-    readOnly: boolean;
-  }) => (props.readOnly ? "none" : "auto")};
-  background: ${(props: {
-    lastItem: boolean;
-    isSelected: boolean;
-    readOnly: boolean;
-  }) => (props.isSelected ? "#ffffff22" : "#ffffff11")};
+  cursor: ${(props) =>
+    props.readOnly || props.disabled ? "default" : "pointer"};
+  pointer-events: ${(props) =>
+    props.readOnly || props.disabled ? "none" : "auto"};
+
+  ${(props) => {
+    if (props.disabled) {
+      return "";
+    }
+
+    if (props.isSelected) {
+      return `background: #ffffff22;`;
+    }
+
+    return `background: #ffffff11;`;
+  }}
+
   :hover {
     background: #ffffff22;
 

+ 20 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/EnvironmentList.tsx

@@ -200,7 +200,7 @@ const EnvironmentList = () => {
         if (!Array.isArray(data)) {
           throw Error("Data is not an array");
         }
-        setEnvironmentList(data);
+        setDeploymentList(data);
       })
       .catch((err) => {
         setHasError(true);
@@ -226,7 +226,7 @@ const EnvironmentList = () => {
     );
   }
 
-  if (isLoading || !hasPermissionsLoaded) {
+  if (!hasPermissionsLoaded) {
     return (
       <Placeholder>
         <Loading />
@@ -252,6 +252,14 @@ const EnvironmentList = () => {
   }
 
   let renderDeploymentList = () => {
+    if (isLoading) {
+      return (
+        <Placeholder>
+          <Loading />
+        </Placeholder>
+      );
+    }
+
     if (!deploymentList.length) {
       return (
         <Placeholder>
@@ -262,7 +270,16 @@ const EnvironmentList = () => {
     }
 
     return deploymentList.map((d) => {
-      return <EnvironmentCard deployment={d} />;
+      const environment = environmentList?.find((e) => {
+        return e.id === d.environment_id;
+      });
+      return (
+        <EnvironmentCard
+          deployment={d}
+          environment={environment}
+          onDelete={handleRefresh}
+        />
+      );
     });
   };
 

+ 28 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/components/ConnectNewRepo.tsx

@@ -12,15 +12,15 @@ import styled from "styled-components";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { useRouting } from "shared/routing";
-
-const porterYamlDocsLink =
-  "https://docs.porter.run/preview-environments/porter-yaml-reference";
+import { Environment } from "../EnvironmentList";
 
 const ConnectNewRepo: React.FC = () => {
   const { currentProject, currentCluster, setCurrentError } = useContext(
     Context
   );
   const [repo, setRepo] = useState(null);
+  const [filteredRepos, setFilteredRepos] = useState<string[]>([]);
+
   const [status, setStatus] = useState(null);
   const { pushFiltered } = useRouting();
 
@@ -36,6 +36,30 @@ const ConnectNewRepo: React.FC = () => {
 
   const { url } = useRouteMatch();
 
+  useEffect(() => {
+    api
+      .listEnvironments<Environment[]>(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+        }
+      )
+      .then(({ data }) => {
+        console.log("github account", data);
+
+        if (!Array.isArray(data)) {
+          throw Error("Data is not an array");
+        }
+        const newFilteredRepos = data.map((env) => {
+          return `${env.git_repo_owner}/${env.git_repo_name}`;
+        });
+        setFilteredRepos(newFilteredRepos || []);
+      })
+      .catch(() => {});
+  }, []);
+
   const addRepo = () => {
     let [owner, repoName] = repo.split("/");
     setStatus("loading");
@@ -84,6 +108,7 @@ const ConnectNewRepo: React.FC = () => {
           setRepo(a.git_repo);
         }}
         readOnly={false}
+        filteredRepos={filteredRepos}
       />
       <HelperContainer>
         Note: you will need to add a <CodeBlock>porter.yaml</CodeBlock> file to

+ 121 - 20
dashboard/src/main/home/cluster-dashboard/dashboard/preview-environments/components/EnvironmentCard.tsx

@@ -1,20 +1,51 @@
 import React, { useState } from "react";
-import styled from "styled-components";
-import { PRDeployment } from "../EnvironmentList";
+import styled, { keyframes } from "styled-components";
+import { Environment, PRDeployment } from "../EnvironmentList";
 import pr_icon from "assets/pull_request_icon.svg";
 import { integrationList } from "shared/common";
 import { useRouteMatch } from "react-router";
 import DynamicLink from "components/DynamicLink";
 import { capitalize, readableDate } from "shared/string_utils";
+import api from "shared/api";
+import { useContext } from "react";
+import { Context } from "shared/Context";
 
-const EnvironmentCard: React.FC<{ deployment: PRDeployment }> = ({
-  deployment,
-}) => {
+const EnvironmentCard: React.FC<{
+  deployment: PRDeployment;
+  environment: Environment;
+  onDelete: () => void;
+}> = ({ deployment, environment, onDelete }) => {
+  const { setCurrentOverlay } = useContext(Context);
   const [showRepoTooltip, setShowRepoTooltip] = useState(false);
+  const [isDeleting, setIsDeleting] = useState(false);
   const { url: currentUrl } = useRouteMatch();
 
   let repository = `${deployment.gh_repo_owner}/${deployment.gh_repo_name}`;
 
+  const deleteDeployment = () => {
+    setIsDeleting(true);
+
+    api
+      .deletePRDeployment(
+        "<token>",
+        {
+          namespace: deployment.namespace,
+        },
+        {
+          cluster_id: environment.cluster_id,
+          project_id: environment.project_id,
+          git_installation_id: environment.git_installation_id,
+          git_repo_owner: environment.git_repo_owner,
+          git_repo_name: environment.git_repo_name,
+        }
+      )
+      .then(() => {
+        setIsDeleting(false);
+        onDelete();
+        setCurrentOverlay(null);
+      });
+  };
+
   return (
     <EnvironmentCardWrapper key={deployment.id}>
       <DataContainer>
@@ -52,27 +83,97 @@ const EnvironmentCard: React.FC<{ deployment: PRDeployment }> = ({
         </Flex>
       </DataContainer>
       <Flex>
-        <RowButton
-          to={`${currentUrl}/pr-env-detail/${deployment.namespace}?environment_id=${deployment.environment_id}`}
-          key={deployment.id}
-        >
-          <i className="material-icons-outlined">info</i>
-          Details
-        </RowButton>
-        <RowButton
-          to={deployment.subdomain}
-          key={deployment.subdomain}
-          target="_blank"
-        >
-          <i className="material-icons">open_in_new</i>
-          View Live
-        </RowButton>
+        {!isDeleting ? (
+          <>
+            {deployment.status !== "creating" && (
+              <>
+                <RowButton
+                  to={`${currentUrl}/pr-env-detail/${deployment.namespace}?environment_id=${deployment.environment_id}`}
+                  key={deployment.id}
+                >
+                  <i className="material-icons-outlined">info</i>
+                  Details
+                </RowButton>
+                <RowButton
+                  to={deployment.subdomain}
+                  key={deployment.subdomain}
+                  target="_blank"
+                >
+                  <i className="material-icons">open_in_new</i>
+                  View Live
+                </RowButton>
+              </>
+            )}
+            <RowButton
+              to={"#"}
+              key={deployment.subdomain}
+              onClick={() =>
+                setCurrentOverlay({
+                  message: `Are you sure you want to delete this deployment?`,
+                  onYes: deleteDeployment,
+                  onNo: () => setCurrentOverlay(null),
+                })
+              }
+            >
+              <i className="material-icons">delete</i>
+              Delete
+            </RowButton>
+          </>
+        ) : (
+          <DeleteMessage>
+            Deleting
+            <Dot delay="0s" />
+            <Dot delay="0.1s" />
+            <Dot delay="0.2s" />
+          </DeleteMessage>
+        )}
       </Flex>
     </EnvironmentCardWrapper>
   );
 };
 
 export default EnvironmentCard;
+
+const DeleteMessage = styled.div`
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+`;
+
+export const DissapearAnimation = keyframes`
+  0% { 
+    background-color: #ffffff; 
+  }
+
+  25% {
+    background-color: #ffffff50;
+  }
+
+  50% { 
+    background-color: none;
+  }
+
+  75% {
+    background-color: #ffffff50;
+  }
+
+  100% { 
+    background-color: #ffffff;
+  }
+`;
+
+const Dot = styled.div`
+  background-color: black;
+  border-radius: 50%;
+  width: 5px;
+  height: 5px;
+  margin: 0 0.25rem;
+  margin-bottom: 2px;
+  //Animation
+  animation: ${DissapearAnimation} 0.5s linear infinite;
+  animation-delay: ${(props: { delay: string }) => props.delay};
+`;
+
 const Flex = styled.div`
   display: flex;
   align-items: center;

+ 1 - 1
dashboard/src/main/home/project-settings/InviteList.tsx

@@ -252,7 +252,7 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
                 onClick={() =>
                   replaceInvite(
                     row.values.email,
-                    row.values.id,
+                    row.original.id,
                     row.values.kind
                   )
                 }

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

@@ -338,6 +338,28 @@ const getPRDeployment = baseApi<
   return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${git_repo_owner}/${git_repo_name}/clusters/${cluster_id}/deployment`;
 });
 
+const deletePRDeployment = baseApi<
+  {
+    namespace: string;
+  },
+  {
+    cluster_id: number;
+    project_id: number;
+    git_installation_id: number;
+    git_repo_owner: string;
+    git_repo_name: string;
+  }
+>("DELETE", (pathParams) => {
+  const {
+    cluster_id,
+    project_id,
+    git_installation_id,
+    git_repo_owner,
+    git_repo_name,
+  } = pathParams;
+  return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${git_repo_owner}/${git_repo_name}/clusters/${cluster_id}/deployment`;
+});
+
 const getNotificationConfig = baseApi<
   {},
   {
@@ -1803,4 +1825,5 @@ export default {
   getIncidentById,
   getIncidentLogsByLogId,
   upgradePorterAgent,
+  deletePRDeployment,
 };

+ 93 - 5
internal/integrations/ci/actions/preview.go

@@ -73,6 +73,12 @@ func SetupEnv(opts *EnvOpts) error {
 		return err
 	}
 
+	deleteWorkflowYAML, err := getPreviewDeleteActionYAML(opts)
+
+	if err != nil {
+		return err
+	}
+
 	_, err = commitGithubFile(
 		opts.Client,
 		fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
@@ -84,12 +90,57 @@ func SetupEnv(opts *EnvOpts) error {
 	)
 
 	if err != nil {
-		return err
-	}
-
-	deleteWorkflowYAML, err := getPreviewDeleteActionYAML(opts)
+		if strings.Contains(err.Error(), "409 Could not create file") {
+			// possibly a write-protected branch
+			err = createPorterPreviewBranch(opts, defaultBranch)
+
+			if err != nil {
+				return fmt.Errorf("write-protected branch %s. Error creating porter-preview branch: %w", defaultBranch, err)
+			}
+
+			_, err = commitGithubFile(
+				opts.Client,
+				fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
+				applyWorkflowYAML,
+				opts.GitRepoOwner,
+				opts.GitRepoName,
+				"porter-preview",
+				false,
+			)
+
+			if err != nil {
+				return fmt.Errorf("write-protected branch %s. Error committing to porter-preview branch: %w", defaultBranch, err)
+			}
+
+			_, err = commitGithubFile(
+				opts.Client,
+				fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
+				deleteWorkflowYAML,
+				opts.GitRepoOwner,
+				opts.GitRepoName,
+				"porter-preview",
+				false,
+			)
+
+			if err != nil {
+				return fmt.Errorf("write-protected branch %s. Error committing to porter-preview branch: %w", defaultBranch, err)
+			}
+
+			pr, _, err := opts.Client.PullRequests.Create(
+				context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.NewPullRequest{
+					Title: github.String("Merge Porter preview environment Github Actions workflow files"),
+					Base:  github.String(defaultBranch),
+					Head:  github.String("porter-preview"),
+				},
+			)
+
+			if err != nil {
+				return err
+			}
+
+			return fmt.Errorf("write-protected branch %s. Please merge %s to enable preview environment for your repository", defaultBranch, pr.GetURL())
+		}
 
-	if err != nil {
 		return err
 	}
 
@@ -227,3 +278,40 @@ func getPreviewDeleteActionYAML(opts *EnvOpts) ([]byte, error) {
 
 	return yaml.Marshal(actionYAML)
 }
+
+func createPorterPreviewBranch(opts *EnvOpts, defaultBranch string) error {
+	_, resp, err := opts.Client.Repositories.GetBranch(
+		context.Background(), opts.GitRepoOwner, opts.GitRepoName, "porter-preview", false,
+	)
+
+	if resp.StatusCode == http.StatusNotFound {
+		branch, _, err := opts.Client.Repositories.GetBranch(
+			context.Background(), opts.GitRepoOwner, opts.GitRepoName, defaultBranch, false,
+		)
+
+		if err != nil {
+			return err
+		}
+
+		_, _, err = opts.Client.Git.CreateRef(
+			context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.Reference{
+				Ref: github.String("refs/heads/porter-preview"),
+				Object: &github.GitObject{
+					SHA: branch.Commit.SHA,
+				},
+			},
+		)
+
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 99 - 26
internal/kubernetes/prometheus/metrics.go

@@ -295,109 +295,182 @@ func getSelectionRegex(kind, name string) (string, error) {
 }
 
 func createHPAAbsoluteCPUThresholdQuery(cpuMetricName, metricName, podSelectionRegex, hpaName, namespace, appLabel, hpaMetricName string) string {
-	kubeMetricsPodSelector := getKubeMetricsPodSelector(podSelectionRegex, namespace)
+	kubeMetricsPodSelectorOne := getKubeMetricsPodSelector(podSelectionRegex, namespace, "namespace")
+	kubeMetricsPodSelectorTwo := getKubeMetricsPodSelector(podSelectionRegex, namespace, "exported_namespace")
 
-	kubeMetricsHPASelector := fmt.Sprintf(
+	kubeMetricsHPASelectorOne := fmt.Sprintf(
 		`%s="%s",namespace="%s",metric_name="cpu",metric_target_type="utilization"`,
 		hpaMetricName,
 		hpaName,
 		namespace,
 	)
 
+	kubeMetricsHPASelectorTwo := fmt.Sprintf(
+		`%s="%s",exported_namespace="%s",metric_name="cpu",metric_target_type="utilization"`,
+		hpaMetricName,
+		hpaName,
+		namespace,
+	)
+
 	if cpuMetricName == "kube_pod_container_resource_requests" {
-		kubeMetricsPodSelector += `,resource="cpu",unit="core"`
+		kubeMetricsPodSelectorOne += `,resource="cpu",unit="core"`
+		kubeMetricsPodSelectorTwo += `,resource="cpu",unit="core"`
 	}
 
 	// the kube-state-metrics queries are less prone to error if the field app_kubernetes_io_instance is matched
 	// as well
 	if appLabel != "" {
-		kubeMetricsPodSelector += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
-		kubeMetricsHPASelector += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsPodSelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsPodSelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsHPASelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsHPASelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
 	}
 
-	requestCPU := fmt.Sprintf(
+	requestCPUOne := fmt.Sprintf(
+		`sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
+		hpaMetricName,
+		cpuMetricName,
+		kubeMetricsPodSelectorOne,
+		hpaMetricName,
+		hpaName,
+	)
+
+	targetCPUUtilThresholdOne := fmt.Sprintf(
+		`%s{%s} / 100`,
+		metricName,
+		kubeMetricsHPASelectorOne,
+	)
+
+	requestCPUTwo := fmt.Sprintf(
 		`sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
 		hpaMetricName,
 		cpuMetricName,
-		kubeMetricsPodSelector,
+		kubeMetricsPodSelectorTwo,
 		hpaMetricName,
 		hpaName,
 	)
 
-	targetCPUUtilThreshold := fmt.Sprintf(
+	targetCPUUtilThresholdTwo := fmt.Sprintf(
 		`%s{%s} / 100`,
 		metricName,
-		kubeMetricsHPASelector,
+		kubeMetricsHPASelectorTwo,
 	)
 
-	return fmt.Sprintf(`%s * on(%s) %s`, requestCPU, hpaMetricName, targetCPUUtilThreshold)
+	return fmt.Sprintf(
+		`(%s * on(%s) %s) or (%s * on(%s) %s)`,
+		requestCPUOne, hpaMetricName, targetCPUUtilThresholdOne,
+		requestCPUTwo, hpaMetricName, targetCPUUtilThresholdTwo,
+	)
 }
 
 func createHPAAbsoluteMemoryThresholdQuery(memMetricName, metricName, podSelectionRegex, hpaName, namespace, appLabel, hpaMetricName string) string {
-	kubeMetricsPodSelector := getKubeMetricsPodSelector(podSelectionRegex, namespace)
+	kubeMetricsPodSelectorOne := getKubeMetricsPodSelector(podSelectionRegex, namespace, "namespace")
+	kubeMetricsPodSelectorTwo := getKubeMetricsPodSelector(podSelectionRegex, namespace, "exported_namespace")
 
-	kubeMetricsHPASelector := fmt.Sprintf(
+	kubeMetricsHPASelectorOne := fmt.Sprintf(
 		`%s="%s",namespace="%s",metric_name="memory",metric_target_type="utilization"`,
 		hpaMetricName,
 		hpaName,
 		namespace,
 	)
 
+	kubeMetricsHPASelectorTwo := fmt.Sprintf(
+		`%s="%s",exported_namespace="%s",metric_name="memory",metric_target_type="utilization"`,
+		hpaMetricName,
+		hpaName,
+		namespace,
+	)
+
 	if memMetricName == "kube_pod_container_resource_requests" {
-		kubeMetricsPodSelector += `,resource="memory",unit="byte"`
+		kubeMetricsPodSelectorOne += `,resource="memory",unit="byte"`
+		kubeMetricsPodSelectorTwo += `,resource="memory",unit="byte"`
 	}
 
 	// the kube-state-metrics queries are less prone to error if the field app_kubernetes_io_instance is matched
 	// as well
 	if appLabel != "" {
-		kubeMetricsPodSelector += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
-		kubeMetricsHPASelector += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsPodSelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsPodSelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsHPASelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsHPASelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
 	}
 
-	requestMem := fmt.Sprintf(
+	requestMemOne := fmt.Sprintf(
 		`sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
 		hpaMetricName,
 		memMetricName,
-		kubeMetricsPodSelector,
+		kubeMetricsPodSelectorOne,
 		hpaMetricName,
 		hpaName,
 	)
 
-	targetMemUtilThreshold := fmt.Sprintf(
+	targetMemUtilThresholdOne := fmt.Sprintf(
 		`%s{%s} / 100`,
 		metricName,
-		kubeMetricsHPASelector,
+		kubeMetricsHPASelectorOne,
 	)
 
-	return fmt.Sprintf(`%s * on(%s) %s`, requestMem, hpaMetricName, targetMemUtilThreshold)
+	requestMemTwo := fmt.Sprintf(
+		`sum by (%s) (label_replace(%s{%s},"%s", "%s", "", ""))`,
+		hpaMetricName,
+		memMetricName,
+		kubeMetricsPodSelectorTwo,
+		hpaMetricName,
+		hpaName,
+	)
+
+	targetMemUtilThresholdTwo := fmt.Sprintf(
+		`%s{%s} / 100`,
+		metricName,
+		kubeMetricsHPASelectorTwo,
+	)
+
+	fmt.Println("query is:")
+
+	return fmt.Sprintf(
+		`(%s * on(%s) %s) or (%s * on(%s) %s)`,
+		requestMemOne, hpaMetricName, targetMemUtilThresholdOne,
+		requestMemTwo, hpaMetricName, targetMemUtilThresholdTwo,
+	)
 }
 
-func getKubeMetricsPodSelector(podSelectionRegex, namespace string) string {
+func getKubeMetricsPodSelector(podSelectionRegex, namespace, namespaceLabel string) string {
 	return fmt.Sprintf(
-		`pod=~"%s",namespace="%s",container!="POD",container!=""`,
+		`pod=~"%s",%s="%s",container!="POD",container!=""`,
 		podSelectionRegex,
+		namespaceLabel,
 		namespace,
 	)
 }
 
 func createHPACurrentReplicasQuery(metricName, hpaName, namespace, appLabel, hpaMetricName string) string {
-	kubeMetricsHPASelector := fmt.Sprintf(
+	kubeMetricsHPASelectorOne := fmt.Sprintf(
 		`%s="%s",namespace="%s"`,
 		hpaMetricName,
 		hpaName,
 		namespace,
 	)
 
+	kubeMetricsHPASelectorTwo := fmt.Sprintf(
+		`%s="%s",exported_namespace="%s"`,
+		hpaMetricName,
+		hpaName,
+		namespace,
+	)
+
 	// the kube-state-metrics queries are less prone to error if the field app_kubernetes_io_instance is matched
 	// as well
 	if appLabel != "" {
-		kubeMetricsHPASelector += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsHPASelectorOne += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
+		kubeMetricsHPASelectorTwo += fmt.Sprintf(`,app_kubernetes_io_instance="%s"`, appLabel)
 	}
 
 	return fmt.Sprintf(
-		`%s{%s}`,
+		`(%s{%s}) or (%s{%s})`,
+		metricName,
+		kubeMetricsHPASelectorOne,
 		metricName,
-		kubeMetricsHPASelector,
+		kubeMetricsHPASelectorTwo,
 	)
 }
 

+ 8 - 0
provisioner/integrations/redis_stream/global.go

@@ -126,12 +126,14 @@ func GlobalStreamListener(
 			id, exists := msg.Values["id"]
 
 			if !exists {
+				config.Logger.Debug().Msg("skipping message parsing as id does not exist")
 				continue
 			}
 
 			workspaceID, ok := id.(string)
 
 			if !ok {
+				config.Logger.Debug().Msg("skipping message parsing as workspace id does not exist")
 				continue
 			}
 
@@ -139,24 +141,30 @@ func GlobalStreamListener(
 			name, err := models.ParseWorkspaceID(workspaceID)
 
 			if err != nil {
+				config.Logger.Debug().Msg(fmt.Sprintf("could not parse workspace ID: %s %s", workspaceID, err.Error()))
 				continue
 			}
 
+			config.Logger.Debug().Msg(fmt.Sprintf("reading infra %d and operation %s for project %d", name.InfraID, name.OperationUID, name.ProjectID))
+
 			infra, err := repo.Infra().ReadInfra(name.ProjectID, name.InfraID)
 
 			if err != nil {
+				config.Logger.Debug().Msg(fmt.Sprintf("could not read infra %d in project %d: %s", name.InfraID, name.ProjectID, err.Error()))
 				continue
 			}
 
 			operation, err := repo.Infra().ReadOperation(name.InfraID, name.OperationUID)
 
 			if err != nil {
+				config.Logger.Debug().Msg(fmt.Sprintf("could not read operation %s, infra %d in project %d: %s", name.OperationUID, name.InfraID, name.ProjectID, err.Error()))
 				continue
 			}
 
 			statusVal, exists := msg.Values["status"]
 
 			if !exists {
+				config.Logger.Debug().Msg("skipping message parsing as status does not exist")
 				continue
 			}
 

+ 2 - 6
provisioner/server/handlers/state/delete_resource.go

@@ -68,13 +68,9 @@ func (c *DeleteResourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	// switch on the kind of resource and write the corresponding objects to the database
 	switch infra.Kind {
-	case types.InfraECR:
-	case types.InfraGCR:
-	case types.InfraDOCR:
+	case types.InfraECR, types.InfraGCR, types.InfraDOCR:
 		_, err = deleteRegistry(c.Config, infra, operation)
-	case types.InfraEKS:
-	case types.InfraDOKS:
-	case types.InfraGKE:
+	case types.InfraEKS, types.InfraDOKS, types.InfraGKE:
 		_, err = deleteCluster(c.Config, infra, operation)
 	case types.InfraRDS:
 		_, err = deleteDatabase(c.Config, infra, operation)