Pārlūkot izejas kodu

show azure icon in provisioning, hide secret in UI, patch acr registry integration (#3156)

show azure icon in provisioning, hide secret in UI, patch acr registry integration

---------

Co-authored-by: David Townley <davidtownley@Davids-MacBook-Air.local>
d-g-town 2 gadi atpakaļ
vecāks
revīzija
b8cf3c6124

+ 13 - 2
api/server/handlers/registry/list_repositories.go

@@ -3,6 +3,8 @@ package registry
 import (
 	"net/http"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	"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"
@@ -26,16 +28,25 @@ func NewRegistryListRepositoriesHandler(
 }
 
 func (c *RegistryListRepositoriesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx := r.Context()
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-registry-list-repositories")
+	defer span.End()
+
 	reg, _ := ctx.Value(types.RegistryScope).(*models.Registry)
 
 	// cast to a registry from registry package
 	_reg := registry.Registry(*reg)
 	regAPI := &_reg
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "registry-name", Value: regAPI.Name},
+		telemetry.AttributeKV{Key: "registry-id", Value: regAPI.ID},
+		telemetry.AttributeKV{Key: "project-id", Value: regAPI.ProjectID},
+	)
+
 	repos, err := regAPI.ListRepositories(ctx, c.Repo(), c.Config())
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		err := telemetry.Error(ctx, span, err, "error listing repositories")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
 		return
 	}
 

+ 16 - 30
dashboard/package-lock.json

@@ -3096,7 +3096,7 @@
       "version": "18.0.28",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
       "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
-      "devOptional": true,
+      "dev": true,
       "dependencies": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
