snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
31
oryx/cmdx/env.go
Normal file
31
oryx/cmdx/env.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
// EnvVarExamplesHelpMessage returns a string containing documentation on how to use environment variables.
|
||||
func EnvVarExamplesHelpMessage(name string) string {
|
||||
return `This command exposes a variety of controls via environment variables. Here are some examples on how to
|
||||
configure environment variables:
|
||||
|
||||
Linux / macOS:
|
||||
$ export FOO=bar
|
||||
$ export BAZ=bar
|
||||
$ ` + name + ` ...
|
||||
|
||||
$ FOO=bar BAZ=bar ` + name + ` ...
|
||||
|
||||
Docker:
|
||||
$ docker run -e FOO=bar -e BAZ=bar ...
|
||||
|
||||
Windows (cmd):
|
||||
> set FOO=bar
|
||||
> set BAZ=bar
|
||||
> ` + name + ` ...
|
||||
|
||||
Windows (powershell):
|
||||
> $env:FOO = "bar"
|
||||
> $env:BAZ = "bar"
|
||||
> ` + name + `
|
||||
`
|
||||
}
|
||||
247
oryx/cmdx/helper.go
Normal file
247
oryx/cmdx/helper.go
Normal file
@@ -0,0 +1,247 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoPrintButFail is returned to detect a failure state that was already reported to the user in some way
|
||||
ErrNoPrintButFail = fmt.Errorf("this error should never be printed")
|
||||
|
||||
debugStdout, debugStderr = io.Discard, io.Discard
|
||||
)
|
||||
|
||||
func init() {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
debugStdout = os.Stdout
|
||||
debugStderr = os.Stderr
|
||||
}
|
||||
}
|
||||
|
||||
// FailSilently is supposed to be used within a commands RunE function.
|
||||
// It silences cobras error handling and returns the ErrNoPrintButFail error.
|
||||
func FailSilently(cmd *cobra.Command) error {
|
||||
cmd.SilenceErrors = true
|
||||
cmd.SilenceUsage = true
|
||||
return errors.WithStack(ErrNoPrintButFail)
|
||||
}
|
||||
|
||||
// Must fatals with the optional message if err is not nil.
|
||||
// Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error.
|
||||
func Must(err error, message string, args ...interface{}) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(os.Stderr, message+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// CheckResponse fatals if err is nil or the response.StatusCode does not match the expectedStatusCode
|
||||
// Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error.
|
||||
func CheckResponse(err error, expectedStatusCode int, response *http.Response) {
|
||||
Must(err, "Command failed because error occurred: %s", err)
|
||||
|
||||
if response.StatusCode != expectedStatusCode {
|
||||
out, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
out = []byte{}
|
||||
}
|
||||
pretty, err := json.MarshalIndent(json.RawMessage(out), "", "\t")
|
||||
if err == nil {
|
||||
out = pretty
|
||||
}
|
||||
|
||||
Fatalf(
|
||||
`Command failed because status code %d was expected but code %d was received.
|
||||
|
||||
Response payload:
|
||||
|
||||
%s`,
|
||||
expectedStatusCode,
|
||||
response.StatusCode,
|
||||
out,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FormatResponse takes an object and prints a json.MarshalIdent version of it or fatals.
|
||||
// Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error.
|
||||
func FormatResponse(o interface{}) string {
|
||||
out, err := json.MarshalIndent(o, "", "\t")
|
||||
Must(err, `Command failed because an error occurred while prettifying output: %s`, err)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// Fatalf prints to os.Stderr and exists with code 1.
|
||||
// Deprecated: do not use this function in commands, as it makes it impossible to test them. Instead, return the error.
|
||||
func Fatalf(message string, args ...interface{}) {
|
||||
if len(args) > 0 {
|
||||
_, _ = fmt.Fprintf(os.Stderr, message+"\n", args...)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(os.Stderr, message)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// CallbackWriter will execute each callback once the message is received.
|
||||
// The full matched message is passed to the callback. An error returned from the callback is returned by Write.
|
||||
type CallbackWriter struct {
|
||||
Callbacks map[string]func([]byte) error
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *CallbackWriter) Write(msg []byte) (int, error) {
|
||||
for p, cb := range c.Callbacks {
|
||||
if bytes.Contains(msg, []byte(p)) {
|
||||
if err := cb(msg); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.buf.Write(msg)
|
||||
}
|
||||
|
||||
func (c *CallbackWriter) String() string {
|
||||
return c.buf.String()
|
||||
}
|
||||
|
||||
var _ io.Writer = (*CallbackWriter)(nil)
|
||||
|
||||
func prepareCmd(cmd *cobra.Command, stdIn io.Reader, stdOut, stdErr io.Writer, args []string) {
|
||||
cmd.SetIn(stdIn)
|
||||
outs := []io.Writer{debugStdout}
|
||||
if stdOut != nil {
|
||||
outs = append(outs, stdOut)
|
||||
}
|
||||
cmd.SetOut(io.MultiWriter(outs...))
|
||||
errs := []io.Writer{debugStderr}
|
||||
if stdErr != nil {
|
||||
errs = append(errs, stdErr)
|
||||
}
|
||||
cmd.SetErr(io.MultiWriter(errs...))
|
||||
|
||||
if args == nil {
|
||||
args = []string{}
|
||||
}
|
||||
cmd.SetArgs(args)
|
||||
}
|
||||
|
||||
// ExecBackgroundCtx runs the cobra command in the background.
|
||||
func ExecBackgroundCtx(ctx context.Context, cmd *cobra.Command, stdIn io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group {
|
||||
prepareCmd(cmd, stdIn, stdOut, stdErr, args)
|
||||
|
||||
eg := &errgroup.Group{}
|
||||
eg.Go(func() error {
|
||||
defer cmd.SetIn(nil)
|
||||
return cmd.ExecuteContext(ctx)
|
||||
})
|
||||
|
||||
return eg
|
||||
}
|
||||
|
||||
// Exec runs the provided cobra command with the given reader as STD_IN and the given args.
|
||||
// Returns STD_OUT, STD_ERR and the error from the execution.
|
||||
func Exec(t testing.TB, cmd *cobra.Command, stdIn io.Reader, args ...string) (string, string, error) {
|
||||
return ExecCtx(t.Context(), cmd, stdIn, args...)
|
||||
}
|
||||
|
||||
func ExecCtx(ctx context.Context, cmd *cobra.Command, stdIn io.Reader, args ...string) (string, string, error) {
|
||||
stdOut, stdErr := &bytes.Buffer{}, &bytes.Buffer{}
|
||||
|
||||
prepareCmd(cmd, stdIn, stdOut, stdErr, args)
|
||||
|
||||
// needs to be on a separate line to ensure that the output buffers are read AFTER the command ran
|
||||
err := cmd.ExecuteContext(ctx)
|
||||
|
||||
return stdOut.String(), stdErr.String(), err
|
||||
}
|
||||
|
||||
// ExecNoErr is a helper that assumes a successful run from Exec.
|
||||
// Returns STD_OUT.
|
||||
func ExecNoErr(t testing.TB, cmd *cobra.Command, args ...string) string {
|
||||
return ExecNoErrCtx(t.Context(), t, cmd, args...)
|
||||
}
|
||||
|
||||
func ExecNoErrCtx(ctx context.Context, t require.TestingT, cmd *cobra.Command, args ...string) string {
|
||||
stdOut, stdErr, err := ExecCtx(ctx, cmd, nil, args...)
|
||||
if err == nil {
|
||||
require.Len(t, stdErr, 0, "std_out: %s\nstd_err: %s", stdOut, stdErr)
|
||||
} else {
|
||||
require.ErrorIsf(t, err, context.Canceled, "std_out: %s\nstd_err: %s", stdOut, stdErr)
|
||||
}
|
||||
return stdOut
|
||||
}
|
||||
|
||||
// ExecExpectedErr is a helper that assumes a failing run from Exec returning ErrNoPrintButFail
|
||||
// Returns STD_ERR.
|
||||
func ExecExpectedErr(t testing.TB, cmd *cobra.Command, args ...string) string {
|
||||
return ExecExpectedErrCtx(t.Context(), t, cmd, args...)
|
||||
}
|
||||
|
||||
func ExecExpectedErrCtx(ctx context.Context, t require.TestingT, cmd *cobra.Command, args ...string) string {
|
||||
stdOut, stdErr, err := ExecCtx(ctx, cmd, nil, args...)
|
||||
require.True(t, errors.Is(err, ErrNoPrintButFail), "std_out: %s\nstd_err: %s", stdOut, stdErr)
|
||||
require.Len(t, stdOut, 0, stdErr)
|
||||
return stdErr
|
||||
}
|
||||
|
||||
type CommandExecuter struct {
|
||||
New func() *cobra.Command
|
||||
Ctx context.Context
|
||||
PersistentArgs []string
|
||||
}
|
||||
|
||||
func (c *CommandExecuter) Exec(stdin io.Reader, args ...string) (string, string, error) {
|
||||
return ExecCtx(c.Ctx, c.New(), stdin, append(c.PersistentArgs, args...)...)
|
||||
}
|
||||
|
||||
func (c *CommandExecuter) ExecBackground(stdin io.Reader, stdOut, stdErr io.Writer, args ...string) *errgroup.Group {
|
||||
return ExecBackgroundCtx(c.Ctx, c.New(), stdin, stdOut, stdErr, append(c.PersistentArgs, args...)...)
|
||||
}
|
||||
|
||||
func (c *CommandExecuter) ExecNoErr(t require.TestingT, args ...string) string {
|
||||
return ExecNoErrCtx(c.Ctx, t, c.New(), append(c.PersistentArgs, args...)...)
|
||||
}
|
||||
|
||||
func (c *CommandExecuter) ExecExpectedErr(t require.TestingT, args ...string) string {
|
||||
return ExecExpectedErrCtx(c.Ctx, t, c.New(), append(c.PersistentArgs, args...)...)
|
||||
}
|
||||
|
||||
type URL struct {
|
||||
url.URL
|
||||
}
|
||||
|
||||
var _ pflag.Value = (*URL)(nil)
|
||||
|
||||
func (u *URL) Set(s string) error {
|
||||
uu, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.URL = *uu
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*URL) Type() string {
|
||||
return "url"
|
||||
}
|
||||
125
oryx/cmdx/http.go
Normal file
125
oryx/cmdx/http.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/ory/x/httpx"
|
||||
)
|
||||
|
||||
const (
|
||||
envKeyEndpoint = "ORY_SDK_URL"
|
||||
FlagEndpoint = "endpoint"
|
||||
FlagSkipTLSVerify = "skip-tls-verify"
|
||||
FlagHeaders = "http-header"
|
||||
)
|
||||
|
||||
// Remote returns the remote endpoint for the given command.
|
||||
func Remote(cmd *cobra.Command) (string, error) {
|
||||
endpoint, err := cmd.Flags().GetString(FlagEndpoint)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
return strings.TrimRight(endpoint, "/"), nil
|
||||
} else if endpoint := os.Getenv("ORY_SDK_URL"); endpoint != "" {
|
||||
return strings.TrimRight(endpoint, "/"), nil
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "To execute this command, the endpoint URL must point to the URL where Ory is located. To set the endpoint URL, use flag `--endpoint` or environment variable `ORY_SDK_URL`.")
|
||||
return "", FailSilently(cmd)
|
||||
}
|
||||
|
||||
// RemoteURI returns the remote URI for the given command.
|
||||
func RemoteURI(cmd *cobra.Command) (*url.URL, error) {
|
||||
remote, err := Remote(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint, err := url.ParseRequestURI(remote)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse endpoint URL: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// NewClient creates a new HTTP client.
|
||||
func NewClient(cmd *cobra.Command) (*http.Client, *url.URL, error) {
|
||||
endpoint, err := cmd.Flags().GetString(FlagEndpoint)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if endpoint == "" {
|
||||
endpoint = os.Getenv(envKeyEndpoint)
|
||||
}
|
||||
|
||||
if endpoint == "" {
|
||||
return nil, nil, errors.Errorf("you have to set the remote endpoint, try --help for details")
|
||||
}
|
||||
|
||||
u, err := url.Parse(strings.TrimRight(endpoint, "/"))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, `could not parse the endpoint URL "%s"`, endpoint)
|
||||
}
|
||||
|
||||
hc := retryablehttp.NewClient().StandardClient()
|
||||
hc.Timeout = time.Second * 10
|
||||
|
||||
rawHeaders, err := cmd.Flags().GetStringSlice(FlagHeaders)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
header := http.Header{}
|
||||
for _, h := range rawHeaders {
|
||||
parts := strings.Split(h, ":")
|
||||
if len(parts) != 2 {
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Unable to parse `--http-header` flag. Format of flag value is a `: ` delimited string like `--http-header 'Some-Header: some-values; other values`. Received: %v", rawHeaders)
|
||||
return nil, nil, FailSilently(cmd)
|
||||
}
|
||||
|
||||
for k := range parts {
|
||||
parts[k] = strings.TrimSpace(parts[k])
|
||||
}
|
||||
|
||||
header.Add(parts[0], parts[1])
|
||||
}
|
||||
|
||||
skipVerify, err := cmd.Flags().GetBool(FlagSkipTLSVerify)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
rt := httpx.NewTransportWithHeader(header)
|
||||
rt.RoundTripper = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipVerify, //nolint:gosec // This is a false positive
|
||||
},
|
||||
}
|
||||
hc.Transport = rt
|
||||
return hc, u, nil
|
||||
}
|
||||
|
||||
// RegisterHTTPClientFlags registers HTTP client configuration flags.
|
||||
func RegisterHTTPClientFlags(flags *pflag.FlagSet) {
|
||||
flags.StringP(FlagEndpoint, FlagEndpoint[:1], "", fmt.Sprintf("The API URL this command should target. Alternatively set using the %s environmental variable.", envKeyEndpoint))
|
||||
flags.Bool(FlagSkipTLSVerify, false, "Do not verify TLS certificates. Useful when dealing with self-signed certificates. Do not use in production!")
|
||||
flags.StringSliceP(FlagHeaders, "H", []string{}, "A list of additional HTTP headers to set. HTTP headers is separated by a `: `, for example: `-H 'Authorization: bearer some-token'`.")
|
||||
}
|
||||
137
oryx/cmdx/noise_printer.go
Normal file
137
oryx/cmdx/noise_printer.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type ConditionalPrinter struct {
|
||||
w io.Writer
|
||||
print bool
|
||||
}
|
||||
|
||||
const (
|
||||
FlagQuiet = "quiet"
|
||||
)
|
||||
|
||||
func RegisterNoiseFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolP(FlagQuiet, FlagQuiet[:1], false, "Be quiet with output printing.")
|
||||
}
|
||||
|
||||
// NewLoudOutPrinter returns a ConditionalPrinter that
|
||||
// only prints to cmd.OutOrStdout when --quiet is not set
|
||||
func NewLoudOutPrinter(cmd *cobra.Command) *ConditionalPrinter {
|
||||
quiet, err := cmd.Flags().GetBool(FlagQuiet)
|
||||
if err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return &ConditionalPrinter{
|
||||
w: cmd.OutOrStdout(),
|
||||
print: !quiet,
|
||||
}
|
||||
}
|
||||
|
||||
// NewQuietOutPrinter returns a ConditionalPrinter that
|
||||
// only prints to cmd.OutOrStdout when --quiet is set
|
||||
func NewQuietOutPrinter(cmd *cobra.Command) *ConditionalPrinter {
|
||||
quiet, err := cmd.Flags().GetBool(FlagQuiet)
|
||||
if err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return &ConditionalPrinter{
|
||||
w: cmd.OutOrStdout(),
|
||||
print: quiet,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLoudErrPrinter returns a ConditionalPrinter that
|
||||
// only prints to cmd.ErrOrStderr when --quiet is not set
|
||||
func NewLoudErrPrinter(cmd *cobra.Command) *ConditionalPrinter {
|
||||
quiet, err := cmd.Flags().GetBool(FlagQuiet)
|
||||
if err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return &ConditionalPrinter{
|
||||
w: cmd.ErrOrStderr(),
|
||||
print: !quiet,
|
||||
}
|
||||
}
|
||||
|
||||
// NewQuietErrPrinter returns a ConditionalPrinter that
|
||||
// only prints to cmd.ErrOrStderr when --quiet is set
|
||||
func NewQuietErrPrinter(cmd *cobra.Command) *ConditionalPrinter {
|
||||
quiet, err := cmd.Flags().GetBool(FlagQuiet)
|
||||
if err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return &ConditionalPrinter{
|
||||
w: cmd.ErrOrStderr(),
|
||||
print: quiet,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLoudPrinter returns a ConditionalPrinter that
|
||||
// only prints to w when --quiet is not set
|
||||
func NewLoudPrinter(cmd *cobra.Command, w io.Writer) *ConditionalPrinter {
|
||||
quiet, err := cmd.Flags().GetBool(FlagQuiet)
|
||||
if err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return &ConditionalPrinter{
|
||||
w: w,
|
||||
print: !quiet,
|
||||
}
|
||||
}
|
||||
|
||||
// NewQuietPrinter returns a ConditionalPrinter that
|
||||
// only prints to w when --quiet is set
|
||||
func NewQuietPrinter(cmd *cobra.Command, w io.Writer) *ConditionalPrinter {
|
||||
quiet, err := cmd.Flags().GetBool(FlagQuiet)
|
||||
if err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
|
||||
return &ConditionalPrinter{
|
||||
w: w,
|
||||
print: quiet,
|
||||
}
|
||||
}
|
||||
|
||||
func NewConditionalPrinter(w io.Writer, print bool) *ConditionalPrinter {
|
||||
return &ConditionalPrinter{
|
||||
w: w,
|
||||
print: print,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConditionalPrinter) Println(a ...interface{}) (n int, err error) {
|
||||
if p.print {
|
||||
return fmt.Fprintln(p.w, a...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ConditionalPrinter) Print(a ...interface{}) (n int, err error) {
|
||||
if p.print {
|
||||
return fmt.Fprint(p.w, a...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ConditionalPrinter) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
if p.print {
|
||||
return fmt.Fprintf(p.w, format, a...)
|
||||
}
|
||||
return
|
||||
}
|
||||
84
oryx/cmdx/output.go
Normal file
84
oryx/cmdx/output.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import "strconv"
|
||||
|
||||
type (
|
||||
// OutputIder outputs an ID
|
||||
OutputIder string
|
||||
// OutputIderCollection outputs a list of IDs
|
||||
OutputIderCollection struct {
|
||||
Items []OutputIder
|
||||
}
|
||||
)
|
||||
|
||||
func (OutputIder) Header() []string {
|
||||
return []string{"ID"}
|
||||
}
|
||||
|
||||
func (i OutputIder) Columns() []string {
|
||||
return []string{string(i)}
|
||||
}
|
||||
|
||||
func (i OutputIder) Interface() interface{} {
|
||||
return i
|
||||
}
|
||||
|
||||
func (OutputIderCollection) Header() []string {
|
||||
return []string{"ID"}
|
||||
}
|
||||
|
||||
func (c OutputIderCollection) Table() [][]string {
|
||||
rows := make([][]string, len(c.Items))
|
||||
for i, ident := range c.Items {
|
||||
rows[i] = []string{string(ident)}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (c OutputIderCollection) Interface() interface{} {
|
||||
return c.Items
|
||||
}
|
||||
|
||||
func (c OutputIderCollection) Len() int {
|
||||
return len(c.Items)
|
||||
}
|
||||
|
||||
type PaginatedList struct {
|
||||
Collection interface {
|
||||
Table
|
||||
IDs() []string
|
||||
} `json:"-"`
|
||||
Items []interface{} `json:"items"`
|
||||
NextPageToken string `json:"next_page_token"`
|
||||
IsLastPage bool `json:"is_last_page"`
|
||||
}
|
||||
|
||||
func (r *PaginatedList) Header() []string {
|
||||
return r.Collection.Header()
|
||||
}
|
||||
|
||||
func (r *PaginatedList) Table() [][]string {
|
||||
return append(
|
||||
r.Collection.Table(),
|
||||
[]string{},
|
||||
[]string{"NEXT PAGE TOKEN", r.NextPageToken},
|
||||
[]string{"IS LAST PAGE", strconv.FormatBool(r.IsLastPage)},
|
||||
)
|
||||
}
|
||||
|
||||
func (r *PaginatedList) Interface() interface{} {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *PaginatedList) Len() int {
|
||||
return r.Collection.Len() + 3
|
||||
}
|
||||
|
||||
func (r *PaginatedList) IDs() []string {
|
||||
return r.Collection.IDs()
|
||||
}
|
||||
|
||||
var _ Table = (*PaginatedList)(nil)
|
||||
57
oryx/cmdx/pagination.go
Normal file
57
oryx/cmdx/pagination.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagPageSize = "page-size"
|
||||
FlagPageToken = "page-token"
|
||||
)
|
||||
|
||||
func RegisterTokenPaginationFlags(cmd *cobra.Command) (pageSize int, pageToken string) {
|
||||
cmd.Flags().StringVar(&pageToken, FlagPageToken, "", "page token acquired from a previous response")
|
||||
cmd.Flags().IntVar(&pageSize, FlagPageSize, 100, "maximum number of items to return")
|
||||
return
|
||||
}
|
||||
|
||||
// ParsePaginationArgs parses pagination arguments from the command line.
|
||||
func ParsePaginationArgs(cmd *cobra.Command, pageArg, perPageArg string) (page, perPage int64, err error) {
|
||||
if len(pageArg+perPageArg) > 0 {
|
||||
page, err = strconv.ParseInt(pageArg, 0, 64)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse page argument\"%s\": %s", pageArg, err)
|
||||
return 0, 0, FailSilently(cmd)
|
||||
}
|
||||
|
||||
perPage, err = strconv.ParseInt(perPageArg, 0, 64)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse per-page argument\"%s\": %s", perPageArg, err)
|
||||
return 0, 0, FailSilently(cmd)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTokenPaginationArgs parses token-based pagination arguments from the command line.
|
||||
func ParseTokenPaginationArgs(cmd *cobra.Command) (page string, perPage int, err error) {
|
||||
pageArg, err := cmd.Flags().GetString(FlagPageToken)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse %s argument \"%s\": %s", FlagPageToken, pageArg, err)
|
||||
return "", 0, FailSilently(cmd)
|
||||
}
|
||||
|
||||
perPageArg, err := cmd.Flags().GetInt(FlagPageSize)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not parse %s argument \"%d\": %s", FlagPageSize, perPageArg, err)
|
||||
return "", 0, FailSilently(cmd)
|
||||
}
|
||||
|
||||
return pageArg, perPageArg, nil
|
||||
}
|
||||
355
oryx/cmdx/printing.go
Normal file
355
oryx/cmdx/printing.go
Normal file
@@ -0,0 +1,355 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type (
|
||||
TableHeader interface {
|
||||
Header() []string
|
||||
}
|
||||
TableRow interface {
|
||||
TableHeader
|
||||
Columns() []string
|
||||
Interface() interface{}
|
||||
}
|
||||
Table interface {
|
||||
TableHeader
|
||||
Table() [][]string
|
||||
Interface() interface{}
|
||||
Len() int
|
||||
}
|
||||
Nil struct{}
|
||||
|
||||
Format string
|
||||
)
|
||||
|
||||
const (
|
||||
FormatQuiet Format = "quiet"
|
||||
FormatTable Format = "table"
|
||||
FormatJSON Format = "json"
|
||||
FormatJSONPath Format = "jsonpath"
|
||||
FormatJSONPointer Format = "jsonpointer"
|
||||
FormatJSONPretty Format = "json-pretty"
|
||||
FormatYAML Format = "yaml"
|
||||
FormatDefault Format = "default"
|
||||
|
||||
FlagFormat = "format"
|
||||
|
||||
None = "<none>"
|
||||
)
|
||||
|
||||
func (Nil) String() string {
|
||||
return "null"
|
||||
}
|
||||
|
||||
func (Nil) Interface() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type printOptions struct {
|
||||
format string
|
||||
}
|
||||
|
||||
type PrintOption func(*printOptions)
|
||||
|
||||
func WithFormat(v string) PrintOption {
|
||||
return func(o *printOptions) {
|
||||
o.format = v
|
||||
}
|
||||
}
|
||||
|
||||
func PrintErrors(cmd *cobra.Command, errs map[string]error) {
|
||||
for src, err := range errs {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s: %s\n", src, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func PrintRow(cmd *cobra.Command, row TableRow) {
|
||||
PrintRowf(cmd.OutOrStdout(), row, WithFormat(getFormatValue(cmd)))
|
||||
}
|
||||
|
||||
func PrintRowf(w io.Writer, row TableRow, opts ...PrintOption) {
|
||||
o := &printOptions{}
|
||||
for _, fn := range opts {
|
||||
fn(o)
|
||||
}
|
||||
|
||||
switch parseFormat(o.format) {
|
||||
case FormatQuiet:
|
||||
if idAble, ok := row.(interface{ ID() string }); ok {
|
||||
_, _ = fmt.Fprintln(w, idAble.ID())
|
||||
break
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, row.Columns()[0])
|
||||
case FormatJSON:
|
||||
printJSON(w, row.Interface(), false, "")
|
||||
case FormatYAML:
|
||||
printYAML(w, row.Interface())
|
||||
case FormatJSONPretty:
|
||||
printJSON(w, row.Interface(), true, "")
|
||||
case FormatJSONPath:
|
||||
printJSON(w, row.Interface(), true, getPath(o.format))
|
||||
case FormatJSONPointer:
|
||||
printJSON(w, filterJSONPointer(o.format, row.Interface()), true, "")
|
||||
case FormatTable, FormatDefault:
|
||||
w := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0)
|
||||
|
||||
fields := row.Columns()
|
||||
for i, h := range row.Header() {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t\n", h, fields[i])
|
||||
}
|
||||
|
||||
_ = w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func filterJSONPointer(f string, data any) any {
|
||||
_, jsonptr, found := strings.Cut(f, "=")
|
||||
if !found {
|
||||
_, _ = fmt.Fprintf(os.Stderr,
|
||||
"Format %s is missing a JSON pointer, e.g., --%s=%s=<jsonpointer>. The path syntax is described at https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-07.",
|
||||
f, FlagFormat, f)
|
||||
os.Exit(1)
|
||||
}
|
||||
ptr, err := jsonpointer.New(jsonptr)
|
||||
Must(err, "invalid JSON pointer: %s", err)
|
||||
|
||||
result, _, err := ptr.Get(data)
|
||||
Must(err, "failed to apply JSON pointer: %s", err)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func PrintTable(cmd *cobra.Command, table Table) {
|
||||
PrintTablef(cmd.OutOrStdout(), table, WithFormat(getFormatValue(cmd)))
|
||||
}
|
||||
|
||||
func PrintTablef(w io.Writer, table Table, opts ...PrintOption) {
|
||||
o := &printOptions{}
|
||||
for _, fn := range opts {
|
||||
fn(o)
|
||||
}
|
||||
|
||||
switch parseFormat(o.format) {
|
||||
case FormatQuiet:
|
||||
if table.Len() == 0 {
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
if idAble, ok := table.(interface{ IDs() []string }); ok {
|
||||
for _, row := range idAble.IDs() {
|
||||
fmt.Fprintln(w, row)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
for _, row := range table.Table() {
|
||||
fmt.Fprintln(w, row[0])
|
||||
}
|
||||
case FormatJSON:
|
||||
printJSON(w, table.Interface(), false, "")
|
||||
case FormatJSONPretty:
|
||||
printJSON(w, table.Interface(), true, "")
|
||||
case FormatJSONPath:
|
||||
printJSON(w, table.Interface(), true, getPath(o.format))
|
||||
case FormatJSONPointer:
|
||||
printJSON(w, filterJSONPointer(o.format, table.Interface()), true, "")
|
||||
case FormatYAML:
|
||||
printYAML(w, table.Interface())
|
||||
default:
|
||||
w := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0)
|
||||
|
||||
for _, h := range table.Header() {
|
||||
fmt.Fprintf(w, "%s\t", h)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
||||
for _, row := range table.Table() {
|
||||
fmt.Fprintln(w, strings.Join(row, "\t")+"\t")
|
||||
}
|
||||
|
||||
_ = w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type interfacer interface{ Interface() interface{} }
|
||||
|
||||
func PrintJSONAble(cmd *cobra.Command, d interface{ String() string }) {
|
||||
PrintJSONAblef(cmd.OutOrStdout(), d, WithFormat(getFormatValue(cmd)))
|
||||
}
|
||||
|
||||
func PrintJSONAblef(w io.Writer, d interface{ String() string }, opts ...PrintOption) {
|
||||
if d == nil {
|
||||
d = Nil{}
|
||||
}
|
||||
|
||||
o := &printOptions{}
|
||||
for _, fn := range opts {
|
||||
fn(o)
|
||||
}
|
||||
|
||||
var path string
|
||||
switch parseFormat(o.format) {
|
||||
default:
|
||||
_, _ = fmt.Fprint(w, d.String())
|
||||
case FormatJSON:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printJSON(w, v, false, "")
|
||||
case FormatJSONPath:
|
||||
path = getPath(o.format)
|
||||
fallthrough
|
||||
case FormatJSONPretty:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printJSON(w, v, true, path)
|
||||
case FormatJSONPointer:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printJSON(w, filterJSONPointer(o.format, v), true, "")
|
||||
case FormatYAML:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printYAML(w, v)
|
||||
}
|
||||
}
|
||||
|
||||
func getQuiet(cmd *cobra.Command) bool {
|
||||
// ignore the error here as we use this function also when the flag might not be registered
|
||||
q, _ := cmd.Flags().GetBool(FlagQuiet)
|
||||
return q
|
||||
}
|
||||
|
||||
func getFormatValue(cmd *cobra.Command) string {
|
||||
if getQuiet(cmd) {
|
||||
return string(FormatQuiet)
|
||||
}
|
||||
|
||||
f, _ := cmd.Flags().GetString(FlagFormat)
|
||||
return f
|
||||
}
|
||||
|
||||
func parseFormat(f string) Format {
|
||||
switch {
|
||||
case f == string(FormatQuiet):
|
||||
return FormatQuiet
|
||||
case f == string(FormatTable):
|
||||
return FormatTable
|
||||
case f == string(FormatJSON):
|
||||
return FormatJSON
|
||||
case strings.HasPrefix(f, string(FormatJSONPath)):
|
||||
return FormatJSONPath
|
||||
case strings.HasPrefix(f, string(FormatJSONPointer)):
|
||||
return FormatJSONPointer
|
||||
case f == string(FormatJSONPretty):
|
||||
return FormatJSONPretty
|
||||
case f == string(FormatYAML):
|
||||
return FormatYAML
|
||||
default:
|
||||
return FormatDefault
|
||||
}
|
||||
}
|
||||
|
||||
func getPath(f string) string {
|
||||
_, path, found := strings.Cut(f, "=")
|
||||
if !found {
|
||||
_, _ = fmt.Fprintf(os.Stderr,
|
||||
"Format %s is missing a path, e.g., --%s=%s=<path>. The path syntax is described at https://github.com/tidwall/gjson/blob/master/SYNTAX.md",
|
||||
f, FlagFormat, f)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func printJSON(w io.Writer, v interface{}, pretty bool, path string) {
|
||||
if path != "" {
|
||||
temp, err := json.Marshal(v)
|
||||
Must(err, "Error encoding JSON: %s", err)
|
||||
v = gjson.GetBytes(temp, path).Value()
|
||||
}
|
||||
|
||||
e := json.NewEncoder(w)
|
||||
if pretty {
|
||||
e.SetIndent("", " ")
|
||||
}
|
||||
err := e.Encode(v)
|
||||
// unexpected error
|
||||
Must(err, "Error encoding JSON: %s", err)
|
||||
}
|
||||
|
||||
func printYAML(w io.Writer, v interface{}) {
|
||||
j, err := json.Marshal(v)
|
||||
Must(err, "Error encoding JSON: %s", err)
|
||||
e, err := yaml.JSONToYAML(j)
|
||||
Must(err, "Error encoding YAML: %s", err)
|
||||
_, _ = w.Write(e)
|
||||
}
|
||||
|
||||
func RegisterJSONFormatFlags(flags *pflag.FlagSet) {
|
||||
flags.String(FlagFormat, string(FormatDefault), fmt.Sprintf("Set the output format. One of %s, %s, %s, %s, %s and %s.", FormatDefault, FormatJSON, FormatYAML, FormatJSONPretty, FormatJSONPath, FormatJSONPointer))
|
||||
}
|
||||
|
||||
func RegisterFormatFlags(flags *pflag.FlagSet) {
|
||||
RegisterNoiseFlags(flags)
|
||||
flags.String(FlagFormat, string(FormatTable), fmt.Sprintf("Set the output format. One of %s, %s, %s, %s, %s and %s.", FormatTable, FormatJSON, FormatYAML, FormatJSONPretty, FormatJSONPath, FormatJSONPointer))
|
||||
}
|
||||
|
||||
func PrintOpenAPIError(cmd *cobra.Command, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var be interface {
|
||||
Body() []byte
|
||||
}
|
||||
if !errors.As(err, &be) {
|
||||
return err
|
||||
}
|
||||
|
||||
body := be.Body()
|
||||
didPrettyPrint := false
|
||||
if message := gjson.GetBytes(body, "error.message"); message.Exists() {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\n", message.String())
|
||||
didPrettyPrint = true
|
||||
}
|
||||
if reason := gjson.GetBytes(body, "error.reason"); reason.Exists() {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\n", reason.String())
|
||||
didPrettyPrint = true
|
||||
}
|
||||
|
||||
if didPrettyPrint {
|
||||
return FailSilently(cmd)
|
||||
}
|
||||
|
||||
if body, err := json.MarshalIndent(json.RawMessage(body), "", " "); err == nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\nFailed to execute API request, see error above.\n", body)
|
||||
return FailSilently(cmd)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
122
oryx/cmdx/usage.go
Normal file
122
oryx/cmdx/usage.go
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"maps"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
usageTemplateFuncs = sprig.TxtFuncMap()
|
||||
usageTemplateFuncsMux sync.RWMutex
|
||||
enableUsageTemplatingOnce sync.Once
|
||||
)
|
||||
|
||||
// AddUsageTemplateFunc adds a template function to the usage template.
|
||||
func AddUsageTemplateFunc(name string, f interface{}) {
|
||||
usageTemplateFuncsMux.Lock()
|
||||
usageTemplateFuncs[name] = f
|
||||
usageTemplateFuncsMux.Unlock()
|
||||
}
|
||||
|
||||
const (
|
||||
helpTemplate = `{{insertTemplate . (or .Long .Short) | trimTrailingWhitespaces}}
|
||||
|
||||
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
||||
usageTemplate = `Usage:{{if .Runnable}}
|
||||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
||||
|
||||
Aliases:
|
||||
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
||||
|
||||
Examples:
|
||||
{{insertTemplate . .Example}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
|
||||
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
||||
|
||||
Flags:
|
||||
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
||||
|
||||
Global Flags:
|
||||
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
|
||||
|
||||
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
||||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
|
||||
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
||||
`
|
||||
)
|
||||
|
||||
// EnableUsageTemplating enables gotemplates for usage strings, i.e. cmd.Short, cmd.Long, and cmd.Example.
|
||||
// The data for the template is the command itself. Especially useful are `.Root.Name` and `.CommandPath`.
|
||||
// This will be inherited by all subcommands, so enabling it on the root command is sufficient.
|
||||
func EnableUsageTemplating(cmds ...*cobra.Command) {
|
||||
enableUsageTemplatingOnce.Do(func() {
|
||||
cobra.AddTemplateFunc("insertTemplate", TemplateCommandField)
|
||||
})
|
||||
for _, cmd := range cmds {
|
||||
cmd.SetHelpTemplate(helpTemplate)
|
||||
cmd.SetUsageTemplate(usageTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
func TemplateCommandField(cmd *cobra.Command, field string) (string, error) {
|
||||
t := template.New("")
|
||||
usageTemplateFuncsMux.RLock()
|
||||
t.Funcs(maps.Clone(usageTemplateFuncs))
|
||||
usageTemplateFuncsMux.RUnlock()
|
||||
t, err := t.Parse(field)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var out bytes.Buffer
|
||||
if err := t.Execute(&out, cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
// DisableUsageTemplating resets the commands usage template to the default.
|
||||
// This can be used to undo the effects of EnableUsageTemplating, specifically for a subcommand.
|
||||
func DisableUsageTemplating(cmds ...*cobra.Command) {
|
||||
defaultCmd := new(cobra.Command)
|
||||
for _, cmd := range cmds {
|
||||
cmd.SetHelpTemplate(defaultCmd.HelpTemplate())
|
||||
cmd.SetUsageTemplate(defaultCmd.UsageTemplate())
|
||||
}
|
||||
}
|
||||
|
||||
// AssertUsageTemplates asserts that the usage string of the commands are properly templated.
|
||||
func AssertUsageTemplates(t require.TestingT, cmd *cobra.Command) {
|
||||
var usage, help string
|
||||
require.NotPanics(t, func() {
|
||||
usage = cmd.UsageString()
|
||||
|
||||
out, err := cmd.OutOrStdout(), cmd.ErrOrStderr()
|
||||
bb := new(bytes.Buffer)
|
||||
|
||||
cmd.SetOut(bb)
|
||||
cmd.SetErr(bb)
|
||||
require.NoError(t, cmd.Help())
|
||||
help = bb.String()
|
||||
|
||||
cmd.SetOut(out)
|
||||
cmd.SetErr(err)
|
||||
})
|
||||
assert.NotContains(t, usage, "{{")
|
||||
assert.NotContains(t, help, "{{")
|
||||
for _, child := range cmd.Commands() {
|
||||
AssertUsageTemplates(t, child)
|
||||
}
|
||||
}
|
||||
57
oryx/cmdx/user_input.go
Normal file
57
oryx/cmdx/user_input.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// asks for confirmation with the question string s and reads the answer
|
||||
// pass nil to use os.Stdin and os.Stdout
|
||||
func AskForConfirmation(s string, stdin io.Reader, stdout io.Writer) bool {
|
||||
if stdin == nil {
|
||||
stdin = os.Stdin
|
||||
}
|
||||
if stdout == nil {
|
||||
stdout = os.Stdout
|
||||
}
|
||||
|
||||
ok, err := AskScannerForConfirmation(s, bufio.NewReader(stdin), stdout)
|
||||
if err != nil {
|
||||
Must(err, "Unable to confirm: %s", err)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func AskScannerForConfirmation(s string, reader *bufio.Reader, stdout io.Writer) (bool, error) {
|
||||
if stdout == nil {
|
||||
stdout = os.Stdout
|
||||
}
|
||||
|
||||
for {
|
||||
_, err := fmt.Fprintf(stdout, "%s [y/n]: ", s)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "unable to print to stdout")
|
||||
}
|
||||
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "unable to read from stdin")
|
||||
}
|
||||
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
if response == "y" || response == "yes" {
|
||||
return true, nil
|
||||
} else if response == "n" || response == "no" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
38
oryx/cmdx/version.go
Normal file
38
oryx/cmdx/version.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Version returns a *cobra.Command that handles the `version` command.
|
||||
func Version(gitTag, gitHash, buildTime *string) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show the build version, build time, and git hash",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(*gitTag) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Unable to determine version because the build process did not properly configure it.")
|
||||
} else {
|
||||
fmt.Printf("Version: %s\n", *gitTag)
|
||||
}
|
||||
|
||||
if len(*gitHash) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Unable to determine build commit because the build process did not properly configure it.")
|
||||
} else {
|
||||
fmt.Printf("Build Commit: %s\n", *gitHash)
|
||||
}
|
||||
|
||||
if len(*buildTime) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Unable to determine build timestamp because the build process did not properly configure it.")
|
||||
} else {
|
||||
fmt.Printf("Build Timestamp: %s\n", *buildTime)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user