Forráskód Böngészése

clean up delete cluster modal

Justin Rhee 3 éve
szülő
commit
0102654531

+ 27 - 14
api/server/handlers/api_contract/update.go

@@ -2,11 +2,12 @@ package api_contract
 
 import (
 	"encoding/base64"
+	"errors"
 	"fmt"
 	"net/http"
 
-	"github.com/golang/protobuf/jsonpb"
 	"github.com/nats-io/nats.go"
+	helpers "github.com/porter-dev/api-contracts/generated/go/helpers"
 	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -14,7 +15,6 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
-	"google.golang.org/protobuf/proto"
 )
 
 type APIContractUpdateHandler struct {
@@ -32,16 +32,25 @@ func NewAPIContractUpdateHandler(
 }
 
 // ServeHTTP parses the Porter API contract for validity, and forwards the requests for handling on to another service
-// For now, this handling cluster creation only, by insertin a row into the cluster table in order to create an ID for this cluster, as well as stores the raw request JSON for updating later
+// For now, this handling cluster creation only, by inserting a row into the cluster table in order to create an ID for this cluster, as well as stores the raw request JSON for updating later
 func (c *APIContractUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	var apiContract porterv1.Contract
 	ctx := r.Context()
 
-	if ok := c.DecodeAndValidate(w, r, &apiContract); !ok {
+	var apiContract porterv1.Contract
+
+	err := helpers.UnmarshalContractObject(r.Body, &apiContract)
+	if err != nil {
+		e := fmt.Errorf("error parsing api contract: %w", err)
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
+		return
+	}
+
+	if apiContract.Cluster == nil {
+		e := errors.New("missing cluster object")
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
 		return
 	}
 
-	// handle cluster object for now
 	cl := apiContract.Cluster
 
 	if cl.ClusterId == 0 {
@@ -62,8 +71,7 @@ func (c *APIContractUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		apiContract.Cluster.ClusterId = int32(dbCluster.ID)
 	}
 
-	var jpbm jsonpb.Marshaler
-	by, err := jpbm.MarshalToString(&apiContract)
+	by, err := helpers.MarshalContractObject(ctx, &apiContract)
 	if err != nil {
 		e := fmt.Errorf("error marshalling api contract: %w", err)
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
@@ -86,13 +94,18 @@ func (c *APIContractUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	// This gates the cluster actually being provisioned by CAPI
 	// This can be removed whenever we are able to run NATS and CCP locally, easier
-	kubeBy, err := proto.Marshal(&apiContract)
-	if err != nil {
-		e := fmt.Errorf("error marshalling proto: %w", err)
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
-		return
-	}
 	if !c.Config().DisableCAPIProvisioner {
+		resp := porterv1.ContractRevision{
+			ProjectId:  cl.ProjectId,
+			ClusterId:  cl.ClusterId,
+			RevisionId: contractRevision.ID.String(),
+		}
+		kubeBy, err := helpers.MarshalContractObject(ctx, &resp)
+		if err != nil {
+			e := fmt.Errorf("error marshalling api contract: %w", err)
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(e))
+			return
+		}
 		subject := "porter.system.infrastructure.update"
 		_, err = c.Config().NATS.JetStream.Publish(subject, kubeBy, nats.Context(ctx))
 		if err != nil {

+ 8 - 2
cmd/migrate/main.go

@@ -37,12 +37,10 @@ func main() {
 	}
 
 	err = gorm.AutoMigrate(db, envConf.ServerConf.Debug)
-
 	if err != nil {
 		logger.Fatal().Err(err).Msg("gorm auto-migration failed")
 		return
 	}
-
 	if err := db.Raw("ALTER TABLE clusters DROP CONSTRAINT IF EXISTS fk_cluster_token_caches").Error; err != nil {
 		logger.Fatal().Err(err).Msg("failed to drop cluster token cache constraint")
 		return
@@ -51,6 +49,14 @@ func main() {
 		logger.Fatal().Err(err).Msg("failed to drop clusters token cache constraint")
 		return
 	}
+	if err := db.Exec("alter table aws_assume_role_chains ADD CONSTRAINT fk_projects FOREIGN KEY(project_id) REFERENCES projects(id);").Error; err != nil {
+		logger.Fatal().Err(err).Msg("failed to create fk constraint for assume role chains")
+		return
+	}
+	if err := db.Exec("alter table aws_assume_role_chains ADD unique (project_id, source_arn, target_arn);").Error; err != nil {
+		logger.Fatal().Err(err).Msg("failed to create unique constraint for assume role chains")
+		return
+	}
 
 	tx := db.Begin()
 

+ 11 - 2
dashboard/package-lock.json

@@ -1199,6 +1199,11 @@
         "to-fast-properties": "^2.0.0"
       }
     },
+    "@bufbuild/protobuf": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.1.0.tgz",
+      "integrity": "sha512-NYqSqOp+7u37sfQugoJaIWbnQdUTwB0UtfNhIeN71rr/xY8upO9zIvM865Ju7ws/21xbu05RUdC1gNhSggmCYg=="
+    },
     "@emotion/hash": {
       "version": "0.8.0",
       "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
@@ -1439,8 +1444,12 @@
       "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
     },
     "@porter-dev/api-contracts": {
-      "version": "https://gitpkg.now.sh/porter-dev/api-contracts/generated/js?main",
-      "integrity": "sha512-h0GjhWAC3wE6m568/CxHo9xSqG5XhOxKHxA27e5tgQyziN163bVkGmdFqinne+P+pmtey10vQBiv0TwEqjn7Aw=="
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.33.tgz",
+      "integrity": "sha512-TMkGjDo4wt/VZ5Djhnqi8e9CHkn7LhbaJMZXrtWHYHh9/etT+Ktyr5v7Gnf597vxGSz3WsgDC3Uz3nFCU07mdA==",
+      "requires": {
+        "@bufbuild/protobuf": "^1.1.0"
+      }
     },
     "@sentry/browser": {
       "version": "6.15.0",

+ 1 - 1
dashboard/package.json

@@ -7,7 +7,7 @@
     "@loadable/component": "^5.15.2",
     "@material-ui/core": "^4.11.3",
     "@material-ui/lab": "^4.0.0-alpha.61",
-    "@porter-dev/api-contracts": "https://gitpkg.now.sh/porter-dev/api-contracts/generated/js?main",
+    "@porter-dev/api-contracts": "^0.0.33",
     "@sentry/react": "^6.13.2",
     "@sentry/tracing": "^6.13.2",
     "@tanstack/react-query": "^4.13.0",

+ 16 - 19
dashboard/src/components/ProvisionerSettings.tsx

@@ -9,7 +9,7 @@ import SelectRow from "components/form-components/SelectRow";
 import Heading from "components/form-components/Heading";
 import InputRow from "./form-components/InputRow";
 import SaveButton from "./SaveButton";
-import { Contract, EnumKubernetesKind, EnumCloudProvider, NodeGroupType } from "@porter-dev/api-contracts";
+import { Contract, EnumKubernetesKind, EnumCloudProvider, NodeGroupType, EKSNodeGroup, EKS, Cluster } from "@porter-dev/api-contracts";
 
 const regionOptions = [
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
@@ -61,61 +61,58 @@ const ProvisionerForm: React.FC<Props> = ({
   const [isReadOnly, setIsReadOnly] = useState(false);
 
   const createCluster = async () => {
-    var data: Contract = {
-      cluster: {
+    var data = new Contract({
+      cluster: new Cluster({
         projectId: currentProject.id,
-        clusterId: null,
         kind: EnumKubernetesKind.EKS,
         cloudProvider: EnumCloudProvider.AWS,
         cloudProviderCredentialsId: String(credentialId),
         kindValues: {
           case: "eksKind",
-          value: {
+          value: new EKS({
             clusterName,
             clusterVersion: "v1.24.0",
             cidrRange: cidrRange || "172.0.0.0/16",
             region: awsRegion,
             nodeGroups: [
-              {
+              new EKSNodeGroup({
                 instanceType: "t3.medium",
                 minInstances: 1,
                 maxInstances: 5,
                 nodeGroupType: NodeGroupType.SYSTEM,
                 isStateful: false,
-                nameSuffix: "",
-              },
-              {
+              }),
+              new EKSNodeGroup({
                 instanceType: "t3.large",
                 minInstances: 1,
                 maxInstances: 5,
                 nodeGroupType: NodeGroupType.MONITORING,
                 isStateful: false,
-                nameSuffix: "",
-              },
-              {
+              }),
+              new EKSNodeGroup({
                 instanceType: machineType,
                 minInstances: minInstances || 1,
                 maxInstances: maxInstances || 10,
                 nodeGroupType: NodeGroupType.APPLICATION,
                 isStateful: false,
-                nameSuffix: "",
-              }
+              })
             ]
-          }
+          })
         },
-      }
-    };
+      })
+    });
 
     if (clusterId) {
       data["cluster"]["clusterId"] = clusterId;
     }
 
     try {
-      await api.createContract(
+      const res = await api.createContract(
         "<token>",
         data,
         { project_id: currentProject.id }
       );
+      console.log(res.data);
     } catch (err) {
       console.log(err);
     }
@@ -124,7 +121,7 @@ const ProvisionerForm: React.FC<Props> = ({
   useEffect(() => {
     setIsReadOnly(
       clusterId && (
-        currentCluster.status === "UPDATING" || 
+        currentCluster.status === "UPDATING" ||
         currentCluster.status === "UPDATING_UNAVAILABLE"
       )
     );

+ 0 - 2
dashboard/src/main/home/Home.tsx

@@ -65,8 +65,6 @@ type StateType = {
   showWelcomeForm: boolean;
 };
 
-// TODO: Handle cluster connected but with some failed infras (no successful set)
-// TODO: Set up current view / sidebar tab as dynamic Routes
 class Home extends Component<PropsType, StateType> {
   state = {
     forceSidebar: true,

+ 2 - 2
dashboard/src/main/home/ModalHandler.tsx

@@ -108,9 +108,9 @@ const ModalHandler: React.FC<{
         modal === "UpdateClusterModal" && (
           <Modal
             onRequestClose={() => setCurrentModal(null, null)}
-            width="565px"
+            width="600px"
             height="275px"
-            title="Cluster Settings"
+            title="Delete cluster"
           >
             <UpdateClusterModal
               setRefreshClusters={(x: boolean) => setRefreshClusters(x)}

+ 11 - 13
dashboard/src/main/home/modals/UpdateClusterModal.tsx

@@ -85,9 +85,7 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
     if (!currentCluster?.infra_id || !currentCluster.service) {
       return (
         <Warning highlight={true}>
-          ⚠️ Since this cluster was not provisioned by Porter, deleting the
-          cluster will only detach this cluster from your project. To delete the
-          cluster itself, you must do so manually.
+          ⚠️ Deleting the cluster will only detach this cluster from your project. To delete resources you must do so manually.
         </Warning>
       );
     }
@@ -115,7 +113,7 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
             value={this.state.clusterName}
             setValue={(x: string) => this.setState({ clusterName: x })}
             placeholder="ex: perspective-vortex"
-            width="470px"
+            width="490px"
           />
         </InputWrapper>
 
@@ -125,11 +123,11 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
           href="https://docs.getporter.dev/docs/deleting-dangling-resources"
           target="_blank"
         >
-          <i className="material-icons">help_outline</i> Help
+          <i className="material-icons">help_outline</i> How to delete resources
         </Help>
 
         <SaveButton
-          text="Delete Cluster"
+          text="Delete cluster"
           color="#b91133"
           onClick={() => this.setState({ showDeleteOverlay: true })}
           status={this.state.status}
@@ -156,6 +154,7 @@ const Help = styled.a`
   bottom: 35px;
   display: flex;
   align-items: center;
+  z-index: 999;
   justify-content: center;
   color: #ffffff55;
   font-size: 13px;
@@ -164,8 +163,8 @@ const Help = styled.a`
   }
 
   > i {
-    margin-right: 9px;
-    font-size: 16px;
+    margin-right: 5px;
+    font-size: 14px;
   }
 `;
 
@@ -175,7 +174,6 @@ const Warning = styled.div`
   border-radius: 3px;
   width: calc(100%);
   margin-top: 10px;
-  margin-left: 2px;
   line-height: 1.4em;
   align-items: center;
   color: white;
@@ -188,15 +186,15 @@ const Warning = styled.div`
 `;
 
 const DashboardIcon = styled.div`
-  width: 25px;
-  min-width: 25px;
-  height: 25px;
+  width: 35px;
+  min-width: 35px;
+  height: 35px;
   border-radius: 3px;
   overflow: hidden;
   position: relative;
   margin-right: 10px;
   font-weight: 400;
-  margin-top: 14px;
+  margin-top: 8px;
   display: flex;
   align-items: center;
   justify-content: center;

+ 3 - 3
go.mod

@@ -24,7 +24,7 @@ require (
 	github.com/go-redis/redis/v8 v8.11.0
 	github.com/go-test/deep v1.0.7
 	github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.2
 	github.com/google/go-github/v39 v39.2.0
 	github.com/google/go-github/v41 v41.0.0
 	github.com/gorilla/schema v1.2.0
@@ -52,7 +52,7 @@ require (
 	google.golang.org/api v0.103.0
 	google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd
 	google.golang.org/grpc v1.50.1
-	google.golang.org/protobuf v1.28.1
+	google.golang.org/protobuf v1.29.0
 	gorm.io/gorm v1.24.2
 	k8s.io/api v0.26.0
 	k8s.io/apimachinery v0.26.0
@@ -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.23
+	github.com/porter-dev/api-contracts v0.0.38
 	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

+ 6 - 8
go.sum

@@ -1466,12 +1466,10 @@ 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.10 h1:TIlyVtrufoJh9mnbOGlIuf1w99/8YWppBVzFWcRHI8c=
-github.com/porter-dev/api-contracts v0.0.10/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
-github.com/porter-dev/api-contracts v0.0.22 h1:71uJjDDFUJ++XR98weglbKHm+ZMaIJB+dLD7ic2drgU=
-github.com/porter-dev/api-contracts v0.0.22/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
-github.com/porter-dev/api-contracts v0.0.23 h1:WlVx4usw3zsActtZphEUhkQZijPCZNitH23uCxik/II=
-github.com/porter-dev/api-contracts v0.0.23/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
+github.com/porter-dev/api-contracts v0.0.37 h1:q9J7d5Z2DsS8HFM/x6lXU+Bescc8D5FhFCH0mxgmofM=
+github.com/porter-dev/api-contracts v0.0.37/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
+github.com/porter-dev/api-contracts v0.0.38 h1:rR+z+t++6gA8DKmDYKeFJoPB2LB+ABBkpI/l6iFfSBI=
+github.com/porter-dev/api-contracts v0.0.38/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=
@@ -2460,8 +2458,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
+google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 9 - 7
internal/models/aws_assume_role_chain.go

@@ -5,25 +5,27 @@ import (
 	"gorm.io/gorm"
 )
 
-// AWSAssumeRoleChain represents an assume role chain link
+// AWSAssumeRoleChain represents an assume role chain link.
+// a unique constraint is created on this table by the migration script
+// because gorm creates unique indices, instead of unique constraints, which is utterly useless.
 type AWSAssumeRoleChain struct {
 	gorm.Model
 
 	// ID is a UUID for the CAPI Cluster's config
 	ID uuid.UUID `gorm:"type:uuid;primaryKey"`
 
+	// ProjectID is the ID of the project that the config belongs to.
+	// This should be a foreign key, but GORM doesnt play well with FKs.
+	ProjectID int `json:"project_id"`
+
 	// SourceARN is ARN which will assume the target ARN
-	SourceARN string `json:"source_arn" gorm:"UNIQUE_INDEX:aws_assume_role_chains_project_id_source_arn_target_arn_key"`
+	SourceARN string `json:"source_arn"`
 
 	// TargetARN is ARN which will assume the target ARN
-	TargetARN string `json:"target_arn" gorm:"UNIQUE_INDEX:aws_assume_role_chains_project_id_source_arn_target_arn_key"`
+	TargetARN string `json:"target_arn"`
 
 	// ExternalID is ID which is required when assuming a role
 	ExternalID string `json:"external_id"`
-
-	// ProjectID is the ID of the project that the config belongs to.
-	// This should be a foreign key, but GORM doesnt play well with FKs.
-	ProjectID int `json:"project_id" gorm:"UNIQUE_INDEX:aws_assume_role_chains_project_id_source_arn_target_arn_key"`
 }
 
 // TableName overrides the table name