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

Merge branch 'nafees/preview-env-namespace-labels' into dev

Mohammed Nafees 3 лет назад
Родитель
Сommit
f985a8a3db
34 измененных файлов с 584 добавлено и 141 удалено
  1. 1 1
      api/server/handlers/cluster/create_namespace.go
  2. 5 5
      api/server/handlers/environment/create.go
  3. 6 6
      api/server/handlers/environment/update_environment_settings.go
  4. 17 1
      api/server/handlers/namespace/list_releases.go
  5. 78 0
      api/server/handlers/release/update_canonical_name.go
  6. 32 2
      api/server/router/release.go
  7. 1 1
      api/server/router/v1/registry.go
  8. 2 2
      api/types/cluster.go
  9. 10 10
      api/types/environment.go
  10. 1 2
      api/types/namespace.go
  11. 7 0
      api/types/release.go
  12. 2 2
      cli/cmd/apply.go
  13. 18 1
      cli/cmd/connect/gar.go
  14. 7 1
      dashboard/src/components/image-selector/TagList.tsx
  15. 1 1
      dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx
  16. 152 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/CanonicalName.tsx
  17. 14 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  18. 24 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx
  19. 8 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  20. 15 19
      dashboard/src/main/home/cluster-dashboard/preview-environments/ConnectNewRepo.tsx
  21. 4 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx
  22. 2 2
      dashboard/src/main/home/cluster-dashboard/preview-environments/components/NamespaceLabels.tsx
  23. 20 24
      dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentSettings.tsx
  24. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/types.ts
  25. 26 1
      dashboard/src/main/home/integrations/create-integration/GARForm.tsx
  26. 1 1
      dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx
  27. 27 4
      dashboard/src/main/home/onboarding/steps/ConnectRegistry/forms/_GCPRegistryForm.tsx
  28. 19 2
      dashboard/src/shared/api.tsx
  29. 1 0
      dashboard/src/shared/types.tsx
  30. 12 1
      internal/helm/postrenderer.go
  31. 3 3
      internal/kubernetes/agent.go
  32. 8 7
      internal/models/environment.go
  33. 7 3
      internal/models/release.go
  34. 52 36
      internal/registry/registry.go

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

@@ -55,7 +55,7 @@ func (c *CreateNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
-	namespace, err := agent.CreateNamespace(request.Name, request.Annotations)
+	namespace, err := agent.CreateNamespace(request.Name, request.Labels)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 5 - 5
api/server/handlers/environment/create.go

@@ -76,14 +76,14 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		NewCommentsDisabled: request.DisableNewComments,
 	}
 
