diff --git a/internal/cmd/base/describe.go b/internal/cmd/base/describe.go index 641e48c5..d15b757c 100644 --- a/internal/cmd/base/describe.go +++ b/internal/cmd/base/describe.go @@ -2,9 +2,7 @@ package base import ( "context" - "encoding/json" "fmt" - "io" "reflect" "strings" @@ -15,7 +13,6 @@ 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" ) // DescribeCmd allows defining commands for describing a resource. @@ -27,8 +24,10 @@ type DescribeCmd struct { JSONKeyGetByName string // e.g. "servers" NameSuggestions func(client hcapi2.Client) func() []string AdditionalFlags func(*cobra.Command) - Fetch func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) - PrintText func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error + // Fetch is called to fetch the resource to describe. + // The first returned interface is the resource itself as a hcloud struct, the second is the schema for the resource. + Fetch func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) + PrintText func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error } // CobraCommand creates a command that can be registered with cobra. @@ -59,7 +58,7 @@ func (dc *DescribeCmd) Run(ctx context.Context, client hcapi2.Client, cmd *cobra outputFlags := output.FlagsForCommand(cmd) idOrName := args[0] - resource, resp, err := dc.Fetch(ctx, client, cmd, idOrName) + resource, schema, err := dc.Fetch(ctx, client, cmd, idOrName) if err != nil { return err } @@ -72,28 +71,12 @@ func (dc *DescribeCmd) Run(ctx context.Context, client hcapi2.Client, cmd *cobra switch { case outputFlags.IsSet("json"): - return dc.describe(resp.Body, util.DescribeJSON) + return util.DescribeJSON(schema) case outputFlags.IsSet("yaml"): - return dc.describe(resp.Body, util.DescribeYAML) + return util.DescribeYAML(schema) case outputFlags.IsSet("format"): return util.DescribeFormat(resource, outputFlags["format"][0]) default: return dc.PrintText(ctx, client, cmd, resource) } } - -func (dc *DescribeCmd) describe(body io.ReadCloser, describeFunc func(interface{}) error) error { - var schema map[string]interface{} - if err := json.NewDecoder(body).Decode(&schema); err != nil { - return err - } - if resource, ok := schema[dc.JSONKeyGetByID]; ok { - return describeFunc(resource) - } - if resources, ok := schema[dc.JSONKeyGetByName].([]interface{}); ok { - // We check whether we got a resource at all above (see reflect-based nil check), so it's - // ok to assume there's an element in resources. - return describeFunc(resources[0]) - } - return fmt.Errorf("got invalid JSON response") -} diff --git a/internal/cmd/certificate/describe.go b/internal/cmd/certificate/describe.go index c5a80119..c675ec09 100644 --- a/internal/cmd/certificate/describe.go +++ b/internal/cmd/certificate/describe.go @@ -18,8 +18,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "certificate", JSONKeyGetByName: "certificates", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Certificate().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Certificate().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + cert, _, err := client.Certificate().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return cert, hcloud.SchemaFromCertificate(cert), nil }, PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { cert := resource.(*hcloud.Certificate) diff --git a/internal/cmd/datacenter/describe.go b/internal/cmd/datacenter/describe.go index 18cb7411..70103586 100644 --- a/internal/cmd/datacenter/describe.go +++ b/internal/cmd/datacenter/describe.go @@ -16,8 +16,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "datacenter", JSONKeyGetByName: "datacenters", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Datacenter().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Datacenter().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + dc, _, err := client.Datacenter().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return dc, hcloud.SchemaFromDatacenter(dc), nil }, PrintText: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { datacenter := resource.(*hcloud.Datacenter) diff --git a/internal/cmd/firewall/describe.go b/internal/cmd/firewall/describe.go index 79aeb6fa..45367df7 100644 --- a/internal/cmd/firewall/describe.go +++ b/internal/cmd/firewall/describe.go @@ -19,8 +19,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "firewall", JSONKeyGetByName: "firewalls", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Firewall().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Firewall().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + fw, _, err := client.Firewall().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return fw, hcloud.SchemaFromFirewall(fw), nil }, PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { firewall := resource.(*hcloud.Firewall) diff --git a/internal/cmd/floatingip/describe.go b/internal/cmd/floatingip/describe.go index fe2a6c6e..3f5b492b 100644 --- a/internal/cmd/floatingip/describe.go +++ b/internal/cmd/floatingip/describe.go @@ -18,8 +18,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "floating_ip", JSONKeyGetByName: "floating_ips", NameSuggestions: func(c hcapi2.Client) func() []string { return c.FloatingIP().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.FloatingIP().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + ip, _, err := client.FloatingIP().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return ip, hcloud.SchemaFromFloatingIP(ip), nil }, PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { floatingIP := resource.(*hcloud.FloatingIP) diff --git a/internal/cmd/image/describe.go b/internal/cmd/image/describe.go index 22d17639..49554517 100644 --- a/internal/cmd/image/describe.go +++ b/internal/cmd/image/describe.go @@ -26,7 +26,7 @@ var DescribeCmd = base.DescribeCmd{ }, NameSuggestions: func(c hcapi2.Client) func() []string { return c.Image().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { arch, err := cmd.Flags().GetString("architecture") if err != nil { return nil, nil, err @@ -34,7 +34,11 @@ var DescribeCmd = base.DescribeCmd{ if !cmd.Flags().Changed("architecture") { _, _ = fmt.Fprintln(os.Stderr, "INFO: This command only returns x86 images by default. Explicitly set the --architecture=x86|arm flag to hide this message.") } - return client.Image().GetForArchitecture(ctx, idOrName, hcloud.Architecture(arch)) + img, _, err := client.Image().GetForArchitecture(ctx, idOrName, hcloud.Architecture(arch)) + if err != nil { + return nil, nil, err + } + return img, hcloud.SchemaFromImage(img), nil }, PrintText: func(_ context.Context, _ hcapi2.Client, cmd *cobra.Command, resource interface{}) error { image := resource.(*hcloud.Image) diff --git a/internal/cmd/iso/describe.go b/internal/cmd/iso/describe.go index 6fbea1d0..591e8700 100644 --- a/internal/cmd/iso/describe.go +++ b/internal/cmd/iso/describe.go @@ -18,8 +18,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "iso", JSONKeyGetByName: "isos", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Location().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.ISO().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + iso, _, err := client.ISO().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return iso, hcloud.SchemaFromISO(iso), nil }, PrintText: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { iso := resource.(*hcloud.ISO) diff --git a/internal/cmd/loadbalancer/describe.go b/internal/cmd/loadbalancer/describe.go index b251004f..d488dd21 100644 --- a/internal/cmd/loadbalancer/describe.go +++ b/internal/cmd/loadbalancer/describe.go @@ -19,8 +19,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "load_balancer", JSONKeyGetByName: "load_balancers", NameSuggestions: func(c hcapi2.Client) func() []string { return c.LoadBalancer().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.LoadBalancer().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + lb, _, err := client.LoadBalancer().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return lb, hcloud.SchemaFromLoadBalancer(lb), nil }, AdditionalFlags: func(cmd *cobra.Command) { cmd.Flags().Bool("expand-targets", false, "Expand all label_selector targets") diff --git a/internal/cmd/loadbalancertype/describe.go b/internal/cmd/loadbalancertype/describe.go index 4ad11291..30a126a9 100644 --- a/internal/cmd/loadbalancertype/describe.go +++ b/internal/cmd/loadbalancertype/describe.go @@ -16,8 +16,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "load_balancer_type", JSONKeyGetByName: "load_balancer_types", NameSuggestions: func(c hcapi2.Client) func() []string { return c.LoadBalancerType().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.LoadBalancerType().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + lbt, _, err := client.LoadBalancerType().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return lbt, hcloud.SchemaFromLoadBalancerType(lbt), nil }, PrintText: func(_ context.Context, _ hcapi2.Client, cmd *cobra.Command, resource interface{}) error { loadBalancerType := resource.(*hcloud.LoadBalancerType) diff --git a/internal/cmd/location/describe.go b/internal/cmd/location/describe.go index 2b1826b6..2cff3899 100644 --- a/internal/cmd/location/describe.go +++ b/internal/cmd/location/describe.go @@ -17,8 +17,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "location", JSONKeyGetByName: "locations", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Location().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Location().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + l, _, err := client.Location().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return l, hcloud.SchemaFromLocation(l), nil }, PrintText: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { location := resource.(*hcloud.Location) diff --git a/internal/cmd/network/describe.go b/internal/cmd/network/describe.go index 5d4986f5..cf35774a 100644 --- a/internal/cmd/network/describe.go +++ b/internal/cmd/network/describe.go @@ -19,8 +19,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "network", JSONKeyGetByName: "networks", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Network().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Network().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + n, _, err := client.Network().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return n, hcloud.SchemaFromNetwork(n), nil }, PrintText: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { network := resource.(*hcloud.Network) diff --git a/internal/cmd/placementgroup/describe.go b/internal/cmd/placementgroup/describe.go index 3ad3eae5..4f581cb6 100644 --- a/internal/cmd/placementgroup/describe.go +++ b/internal/cmd/placementgroup/describe.go @@ -18,8 +18,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "placement_group", JSONKeyGetByName: "placement_groups", NameSuggestions: func(c hcapi2.Client) func() []string { return c.PlacementGroup().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.PlacementGroup().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + pg, _, err := client.PlacementGroup().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return pg, hcloud.SchemaFromPlacementGroup(pg), nil }, PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { placementGroup := resource.(*hcloud.PlacementGroup) diff --git a/internal/cmd/primaryip/describe.go b/internal/cmd/primaryip/describe.go index 653622c1..26150063 100644 --- a/internal/cmd/primaryip/describe.go +++ b/internal/cmd/primaryip/describe.go @@ -18,8 +18,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "primary_ip", JSONKeyGetByName: "primary_ips", NameSuggestions: func(c hcapi2.Client) func() []string { return c.PrimaryIP().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.PrimaryIP().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + ip, _, err := client.PrimaryIP().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return ip, hcloud.SchemaFromPrimaryIP(ip), nil }, PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { primaryIP := resource.(*hcloud.PrimaryIP) diff --git a/internal/cmd/server/describe.go b/internal/cmd/server/describe.go index c1caade6..9e760f5e 100644 --- a/internal/cmd/server/describe.go +++ b/internal/cmd/server/describe.go @@ -19,8 +19,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "server", JSONKeyGetByName: "servers", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Server().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Server().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + srv, _, err := client.Server().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return srv, hcloud.SchemaFromServer(srv), nil }, PrintText: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { server := resource.(*hcloud.Server) diff --git a/internal/cmd/servertype/describe.go b/internal/cmd/servertype/describe.go index 5c368278..7768b4de 100644 --- a/internal/cmd/servertype/describe.go +++ b/internal/cmd/servertype/describe.go @@ -17,8 +17,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "server_type", JSONKeyGetByName: "server_types", NameSuggestions: func(c hcapi2.Client) func() []string { return c.ServerType().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.ServerType().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + st, _, err := client.ServerType().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return st, hcloud.SchemaFromServerType(st), nil }, PrintText: func(_ context.Context, _ hcapi2.Client, cmd *cobra.Command, resource interface{}) error { serverType := resource.(*hcloud.ServerType) diff --git a/internal/cmd/sshkey/describe.go b/internal/cmd/sshkey/describe.go index b91e1188..4626c96a 100644 --- a/internal/cmd/sshkey/describe.go +++ b/internal/cmd/sshkey/describe.go @@ -19,8 +19,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "ssh_key", JSONKeyGetByName: "ssh_keys", NameSuggestions: func(c hcapi2.Client) func() []string { return c.SSHKey().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.SSHKey().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + key, _, err := client.SSHKey().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return key, hcloud.SchemaFromSSHKey(key), nil }, PrintText: func(_ context.Context, _ hcapi2.Client, cmd *cobra.Command, resource interface{}) error { sshKey := resource.(*hcloud.SSHKey) diff --git a/internal/cmd/volume/describe.go b/internal/cmd/volume/describe.go index 70af3488..9dd4ceb2 100644 --- a/internal/cmd/volume/describe.go +++ b/internal/cmd/volume/describe.go @@ -18,8 +18,12 @@ var DescribeCmd = base.DescribeCmd{ JSONKeyGetByID: "volume", JSONKeyGetByName: "volumes", NameSuggestions: func(c hcapi2.Client) func() []string { return c.Volume().Names }, - Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) { - return client.Volume().Get(ctx, idOrName) + Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) { + v, _, err := client.Volume().Get(ctx, idOrName) + if err != nil { + return nil, nil, err + } + return v, hcloud.SchemaFromVolume(v), nil }, PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error { volume := resource.(*hcloud.Volume)