Skip to content
Merged
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
25 changes: 25 additions & 0 deletions api/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ type WriteResponse struct {
ID *pbresource.ID `json:"id"`
Version string `json:"version"`
Generation string `json:"generation"`
Status map[string]any `json:"status"`
}

type ListResponse struct {
Resources []WriteResponse `json:"resources"`
}

// Config returns a handle to the Config endpoints
Expand Down Expand Up @@ -84,3 +89,23 @@ func (resource *Resource) Apply(gvk *GVK, resourceName string, q *QueryOptions,
}
return out, wm, nil
}

func (resource *Resource) List(gvk *GVK, q *QueryOptions) (*ListResponse, error) {
r := resource.c.newRequest("GET", strings.ToLower(fmt.Sprintf("/api/%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)))
r.setQueryOptions(q)
_, resp, err := resource.c.doRequest(r)
if err != nil {
return nil, err
}
defer closeResponseBody(resp)
if err := requireOK(resp); err != nil {
return nil, err
}

var out *ListResponse
if err := decodeBody(resp, &out); err != nil {
return nil, err
}

return out, nil
}
2 changes: 2 additions & 0 deletions command/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import (
"github.com/hashicorp/consul/command/reload"
"github.com/hashicorp/consul/command/resource"
resourceapply "github.com/hashicorp/consul/command/resource/apply"
resourcelist "github.com/hashicorp/consul/command/resource/list"
resourceread "github.com/hashicorp/consul/command/resource/read"
"github.com/hashicorp/consul/command/rtt"
"github.com/hashicorp/consul/command/services"
Expand Down Expand Up @@ -244,6 +245,7 @@ func RegisteredCommands(ui cli.Ui) map[string]mcli.CommandFactory {
entry{"resource", func(cli.Ui) (cli.Command, error) { return resource.New(), nil }},
entry{"resource read", func(ui cli.Ui) (cli.Command, error) { return resourceread.New(ui), nil }},
entry{"resource apply", func(ui cli.Ui) (cli.Command, error) { return resourceapply.New(ui), nil }},
entry{"resource list", func(ui cli.Ui) (cli.Command, error) { return resourcelist.New(ui), nil }},
entry{"rtt", func(ui cli.Ui) (cli.Command, error) { return rtt.New(ui), nil }},
entry{"services", func(cli.Ui) (cli.Command, error) { return services.New(), nil }},
entry{"services register", func(ui cli.Ui) (cli.Command, error) { return svcsregister.New(ui), nil }},
Expand Down
20 changes: 7 additions & 13 deletions command/resource/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/consul/command/helpers"
"github.com/hashicorp/consul/command/resource"
"github.com/hashicorp/consul/internal/resourcehcl"
"github.com/hashicorp/consul/proto-public/pbresource"
)
Expand Down Expand Up @@ -69,27 +69,21 @@ func makeWriteRequest(parsedResource *pbresource.Resource) (payload *api.WriteRe

func (c *cmd) Run(args []string) int {
if err := c.flags.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return 0
if !errors.Is(err, flag.ErrHelp) {
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
return 1
}
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
return 1
}

var parsedResource *pbresource.Resource

if c.filePath != "" {
data, err := helpers.LoadDataSourceNoRaw(c.filePath, nil)
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to load data: %v", err))
return 1
}

parsedResource, err = parseResource(data)
data, err := resource.ParseResourceFromFile(c.filePath)
if err != nil {
c.UI.Error(fmt.Sprintf("Your argument format is incorrect: %s", err))
c.UI.Error(fmt.Sprintf("Failed to decode resource from input file: %v", err))
return 1
}
parsedResource = data
} else {
c.UI.Error("Flag -f is required")
return 1
Expand Down
37 changes: 37 additions & 0 deletions command/resource/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package resource

import (
"errors"
"flag"
"fmt"

"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/command/helpers"
"github.com/hashicorp/consul/internal/resourcehcl"
"github.com/hashicorp/consul/proto-public/pbresource"
)

func ParseResourceFromFile(filePath string) (*pbresource.Resource, error) {
data, err := helpers.LoadDataSourceNoRaw(filePath, nil)
if err != nil {
return nil, fmt.Errorf("Failed to load data: %v", err)
}
parsedResource, err := resourcehcl.Unmarshal([]byte(data), consul.NewTypeRegistry())
if err != nil {
return nil, fmt.Errorf("Failed to decode resource from input file: %v", err)
}

return parsedResource, nil
}

func ParseInputParams(inputArgs []string, flags *flag.FlagSet) error {
if err := flags.Parse(inputArgs); err != nil {
if !errors.Is(err, flag.ErrHelp) {
return fmt.Errorf("Failed to parse args: %v", err)
}
}
return nil
}
193 changes: 193 additions & 0 deletions command/resource/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package list

import (
"encoding/json"
"errors"
"flag"
"fmt"
"strings"

"github.com/mitchellh/cli"

"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/consul/command/resource"
)

func New(ui cli.Ui) *cmd {
c := &cmd{UI: ui}
c.init()
return c
}

type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string

filePath string
}

func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.http = &flags.HTTPFlags{}
c.flags.StringVar(&c.filePath, "f", "", "File path with resource definition")
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
flags.Merge(c.flags, c.http.MultiTenancyFlags())
flags.Merge(c.flags, c.http.AddPeerName())
c.help = flags.Usage(help, c.flags)
}

func (c *cmd) Run(args []string) int {
var gvk *api.GVK
var opts *api.QueryOptions

if len(args) == 0 {
c.UI.Error("Please provide required arguments")
return 1
}

if err := c.flags.Parse(args); err != nil {
if !errors.Is(err, flag.ErrHelp) {
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
return 1
}
}

if c.flags.Lookup("f").Value.String() != "" {
if c.filePath != "" {
parsedResource, err := resource.ParseResourceFromFile(c.filePath)
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to decode resource from input file: %v", err))
return 1
}

if parsedResource == nil {
c.UI.Error("Unable to parse the file argument")
return 1
}

gvk = &api.GVK{
Group: parsedResource.Id.Type.GetGroup(),
Version: parsedResource.Id.Type.GetGroupVersion(),
Kind: parsedResource.Id.Type.GetKind(),
}
opts = &api.QueryOptions{
Namespace: parsedResource.Id.Tenancy.GetNamespace(),
Partition: parsedResource.Id.Tenancy.GetPartition(),
Peer: parsedResource.Id.Tenancy.GetPeerName(),
Token: c.http.Token(),
RequireConsistent: !c.http.Stale(),
}
} else {
c.UI.Error(fmt.Sprintf("Please provide an input file with resource definition"))
return 1
}
} else {
var err error
// extract resource type
gvk, err = getResourceType(c.flags.Args())
if err != nil {
c.UI.Error(fmt.Sprintf("Your argument format is incorrect: %v", err))
return 1
}
// skip resource type to parse remaining args
inputArgs := c.flags.Args()[1:]
err = resource.ParseInputParams(inputArgs, c.flags)
if err != nil {
c.UI.Error(fmt.Sprintf("Error parsing input arguments: %v", err))
return 1
}
if c.filePath != "" {
c.UI.Warn(fmt.Sprintf("File argument is ignored when resource definition is provided with the command"))
}

opts = &api.QueryOptions{
Namespace: c.http.Namespace(),
Partition: c.http.Partition(),
Peer: c.http.PeerName(),
Token: c.http.Token(),
RequireConsistent: !c.http.Stale(),
}
}

client, err := c.http.APIClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connect to Consul agent: %s", err))
return 1
}

entry, err := client.Resource().List(gvk, opts)
if err != nil {
c.UI.Error(fmt.Sprintf("Error reading resources for type %s: %v", gvk, err))
return 1
}

b, err := json.MarshalIndent(entry, "", " ")
if err != nil {
c.UI.Error("Failed to encode output data")
return 1
}

c.UI.Info(string(b))
return 0
}

func getResourceType(args []string) (gvk *api.GVK, e error) {
if len(args) < 1 {
return nil, fmt.Errorf("Must include resource type argument")
}

s := strings.Split(args[0], ".")
if len(s) < 3 {
return nil, fmt.Errorf("Must include resource type argument in group.verion.kind format")
}
gvk = &api.GVK{
Group: s[0],
Version: s[1],
Kind: s[2],
}

return
}

func (c *cmd) Synopsis() string {
return synopsis
}

func (c *cmd) Help() string {
return flags.Usage(c.help, nil)
}

const synopsis = "Reads all resources by type"
const help = `
Usage: consul resource list [type] -partition=<default> -namespace=<default> -peer=<local>
or
consul resource list -f [path/to/file.hcl]

Lists all the resources specified by the type under the given partition, namespace and peer
and outputs in JSON format.

Example:

$ consul resource list catalog.v1alpha1.Service card-processor -partition=billing -namespace=payments -peer=eu

$ consul resource list -f=demo.hcl

Sample demo.hcl:

ID {
Type = gvk("group.version.kind")
Name = "resource-name"
Tenancy {
Namespace = "default"
Partition = "default"
PeerName = "local"
}
}
`
Loading