diff --git a/internal/cmd/base/create.go b/internal/cmd/base/create.go index b379c35e..d7c441f2 100644 --- a/internal/cmd/base/create.go +++ b/internal/cmd/base/create.go @@ -2,8 +2,6 @@ package base import ( "context" - "encoding/json" - "io" "os" "github.com/spf13/cobra" @@ -12,14 +10,15 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" - "github.com/hetznercloud/hcloud-go/v2/hcloud" ) // CreateCmd allows defining commands for resource creation type CreateCmd struct { BaseCobraCommand func(hcapi2.Client) *cobra.Command - Run func(context.Context, hcapi2.Client, state.ActionWaiter, *cobra.Command, []string) (*hcloud.Response, any, error) - PrintResource func(context.Context, hcapi2.Client, *cobra.Command, any) + // Run is the function that will be called when the command is executed. + // It should return the created resource, the schema of the resource and an error. + Run func(context.Context, hcapi2.Client, state.ActionWaiter, *cobra.Command, []string) (any, any, error) + PrintResource func(context.Context, hcapi2.Client, *cobra.Command, any) } // CobraCommand creates a command that can be registered with cobra. @@ -53,23 +52,12 @@ func (cc *CreateCmd) CobraCommand( cmd.SetOut(os.Stdout) } - response, resource, err := cc.Run(ctx, client, actionWaiter, cmd, args) + resource, schema, err := cc.Run(ctx, client, actionWaiter, cmd, args) if err != nil { return err } if isSchema { - bytes, _ := io.ReadAll(response.Body) - - var schema map[string]any - if err := json.Unmarshal(bytes, &schema); err != nil { - return err - } - - delete(schema, "action") - delete(schema, "actions") - delete(schema, "next_actions") - if outputFlags.IsSet("json") { return util.DescribeJSON(schema) } else { diff --git a/internal/cmd/certificate/create.go b/internal/cmd/certificate/create.go index dafd5fbd..5cc9c158 100644 --- a/internal/cmd/certificate/create.go +++ b/internal/cmd/certificate/create.go @@ -13,6 +13,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -41,26 +42,33 @@ var CreateCmd = base.CreateCmd{ return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, strings []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, strings []string) (any, any, error) { certType, err := cmd.Flags().GetString("type") if err != nil { return nil, nil, err } + var cert *hcloud.Certificate switch hcloud.CertificateType(certType) { case hcloud.CertificateTypeManaged: - response, err := createManaged(ctx, client, waiter, cmd) - return response, nil, err + cert, err = createManaged(ctx, client, waiter, cmd) default: // Uploaded - response, err := createUploaded(ctx, client, cmd) - return response, nil, err + cert, err = createUploaded(ctx, client, cmd) } + if err != nil { + return nil, nil, err + } + return cert, struct { + Certificate schema.Certificate `json:"certificate"` + }{ + Certificate: hcloud.SchemaFromCertificate(cert), + }, nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, _ *cobra.Command, _ any) { // no-op }, } -func createUploaded(ctx context.Context, client hcapi2.Client, cmd *cobra.Command) (*hcloud.Response, error) { +func createUploaded(ctx context.Context, client hcapi2.Client, cmd *cobra.Command) (*hcloud.Certificate, error) { var ( name string certFile, keyFile string @@ -96,15 +104,15 @@ func createUploaded(ctx context.Context, client hcapi2.Client, cmd *cobra.Comman Certificate: string(certPEM), PrivateKey: string(keyPEM), } - cert, response, err := client.Certificate().Create(ctx, createOpts) + cert, _, err = client.Certificate().Create(ctx, createOpts) if err != nil { return nil, err } cmd.Printf("Certificate %d created\n", cert.ID) - return response, nil + return cert, nil } -func createManaged(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command) (*hcloud.Response, error) { +func createManaged(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command) (*hcloud.Certificate, error) { var ( name string domains []string @@ -127,7 +135,7 @@ func createManaged(ctx context.Context, client hcapi2.Client, waiter state.Actio Type: hcloud.CertificateTypeManaged, DomainNames: domains, } - res, response, err := client.Certificate().CreateCertificate(ctx, createOpts) + res, _, err = client.Certificate().CreateCertificate(ctx, createOpts) if err != nil { return nil, err } @@ -135,5 +143,5 @@ func createManaged(ctx context.Context, client hcapi2.Client, waiter state.Actio return nil, err } cmd.Printf("Certificate %d created\n", res.Certificate.ID) - return response, nil + return res.Certificate, nil } diff --git a/internal/cmd/firewall/create.go b/internal/cmd/firewall/create.go index bacf839c..a18de870 100644 --- a/internal/cmd/firewall/create.go +++ b/internal/cmd/firewall/create.go @@ -32,7 +32,7 @@ var CreateCmd = base.CreateCmd{ cmd.Flags().String("rules-file", "", "JSON file containing your routes (use - to read from stdin). The structure of the file needs to be the same as within the API: https://docs.hetzner.cloud/#firewalls-get-a-firewall ") return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, strings []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, strings []string) (any, any, error) { name, _ := cmd.Flags().GetString("name") labels, _ := cmd.Flags().GetStringToString("label") @@ -78,17 +78,21 @@ var CreateCmd = base.CreateCmd{ } } - result, response, err := client.Firewall().Create(ctx, opts) + res, _, err := client.Firewall().Create(ctx, opts) if err != nil { return nil, nil, err } - if err := waiter.WaitForActions(ctx, result.Actions); err != nil { + if err := waiter.WaitForActions(ctx, res.Actions); err != nil { return nil, nil, err } - cmd.Printf("Firewall %d created\n", result.Firewall.ID) + cmd.Printf("Firewall %d created\n", res.Firewall.ID) - return response, nil, err + return res.Firewall, struct { + Firewall schema.Firewall `json:"firewall"` + }{ + Firewall: hcloud.SchemaFromFirewall(res.Firewall), + }, err }, } diff --git a/internal/cmd/floatingip/create.go b/internal/cmd/floatingip/create.go index 3e2c2ca0..f1ed5c97 100644 --- a/internal/cmd/floatingip/create.go +++ b/internal/cmd/floatingip/create.go @@ -12,6 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -44,7 +45,7 @@ var CreateCmd = base.CreateCmd{ return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { typ, _ := cmd.Flags().GetString("type") if typ == "" { return nil, nil, errors.New("type is required") @@ -89,24 +90,28 @@ var CreateCmd = base.CreateCmd{ createOpts.Server = server } - result, response, err := client.FloatingIP().Create(ctx, createOpts) + res, _, err := client.FloatingIP().Create(ctx, createOpts) if err != nil { return nil, nil, err } - if result.Action != nil { - if err := waiter.ActionProgress(ctx, result.Action); err != nil { + if res.Action != nil { + if err := waiter.ActionProgress(ctx, res.Action); err != nil { return nil, nil, err } } - cmd.Printf("Floating IP %d created\n", result.FloatingIP.ID) + cmd.Printf("Floating IP %d created\n", res.FloatingIP.ID) - if err := changeProtection(ctx, client, waiter, cmd, result.FloatingIP, true, protectionOps); err != nil { + if err := changeProtection(ctx, client, waiter, cmd, res.FloatingIP, true, protectionOps); err != nil { return nil, nil, err } - return response, result.FloatingIP, nil + return res.FloatingIP, struct { + FloatingIP schema.FloatingIP `json:"floating_ip"` + }{ + FloatingIP: hcloud.SchemaFromFloatingIP(res.FloatingIP), + }, nil }, PrintResource: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource any) { diff --git a/internal/cmd/loadbalancer/create.go b/internal/cmd/loadbalancer/create.go index fb4785b8..6f788199 100644 --- a/internal/cmd/loadbalancer/create.go +++ b/internal/cmd/loadbalancer/create.go @@ -10,6 +10,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -47,7 +48,7 @@ var CreateCmd = base.CreateCmd{ return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { name, _ := cmd.Flags().GetString("name") serverType, _ := cmd.Flags().GetString("type") algorithmType, _ := cmd.Flags().GetString("algorithm-type") @@ -77,15 +78,15 @@ var CreateCmd = base.CreateCmd{ if location != "" { createOpts.Location = &hcloud.Location{Name: location} } - result, response, err := client.LoadBalancer().Create(ctx, createOpts) + res, _, err := client.LoadBalancer().Create(ctx, createOpts) if err != nil { return nil, nil, err } - if err := waiter.ActionProgress(ctx, result.Action); err != nil { + if err := waiter.ActionProgress(ctx, res.Action); err != nil { return nil, nil, err } - loadBalancer, _, err := client.LoadBalancer().GetByID(ctx, result.LoadBalancer.ID) + loadBalancer, _, err := client.LoadBalancer().GetByID(ctx, res.LoadBalancer.ID) if err != nil { return nil, nil, err } @@ -95,7 +96,11 @@ var CreateCmd = base.CreateCmd{ return nil, nil, err } - return response, loadBalancer, nil + return res.LoadBalancer, struct { + LoadBalancer schema.LoadBalancer `json:"load_balancer"` + }{ + LoadBalancer: hcloud.SchemaFromLoadBalancer(loadBalancer), + }, nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, cmd *cobra.Command, resource any) { diff --git a/internal/cmd/network/create.go b/internal/cmd/network/create.go index 77e6decd..056bfdc8 100644 --- a/internal/cmd/network/create.go +++ b/internal/cmd/network/create.go @@ -11,6 +11,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -35,7 +36,7 @@ var CreateCmd = base.CreateCmd{ cmd.RegisterFlagCompletionFunc("enable-protection", cmpl.SuggestCandidates("delete")) return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { name, _ := cmd.Flags().GetString("name") ipRange, _ := cmd.Flags().GetIPNet("ip-range") labels, _ := cmd.Flags().GetStringToString("label") @@ -54,14 +55,22 @@ var CreateCmd = base.CreateCmd{ ExposeRoutesToVSwitch: exposeRoutesToVSwitch, } - network, response, err := client.Network().Create(ctx, createOpts) + network, _, err := client.Network().Create(ctx, createOpts) if err != nil { return nil, nil, err } cmd.Printf("Network %d created\n", network.ID) - return response, nil, changeProtection(ctx, client, waiter, cmd, network, true, protectionOpts) + if err := changeProtection(ctx, client, waiter, cmd, network, true, protectionOpts); err != nil { + return nil, nil, err + } + + return network, struct { + Network schema.Network `json:"network"` + }{ + Network: hcloud.SchemaFromNetwork(network), + }, nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, _ *cobra.Command, _ any) { // no-op diff --git a/internal/cmd/placementgroup/create.go b/internal/cmd/placementgroup/create.go index 7a4443f8..219b71f2 100644 --- a/internal/cmd/placementgroup/create.go +++ b/internal/cmd/placementgroup/create.go @@ -9,6 +9,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -26,7 +27,7 @@ var CreateCmd = base.CreateCmd{ cmd.MarkFlagRequired("type") return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { name, _ := cmd.Flags().GetString("name") labels, _ := cmd.Flags().GetStringToString("label") placementGroupType, _ := cmd.Flags().GetString("type") @@ -37,20 +38,24 @@ var CreateCmd = base.CreateCmd{ Type: hcloud.PlacementGroupType(placementGroupType), } - result, response, err := client.PlacementGroup().Create(ctx, opts) + res, _, err := client.PlacementGroup().Create(ctx, opts) if err != nil { return nil, nil, err } - if result.Action != nil { - if err := waiter.ActionProgress(ctx, result.Action); err != nil { + if res.Action != nil { + if err := waiter.ActionProgress(ctx, res.Action); err != nil { return nil, nil, err } } - cmd.Printf("Placement group %d created\n", result.PlacementGroup.ID) + cmd.Printf("Placement group %d created\n", res.PlacementGroup.ID) - return response, nil, nil + return res.PlacementGroup, struct { + PlacementGroup schema.PlacementGroup `json:"placement_group"` + }{ + PlacementGroup: hcloud.SchemaFromPlacementGroup(res.PlacementGroup), + }, nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, _ *cobra.Command, _ any) { // no-op diff --git a/internal/cmd/primaryip/create.go b/internal/cmd/primaryip/create.go index 25efa20e..f99259b9 100644 --- a/internal/cmd/primaryip/create.go +++ b/internal/cmd/primaryip/create.go @@ -10,6 +10,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -40,7 +41,7 @@ var CreateCmd = base.CreateCmd{ return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { typ, _ := cmd.Flags().GetString("type") name, _ := cmd.Flags().GetString("name") assigneeID, _ := cmd.Flags().GetInt64("assignee-id") @@ -62,26 +63,30 @@ var CreateCmd = base.CreateCmd{ createOpts.AssigneeID = &assigneeID } - result, response, err := client.PrimaryIP().Create(ctx, createOpts) + res, _, err := client.PrimaryIP().Create(ctx, createOpts) if err != nil { return nil, nil, err } - if result.Action != nil { - if err := waiter.ActionProgress(ctx, result.Action); err != nil { + if res.Action != nil { + if err := waiter.ActionProgress(ctx, res.Action); err != nil { return nil, nil, err } } - cmd.Printf("Primary IP %d created\n", result.PrimaryIP.ID) + cmd.Printf("Primary IP %d created\n", res.PrimaryIP.ID) if len(protection) > 0 { - if err := changeProtection(ctx, client, waiter, cmd, result.PrimaryIP, true, protectionOpts); err != nil { + if err := changeProtection(ctx, client, waiter, cmd, res.PrimaryIP, true, protectionOpts); err != nil { return nil, nil, err } } - return response, result.PrimaryIP, nil + return res.PrimaryIP, struct { + PrimaryIP schema.PrimaryIP `json:"primary_ip"` + }{ + PrimaryIP: hcloud.SchemaFromPrimaryIP(res.PrimaryIP), + }, nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, cmd *cobra.Command, resource any) { primaryIP := resource.(*hcloud.PrimaryIP) diff --git a/internal/cmd/server/create.go b/internal/cmd/server/create.go index 92cd1957..bbb90937 100644 --- a/internal/cmd/server/create.go +++ b/internal/cmd/server/create.go @@ -20,6 +20,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) type createResult struct { @@ -27,6 +28,11 @@ type createResult struct { RootPassword string } +type createResultSchema struct { + Server schema.Server `json:"server"` + RootPassword string `json:"root_password,omitempty"` +} + // CreateCmd defines a command for creating a server. var CreateCmd = base.CreateCmd{ BaseCobraCommand: func(client hcapi2.Client) *cobra.Command { @@ -91,13 +97,13 @@ var CreateCmd = base.CreateCmd{ return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { createOpts, protectionOpts, err := createOptsFromFlags(ctx, client, cmd) if err != nil { return nil, nil, err } - result, response, err := client.Server().Create(ctx, createOpts) + result, _, err := client.Server().Create(ctx, createOpts) if err != nil { return nil, nil, err } @@ -134,7 +140,8 @@ var CreateCmd = base.CreateCmd{ cmd.Printf("Backups enabled for server %d\n", server.ID) } - return response, createResult{Server: server, RootPassword: result.RootPassword}, nil + return createResult{Server: server, RootPassword: result.RootPassword}, + createResultSchema{Server: hcloud.SchemaFromServer(server), RootPassword: result.RootPassword}, nil }, PrintResource: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource any) { diff --git a/internal/cmd/sshkey/create.go b/internal/cmd/sshkey/create.go index df1b8644..3115368f 100644 --- a/internal/cmd/sshkey/create.go +++ b/internal/cmd/sshkey/create.go @@ -11,6 +11,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) var CreateCmd = base.CreateCmd{ @@ -30,7 +31,7 @@ var CreateCmd = base.CreateCmd{ cmd.Flags().StringToString("label", nil, "User-defined labels ('key=value') (can be specified multiple times)") return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { name, _ := cmd.Flags().GetString("name") publicKey, _ := cmd.Flags().GetString("public-key") publicKeyFile, _ := cmd.Flags().GetString("public-key-from-file") @@ -57,14 +58,18 @@ var CreateCmd = base.CreateCmd{ PublicKey: publicKey, Labels: labels, } - sshKey, response, err := client.SSHKey().Create(ctx, opts) + sshKey, _, err := client.SSHKey().Create(ctx, opts) if err != nil { return nil, nil, err } cmd.Printf("SSH key %d created\n", sshKey.ID) - return response, nil, nil + return sshKey, struct { + SSHKey schema.SSHKey `json:"ssh_key"` + }{ + SSHKey: hcloud.SchemaFromSSHKey(sshKey), + }, nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, _ *cobra.Command, _ any) { // no-op diff --git a/internal/cmd/volume/create.go b/internal/cmd/volume/create.go index 473a03f5..5a963d58 100644 --- a/internal/cmd/volume/create.go +++ b/internal/cmd/volume/create.go @@ -47,7 +47,7 @@ var CreateCmd = base.CreateCmd{ return cmd }, - Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (*hcloud.Response, any, error) { + Run: func(ctx context.Context, client hcapi2.Client, waiter state.ActionWaiter, cmd *cobra.Command, args []string) (any, any, error) { name, _ := cmd.Flags().GetString("name") serverIDOrName, _ := cmd.Flags().GetString("server") size, _ := cmd.Flags().GetInt("size") @@ -93,20 +93,24 @@ var CreateCmd = base.CreateCmd{ createOpts.Format = &format } - result, response, err := client.Volume().Create(ctx, createOpts) + res, _, err := client.Volume().Create(ctx, createOpts) if err != nil { return nil, nil, err } - if err := waiter.ActionProgress(ctx, result.Action); err != nil { + if err := waiter.ActionProgress(ctx, res.Action); err != nil { return nil, nil, err } - if err := waiter.WaitForActions(ctx, result.NextActions); err != nil { + if err := waiter.WaitForActions(ctx, res.NextActions); err != nil { return nil, nil, err } - cmd.Printf("Volume %d created\n", result.Volume.ID) + cmd.Printf("Volume %d created\n", res.Volume.ID) - return response, nil, changeProtection(ctx, client, waiter, cmd, result.Volume, true, protectionOpts) + if err := changeProtection(ctx, client, waiter, cmd, res.Volume, true, protectionOpts); err != nil { + return nil, nil, err + } + + return res.Volume, hcloud.SchemaFromVolume(res.Volume), nil }, PrintResource: func(_ context.Context, _ hcapi2.Client, _ *cobra.Command, _ any) { // no-op