From d8edb0395b8e8354b5d23662d2e858a19af48705 Mon Sep 17 00:00:00 2001 From: Thomas Kooi Date: Fri, 13 Feb 2026 17:56:14 +0100 Subject: [PATCH] feat(api-raw): add raw api request command Signed-off-by: Thomas Kooi --- cmd/api/api.go | 12 ++++ cmd/api/raw.go | 89 ++++++++++++++++++++++++++++++ cmd/cmd.go | 2 + cmd/dbaas/backup-schedules/list.go | 2 +- go.mod | 2 +- go.sum | 4 +- 6 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 cmd/api/api.go create mode 100644 cmd/api/raw.go diff --git a/cmd/api/api.go b/cmd/api/api.go new file mode 100644 index 0000000..5923f1e --- /dev/null +++ b/cmd/api/api.go @@ -0,0 +1,12 @@ +package api + +import ( + "github.com/spf13/cobra" +) + +// ApiCmd represents the api command +var ApiCmd = &cobra.Command{ + Use: "api", + Short: "Direct API access", + Long: "Make raw HTTP requests to the Thalassa Cloud API.", +} diff --git a/cmd/api/raw.go b/cmd/api/raw.go new file mode 100644 index 0000000..37c5ade --- /dev/null +++ b/cmd/api/raw.go @@ -0,0 +1,89 @@ +package api + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var ( + rawMethod string + rawData string + rawShowHeaders bool +) + +// rawCmd represents the raw API request command +var rawCmd = &cobra.Command{ + Use: "raw PATH", + Short: "Make a raw HTTP request to the API", + Long: `Make a raw HTTP request to the Thalassa Cloud API. + +Similar to 'kubectl get --raw', this bypasses the CLI resource layer and sends +the request directly to the API server. Uses the same authentication and +context (organisation, endpoint) as other tcloud commands. + +PATH must start with a slash (e.g. /v1/me/organisations). +Requires client-go with RawRequest support.`, + Example: ` tcloud api raw /v1/me/organisations + tcloud api raw -X GET /v1/iaas/regions + tcloud api raw -X POST -d '{"name":"test"}' /v1/some/resource + tcloud api raw --show-headers /v1/me`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + path := args[0] + if !strings.HasPrefix(path, "/") { + return fmt.Errorf("path must start with /") + } + + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + + rq := client.GetClient() + method := strings.ToUpper(rawMethod) + if method == "" { + method = "GET" + } + + var body []byte + if rawData != "" { + body = []byte(rawData) + } + + resp, err := rq.RawRequest(cmd.Context(), method, path, body) + if err != nil { + return fmt.Errorf("request failed: %w", err) + } + defer resp.RawResponse.Body.Close() + + if rawShowHeaders { + for k, v := range resp.RawResponse.Header { + fmt.Printf("%s: %s\n", k, strings.Join(v, ", ")) + } + fmt.Println() + } + + out := resp.Body() + fmt.Print(string(out)) + if len(out) > 0 && out[len(out)-1] != '\n' { + fmt.Println() + } + + if resp.StatusCode() < 200 || resp.StatusCode() >= 300 { + return fmt.Errorf("API returned status %d", resp.StatusCode()) + } + return nil + }, +} + +func init() { + ApiCmd.AddCommand(rawCmd) + + rawCmd.Flags().StringVarP(&rawMethod, "request", "X", "GET", "HTTP method (GET, POST, PUT, PATCH, DELETE)") + rawCmd.Flags().StringVarP(&rawData, "data", "d", "", "Request body (for POST, PUT, PATCH)") + rawCmd.Flags().BoolVar(&rawShowHeaders, "show-headers", false, "Print response headers") +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 26d4023..f5706cd 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" + "github.com/thalassa-cloud/cli/cmd/api" "github.com/thalassa-cloud/cli/cmd/audit" "github.com/thalassa-cloud/cli/cmd/context" "github.com/thalassa-cloud/cli/cmd/dbaas" @@ -54,6 +55,7 @@ func init() { // Register completions RootCmd.RegisterFlagCompletionFunc("organisation", completion.CompleteOrganisation) + RootCmd.AddCommand(api.ApiCmd) RootCmd.AddCommand(context.ContextCmd) RootCmd.AddCommand(version.VersionCmd) diff --git a/cmd/dbaas/backup-schedules/list.go b/cmd/dbaas/backup-schedules/list.go index b4f3d3d..045497f 100644 --- a/cmd/dbaas/backup-schedules/list.go +++ b/cmd/dbaas/backup-schedules/list.go @@ -45,7 +45,7 @@ var backupScheduleListCmd = &cobra.Command{ // If cluster identity is provided as argument, list schedules for that cluster if len(args) > 0 { clusterIdentity := args[0] - schedules, err = client.DBaaS().ListDbBackupSchedules(cmd.Context(), clusterIdentity) + schedules, err = client.DBaaS().ListDbBackupSchedules(cmd.Context(), clusterIdentity, &dbaas.ListDbBackupSchedulesRequest{}) if err != nil { return fmt.Errorf("failed to list backup schedules for cluster: %w", err) } diff --git a/go.mod b/go.mod index a1b5d6f..ed52207 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 - github.com/thalassa-cloud/client-go v0.28.2 + github.com/thalassa-cloud/client-go v0.29.3 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.35.0 k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 diff --git a/go.sum b/go.sum index ef2f041..13cd3ce 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/thalassa-cloud/client-go v0.28.2 h1:UHaR0BMYgVjXbdKkiRQlAVwIblC7vXaGXrbQwELtdN8= -github.com/thalassa-cloud/client-go v0.28.2/go.mod h1:CPY800FtJifCr1rJP4giXaXMoq7ierOyMl6KjkAvzm4= +github.com/thalassa-cloud/client-go v0.29.3 h1:nwPiZcxiK/QnEw5X9iwmFLb36IVnSw99UC5zlOZlZ1s= +github.com/thalassa-cloud/client-go v0.29.3/go.mod h1:CPY800FtJifCr1rJP4giXaXMoq7ierOyMl6KjkAvzm4= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=