mirror of
https://github.com/holos-run/holos.git
synced 2026-03-20 01:04:59 +00:00
I'd like to add the kargo-demo repository to Unity to test evalv3, but can't get a handle on the main function to wire up to testscript. This patch fixes the problem by moving the MakeMain function to a public package so the kargo-demo go module can import and call it using the go mod tools technique.
187 lines
5.9 KiB
Go
187 lines
5.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"connectrpc.com/connect"
|
|
"github.com/spf13/cobra"
|
|
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
|
|
"github.com/holos-run/holos/version"
|
|
|
|
"github.com/holos-run/holos/internal/errors"
|
|
"github.com/holos-run/holos/internal/holos"
|
|
"github.com/holos-run/holos/internal/logger"
|
|
"github.com/holos-run/holos/internal/server"
|
|
|
|
"github.com/holos-run/holos/internal/cli/command"
|
|
"github.com/holos-run/holos/internal/cli/create"
|
|
"github.com/holos-run/holos/internal/cli/destroy"
|
|
"github.com/holos-run/holos/internal/cli/get"
|
|
"github.com/holos-run/holos/internal/cli/kv"
|
|
"github.com/holos-run/holos/internal/cli/login"
|
|
"github.com/holos-run/holos/internal/cli/logout"
|
|
"github.com/holos-run/holos/internal/cli/preflight"
|
|
"github.com/holos-run/holos/internal/cli/pull"
|
|
"github.com/holos-run/holos/internal/cli/push"
|
|
"github.com/holos-run/holos/internal/cli/register"
|
|
"github.com/holos-run/holos/internal/cli/render"
|
|
"github.com/holos-run/holos/internal/cli/token"
|
|
"github.com/holos-run/holos/internal/cli/txtar"
|
|
|
|
cueCmd "cuelang.org/go/cmd/cue/cmd"
|
|
cue_errors "cuelang.org/go/cue/errors"
|
|
)
|
|
|
|
//go:embed help.txt
|
|
var helpLong string
|
|
|
|
// New returns a new root *cobra.Command for command line execution.
|
|
func New(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
|
|
rootCmd := &cobra.Command{
|
|
Use: "holos",
|
|
Short: "holos manages a holistic integrated software development platform",
|
|
Long: helpLong,
|
|
Version: version.GetVersion(),
|
|
Args: cobra.NoArgs,
|
|
CompletionOptions: cobra.CompletionOptions{
|
|
HiddenDefaultCmd: true, // Don't complete the complete subcommand itself
|
|
},
|
|
SilenceUsage: true,
|
|
SilenceErrors: true,
|
|
PersistentPreRunE: func(c *cobra.Command, args []string) error {
|
|
if err := cfg.Finalize(); err != nil {
|
|
return err
|
|
}
|
|
log := cfg.Logger()
|
|
c.Root().SetContext(logger.NewContext(c.Context(), log))
|
|
// Set the default logger after flag parsing.
|
|
slog.SetDefault(log)
|
|
return nil
|
|
},
|
|
RunE: func(c *cobra.Command, args []string) error {
|
|
return c.Usage()
|
|
},
|
|
}
|
|
rootCmd.SetVersionTemplate("{{.Version}}\n")
|
|
rootCmd.SetOut(cfg.Stdout())
|
|
rootCmd.PersistentFlags().SortFlags = false
|
|
rootCmd.PersistentFlags().AddGoFlagSet(cfg.LogFlagSet())
|
|
|
|
// Hide the help command
|
|
rootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
|
|
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
|
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
|
|
|
// subcommands
|
|
rootCmd.AddCommand(render.New(cfg, feature))
|
|
rootCmd.AddCommand(get.New(cfg, feature))
|
|
rootCmd.AddCommand(create.New(cfg, feature))
|
|
rootCmd.AddCommand(destroy.New(cfg, feature))
|
|
rootCmd.AddCommand(preflight.New(cfg, feature))
|
|
rootCmd.AddCommand(login.New(cfg, feature))
|
|
rootCmd.AddCommand(logout.New(cfg, feature))
|
|
rootCmd.AddCommand(token.New(cfg, feature))
|
|
rootCmd.AddCommand(newInitCommand(feature))
|
|
rootCmd.AddCommand(register.New(cfg, feature))
|
|
rootCmd.AddCommand(pull.New(cfg, feature))
|
|
rootCmd.AddCommand(push.New(cfg, feature))
|
|
rootCmd.AddCommand(newOrgCmd(feature))
|
|
|
|
// Maybe not needed?
|
|
rootCmd.AddCommand(txtar.New(cfg))
|
|
|
|
// Deprecated, remove?
|
|
rootCmd.AddCommand(kv.New(cfg, feature))
|
|
|
|
// Server
|
|
rootCmd.AddCommand(server.New(cfg, feature))
|
|
|
|
// CUE
|
|
rootCmd.AddCommand(newCueCmd())
|
|
|
|
// Show
|
|
rootCmd.AddCommand(newShowCmd())
|
|
|
|
return rootCmd
|
|
}
|
|
|
|
func newOrgCmd(feature holos.Flagger) (cmd *cobra.Command) {
|
|
cmd = command.New("orgid")
|
|
cmd.Short = "print the current context org id."
|
|
cmd.Hidden = !feature.Flag(holos.ServerFeature)
|
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
|
ctx := cmd.Root().Context()
|
|
cc := holos.NewClientContext(ctx)
|
|
_, err := fmt.Fprintln(cmd.OutOrStdout(), cc.OrgID)
|
|
return err
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func newCueCmd() (cmd *cobra.Command) {
|
|
// Get a handle on the cue root command fields.
|
|
root, _ := cueCmd.New([]string{})
|
|
// Copy the fields to our embedded command.
|
|
cmd = command.New("cue")
|
|
cmd.Short = root.Short
|
|
cmd.Long = root.Long
|
|
// Pass all arguments through to RunE.
|
|
cmd.DisableFlagParsing = true
|
|
cmd.Args = cobra.ArbitraryArgs
|
|
|
|
// We do it this way so we handle errors correctly.
|
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
|
cueRootCommand, _ := cueCmd.New(args)
|
|
return cueRootCommand.Run(cmd.Root().Context())
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
// HandleError is the top level error handler that unwraps and logs errors.
|
|
func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
|
|
// Connect errors have codes, log them.
|
|
log := hc.NewTopLevelLogger().With("code", connect.CodeOf(err))
|
|
var cueErr cue_errors.Error
|
|
var errAt *errors.ErrorAt
|
|
|
|
if errors.As(err, &errAt) {
|
|
loc := errAt.Source.Loc()
|
|
err2 := errAt.Unwrap()
|
|
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s at %s", err2, loc), "err", err2, "loc", loc)
|
|
} else {
|
|
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s", err), "err", err)
|
|
}
|
|
|
|
// cue errors are bundled up as a list and refer to multiple files / lines.
|
|
if errors.As(err, &cueErr) {
|
|
msg := cue_errors.Details(cueErr, nil)
|
|
if _, err := fmt.Fprint(hc.Stderr(), msg); err != nil {
|
|
log.ErrorContext(ctx, "could not write CUE error details: "+err.Error(), "err", err)
|
|
}
|
|
}
|
|
// connect errors have details and codes.
|
|
// Refer to https://connectrpc.com/docs/go/errors
|
|
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
|
|
for _, detail := range connectErr.Details() {
|
|
msg, valueErr := detail.Value()
|
|
if valueErr != nil {
|
|
log.WarnContext(ctx, "could not decode error detail", "err", err, "type", detail.Type(), "note", "this usually means we don't have the schema for the protobuf message type")
|
|
continue
|
|
}
|
|
if info, ok := msg.(*errdetails.ErrorInfo); ok {
|
|
logDetail := log.With("reason", info.GetReason(), "domain", info.GetDomain())
|
|
for k, v := range info.GetMetadata() {
|
|
logDetail = logDetail.With(k, v)
|
|
}
|
|
logDetail.ErrorContext(ctx, info.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1
|
|
}
|