Jelajahi Sumber

CAPI Dead letter queue (#2816)

* updated error function, added contract revision

* updated delete cluster, added provisioning error

* release api-contracts
Stefan McShane 3 tahun lalu
induk
melakukan
247e71c99c

+ 6 - 1
api/server/handlers/cluster/delete.go

@@ -42,9 +42,14 @@ func (c *ClusterDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 				c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
 				return
 			}
+			if cluster.Status == types.UpdatingUnavailable || cluster.Status == types.Updating {
+				e := fmt.Errorf("unable to delete cluster %d that is updating", cluster.ID)
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
+				return
+			}
 			var revisionID string
 			for _, rev := range revisions {
-				if rev.Condition == "SUCCESS" {
+				if rev.Condition != "" {
 					revisionID = rev.ID.String()
 					break
 				}

+ 16 - 3
cli/cmd/docker/agent.go

@@ -315,7 +315,12 @@ func (a *Agent) PushImage(image string) error {
 
 	termFd, isTerm := term.GetFdInfo(os.Stderr)
 
-	return jsonmessage.DisplayJSONMessagesStream(out, os.Stderr, termFd, isTerm, nil)
+	err = jsonmessage.DisplayJSONMessagesStream(out, os.Stderr, termFd, isTerm, nil)
+	if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 func (a *Agent) getPullOptions(image string) (types.ImagePullOptions, error) {
@@ -361,13 +366,21 @@ func (a *Agent) getEncodedRegistryAuth(image string) (string, error) {
 		return "", err
 	}
 
+	serverAddress := serverURL
+	if !strings.Contains(serverURL, "https://") {
+		serverAddress = fmt.Sprintf("https://%s", serverURL)
+	}
+
 	authConfig := types.AuthConfig{
 		Username:      user,
 		Password:      secret,
-		ServerAddress: "https://" + serverURL,
+		ServerAddress: serverAddress,
 	}
 
-	authConfigBytes, _ := json.Marshal(authConfig)
+	authConfigBytes, err := json.Marshal(authConfig)
+	if err != nil {
+		return "", fmt.Errorf("unable to marshal docker auth config: %w", err)
+	}
 
 	return base64.URLEncoding.EncodeToString(authConfigBytes), nil
 }

+ 14 - 14
dashboard/src/main/home/cluster-dashboard/dashboard/ClusterRevisionSelector.tsx

@@ -8,13 +8,13 @@ import warning from "assets/warning.png";
 import { readableDate } from "shared/string_utils";
 import { Context } from "shared/Context";
 import ExpandableSection from "components/porter/ExpandableSection";
-import { 
-  Contract, 
-  Cluster, 
-  EKS, 
-  NodeGroupType, 
-  EnumKubernetesKind, 
-  EnumCloudProvider 
+import {
+  Contract,
+  Cluster,
+  EKS,
+  NodeGroupType,
+  EnumKubernetesKind,
+  EnumCloudProvider
 } from "@porter-dev/api-contracts";
 import Spacer from "components/porter/Spacer";
 
@@ -51,7 +51,7 @@ const ClusterRevisionSelector: React.FC<Props> = ({
 
       if (data[0].condition !== "") {
         setFailedContractId(data[0].id);
-        setProvisionFailureReason(data[0].condition);
+        setProvisionFailureReason(data[0].condition_metadata?.message || data[0].condition);
       }
     }
 
@@ -97,7 +97,7 @@ const ClusterRevisionSelector: React.FC<Props> = ({
   useEffect(() => {
     updateContracts();
   }, [currentCluster]);
-  
+
   const createContract = () => {
     if (false) {
       api.createContract(
@@ -113,11 +113,11 @@ const ClusterRevisionSelector: React.FC<Props> = ({
     }
   };
 
-const deleteContract = () => {
+  const deleteContract = () => {
     api.deleteContract(
       "<token>",
       {},
-      { 
+      {
         project_id: currentProject.id,
         revision_id: failedContractId,
       }
@@ -182,7 +182,7 @@ const deleteContract = () => {
         {
           failedContractId && (
             <DeleteButton>
-              <i 
+              <i
                 className="material-icons-outlined"
                 onClick={deleteContract}
               >
@@ -337,7 +337,7 @@ const RollbackButton = styled.div`
     props.disabled ? "#aaaabbee" : "#616FEEcc"};
   :hover {
     background: ${(props: { disabled: boolean }) =>
-      props.disabled ? "" : "#405eddbb"};
+    props.disabled ? "" : "#405eddbb"};
   }
 `;
 
@@ -351,7 +351,7 @@ const Tr = styled.tr`
     props.selected ? "#ffffff11" : ""};
   :hover {
     background: ${(props: { disableHover?: boolean; selected?: boolean }) =>
-      props.disableHover ? "" : "#ffffff22"};
+    props.disableHover ? "" : "#ffffff22"};
   }
 `;
 

+ 1 - 1
go.mod

@@ -74,7 +74,7 @@ require (
 	github.com/glebarez/sqlite v1.6.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.0.56
+	github.com/porter-dev/api-contracts v0.0.60
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d
 	github.com/xanzy/go-gitlab v0.68.0

+ 2 - 2
go.sum

@@ -1466,8 +1466,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.0.56 h1:4PpnLfiXvvl70pgmTw+Nr1//sZyNo/RSVA61GYIU+u8=
-github.com/porter-dev/api-contracts v0.0.56/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
+github.com/porter-dev/api-contracts v0.0.60 h1:wqsN9XkqcQLIiOK2wdTyknIG8ZCw9y80SX9lpnri0is=
+github.com/porter-dev/api-contracts v0.0.60/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
 github.com/porter-dev/switchboard v0.0.0-20221019155755-67ff2bf04935 h1:hfb3nt3AJXIBbevu6ARTg9SdOkMP6WLbKBiG5hT5rcc=
 github.com/porter-dev/switchboard v0.0.0-20221019155755-67ff2bf04935/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=

+ 35 - 0
internal/models/api_contract_revision.go

@@ -1,6 +1,9 @@
 package models
 
 import (
+	"database/sql/driver"
+	"encoding/json"
+
 	"github.com/google/uuid"
 	"gorm.io/gorm"
 )
@@ -25,10 +28,42 @@ type APIContractRevision struct {
 
 	// Condition is the status of the apply that happened for this revision.
 	// Condition will contain any failure reasons for a revision, or "SUCCESS" if the revision was applied successfully.
+	// Further details to expand upon this condition are available in ConditionMetadata
 	Condition string `json:"condition"`
+
+	// ConditionMetadata contains all information about the condition of a given revision.
+	// If condition is "SUCCESS", this will likely be empty.
+	// This will follow the error response contract, with the following keys:
+	// {
+	// 	"errors": [
+	// 		{
+	// 			"code": "string",
+	// 			"message": "string",
+	// 			"metadata": {}
+	// 		}
+	// 	]
+	// }
+	ConditionMetadata JSONB `json:"condition_metadata" sql:"type:jsonb" gorm:"type:jsonb"`
 }
 
 // TableName overrides the table name
 func (APIContractRevision) TableName() string {
 	return "api_contract_revisions"
 }
+
+// JSONB implements the jsonb type in postgres for gorm
+type JSONB map[string]any
+
+// Value implements the driver.Valuer interface
+func (j JSONB) Value() (driver.Value, error) {
+	valueString, err := json.Marshal(j)
+	return string(valueString), err
+}
+
+// Scan implements the sql.Scanner interface
+func (j *JSONB) Scan(value interface{}) error {
+	if err := json.Unmarshal(value.([]byte), &j); err != nil {
+		return err
+	}
+	return nil
+}

+ 2 - 2
internal/repository/gorm/api_contract.go

@@ -39,13 +39,13 @@ func (cr APIContractRepository) List(ctx context.Context, projectID uint, cluste
 	var confs []*models.APIContractRevision
 
 	if clusterID == 0 {
-		tx := cr.db.Where("project_id = ?", projectID).Find(&confs)
+		tx := cr.db.Where("project_id = ?", projectID).Find(&confs).Order("created_at desc")
 		if tx.Error != nil {
 			return nil, tx.Error
 		}
 		return confs, nil
 	}
-	tx := cr.db.Where("project_id = ? and cluster_id = ?", projectID, clusterID).Find(&confs)
+	tx := cr.db.Where("project_id = ? and cluster_id = ?", projectID, clusterID).Find(&confs).Order("created_at desc")
 	if tx.Error != nil {
 		return nil, tx.Error
 	}