-	if len(request.NamespaceAnnotations) > 0 {
-		var annotations []string
+	if len(request.NamespaceLabels) > 0 {
+		var labels []string
 
-		for k, v := range request.NamespaceAnnotations {
-			annotations = append(annotations, fmt.Sprintf("%s=%s", k, v))
+		for k, v := range request.NamespaceLabels {
+			labels = append(labels, fmt.Sprintf("%s=%s", k, v))
 		}
 
-		env.NamespaceAnnotations = []byte(strings.Join(annotations, ","))
+		env.NamespaceLabels = []byte(strings.Join(labels, ","))
 	}
 
 	// write Github actions files to the repo

+ 6 - 6
api/server/handlers/environment/update_environment_settings.go

@@ -89,18 +89,18 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 		changed = true
 	}
 
-	if len(request.NamespaceAnnotations) > 0 {
-		var annotations []string
+	if len(request.NamespaceLabels) > 0 {
+		var labels []string
 
-		for k, v := range request.NamespaceAnnotations {
-			annotations = append(annotations, fmt.Sprintf("%s=%s", k, v))
+		for k, v := range request.NamespaceLabels {
+			labels = append(labels, fmt.Sprintf("%s=%s", k, v))
 		}
 
-		env.NamespaceAnnotations = []byte(strings.Join(annotations, ","))
+		env.NamespaceLabels = []byte(strings.Join(labels, ","))
 
 		changed = true
 	} else {
-		env.NamespaceAnnotations = []byte{}
+		env.NamespaceLabels = []byte{}
 
 		changed = true
 	}

+ 17 - 1
api/server/handlers/namespace/list_releases.go

@@ -56,7 +56,23 @@ func (c *ListReleasesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	var res types.ListReleasesResponse = releases
+	var res types.ListReleasesResponse
+
+	for _, helmRel := range releases {
+		rel, err := c.Repo().Release().ReadRelease(cluster.ID, helmRel.Name, helmRel.Namespace)
+
+		if err == nil {
+			res = append(res, &types.Release{
+				Release:       helmRel,
+				PorterRelease: rel.ToReleaseType(),
+			})
+		} else {
+			res = append(res, &types.Release{
+				Release:       helmRel,
+				PorterRelease: &types.PorterRelease{},
+			})
+		}
+	}
 
 	c.WriteResult(w, r, res)
 }

+ 78 - 0
api/server/handlers/release/update_canonical_name.go

@@ -0,0 +1,78 @@
+package release
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"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/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+	"k8s.io/apimachinery/pkg/util/validation"
+)
+
+type UpdateCanonicalNameHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewUpdateCanonicalNameHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UpdateCanonicalNameHandler {
+	return &UpdateCanonicalNameHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *UpdateCanonicalNameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
+	namespace, _ := requestutils.GetURLParamString(r, types.URLParamNamespace)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.UpdateCanonicalNameRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	release, err := c.Repo().Release().ReadRelease(cluster.ID, name, namespace)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("release %s not found", name)))
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	if release.CanonicalName != request.CanonicalName {
+		if request.CanonicalName != "" {
+			if errStrs := validation.IsDNS1123Label(request.CanonicalName); len(errStrs) > 0 {
+				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid canonical name"), http.StatusBadRequest))
+				return
+			}
+		}
+
+		release.CanonicalName = request.CanonicalName
+
+		release, err = c.Repo().Release().UpdateRelease(release)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
+	c.WriteResult(w, r, release.ToReleaseType())
+}

+ 32 - 2
api/server/router/release.go

@@ -783,8 +783,7 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
-	// PATCH /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/update_tags ->
-	// release.NewGetLatestJobRunHandler
+	// PATCH /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/update_tags -> release.NewUpdateReleaseTagsHandler
 	updateReleaseTagsEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
 			Verb:   types.APIVerbUpdate,
@@ -815,6 +814,37 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
+	// PATCH /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/update_canonical_name -> release.NewUpdateCanonicalNameHandler
+	updateCanonicalNameEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbUpdate,
+			Method: types.HTTPVerbPatch,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/update_canonical_name",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+				types.ReleaseScope,
+			},
+		},
+	)
+
+	updateCanonicalNameHandler := release.NewUpdateCanonicalNameHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: updateCanonicalNameEndpoint,
+		Handler:  updateCanonicalNameHandler,
+		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{

+ 1 - 1
api/server/router/v1/registry.go

@@ -390,7 +390,7 @@ func getV1RegistryRoutes(
 	//   - name: registry_id
 	//   - name: repository
 	//     in: path
-	//     description: The image repository name
+	//     description: The image repository name. Should be of the form REPOSITORY/IMAGE when using Google Artifact Registry.
 	//     type: string
 	//     required: true
 	//   - name: num

+ 2 - 2
api/types/cluster.go

@@ -228,8 +228,8 @@ type CreateNamespaceRequest struct {
 	// example: sampleNS
 	Name string `json:"name" form:"required"`
 
-	// annotations for the kubernetes namespace, if any
-	Annotations map[string]string `json:"annotations,omitempty"`
+	// labels for the kubernetes namespace, if any
+	Labels map[string]string `json:"labels,omitempty"`
 }
 
 type GetTemporaryKubeconfigResponse struct {

+ 10 - 10
api/types/environment.go

@@ -16,15 +16,15 @@ type Environment struct {
 	DeploymentCount      uint              `json:"deployment_count"`
 	LastDeploymentStatus string            `json:"last_deployment_status"`
 	NewCommentsDisabled  bool              `json:"new_comments_disabled"`
-	NamespaceAnnotations map[string]string `json:"namespace_annotations,omitempty"`
+	NamespaceLabels      map[string]string `json:"namespace_labels,omitempty"`
 }
 
 type CreateEnvironmentRequest struct {
-	Name                 string            `json:"name" form:"required"`
-	Mode                 string            `json:"mode" form:"oneof=auto manual" default:"manual"`
-	DisableNewComments   bool              `json:"disable_new_comments"`
-	GitRepoBranches      []string          `json:"git_repo_branches"`
-	NamespaceAnnotations map[string]string `json:"namespace_annotations"`
+	Name               string            `json:"name" form:"required"`
+	Mode               string            `json:"mode" form:"oneof=auto manual" default:"manual"`
+	DisableNewComments bool              `json:"disable_new_comments"`
+	GitRepoBranches    []string          `json:"git_repo_branches"`
+	NamespaceLabels    map[string]string `json:"namespace_labels"`
 }
 
 type GitHubMetadata struct {
@@ -160,8 +160,8 @@ type ValidatePorterYAMLResponse struct {
 }
 
 type UpdateEnvironmentSettingsRequest struct {
-	Mode                 string            `json:"mode" form:"oneof=auto manual"`
-	DisableNewComments   bool              `json:"disable_new_comments"`
-	GitRepoBranches      []string          `json:"git_repo_branches"`
-	NamespaceAnnotations map[string]string `json:"namespace_annotations"`
+	Mode               string            `json:"mode" form:"oneof=auto manual"`
+	DisableNewComments bool              `json:"disable_new_comments"`
+	GitRepoBranches    []string          `json:"git_repo_branches"`
+	NamespaceLabels    map[string]string `json:"namespace_labels"`
 }

+ 1 - 2
api/types/namespace.go

@@ -4,7 +4,6 @@ import (
 	"time"
 
 	"helm.sh/helm/v3/pkg/action"
-	"helm.sh/helm/v3/pkg/release"
 	v1 "k8s.io/api/core/v1"
 )
 
@@ -83,7 +82,7 @@ type ListReleasesRequest struct {
 }
 
 // swagger:model
-type ListReleasesResponse []*release.Release
+type ListReleasesResponse []*Release
 
 type GetConfigMapRequest struct {
 	Name string `schema:"name,required"`

+ 7 - 0
api/types/release.go

@@ -38,6 +38,9 @@ type PorterRelease struct {
 
 	// Whether this release is tied to a stack or not
 	StackID string `json:"stack_id"`
+
+	// The canonical name of this release
+	CanonicalName string `json:"canonical_name"`
 }
 
 // swagger:model
@@ -211,3 +214,7 @@ type PartialGitActionConfig struct {
 type UpdateGitActionConfigRequest struct {
 	GitActionConfig *PartialGitActionConfig `json:"git_action_config"`
 }
+
+type UpdateCanonicalNameRequest struct {
+	CanonicalName string `json:"canonical_name"`
+}

+ 2 - 2
cli/cmd/apply.go

@@ -811,8 +811,8 @@ func (t *DeploymentHook) PreApply() error {
 			Name: t.namespace,
 		}
 
-		if len(deplEnv.NamespaceAnnotations) > 0 {
-			createNS.Annotations = deplEnv.NamespaceAnnotations
+		if len(deplEnv.NamespaceLabels) > 0 {
+			createNS.Labels = deplEnv.NamespaceLabels
 		}
 
 		// create the new namespace

+ 18 - 1
cli/cmd/connect/gar.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"strings"
 
 	"github.com/fatih/color"
 
@@ -68,13 +69,29 @@ Artifact registry region: `)
 			return 0, err
 		}
 
+		// GCP project IDs can have the ':' character like example.com:my-project
+		// if this is the case then we need to case on this
+		//
+		// see: https://cloud.google.com/artifact-registry/docs/docker/names#domain
+		var registryURL string
+
+		if domain, projectID, found := strings.Cut(integration.GCPProjectID, ":"); found {
+			if domain == "" || projectID == "" {
+				return 0, fmt.Errorf("invalid project ID: %s", integration.GCPProjectID)
+			}
+
+			registryURL = fmt.Sprintf("%s-docker.pkg.dev/%s/%s", region, domain, projectID)
+		} else {
+			registryURL = fmt.Sprintf("%s-docker.pkg.dev/%s", region, integration.GCPProjectID)
+		}
+
 		reg, err := client.CreateRegistry(
 			context.Background(),
 			projectID,
 			&types.CreateRegistryRequest{
 				Name:             regName,
 				GCPIntegrationID: integration.ID,
-				URL:              region + "-docker.pkg.dev/" + integration.GCPProjectID,
+				URL:              registryURL,
 			},
 		)
 

+ 7 - 1
dashboard/src/components/image-selector/TagList.tsx

@@ -46,7 +46,13 @@ export default class TagList extends Component<PropsType, StateType> {
     const { currentProject } = this.context;
 
     let splits = this.props.selectedImageUrl.split("/");
-    let repoName = splits[splits.length - 1];
+    let repoName: string;
+
+    if (this.props.selectedImageUrl.includes("pkg.dev")) {
+      repoName = splits[splits.length - 2] + "/" + splits[splits.length - 1];
+    } else {
+      repoName = splits[splits.length - 1];
+    }
 
     let matches = this.props.selectedImageUrl.match(ecrRepoRegex);
 

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx

@@ -131,7 +131,7 @@ const Chart: React.FunctionComponent<Props> = ({
     >
       <Title>
         <IconWrapper>{renderIcon()}</IconWrapper>
-        {chart.name}
+        {chart.canonical_name === "" ? chart.name : chart.canonical_name}
         {chart?.config?.description && (
           <>
             <Dot style={{ marginLeft: "9px", color: "#ffffff88" }}>•</Dot>

+ 152 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/CanonicalName.tsx

@@ -0,0 +1,152 @@
+import React, { useContext, useMemo, useState } from "react";
+import styled from "styled-components";
+import InputRow from "components/form-components/InputRow";
+import SaveButton from "components/SaveButton";
+import api from "shared/api";
+import Color from "color";
+import { Context } from "shared/Context";
+import { ChartType } from "shared/types";
+import { isAlphanumeric } from "shared/common";
+
+type Props = {
+  onSave: (() => void) | (() => Promise<void>);
+  release: ChartType;
+};
+
+const CanonicalName = ({ onSave, release }: Props) => {
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
+  const [buttonStatus, setButtonStatus] = useState("");
+  const [canonicalName, setCanonicalName] = useState<string>(
+    release.canonical_name
+  );
+
+  const handleSave = async () => {
+    setButtonStatus("loading");
+
+    try {
+      await api.updateCanonicalName(
+        "<token>",
+        { canonical_name: canonicalName },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          namespace: release.namespace,
+          release_name: release.name,
+        }
+      );
+      await onSave();
+      setButtonStatus("successful");
+    } catch (error) {
+      console.log(error);
+      setCurrentError(
+        "We couldn't change the canonical name. Please try again."
+      );
+      setButtonStatus("Canonical name not changed.");
+      return;
+    } finally {
+      setTimeout(() => {
+        setButtonStatus("");
+      }, 800);
+    }
+  };
+
+  const shouldDisableSave = useMemo(() => {
+    if (canonicalName !== release.canonical_name) {
+      if (canonicalName === "") {
+        return false;
+      }
+
+      return !isAlphanumeric(canonicalName) || canonicalName.length > 63;
+    }
+
+    return true;
+  }, [canonicalName]);
+
+  const saveButtonHelper = useMemo(() => {
+    if (canonicalName !== release.canonical_name) {
+      if (canonicalName !== "") {
+        if (!isAlphanumeric(canonicalName)) {
+          return "Invalid characters in the name";
+        } else if (canonicalName.length > 63) {
+          return "Name cannot exceed 63 characters";
+        }
+      }
+
+      return "Unsaved changes";
+    }
+
+    return "";
+  }, [canonicalName]);
+
+  return (
+    <>
+      <InputRow
+        type="text"
+        value={canonicalName}
+        setValue={(x: string) => setCanonicalName(x)}
+        placeholder="ex: my-app"
+        isRequired={true}
+        width={"100%"}
+      />
+      <Flex
+        style={{
+          marginTop: "25px",
+        }}
+      >
+        <SaveButton
+          helper={saveButtonHelper}
+          clearPosition
+          disabled={shouldDisableSave}
+          statusPosition="right"
+          text="Save changes"
+          onClick={() => handleSave()}
+          status={buttonStatus}
+        ></SaveButton>
+      </Flex>
+      <Br />
+    </>
+  );
+};
+
+const Br = styled.div`
+  width: 100%;
+  height: 10px;
+`;
+
+export default CanonicalName;
+
+const Flex = styled.div`
+  display: flex;
+  position: relative;
+`;
+
+const Tag = styled.div<{ color: string }>`
+  display: inline-flex;
+  color: ${(props) => Color(props.color).darken(0.4).string() || "inherit"};
+  user-select: none;
+  border: 1px solid ${(props) => Color(props.color).darken(0.4).string()};
+  border-radius: 5px;
+  padding: 4px 8px;
+  position: relative;
+  margin-bottom: 20px;
+  text-align: center;
+  align-items: center;
+  font-size: 13px;
+  background-color: ${(props) => props.color || "inherit"};
+
+  max-width: 150px;
+  min-width: 60px;
+
+  :not(:last-child) {
+    margin-right: 10px;
+  }
+
+  > .material-icons {
+    font-size: 16px;
+    :hover {
+      cursor: pointer;
+    }
+  }
+`;

+ 14 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -655,6 +655,15 @@ const ExpandedChart: React.FC<Props> = (props) => {
     );
   };
 
+  const renderHelmReleaseName = () => {
+    return (
+      <Url>
+        <Bolded>Helm Release Name:</Bolded>
+        {currentChart.name}
+      </Url>
+    );
+  };
+
   const handleUninstallChart = async () => {
     setDeleting(true);
     setCurrentOverlay(null);
@@ -873,7 +882,9 @@ const ExpandedChart: React.FC<Props> = (props) => {
                   icon={currentChart.chart.metadata.icon}
                   iconWidth="33px"
                 >
-                  {currentChart.name}
+                  {currentChart.canonical_name === ""
+                    ? currentChart.name
+                    : currentChart.canonical_name}
                   <DeploymentType currentChart={currentChart} />
                   <TagWrapper>
                     Namespace{" "}
@@ -884,6 +895,8 @@ const ExpandedChart: React.FC<Props> = (props) => {
                 {currentChart.chart.metadata.name != "worker" &&
                   currentChart.chart.metadata.name != "job" &&
                   renderUrl()}
+
+                {currentChart.canonical_name !== "" && renderHelmReleaseName()}
                 <InfoWrapper>
                   {/*
                   <StatusIndicator

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

@@ -447,6 +447,12 @@ const ExpandedJobHeader: React.FC<{
       ) : null}
 
       <InfoWrapper>
+        {chart?.canonical_name !== "" ? (
+          <Url>
+            <Bolded>Helm Release Name:</Bolded>
+            {chart?.name}
+          </Url>
+        ) : null}
         <LastDeployed>
           Run {jobs?.length} times <Dot>•</Dot>Last template update at
           {" " + readableDate(chart.info.last_deployed)}
@@ -777,3 +783,21 @@ const A = styled.a`
   margin-left: 5px;
   cursor: pointer;
 `;
+
+const Bolded = styled.div`
+  font-weight: 500;
+  color: #ffffff44;
+  margin-right: 6px;
+`;
+
+const Url = styled.div`
+  display: block;
+  font-size: 13px;
+  user-select: all;
+  user-select: text;
+  margin-top: -5px;
+  margin-bottom: 10px;
+  display: flex;
+  color: #949eff;
+  align-items: center;
+`;

+ 8 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -20,6 +20,7 @@ import { isDeployedFromGithub } from "shared/release/utils";
 import TagSelector from "./TagSelector";
 import { PORTER_IMAGE_TEMPLATES } from "shared/common";
 import DynamicLink from "components/DynamicLink";
+import CanonicalName from "./CanonicalName";
 
 type PropsType = {
   currentChart: ChartType;
@@ -243,6 +244,13 @@ const SettingsSection: React.FC<PropsType> = ({
         ) : null}
 
         <>
+          <Heading>Canonical Name</Heading>
+          <Helper>Set a canonical name for this application (lowercase letters, numbers, and "-" only)</Helper>
+          <CanonicalName
+            release={currentChart}
+            onSave={() => refreshChart()}
+          />
+
           <Heading>Redeploy Webhook</Heading>
           <Helper>
             Programmatically deploy by calling this secret webhook.

+ 15 - 19
dashboard/src/main/home/cluster-dashboard/preview-environments/ConnectNewRepo.tsx

@@ -15,9 +15,7 @@ import PullRequestIcon from "assets/pull_request_icon.svg";
 import CheckboxRow from "components/form-components/CheckboxRow";
 import BranchFilterSelector from "./components/BranchFilterSelector";
 import Helper from "components/form-components/Helper";
-import NamespaceAnnotations, {
-  KeyValueType,
-} from "./components/NamespaceAnnotations";
+import NamespaceLabels, { KeyValueType } from "./components/NamespaceLabels";
 
 const ConnectNewRepo: React.FC = () => {
   const { currentProject, currentCluster, setCurrentError } = useContext(
@@ -49,10 +47,8 @@ const ConnectNewRepo: React.FC = () => {
   // Disable new comments data
   const [isNewCommentsDisabled, setIsNewCommentsDisabled] = useState(false);
 
-  // Namespace annotations
-  const [namespaceAnnotations, setNamespaceAnnotations] = useState<
-    KeyValueType[]
-  >([]);
+  // Namespace labels
+  const [namespaceLabels, setNamespaceLabels] = useState<KeyValueType[]>([]);
 
   useEffect(() => {
     api
@@ -117,11 +113,11 @@ const ConnectNewRepo: React.FC = () => {
 
   const addRepo = () => {
     let [owner, repoName] = repo.split("/");
-    let annotations: Record<string, string> = {};
+    let labels: Record<string, string> = {};
 
     setStatus("loading");
 
-    namespaceAnnotations
+    namespaceLabels
       .filter((elem: KeyValueType, index: number, self: KeyValueType[]) => {
         // remove any collisions that are duplicates
         let numCollisions = self.reduce((n, _elem: KeyValueType) => {
@@ -139,7 +135,7 @@ const ConnectNewRepo: React.FC = () => {
       })
       .forEach((elem: KeyValueType) => {
         if (elem.key !== "" && elem.value !== "") {
-          annotations[elem.key] = elem.value;
+          labels[elem.key] = elem.value;
         }
       });
 
@@ -151,7 +147,7 @@ const ConnectNewRepo: React.FC = () => {
           mode: enableAutomaticDeployments ? "auto" : "manual",
           disable_new_comments: isNewCommentsDisabled,
           git_repo_branches: selectedBranches,
-          namespace_annotations: annotations,
+          namespace_labels: labels,
         },
         {
           project_id: currentProject.id,
@@ -272,19 +268,19 @@ const ConnectNewRepo: React.FC = () => {
         showLoading={isLoadingBranches}
       />
 
-      <Heading>Namespace annotations</Heading>
+      <Heading>Namespace labels</Heading>
       <Helper>
-        Custom annotations to be injected into the Kubernetes namespace created
-        for each deployment.
+        Custom labels to be injected into the Kubernetes namespace created for
+        each deployment.
       </Helper>
-      <NamespaceAnnotations
-        values={namespaceAnnotations}
+      <NamespaceLabels
+        values={namespaceLabels}
         setValues={(x: KeyValueType[]) => {
-          let annotations: KeyValueType[] = [];
+          let labels: KeyValueType[] = [];
           x.forEach((entry) => {
-            annotations.push({ key: entry.key, value: entry.value });
+            labels.push({ key: entry.key, value: entry.value });
           });
-          setNamespaceAnnotations(annotations);
+          setNamespaceLabels(labels);
         }}
       />
 

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

@@ -84,7 +84,10 @@ const ButtonEnablePREnvironments = ({ setIsReady }: Props) => {
   if (isLoading) {
     return (
       <Container>
-        <Loading />
+        <Button disabled={true} to="">
+          <img src={pr_icon} alt="Pull request icon" />
+          Loading . . .
+        </Button>
       </Container>
     );
   }

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/preview-environments/components/NamespaceAnnotations.tsx → dashboard/src/main/home/cluster-dashboard/preview-environments/components/NamespaceLabels.tsx

@@ -11,7 +11,7 @@ type PropsType = {
   setValues: (x: KeyValueType[]) => void;
 };
 
-const NamespaceAnnotations = ({ values, setValues }: PropsType) => {
+const NamespaceLabels = ({ values, setValues }: PropsType) => {
   useEffect(() => {
     if (!values) {
       setValues([]);
@@ -82,7 +82,7 @@ const NamespaceAnnotations = ({ values, setValues }: PropsType) => {
   );
 };
 
-export default NamespaceAnnotations;
+export default NamespaceLabels;
 
 const Spacer = styled.div`
   width: 10px;

+ 20 - 24
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentSettings.tsx

@@ -18,9 +18,7 @@ import Banner from "components/Banner";
 import InputRow from "components/form-components/InputRow";
 import Modal from "main/home/modals/Modal";
 import { useRouting } from "shared/routing";
-import NamespaceAnnotations, {
-  KeyValueType,
-} from "../components/NamespaceAnnotations";
+import NamespaceLabels, { KeyValueType } from "../components/NamespaceLabels";
 import BranchFilterSelector from "../components/BranchFilterSelector";
 
 const EnvironmentSettings = () => {
@@ -40,9 +38,7 @@ const EnvironmentSettings = () => {
     deploymentMode,
     setDeploymentMode,
   ] = useState<EnvironmentDeploymentMode>("manual");
-  const [namespaceAnnotations, setNamespaceAnnotations] = useState<
-    KeyValueType[]
-  >([]);
+  const [namespaceLabels, setNamespaceLabels] = useState<KeyValueType[]>([]);
   const {
     environment_id: environmentId,
     repo_name: repoName,
@@ -72,17 +68,17 @@ const EnvironmentSettings = () => {
       setNewCommentsDisabled(environment.new_comments_disabled);
       setDeploymentMode(environment.mode);
 
-      if (environment.namespace_annotations) {
-        const annotations: KeyValueType[] = [];
+      if (environment.namespace_labels) {
+        const labels: KeyValueType[] = [];
 
-        Object.keys(environment.namespace_annotations).forEach((k) => {
-          annotations.push({
+        Object.keys(environment.namespace_labels).forEach((k) => {
+          labels.push({
             key: k,
-            value: environment.namespace_annotations[k],
+            value: environment.namespace_labels[k],
           });
         });
 
-        setNamespaceAnnotations(annotations);
+        setNamespaceLabels(labels);
       }
     };
 
@@ -126,11 +122,11 @@ const EnvironmentSettings = () => {
   }, [environment]);
 
   const handleSave = async () => {
-    let annotations: Record<string, string> = {};
+    let labels: Record<string, string> = {};
 
     setSaveStatus("loading");
 
-    namespaceAnnotations
+    namespaceLabels
       .filter((elem: KeyValueType, index: number, self: KeyValueType[]) => {
         // remove any collisions that are duplicates
         let numCollisions = self.reduce((n, _elem: KeyValueType) => {
@@ -148,7 +144,7 @@ const EnvironmentSettings = () => {
       })
       .forEach((elem: KeyValueType) => {
         if (elem.key !== "" && elem.value !== "") {
-          annotations[elem.key] = elem.value;
+          labels[elem.key] = elem.value;
         }
       });
 
@@ -159,7 +155,7 @@ const EnvironmentSettings = () => {
           mode: deploymentMode,
           disable_new_comments: newCommentsDisabled,
           git_repo_branches: selectedBranches,
-          namespace_annotations: annotations,
+          namespace_labels: labels,
         },
         {
           project_id: currentProject.id,
@@ -291,19 +287,19 @@ const EnvironmentSettings = () => {
           showLoading={isLoadingBranches}
         />
         <Br />
-        <Heading>Namespace annotations</Heading>
+        <Heading>Namespace labels</Heading>
         <Helper>
-          Custom annotations to be injected into the Kubernetes namespace
-          created for each deployment.
+          Custom labels to be injected into the Kubernetes namespace created for
+          each deployment.
         </Helper>
-        <NamespaceAnnotations
-          values={namespaceAnnotations}
+        <NamespaceLabels
+          values={namespaceLabels}
           setValues={(x: KeyValueType[]) => {
-            let annotations: KeyValueType[] = [];
+            let labels: KeyValueType[] = [];
             x.forEach((entry) => {
-              annotations.push({ key: entry.key, value: entry.value });
+              labels.push({ key: entry.key, value: entry.value });
             });
-            setNamespaceAnnotations(annotations);
+            setNamespaceLabels(labels);
           }}
         />
         <SavePreviewEnvironmentSettings

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/types.ts

@@ -44,7 +44,7 @@ export type Environment = {
   last_deployment_status: DeploymentStatusUnion;
   deployment_count: number;
   mode: EnvironmentDeploymentMode;
-  namespace_annotations: Record<string, string>;
+  namespace_labels: Record<string, string>;
 };
 
 export type PullRequest = {

+ 26 - 1
dashboard/src/main/home/integrations/create-integration/GARForm.tsx

@@ -59,12 +59,37 @@ const GARForm = (props: { closeForm: () => void }) => {
     }
 
     try {
+      let registryURL: string;
+
+      // GCP project IDs can have the ':' character like example.com:my-project
+      // if this is the case then we need to case on this
+      //
+      // see: https://cloud.google.com/artifact-registry/docs/docker/names#domain
+      if (integration.gcp_project_id.includes(":")) {
+        const domainProjectID = integration.gcp_project_id.split(":");
+
+        if (
+          domainProjectID.length !== 2 ||
+          domainProjectID[0].length === 0 ||
+          domainProjectID[1].length === 0
+        ) {
+          setButtonStatus(
+            "Invalid GCP project ID. Please check your credentials."
+          );
+          return;
+        }
+
+        registryURL = `${region}-docker.pkg.dev/${domainProjectID[0]}/${domainProjectID[1]}`;
+      } else {
+        registryURL = `${region}-docker.pkg.dev/${integration.gcp_project_id}`;
+      }
+
       await api.connectGCRRegistry(
         "token",
         {
           gcp_integration_id: integration.id,
           name: credentialsName,
-          url: `${region}-docker.pkg.dev/${integration.gcp_project_id}`,
+          url: registryURL,
         },
         { id: currentProject.id }
       );

+ 1 - 1
dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx

@@ -285,7 +285,7 @@ const LaunchFlow: React.FC<PropsType> = (props) => {
 
     const release_name = templateName || generateRandomName();
     // check if template is docker and create external domain if necessary
-    if (props.currentTemplate.name == "web") {
+    if (props.currentTemplate.name == "web" && context.capabilities?.default_app_helm_repo_url.includes("getporter.dev")) {
       if (values?.ingress?.enabled && !values?.ingress?.custom_domain) {
         external_domain = await new Promise((resolve, reject) => {
           api

+ 27 - 4
dashboard/src/main/home/onboarding/steps/ConnectRegistry/forms/_GCPRegistryForm.tsx

@@ -351,7 +351,7 @@ export const GARegistryConfig: React.FC<{
 
     setButtonStatus("loading");
 
-    let gcpProjectId = NaN;
+    let gcpProjectId: string;
 
     try {
       const gcp_integration = await api
@@ -375,7 +375,30 @@ export const GARegistryConfig: React.FC<{
       return;
     }
 
-    const registryUrl = `${region}-docker.pkg.dev/${gcpProjectId}`;
+    let registryURL: string;
+
+    // GCP project IDs can have the ':' character like example.com:my-project
+    // if this is the case then we need to case on this
+    //
+    // see: https://cloud.google.com/artifact-registry/docs/docker/names#domain
+    if (gcpProjectId.includes(":")) {
+      const domainProjectID = gcpProjectId.split(":");
+
+      if (
+        domainProjectID.length !== 2 ||
+        domainProjectID[0].length === 0 ||
+        domainProjectID[1].length === 0
+      ) {
+        setButtonStatus(
+          "Invalid GCP project ID. Please check your credentials."
+        );
+        return;
+      }
+
+      registryURL = `${region}-docker.pkg.dev/${domainProjectID[0]}/${domainProjectID[1]}`;
+    } else {
+      registryURL = `${region}-docker.pkg.dev/${gcpProjectId}`;
+    }
 
     try {
       const data = await api
@@ -385,7 +408,7 @@ export const GARegistryConfig: React.FC<{
             name: registryName,
             gcp_integration_id:
               snap.StateHandler.connected_registry.credentials.id,
-            url: registryUrl,
+            url: registryURL,
           },
           {
             id: project.id,
@@ -395,7 +418,7 @@ export const GARegistryConfig: React.FC<{
       nextFormStep({
         settings: {
           registry_connection_id: data.id,
-          gcr_url: registryUrl,
+          gcr_url: registryURL,
           registry_name: registryName,
         },
       });

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

@@ -151,7 +151,7 @@ const createEnvironment = baseApi<
     mode: "auto" | "manual";
     disable_new_comments: boolean;
     git_repo_branches: string[];
-    namespace_annotations: Record<string, string>;
+    namespace_labels: Record<string, string>;
   },
   {
     project_id: number;
@@ -176,7 +176,7 @@ const updateEnvironment = baseApi<
     mode: "auto" | "manual";
     disable_new_comments: boolean;
     git_repo_branches: string[]; // Array with branch names
-    namespace_annotations: Record<string, string>;
+    namespace_labels: Record<string, string>;
   },
   {
     project_id: number;
@@ -1983,6 +1983,22 @@ const updateReleaseTags = baseApi<
     `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${release_name}/0/update_tags`
 );
 
+const updateCanonicalName = baseApi<
+  {
+    canonical_name: string;
+  },
+  {
+    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}/0/update_canonical_name`
+);
+
 const getGitProviders = baseApi<{}, { project_id: number }>(
   "GET",
   ({ project_id }) => `/api/projects/${project_id}/integrations/git`
@@ -2574,6 +2590,7 @@ export default {
   getTagsByProjectId,
   createTag,
   updateReleaseTags,
+  updateCanonicalName,
   getGitProviders,
   getGitlabRepos,
   getGitlabBranches,

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

@@ -56,6 +56,7 @@ export interface ChartType {
   namespace: string;
   latest_version: string;
   tags: any;
+  canonical_name: string;
 }
 
 export interface ChartTypeWithExtendedConfig extends ChartType {

+ 12 - 1
internal/helm/postrenderer.go

@@ -815,7 +815,18 @@ func getRegNameFromImageRef(image string) (string, error) {
 	if strings.Contains(domain, "docker.io") {
 		regName = "index.docker.io/" + path
 	} else if strings.Contains(domain, "pkg.dev") {
-		regName = domain + "/" + strings.Split(path, "/")[0]
+		pathSlice := strings.Split(path, "/")
+
+		// a GAR image path can either be PROJECT-ID/REPOSITORY/IMAGE or DOMAIN/PROJECT-ID/REPOSITORY/IMAGE
+		//
+		// see: https://cloud.google.com/artifact-registry/docs/docker/names#domain
+		if len(pathSlice) == 3 {
+			regName = fmt.Sprintf("%s/%s", domain, pathSlice[0])
+		} else if len(pathSlice) == 4 {
+			regName = fmt.Sprintf("%s/%s/%s", domain, pathSlice[0], pathSlice[1])
+		} else {
+			return "", fmt.Errorf("invalid GAR image: %s", image)
+		}
 	} else {
 		regName = domain
 

+ 3 - 3
internal/kubernetes/agent.go

@@ -616,7 +616,7 @@ func (a *Agent) ListNamespaces() (*v1.NamespaceList, error) {
 }
 
 // CreateNamespace creates a namespace with the given name.
-func (a *Agent) CreateNamespace(name string, annotations map[string]string) (*v1.Namespace, error) {
+func (a *Agent) CreateNamespace(name string, labels map[string]string) (*v1.Namespace, error) {
 	// check if namespace exists
 	checkNS, err := a.Clientset.CoreV1().Namespaces().Get(
 		context.TODO(),
@@ -666,8 +666,8 @@ func (a *Agent) CreateNamespace(name string, annotations map[string]string) (*v1
 		},
 	}
 
-	if len(annotations) > 0 {
-		namespace.SetAnnotations(annotations)
+	if len(labels) > 0 {
+		namespace.SetLabels(labels)
 	}
 
 	return a.Clientset.CoreV1().Namespaces().Create(

+ 8 - 7
internal/models/environment.go

@@ -23,6 +23,7 @@ type Environment struct {
 	Mode string
 
 	NewCommentsDisabled  bool
+	NamespaceLabels      []byte
 	NamespaceAnnotations []byte
 
 	// WebhookID uniquely identifies the environment when other fields (project, cluster)
@@ -59,8 +60,8 @@ func (e *Environment) ToEnvironmentType() *types.Environment {
 		GitRepoOwner:      e.GitRepoOwner,
 		GitRepoName:       e.GitRepoName,
 
-		NewCommentsDisabled:  e.NewCommentsDisabled,
-		NamespaceAnnotations: make(map[string]string),
+		NewCommentsDisabled: e.NewCommentsDisabled,
+		NamespaceLabels:     make(map[string]string),
 
 		Name: e.Name,
 		Mode: e.Mode,
@@ -74,15 +75,15 @@ func (e *Environment) ToEnvironmentType() *types.Environment {
 		env.GitRepoBranches = []string{}
 	}
 
-	if len(e.NamespaceAnnotations) > 0 {
-		env.NamespaceAnnotations = make(map[string]string)
-		annotations := string(e.NamespaceAnnotations)
+	if len(e.NamespaceLabels) > 0 {
+		env.NamespaceLabels = make(map[string]string)
+		labels := string(e.NamespaceLabels)
 
-		for _, a := range strings.Split(annotations, ",") {
+		for _, a := range strings.Split(labels, ",") {
 			k, v, found := strings.Cut(a, "=")
 
 			if found {
-				env.NamespaceAnnotations[k] = v
+				env.NamespaceLabels[k] = v
 			}
 		}
 	}

+ 7 - 3
internal/models/release.go

@@ -28,13 +28,17 @@ type Release struct {
 	NotificationConfig uint
 	BuildConfig        uint
 	Tags               []*Tag `json:"tags" gorm:"many2many:release_tags"`
+
+	// A configurable canonical name of a Porter release
+	CanonicalName string
 }
 
 func (r *Release) ToReleaseType() *types.PorterRelease {
 	res := &types.PorterRelease{
-		ID:           r.ID,
-		WebhookToken: r.WebhookToken,
-		ImageRepoURI: r.ImageRepoURI,
+		ID:            r.ID,
+		WebhookToken:  r.WebhookToken,
+		ImageRepoURI:  r.ImageRepoURI,
+		CanonicalName: r.CanonicalName,
 	}
 
 	if r.GitActionConfig != nil {

+ 52 - 36
internal/registry/registry.go

@@ -293,10 +293,12 @@ func (r *Registry) listGARRepositories(
 				return nil, err
 			}
 
-			repoSlice := strings.Split(resp.GetName(), "/")
-			repoName := repoSlice[len(repoSlice)-1]
+			if resp.GetFormat() == artifactregistrypb.Repository_DOCKER { // we only care about
+				repoSlice := strings.Split(resp.GetName(), "/")
+				repoName := repoSlice[len(repoSlice)-1]
 
-			repoNames = append(repoNames, repoName)
+				repoNames = append(repoNames, repoName)
+			}
 		}
 
 		if it.PageInfo().Token == "" {
@@ -315,48 +317,64 @@ func (r *Registry) listGARRepositories(
 		return nil, err
 	}
 
-	resMap := make(map[string]*ptypes.RegistryRepository)
+	nextToken = ""
+
 	dockerSvc := v1artifactregistry.NewProjectsLocationsRepositoriesDockerImagesService(svc)
 
+	var (
+		wg     sync.WaitGroup
+		resMap sync.Map
+	)
+
 	for _, repoName := range repoNames {
-		for {
-			resp, err := dockerSvc.List(fmt.Sprintf("projects/%s/locations/%s/repositories/%s",
-				gcpInt.GCPProjectID, location, repoName)).PageSize(1000).PageToken(nextToken).Do()
+		wg.Add(1)
 
-			if err != nil {
-				return nil, err
-			}
+		go func(repoName string) {
+			defer wg.Done()
 
-			for _, image := range resp.DockerImages {
-				named, err := reference.ParseNamed(image.Uri)
+			for {
+				resp, err := dockerSvc.List(fmt.Sprintf("projects/%s/locations/%s/repositories/%s",
+					gcpInt.GCPProjectID, location, repoName)).PageSize(1000).PageToken(nextToken).Do()
 
 				if err != nil {
-					// let us skip this image becaue it has a malformed URI coming from the GCP API
-					continue
+					// FIXME: we should report this error using a channel
+					return
 				}
 
-				uploadTime, _ := time.Parse(time.RFC3339, image.UploadTime)
+				for _, image := range resp.DockerImages {
+					named, err := reference.ParseNamed(image.Uri)
 
-				resMap[named.Name()] = &ptypes.RegistryRepository{
-					Name:      repoName,
-					URI:       named.Name(),
-					CreatedAt: uploadTime,
+					if err != nil {
+						// let us skip this image becaue it has a malformed URI coming from the GCP API
+						continue
+					}
+
+					uploadTime, _ := time.Parse(time.RFC3339, image.UploadTime)
+
+					resMap.Store(named.Name(), &ptypes.RegistryRepository{
+						Name:      repoName,
+						URI:       named.Name(),
+						CreatedAt: uploadTime,
+					})
 				}
-			}
 
-			if resp.NextPageToken == "" {
-				break
-			}
+				if resp.NextPageToken == "" {
+					break
+				}
 
-			nextToken = resp.NextPageToken
-		}
+				nextToken = resp.NextPageToken
+			}
+		}(repoName)
 	}
 
+	wg.Wait()
+
 	var res []*ptypes.RegistryRepository
 
-	for _, v := range resMap {
-		res = append(res, v)
-	}
+	resMap.Range(func(_, value any) bool {
+		res = append(res, value.(*ptypes.RegistryRepository))
+		return true
+	})
 
 	return res, nil
 }
@@ -1224,13 +1242,11 @@ func (r *Registry) listGCRImages(repoName string, repo repository.Repository) ([
 }
 
 func (r *Registry) listGARImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
-	// FIXME: GAR implemets the repo/image scheme so we should be looking out for that
-
-	// repoImageSlice := strings.Split(repoName, "/")
+	repoImageSlice := strings.Split(repoName, "/")
 
-	// if len(repoImageSlice) != 2 {
-	// 	return nil, fmt.Errorf("invalid GAR repo name: %s", repoName)
-	// }
+	if len(repoImageSlice) != 2 {
+		return nil, fmt.Errorf("invalid GAR repo name: %s. Expected to be in the form of REPOSITORY/IMAGE", repoName)
+	}
 
 	gcpInt, err := repo.GCPIntegration().ReadGCPIntegration(
 		r.ProjectID,
@@ -1264,7 +1280,7 @@ func (r *Registry) listGARImages(repoName string, repo repository.Repository) ([
 
 	for {
 		resp, err := dockerSvc.List(fmt.Sprintf("projects/%s/locations/%s/repositories/%s",
-			gcpInt.GCPProjectID, location, repoName)).PageSize(1000).PageToken(nextToken).Do()
+			gcpInt.GCPProjectID, location, repoImageSlice[0])).PageSize(1000).PageToken(nextToken).Do()
 
 		if err != nil {
 			return nil, err
@@ -1281,7 +1297,7 @@ func (r *Registry) listGARImages(repoName string, repo repository.Repository) ([
 
 			imageName := paths[len(paths)-1]
 
-			if imageName == repoName {
+			if imageName == repoImageSlice[1] {
 				uploadTime, _ := time.Parse(time.RFC3339, image.UploadTime)
 
 				for _, tag := range image.Tags {