Skip to content

Commit

Permalink
chore: add output flag for talosctl config info
Browse files Browse the repository at this point in the history
Add output flag for `talosctl config info`.

This allows to programatically gather endpoints for CI tests.

Eg:

```bash
_out/talosctl-linux-amd64 config info --output json | jq '.Contexts[].Endpoints[0]'
```

Signed-off-by: Noel Georgi <[email protected]>
  • Loading branch information
frezbo authored and smira committed Sep 5, 2023
1 parent 3fbed80 commit 1eebbce
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 37 deletions.
109 changes: 73 additions & 36 deletions cmd/talosctl/cmd/talos/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
Expand All @@ -24,6 +25,7 @@ import (
"github.com/siderolabs/gen/maps"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/durationpb"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
Expand Down Expand Up @@ -444,47 +446,58 @@ var configNewCmd = &cobra.Command{
}

// configNewCmd represents the `config info` command output template.
var configInfoCmdTemplate = template.Must(template.New("configInfoCmdTemplate").Option("missingkey=error").Parse(strings.TrimSpace(`
var configInfoCmdTemplate = template.Must(template.New("configInfoCmdTemplate").
Funcs(template.FuncMap{"join": strings.Join}).
Option("missingkey=error").
Parse(strings.TrimSpace(`
Current context: {{ .Context }}
Nodes: {{ .Nodes }}
Endpoints: {{ .Endpoints }}
Nodes: {{ if .Nodes }}{{ join .Nodes ", " }}{{ else }}not defined{{ end }}
Endpoints: {{ if .Endpoints }}{{ join .Endpoints ", " }}{{ else }}not defined{{ end }}
{{- if .Roles }}
Roles: {{ .Roles }}{{ end }}
Roles: {{ join .Roles ", " }}{{ end }}
{{- if .CertTTL }}
Certificate expires: {{ .CertTTL }} ({{ .CertNotAfter }}){{ end }}
`)))

// configInfoCommand implements `config info` command logic.
func configInfoCommand(config *clientconfig.Config, now time.Time) (string, error) {
type talosconfigInfo struct {
Context string `json:"context" yaml:"context"`
Nodes []string `json:"nodes" yaml:"nodes"`
Endpoints []string `json:"endpoints" yaml:"endpoints"`
Roles []string `json:"roles" yaml:"roles"`
CertTTL string `json:"certTTL" yaml:"certTTL"`
CertNotAfter string `json:"certNotAfter" yaml:"certNotAfter"`
}

// configInfo returns talosct config info.
func configInfo(config *clientconfig.Config, now time.Time) (talosconfigInfo, error) {
cfgContext, err := getContextData(config)
if err != nil {
return "", err
return talosconfigInfo{}, err
}

var (
certTTL, certNotAfter string
roles role.Set
rolesS string
)

if cfgContext.Crt != "" {
var b []byte

b, err = base64.StdEncoding.DecodeString(cfgContext.Crt)
if err != nil {
return "", err
return talosconfigInfo{}, err
}

block, _ := pem.Decode(b)
if block == nil {
return "", fmt.Errorf("error decoding PEM")
return talosconfigInfo{}, fmt.Errorf("error decoding PEM")
}

var crt *x509.Certificate

crt, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return "", err
return talosconfigInfo{}, err
}

roles, _ = role.Parse(crt.Subject.Organization)
Expand All @@ -493,33 +506,33 @@ func configInfoCommand(config *clientconfig.Config, now time.Time) (string, erro
certNotAfter = crt.NotAfter.UTC().Format("2006-01-02")
}

nodesS := "not defined"
if len(cfgContext.Nodes) > 0 {
nodesS = strings.Join(cfgContext.Nodes, ", ")
}

endpointsS := "not defined"
if len(cfgContext.Endpoints) > 0 {
endpointsS = strings.Join(cfgContext.Endpoints, ", ")
}
return talosconfigInfo{
Context: config.Context,
Nodes: cfgContext.Nodes,
Endpoints: cfgContext.Endpoints,
Roles: roles.Strings(),
CertTTL: certTTL,
CertNotAfter: certNotAfter,
}, nil
}

if s := roles.Strings(); len(s) > 0 {
rolesS = strings.Join(s, ", ")
// configInfoCommand implements `config info` command logic.
func configInfoCommand(config *clientconfig.Config, now time.Time) (string, error) {
info, err := configInfo(config, now)
if err != nil {
return "", err
}

var res bytes.Buffer
err = configInfoCmdTemplate.Execute(&res, map[string]string{
"Context": config.Context,
"Nodes": nodesS,
"Endpoints": endpointsS,
"Roles": rolesS,
"CertTTL": certTTL,
"CertNotAfter": certNotAfter,
})
err = configInfoCmdTemplate.Execute(&res, info)

return res.String() + "\n", err
}

var configInfoCmdFlags struct {
output string
}

// configInfoCmd represents the `config info` command.
var configInfoCmd = &cobra.Command{
Use: "info",
Expand All @@ -531,14 +544,36 @@ var configInfoCmd = &cobra.Command{
return err
}

res, err := configInfoCommand(c, time.Now())
if err != nil {
return err
}
switch configInfoCmdFlags.output {
case "text":
res, err := configInfoCommand(c, time.Now())
if err != nil {
return err
}

fmt.Print(res)
fmt.Print(res)

return nil
return nil
case "json":
info, err := configInfo(c, time.Now())
if err != nil {
return err
}

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")

return enc.Encode(&info)
case "yaml":
info, err := configInfo(c, time.Now())
if err != nil {
return err
}

return yaml.NewEncoder(os.Stdout).Encode(&info)
default:
return fmt.Errorf("unknown output format: %q", configInfoCmdFlags.output)
}
},
}

Expand Down Expand Up @@ -584,6 +619,8 @@ func init() {
configNewCmd.Flags().StringSliceVar(&configNewCmdFlags.roles, "roles", role.MakeSet(role.Admin).Strings(), "roles")
configNewCmd.Flags().DurationVar(&configNewCmdFlags.crtTTL, "crt-ttl", 87600*time.Hour, "certificate TTL")

configInfoCmd.Flags().StringVarP(&configInfoCmdFlags.output, "output", "o", "text", "output format (json|yaml|text). Default text.")

addCommand(configCmd)
}

Expand Down
47 changes: 47 additions & 0 deletions internal/integration/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
package cli

import (
"encoding/json"
"path/filepath"
"regexp"
"strings"

"google.golang.org/protobuf/encoding/protojson"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/internal/integration/base"
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
Expand Down Expand Up @@ -39,6 +41,51 @@ func (suite *TalosconfigSuite) TestList() {
func (suite *TalosconfigSuite) TestInfo() {
suite.RunCLI([]string{"config", "info"}, // TODO: remove 10 years once the CABPT & TF providers are updated to 1.5.2+
base.StdoutShouldMatch(regexp.MustCompile(`(1 year|10 years) from now`)))

suite.RunCLI([]string{"config", "info", "--output", "text"}, // TODO: remove 10 years once the CABPT & TF providers are updated to 1.5.2+
base.StdoutShouldMatch(regexp.MustCompile(`(1 year|10 years) from now`)))

suite.RunCLI([]string{"config", "info", "--output", "yaml"},
base.StdoutMatchFunc(func(stdout string) error {
var out map[string]any

err := yaml.Unmarshal([]byte(stdout), &out)
if err != nil {
return err
}

suite.Assert().Contains(out, "endpoints")
suite.Assert().Contains(out, "nodes")
suite.Assert().Contains(out, "roles")
suite.Assert().Contains(out, "context")

return nil
}),
)

suite.RunCLI([]string{"config", "info", "--output", "json"},
base.StdoutMatchFunc(func(stdout string) error {
var out map[string]any

err := json.Unmarshal([]byte(stdout), &out)
if err != nil {
return err
}

suite.Assert().Contains(out, "endpoints")
suite.Assert().Contains(out, "nodes")
suite.Assert().Contains(out, "roles")
suite.Assert().Contains(out, "context")

return nil
}),
)

suite.RunCLI([]string{"config", "info", "--output", "table"},
base.StdoutEmpty(),
base.ShouldFail(),
base.StderrShouldMatch(regexp.MustCompile(`unknown output format: "table"`)),
)
}

// TestMerge checks `talosctl config merge`.
Expand Down
3 changes: 2 additions & 1 deletion website/content/v1.6/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ talosctl config info [flags]
### Options

```
-h, --help help for info
-h, --help help for info
-o, --output string output format (json|yaml|text). Default text. (default "text")
```

### Options inherited from parent commands
Expand Down

0 comments on commit 1eebbce

Please sign in to comment.