Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aabc4ee
chore(deps): update all dependencies and go itself
f0086 Nov 18, 2025
a5978a3
fix(build): disable darwin builds for now
f0086 Nov 18, 2025
bb4ec29
chore(dependencies): update all dependencies, go version, action vers…
f0086 Dec 19, 2025
e99a68a
chore(deps): bump golang.org/x/crypto from 0.46.0 to 0.47.0
dependabot[bot] Jan 13, 2026
950e799
chore(deps): update dependencies and improve cobra command handling
f0086 Mar 2, 2026
9bab5cb
fix(node): add id parameter to node update action
f0086 Mar 2, 2026
5958d1b
fix(project): deprecate description and environment fields and fix ge…
f0086 Mar 2, 2026
282a6fc
fix(network): disable network create command until ListSubnets endpoi…
f0086 Mar 2, 2026
a9b2506
fix(cli): fix parameter descriptions and defaults across commands
f0086 Mar 2, 2026
02c4c21
chore(deps): update dependencies and go version to 1.26.2
f0086 Apr 27, 2026
c31097b
fix(cli): improve enum completion filtering and update cobra setup co…
f0086 May 6, 2026
aaf3b38
chore(deps): bump google.golang.org/grpc from 1.80.0 to 1.81.0
dependabot[bot] May 4, 2026
a7bffe0
ci(release): make next the prerelease branch
f0086 Jun 9, 2026
3120a01
chore(deps): bump gpcore gRPC/protobuf to 20260601 schema
f0086 Jun 8, 2026
4c0f041
feat(generator): add session-sourced params and --project-id override
f0086 Jun 8, 2026
ab0fd7b
fix(cli): accept --project-id or "project use" interchangeably
f0086 Jun 8, 2026
1508384
fix(cli): reconcile command params with the gRPC request fields
f0086 Jun 8, 2026
253efc7
feat(cli): add missing gRPC coverage commands
f0086 Jun 8, 2026
e737af3
fix(cli): keep active project consistent across user contexts
f0086 Jun 8, 2026
fc1e6d8
chore(project): remove disabled network-create stub
f0086 Jun 8, 2026
4cfc0a2
Merge remote-tracking branch 'origin/next' into promote-gpco49-to-main
f0086 Jun 9, 2026
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