@@ -16318,8 +16318,7 @@
     "@icons/material": {
       "version": "0.2.4",
       "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
-      "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
-      "requires": {}
+      "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
     },
     "@ironplans/api": {
       "version": "0.4.1",
@@ -16499,8 +16498,7 @@
     "@material-ui/types": {
       "version": "5.1.0",
       "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
-      "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
-      "requires": {}
+      "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A=="
     },
     "@material-ui/utils": {
       "version": "4.11.3",
@@ -16815,8 +16813,7 @@
       "version": "7.2.1",
       "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
       "integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "@types/body-parser": {
       "version": "1.19.2",
@@ -17129,7 +17126,7 @@
       "version": "18.0.28",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
       "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
-      "devOptional": true,
+      "dev": true,
       "requires": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
@@ -18025,15 +18022,13 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
       "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "ajv-keywords": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
       "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "anser": {
       "version": "2.1.1",
@@ -20950,8 +20945,7 @@
     "goober": {
       "version": "2.1.12",
       "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.12.tgz",
-      "integrity": "sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q==",
-      "requires": {}
+      "integrity": "sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q=="
     },
     "good-listener": {
       "version": "1.2.2",
@@ -21337,8 +21331,7 @@
       "version": "5.1.0",
       "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
       "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "ieee754": {
       "version": "1.2.1",
@@ -22215,8 +22208,7 @@
     "markdown-to-jsx": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.2.0.tgz",
-      "integrity": "sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg==",
-      "requires": {}
+      "integrity": "sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg=="
     },
     "material-colors": {
       "version": "1.2.6",
@@ -23134,8 +23126,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
       "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "postcss-modules-local-by-default": {
       "version": "4.0.0",
@@ -23426,8 +23417,7 @@
     "react-animate-height": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/react-animate-height/-/react-animate-height-3.1.1.tgz",
-      "integrity": "sha512-UkC6+V3ZlCneBRaSM7aUctDJ+PRP6ztcGtxvU7MTeoMMWPhz8BQNaX7QWaZrkzp1ih1G8uZZ+DI9nfLvtD6OdQ==",
-      "requires": {}
+      "integrity": "sha512-UkC6+V3ZlCneBRaSM7aUctDJ+PRP6ztcGtxvU7MTeoMMWPhz8BQNaX7QWaZrkzp1ih1G8uZZ+DI9nfLvtD6OdQ=="
     },
     "react-color": {
       "version": "2.19.3",
@@ -23531,8 +23521,7 @@
     "react-onclickoutside": {
       "version": "6.12.2",
       "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz",
-      "integrity": "sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==",
-      "requires": {}
+      "integrity": "sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA=="
     },
     "react-popper": {
       "version": "2.3.0",
@@ -23582,8 +23571,7 @@
     "react-table": {
       "version": "7.8.0",
       "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz",
-      "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==",
-      "requires": {}
+      "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA=="
     },
     "react-transition-group": {
       "version": "4.4.5",
@@ -25355,8 +25343,7 @@
     "use-sync-external-store": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
-      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
-      "requires": {}
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="
     },
     "util": {
       "version": "0.11.1",
@@ -26650,8 +26637,7 @@
       "version": "7.5.9",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
       "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "xtend": {
       "version": "4.0.2",

+ 1 - 0
dashboard/src/components/AzureCredentialForm.tsx

@@ -106,6 +106,7 @@ const AzureCredentialForm: React.FC<Props> = ({ goBack, proceed }) => {
           />
           <Spacer y={1} />
           <Input
+            type="password"
             label={<Flex>Client Secret</Flex>}
             value={servicePrincipalKey}
             setValue={(e) => {

+ 15 - 2
dashboard/src/main/home/cluster-dashboard/dashboard/ProvisionerStatus.tsx

@@ -2,6 +2,8 @@ import React, { useEffect, useState, useContext } from "react";
 import styled from "styled-components";
 
 import aws from "assets/aws.png";
+import azure from "assets/azure.png";
+
 import api from "shared/api";
 
 import { Context } from "shared/Context";
@@ -81,8 +83,19 @@ const ProvisionerStatus: React.FC<Props> = ({ provisionFailureReason }) => {
     <StyledProvisionerStatus>
       <HeaderSection>
         <Flex>
-          <Icon src={aws} />
-          AWS provisioning status
+          {currentCluster?.cloud_provider == "AWS" && (
+            <>
+              <Icon src={aws} />
+              AWS provisioning status
+            </>
+          )}
+
+          {currentCluster?.cloud_provider == "Azure" && (
+            <>
+              <Icon src={azure} />
+              Azure provisioning status
+            </>
+          )}
         </Flex>
         <Spacer height="18px" />
         <LoadingBar

+ 130 - 39
internal/registry/registry.go

@@ -12,6 +12,8 @@ import (
 	"sync"
 	"time"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	artifactregistry "cloud.google.com/go/artifactregistry/apiv1beta2"
 	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 	"github.com/aws/aws-sdk-go/aws/awserr"
@@ -72,66 +74,139 @@ func (r *Registry) ListRepositories(
 	repo repository.Repository,
 	conf *config.Config,
 ) ([]*ptypes.RegistryRepository, error) {
+	ctx, span := telemetry.NewSpan(ctx, "list-repositories")
+	defer span.End()
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "registry-name", Value: r.Name},
+		telemetry.AttributeKV{Key: "registry-id", Value: r.ID},
+		telemetry.AttributeKV{Key: "project-id", Value: r.ProjectID},
+	)
+
 	// switch on the auth mechanism to get a token
 	if r.AWSIntegrationID != 0 {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "aws"})
 		aws, err := repo.AWSIntegration().ReadAWSIntegration(
 			r.ProjectID,
 			r.AWSIntegrationID,
 		)
 		if err != nil {
-			return nil, err
+			return nil, telemetry.Error(ctx, span, err, "error reading aws integration")
 		}
-		return r.listECRRepositories(aws)
+
+		repos, err := r.listECRRepositories(aws)
+		if err != nil {
+			return nil, telemetry.Error(ctx, span, err, "error listing ecr repositories")
+		}
+
+		return repos, nil
 	}
 
 	if r.GCPIntegrationID != 0 {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "gcp"})
 		if strings.Contains(r.URL, "pkg.dev") {
 			return r.listGARRepositories(repo)
 		}
 
-		return r.listGCRRepositories(repo)
+		repos, err := r.listGCRRepositories(repo)
+		if err != nil {
+			return nil, telemetry.Error(ctx, span, err, "error listing gcr repositories")
+		}
+
+		return repos, nil
 	}
 
 	if r.DOIntegrationID != 0 {
-		return r.listDOCRRepositories(repo, conf.DOConf)
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "do"})
+
+		repos, err := r.listDOCRRepositories(repo, conf.DOConf)
+		if err != nil {
+			return nil, telemetry.Error(ctx, span, err, "error listing docr repositories")
+		}
+
+		return repos, nil
 	}
 
 	if r.AzureIntegrationID != 0 {
-		return r.listACRRepositories(repo)
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "azure"})
+
+		repos, err := r.listACRRepositories(ctx, repo)
+		if err != nil {
+			return nil, telemetry.Error(ctx, span, err, "error listing acr repositories")
+		}
+
+		return repos, nil
 	}
 
 	if r.BasicIntegrationID != 0 {
-		return r.listPrivateRegistryRepositories(repo)
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "basic"})
+
+		repos, err := r.listPrivateRegistryRepositories(repo)
+		if err != nil {
+			return nil, telemetry.Error(ctx, span, err, "error listing private repositories")
+		}
+
+		return repos, nil
 	}
 
 	project, err := conf.Repo.Project().ReadProject(r.ProjectID)
 	if err != nil {
-		return nil, fmt.Errorf("error getting project for repository: %w", err)
+		return nil, telemetry.Error(ctx, span, err, "error getting project for repository")
 	}
 
 	if project.CapiProvisionerEnabled {
-		uri := strings.TrimPrefix(r.URL, "https://")
-		splits := strings.Split(uri, ".")
-		accountID := splits[0]
-		region := splits[3]
-		req := connect.NewRequest(&porterv1.AssumeRoleCredentialsRequest{
-			ProjectId:    int64(r.ProjectID),
-			AwsAccountId: accountID,
-		})
-		creds, err := conf.ClusterControlPlaneClient.AssumeRoleCredentials(ctx, req)
-		if err != nil {
-			return nil, fmt.Errorf("error getting capi credentials for repository: %w", err)
-		}
-		aws := &ints.AWSIntegration{
-			AWSAccessKeyID:     []byte(creds.Msg.AwsAccessId),
-			AWSSecretAccessKey: []byte(creds.Msg.AwsSecretKey),
-			AWSSessionToken:    []byte(creds.Msg.AwsSessionToken),
-			AWSRegion:          region,
+		if strings.Contains(r.URL, ".azurecr.") {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "capi-azure"})
+			creds, err := conf.Repo.AzureIntegration().ListAzureIntegrationsByProjectID(r.ProjectID)
+			if err != nil {
+				return nil, telemetry.Error(ctx, span, err, "error getting azure credentials for capi cluster")
+			}
+			if len(creds) == 0 {
+				return nil, telemetry.Error(ctx, span, err, "no azure credentials for capi cluster")
+			}
+			r.AzureIntegrationID = creds[0].ID
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "azure-integration-id", Value: r.AzureIntegrationID})
+
+			repos, err := r.listACRRepositories(ctx, repo)
+			if err != nil {
+				return nil, telemetry.Error(ctx, span, err, "error listing acr repositories")
+			}
+
+			return repos, nil
+		} else {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "capi-aws"})
+			uri := strings.TrimPrefix(r.URL, "https://")
+			splits := strings.Split(uri, ".")
+			if len(splits) < 4 {
+				return nil, telemetry.Error(ctx, span, nil, "uri does not have enough splits")
+			}
+			accountID := splits[0]
+			region := splits[3]
+			req := connect.NewRequest(&porterv1.AssumeRoleCredentialsRequest{
+				ProjectId:    int64(r.ProjectID),
+				AwsAccountId: accountID,
+			})
+			creds, err := conf.ClusterControlPlaneClient.AssumeRoleCredentials(ctx, req)
+			if err != nil {
+				return nil, telemetry.Error(ctx, span, err, "error getting capi credentials for registry")
+			}
+			aws := &ints.AWSIntegration{
+				AWSAccessKeyID:     []byte(creds.Msg.AwsAccessId),
+				AWSSecretAccessKey: []byte(creds.Msg.AwsSecretKey),
+				AWSSessionToken:    []byte(creds.Msg.AwsSessionToken),
+				AWSRegion:          region,
+			}
+
+			repos, err := r.listECRRepositories(aws)
+			if err != nil {
+				return nil, telemetry.Error(ctx, span, err, "error listing ecr repositories")
+			}
+
+			return repos, nil
 		}
