Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip #4655

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

wip #4655

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading