Skip to content
Open
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
8 changes: 4 additions & 4 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"branches": [
{
"name": "dev",
"prerelease": true
},
{
"name": "main",
"prerelease": false
},
{
"name": "next",
"prerelease": true
}
],
"plugins": [
Expand Down
75 changes: 75 additions & 0 deletions cmd/admin_project/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package admin_project

import (
"buf.build/gen/go/gportal/gpcore/grpc/go/gpcore/api/admin/v1/adminv1grpc"
adminv1 "buf.build/gen/go/gportal/gpcore/protocolbuffers/go/gpcore/api/admin/v1"
cloudv1 "buf.build/gen/go/gportal/gpcore/protocolbuffers/go/gpcore/api/cloud/v1"
"github.com/G-PORTAL/gpcore-cli/pkg/client"
"github.com/G-PORTAL/gpcore-cli/pkg/config"
"github.com/G-PORTAL/gpcore-cli/pkg/protobuf"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)

// update is implemented manually (not generated) because
// admin.UpdateProjectRequest carries a nested BillingProfile message which the
// YAML generator cannot express as command flags. The billing profile is
// referenced by its UUID; the backend resolves the full profile from the ID.

var updateId string
var updateName string
var updateAvatarUrl string
var updateBillingProfileId string
var updateServerPoolIds []string

var updateCmd = &cobra.Command{
Args: cobra.OnlyValidArgs,
DisableFlagsInUseLine: true,
Long: "Update project details (admin)",
RunE: func(cobraCmd *cobra.Command, args []string) error {
ctx := client.ExtractContext(cobraCmd)
grpcConn := ctx.Value("conn").(*grpc.ClientConn)
grpcClient := adminv1grpc.NewAdminServiceClient(grpcConn)

req := &adminv1.UpdateProjectRequest{
Id: updateId,
Name: updateName,
AvatarUrl: updateAvatarUrl,
ServerPoolIds: updateServerPoolIds,
}
if updateBillingProfileId != "" {
req.BillingProfile = cloudv1.BillingProfile_builder{
Id: updateBillingProfileId,
}.Build()
}

resp, err := grpcClient.UpdateProject(cobraCmd.Context(), req)
if err != nil {
return err
}
if config.JSONOutput {
jsonData, err := protobuf.MarshalIndent(resp)
if err != nil {
return err
}
cobraCmd.Println(string(jsonData))
}
return nil
},
Short: "Update project details (admin)",
Use: "update",
ValidArgs: []string{"id", "name", "avatar-url", "billing-profile-id", "server-pool-ids"},
}

func init() {
updateCmd.Flags().StringVar(&updateId, "id", "", "Project UUID (required)")
updateCmd.Flags().StringVar(&updateName, "name", "", "Project name (required)")
updateCmd.Flags().StringVar(&updateAvatarUrl, "avatar-url", "", "Avatar URL")
updateCmd.Flags().StringVar(&updateBillingProfileId, "billing-profile-id", "", "Billing profile UUID")
updateCmd.Flags().StringSliceVar(&updateServerPoolIds, "server-pool-ids", nil, "Server pool UUIDs")

updateCmd.MarkFlagRequired("id")
updateCmd.MarkFlagRequired("name")

RootAdminProjectCommand.AddCommand(updateCmd)
}
1 change: 1 addition & 0 deletions cmd/agent/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func New() *cobra.Command {
cmd.SelfupdateCommand(&rootCmd)
cmd.SetLogLevelCommand(&rootCmd)
cmd.LiveLogCommand(&rootCmd)
cmd.NotificationsCommand(&rootCmd)
//InteractiveCLICommand(&rootCmd)

// Autogenerated commands
Expand Down
3 changes: 2 additions & 1 deletion cmd/agent/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"syscall"

"github.com/G-PORTAL/gpcore-cli/pkg/api"
"github.com/G-PORTAL/gpcore-cli/pkg/client"
"github.com/G-PORTAL/gpcore-cli/pkg/config"
"github.com/G-PORTAL/gpcore-cli/pkg/consts"
"github.com/charmbracelet/log"
Expand Down Expand Up @@ -71,7 +72,7 @@ var startCmd = &cobra.Command{

if err := rootCmd.ExecuteContext(ctx); err != nil {
log.Errorf("Error executing command on agent: %v", err)
rootCmd.Printf("Error executing command on agent: %v\n", err)
rootCmd.Printf("Error: %s\n", client.FormatCommandError(err))
_ = s.Exit(1) // send cmd exit code to the client
return
}
Expand Down
31 changes: 4 additions & 27 deletions cmd/livelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"buf.build/gen/go/gportal/gpcore/grpc/go/gpcore/api/admin/v1/adminv1grpc"
adminv1 "buf.build/gen/go/gportal/gpcore/protocolbuffers/go/gpcore/api/admin/v1"
cloudv1 "buf.build/gen/go/gportal/gpcore/protocolbuffers/go/gpcore/api/cloud/v1"
"github.com/charmbracelet/ssh"
"github.com/G-PORTAL/gpcore-cli/pkg/api"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
"google.golang.org/grpc"
Expand All @@ -18,37 +18,16 @@ func LiveLogCommand(rootCmd *cobra.Command) {
DisableFlagsInUseLine: true,
Args: cobra.OnlyValidArgs,
RunE: func(cobraCmd *cobra.Command, args []string) error {
sshSession := cobraCmd.Context().Value("ssh").(*ssh.Session)
cobraCmd.SetOut(*sshSession)

conn := cobraCmd.Context().Value("conn").(*grpc.ClientConn)
admin := adminv1grpc.NewAdminServiceClient(conn)
res, err := admin.SubscribeServerLogs(cobraCmd.Context(), &adminv1.SubscribeServerLogsRequest{})
stream, err := admin.SubscribeServerLogs(cobraCmd.Context(), &adminv1.SubscribeServerLogsRequest{})
if err != nil {
return err
}

connectionClosed := false
go func() {
breakChan := make(chan bool)
(*sshSession).Break(breakChan)
<-breakChan
connectionClosed = true
}()

cobraCmd.Printf("\033[33mWaiting for new notifications ...\033[0m\n")
// We wait for (and print out) relevant notifications until the break
// request is received.
for {
if connectionClosed {
break
}

msg, err := res.Recv() // Blocking
if err != nil {
return err
}

return api.StreamMessages(cobraCmd, stream, func(msg *adminv1.SubscribeServerLogsResponse) {
// TODO: Filter for source/server/datacenter
// TODO: Only above level ...

Expand Down Expand Up @@ -78,9 +57,7 @@ func LiveLogCommand(rootCmd *cobra.Command) {

cobraCmd.Printf("%s: [%s] [%s] [%s] -> %s\n", color.Sprint(time), datacenter, server, source, color.Sprint(m.GetMessage()))
}
}

return nil
})
},
})
}
18 changes: 16 additions & 2 deletions cmd/node/change_rescue_mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package node
import (
"buf.build/gen/go/gportal/gpcore/grpc/go/gpcore/api/cloud/v1/cloudv1grpc"
cloudv1 "buf.build/gen/go/gportal/gpcore/protocolbuffers/go/gpcore/api/cloud/v1"
"fmt"
"github.com/G-PORTAL/gpcore-cli/pkg/client"
"github.com/G-PORTAL/gpcore-cli/pkg/config"
"github.com/G-PORTAL/gpcore-cli/pkg/protobuf"
Expand All @@ -21,6 +22,20 @@ var changeRescueModeCmd = &cobra.Command{
Long: "Change rescue mode",
RunE: func(cobraCmd *cobra.Command, args []string) error {
ctx := client.ExtractContext(cobraCmd)

// --project-id is optional and falls back to the project selected via
// "project use" (mirrors the generated commands' behavior).
session := ctx.Value("config").(*config.SessionConfig)
if session == nil {
return fmt.Errorf("no session found, please login first")
}
if changeRescueModeProjectId == "" {
if session.CurrentProject == nil {
return fmt.Errorf("no project selected: pass --project-id or select one with \"project use\"")
}
changeRescueModeProjectId = *session.CurrentProject
}

grpcConn := ctx.Value("conn").(*grpc.ClientConn)
client := cloudv1grpc.NewCloudServiceClient(grpcConn)
resp, err := client.ChangeNodeRescueMode(cobraCmd.Context(), &cloudv1.ChangeNodeRescueModeRequest{
Expand Down Expand Up @@ -51,12 +66,11 @@ var changeRescueModeCmd = &cobra.Command{

func init() {
changeRescueModeCmd.Flags().StringVar(&changeRescueModeId, "id", "", "Node ID (required)")
changeRescueModeCmd.Flags().StringVar(&changeRescueModeProjectId, "project-id", "", "Project ID (required)")
changeRescueModeCmd.Flags().StringVar(&changeRescueModeProjectId, "project-id", "", "Project ID (defaults to the project selected via \"project use\")")
changeRescueModeCmd.Flags().BoolVar(&changeRescueModeEnabled, "enabled", false, "Enable or disable rescue mode")
changeRescueModeCmd.Flags().StringVar(&changeRescueModePassword, "password", "", "Password for rescue mode")

changeRescueModeCmd.MarkFlagRequired("id")
changeRescueModeCmd.MarkFlagRequired("project-id")

RootNodeCommand.AddCommand(changeRescueModeCmd)
}
17 changes: 4 additions & 13 deletions cmd/node/root.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
package node

import (
"fmt"
"github.com/G-PORTAL/gpcore-cli/pkg/config"
"github.com/spf13/cobra"
)

// Note: node subcommands no longer require a globally selected project here.
// Each subcommand accepts an optional --project-id that falls back to the
// project selected via "project use" (and errors if neither is set). This
// avoids forcing "project use" when the project is passed explicitly.
var RootNodeCommand = &cobra.Command{
Use: "node",
Short: "Utility to combine multiple nodes api actions",
Long: `Utility to combine multiple nodes api actions`,
GroupID: "resources",
DisableFlagsInUseLine: true,
Args: cobra.MatchAll(cobra.ExactArgs(0), cobra.OnlyValidArgs),
PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error {
// Context is not set in PersistentPreRunE, so we need to get the session config manually
config, err := config.GetSessionConfig()
if err != nil {
return err
}
if config.CurrentProject == nil {
return fmt.Errorf("no project selected")
}
return nil
},
RunE: func(cobraCmd *cobra.Command, args []string) error {
return cobraCmd.Usage()
},
Expand Down
61 changes: 61 additions & 0 deletions cmd/notifications.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cmd

import (
"buf.build/gen/go/gportal/gpcore/grpc/go/gpcore/api/cloud/v1/cloudv1grpc"
cloudv1 "buf.build/gen/go/gportal/gpcore/protocolbuffers/go/gpcore/api/cloud/v1"
"github.com/G-PORTAL/gpcore-cli/pkg/api"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)

// NotificationsCommand registers the user-facing "notifications" command, which
// streams notifications via cloud.SubscribeNotifications. It shares the
// break/recv stream loop with "livelog" through api.StreamMessages; only the
// per-message rendering differs.
func NotificationsCommand(rootCmd *cobra.Command) {
rootCmd.AddCommand(&cobra.Command{
Use: "notifications",
Short: "Live notification stream",
Long: "Subscribe to and print live notifications for your account",
DisableFlagsInUseLine: true,
Args: cobra.OnlyValidArgs,
RunE: func(cobraCmd *cobra.Command, args []string) error {
conn := cobraCmd.Context().Value("conn").(*grpc.ClientConn)
cloudClient := cloudv1grpc.NewCloudServiceClient(conn)
stream, err := cloudClient.SubscribeNotifications(cobraCmd.Context(), &cloudv1.SubscribeNotificationsRequest{})
if err != nil {
return err
}

return api.StreamMessages(cobraCmd, stream, func(msg *cloudv1.SubscribeNotificationsResponse) {
notification := msg.GetNotification()
if notification == nil {
return
}

switch {
case notification.GetNode() != nil:
node := notification.GetNode()
label := text.Colors{text.FgCyan, text.BgBlack}.Sprint("Node")
cobraCmd.Printf("[%s] %s (%s)\n", label, node.GetFqdn(), node.GetId())
case notification.GetProject() != nil:
project := notification.GetProject()
label := text.Colors{text.FgGreen, text.BgBlack}.Sprint("Project")
cobraCmd.Printf("[%s] %s (%s)\n", label, project.GetName(), project.GetId())
case notification.GetUser() != nil:
user := notification.GetUser()
label := text.Colors{text.FgMagenta, text.BgBlack}.Sprint("User")
cobraCmd.Printf("[%s] %s (%s)\n", label, user.GetFullName(), user.GetId())
case notification.GetServerLog() != nil:
m := notification.GetServerLog()
label := text.Colors{text.FgYellow, text.BgBlack}.Sprint("ServerLog")
t := m.GetUpdatedAt().AsTime().Format("15:04:05")
cobraCmd.Printf("[%s] %s -> %s\n", label, t, m.GetMessage())
case notification.GetHeartbeat() != nil:
// Heartbeats keep the stream alive; ignore them in output.
}
})
},
})
}
11 changes: 0 additions & 11 deletions cmd/project/_network_create.go

This file was deleted.

Loading
Loading