Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
d-g-town committed May 17, 2024
1 parent 32af545 commit 69b7847
Show file tree
Hide file tree
Showing 16 changed files with 813 additions and 24 deletions.
94 changes: 94 additions & 0 deletions api/server/handlers/cluster/delete_node_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cluster

import (
"net/http"

"connectrpc.com/connect"

porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
"github.com/porter-dev/porter/internal/telemetry"

"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/types"
"github.com/porter-dev/porter/internal/models"
)

// DeleteNodeGroupHandler is the handler for the /delete-node-group endpoint
type DeleteNodeGroupHandler struct {
handlers.PorterHandlerReadWriter
authz.KubernetesAgentGetter
}

// NewDeleteNodeGroupHandler returns a handler for handling node group requests
func NewDeleteNodeGroupHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *DeleteNodeGroupHandler {
return &DeleteNodeGroupHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
}
}

// DeleteNodeGroupRequest represents the request to delete a node group
type DeleteNodeGroupRequest struct {
// NodeGroupId is the id of the node group to delete
NodeGroupId string `json:"node_group_id"`
}

// DeleteNodeGroupResponse represents the response from deleting a node group
type DeleteNodeGroupResponse struct {
// ContractRevisionId is the id of the contract revision created by the deletion
ContractRevisionId string `json:"contract_revision_id"`
}

// ServeHTTP handles GET requests to list node groups
func (c *DeleteNodeGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-list-nodes")
defer span.End()

project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)

request := &DeleteNodeGroupRequest{}

ok := c.DecodeAndValidate(w, r, request)
if !ok {
err := telemetry.Error(ctx, span, nil, "error decoding delete node group request")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
}

if request.NodeGroupId == "" {
err := telemetry.Error(ctx, span, nil, "node group id is empty")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
}

userNodeGroupReq := connect.NewRequest(&porterv1.DeleteUserNodeGroupRequest{
ProjectId: int64(project.ID),
ClusterId: int64(cluster.ID),
UserNodeGroupId: request.NodeGroupId,
})

ccpResp, err := c.Config().ClusterControlPlaneClient.DeleteUserNodeGroup(ctx, userNodeGroupReq)
if err != nil {
err := telemetry.Error(ctx, span, err, "error deleting user node group")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}
if ccpResp == nil || ccpResp.Msg == nil {
err := telemetry.Error(ctx, span, err, "ccp resp msg is nil")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

resp := &DeleteNodeGroupResponse{
ContractRevisionId: ccpResp.Msg.ContractRevisionId,
}

c.WriteResult(w, r, resp)
}
94 changes: 94 additions & 0 deletions api/server/handlers/cluster/node_groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cluster

import (
"net/http"

"connectrpc.com/connect"

porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
"github.com/porter-dev/porter/internal/telemetry"

"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/types"
"github.com/porter-dev/porter/internal/models"
)

// NodeGroupsHandler is the handler for the /node-groups endpoint
type NodeGroupsHandler struct {
handlers.PorterHandlerWriter
authz.KubernetesAgentGetter
}

// NewNodeGroupsHandler returns a handler for handling node group requests
func NewNodeGroupsHandler(
config *config.Config,
writer shared.ResultWriter,
) *NodeGroupsHandler {
return &NodeGroupsHandler{
PorterHandlerWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
}
}

// NodeGroupsResponse represents the response to a list node groups request
type NodeGroupsResponse struct {
NodeGroups []NodeGroup `json:"node_groups"`
}

// NodeGroup represents a node group managed by a user
type NodeGroup struct {
Name string `json:"name"`
Id string `json:"id"`
InstanceType string `json:"instance_type"`
RamMb int32 `json:"ram_mb"`
CpuCores float32 `json:"cpu_cores"`
GpuCores int32 `json:"gpu_cores"`
}

// ServeHTTP handles GET requests to list node groups
func (c *NodeGroupsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-list-nodes")
defer span.End()

project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)

userNodeGroupReq := connect.NewRequest(&porterv1.UserNodeGroupsRequest{
ProjectId: int64(project.ID),
ClusterId: int64(cluster.ID),
})

ccpResp, err := c.Config().ClusterControlPlaneClient.UserNodeGroups(ctx, userNodeGroupReq)
if err != nil {
err := telemetry.Error(ctx, span, err, "error creating deployment target")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}
if ccpResp == nil || ccpResp.Msg == nil {
err := telemetry.Error(ctx, span, err, "ccp resp msg is nil")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

var nodeGroups []NodeGroup
for _, ng := range ccpResp.Msg.UserNodeGroups {
nodeGroups = append(nodeGroups, NodeGroup{
Name: ng.Name,
Id: ng.Id,
InstanceType: ng.InstanceType,
RamMb: ng.RamMb,
CpuCores: ng.CpuCores,
GpuCores: ng.GpuCores,
})
}

res := &NodeGroupsResponse{
NodeGroups: nodeGroups,
}

c.WriteResult(w, r, res)
}
57 changes: 57 additions & 0 deletions api/server/router/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,63 @@ func getClusterRoutes(
Router: r,
})

// GET /api/projects/{project_id}/clusters/{cluster_id}/node-groups -> cluster.NewNodeGroupsHandler
nodeGroupsEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbGet,
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: relPath + "/node-groups",
},
Scopes: []types.PermissionScope{
types.UserScope,
types.ProjectScope,
types.ClusterScope,
},
},
)

nodeGroupsHandler := cluster.NewNodeGroupsHandler(
config,
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: nodeGroupsEndpoint,
Handler: nodeGroupsHandler,
Router: r,
})

// GET /api/projects/{project_id}/clusters/{cluster_id}/delete-node-group -> cluster.NewNodeGroupsHandler
deleteNodeGroupEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbUpdate,
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: relPath + "/delete-node-group",
},
Scopes: []types.PermissionScope{
types.UserScope,
types.ProjectScope,
types.ClusterScope,
},
},
)

deleteNodeGroupHandler := cluster.NewDeleteNodeGroupHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: deleteNodeGroupEndpoint,
Handler: deleteNodeGroupHandler,
Router: r,
})

// GET /api/projects/{project_id}/clusters/{cluster_id}/nodes/{node_name} -> cluster.NewGetNodeHandler
getNodeEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Expand Down
8 changes: 4 additions & 4 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"@babel/preset-typescript": "^7.15.0",
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@porter-dev/api-contracts": "^0.2.164",
"@porter-dev/api-contracts": "^0.2.167",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
Expand Down
44 changes: 44 additions & 0 deletions dashboard/src/components/porter/TrashDelete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import styled from "styled-components";

type Props = {
handleDelete: () => void;
};

const TrashDelete: React.FC<Props> = ({ handleDelete }) => {
return (
<ActionButton
onClick={(e) => {
e.stopPropagation();
handleDelete();
}}
type={"button"}
>
<span className="material-icons">delete</span>
</ActionButton>
);
};

export default TrashDelete;

const ActionButton = styled.button`
position: relative;
border: none;
background: none;
color: white;
padding: 5px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
cursor: pointer;
color: #aaaabb;
:hover {
color: white;
}
> span {
font-size: 20px;
}
margin-right: 5px;
`;
Loading

0 comments on commit 69b7847

Please sign in to comment.