-		return r.listECRRepositories(aws)
 	}
 
-	return nil, fmt.Errorf("error listing repositories")
+	return nil, telemetry.Error(ctx, span, nil, "error listing repositories")
 }
 
 type gcrJWT struct {
@@ -429,52 +504,68 @@ func (r *Registry) listECRRepositories(aws *ints.AWSIntegration) ([]*ptypes.Regi
 	return res, nil
 }
 
-func (r *Registry) listACRRepositories(repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
+type acrRepositoryResp struct {
+	Repositories []string `json:"repositories"`
+}
+
+func (r *Registry) listACRRepositories(ctx context.Context, repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
+	ctx, span := telemetry.NewSpan(ctx, "list-acr-repositories")
+	defer span.End()
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "registry-name", Value: r.Name},
+		telemetry.AttributeKV{Key: "registry-id", Value: r.ID},
+		telemetry.AttributeKV{Key: "project-id", Value: r.ProjectID},
+	)
+
 	az, err := repo.AzureIntegration().ReadAzureIntegration(
 		r.ProjectID,
 		r.AzureIntegrationID,
 	)
 	if err != nil {
-		return nil, err
+		return nil, telemetry.Error(ctx, span, err, "error reading azure integration")
 	}
 
 	client := &http.Client{}
 
+	acrURL := r.URL
+	if !strings.Contains(acrURL, "http") {
+		acrURL = fmt.Sprintf("https://%s", acrURL)
+	}
+
 	req, err := http.NewRequest(
 		"GET",
-		fmt.Sprintf("%s/v2/_catalog", r.URL),
+		fmt.Sprintf("%s/v2/_catalog", acrURL),
 		nil,
 	)
 	if err != nil {
-		return nil, err
+		return nil, telemetry.Error(ctx, span, err, "error getting http request")
 	}
 
 	req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))
 
 	resp, err := client.Do(req)
 	if err != nil {
-		return nil, err
+		return nil, telemetry.Error(ctx, span, err, "error making http call")
 	}
 
-	gcrResp := gcrRepositoryResp{}
+	acrResp := acrRepositoryResp{}
 
-	if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
-		return nil, fmt.Errorf("Could not read Azure registry repositories: %v", err)
+	if err := json.NewDecoder(resp.Body).Decode(&acrResp); err != nil {
+		return nil, telemetry.Error(ctx, span, err, "could not read Azure registry repository response")
 	}
 
 	res := make([]*ptypes.RegistryRepository, 0)
 
-	if err != nil {
-		return nil, err
-	}
-
-	for _, repo := range gcrResp.Repositories {
+	for _, repo := range acrResp.Repositories {
 		res = append(res, &ptypes.RegistryRepository{
 			Name: repo,
 			URI:  strings.TrimPrefix(r.URL, "https://") + "/" + repo,
 		})
 	}
 
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-count", Value: len(acrResp.Repositories)})
+
 	return res, nil
 }