Skip to content

Commit

Permalink
refactor(cli): remove --local-server flag and untangle root cmd log…
Browse files Browse the repository at this point in the history
…ic (#1574)

Signed-off-by: Hidde Beydals <hidde@hhh.computer>
  • Loading branch information
hiddeco authored Mar 6, 2024
1 parent 8c5c1ac commit 34bcb82
Show file tree
Hide file tree
Showing 30 changed files with 612 additions and 473 deletions.
7 changes: 1 addition & 6 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/pkg/errors"

"github.com/akuity/kargo/internal/cli/config"
"github.com/akuity/kargo/internal/cli/option"
)

func main() {
Expand All @@ -21,11 +20,7 @@ func main() {
}
cfg = config.NewDefaultCLIConfig()
}
cmd, err := NewRootCommand(cfg, option.NewOption(cfg), &rootState{})
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrap(err, "new root command"))
os.Exit(1)
}
cmd := NewRootCommand(cfg)
if err := cmd.ExecuteContext(ctx); err != nil {
os.Exit(1)
}
Expand Down
100 changes: 19 additions & 81 deletions cmd/cli/root.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
package main

import (
"context"
"fmt"
"net"
"os"

"github.com/pkg/errors"
"github.com/spf13/cobra"
cobracompletefig "github.com/withfig/autocomplete-tools/integrations/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"
"sigs.k8s.io/controller-runtime/pkg/client/config"

"github.com/akuity/kargo/internal/api"
apiconfig "github.com/akuity/kargo/internal/api/config"
"github.com/akuity/kargo/internal/api/kubernetes"
"github.com/akuity/kargo/internal/cli/cmd/apply"
"github.com/akuity/kargo/internal/cli/cmd/approve"
cliconfigcmd "github.com/akuity/kargo/internal/cli/cmd/config"
Expand All @@ -27,102 +18,49 @@ import (
"github.com/akuity/kargo/internal/cli/cmd/logout"
"github.com/akuity/kargo/internal/cli/cmd/promote"
"github.com/akuity/kargo/internal/cli/cmd/refresh"
"github.com/akuity/kargo/internal/cli/cmd/server"
"github.com/akuity/kargo/internal/cli/cmd/update"
"github.com/akuity/kargo/internal/cli/cmd/version"
clicfg "github.com/akuity/kargo/internal/cli/config"
"github.com/akuity/kargo/internal/cli/option"
"github.com/akuity/kargo/internal/cli/io"
)

// rootState holds state used internally by the root command.
type rootState struct {
localServerListener net.Listener
}

func NewRootCommand(
cfg clicfg.CLIConfig,
opt *option.Option,
rs *rootState,
) (*cobra.Command, error) {
func NewRootCommand(cfg clicfg.CLIConfig) *cobra.Command {
cmd := &cobra.Command{
Use: "kargo",
DisableAutoGenTag: true,
SilenceUsage: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
ctx := buildRootContext(cmd.Context())

if opt.UseLocalServer {
restCfg, err := config.GetConfig()
if err != nil {
return errors.Wrap(err, "get REST config")
}
client, err :=
kubernetes.NewClient(ctx, restCfg, kubernetes.ClientOptions{})
if err != nil {
return errors.Wrap(err, "error creating Kubernetes client")
}
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return errors.Wrap(err, "start local server")
}
rs.localServerListener = l
srv := api.NewServer(
apiconfig.ServerConfig{
LocalMode: true,
},
client,
client,
)
go srv.Serve(ctx, l) // nolint: errcheck
opt.LocalServerAddress = fmt.Sprintf("http://%s", l.Addr())
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
cmd.HelpFunc()(cmd, args)
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
if rs.localServerListener != nil {
return rs.localServerListener.Close()
}
return nil
},
}

opt.IOStreams = &genericiooptions.IOStreams{
In: cmd.InOrStdin(),
Out: os.Stdout,
ErrOut: os.Stderr,
}
scheme, err := option.NewScheme()
if err != nil {
return nil, err
}
opt.PrintFlags = genericclioptions.NewPrintFlags("").WithTypeSetter(scheme)
// Set up the IOStreams for the commands to use.
streams := genericiooptions.IOStreams{Out: os.Stdout, ErrOut: os.Stderr, In: os.Stdin}
io.SetIOStreams(cmd, streams)

cmd.AddCommand(apply.NewCommand(cfg, opt))
cmd.AddCommand(approve.NewCommand(cfg, opt))
// Register the subcommands.
cmd.AddCommand(apply.NewCommand(cfg, streams))
cmd.AddCommand(approve.NewCommand(cfg))
cmd.AddCommand(cliconfigcmd.NewCommand(cfg))
cmd.AddCommand(create.NewCommand(cfg, opt))
cmd.AddCommand(delete.NewCommand(cfg, opt))
cmd.AddCommand(get.NewCommand(cfg, opt))
cmd.AddCommand(login.NewCommand(opt))
cmd.AddCommand(create.NewCommand(cfg, streams))
cmd.AddCommand(delete.NewCommand(cfg, streams))
cmd.AddCommand(get.NewCommand(cfg, streams))
cmd.AddCommand(login.NewCommand())
cmd.AddCommand(logout.NewCommand())
cmd.AddCommand(refresh.NewCommand(cfg, opt))
cmd.AddCommand(update.NewCommand(cfg, opt))
cmd.AddCommand(refresh.NewCommand(cfg))
cmd.AddCommand(update.NewCommand(cfg))
cmd.AddCommand(dashboard.NewCommand(cfg))
cmd.AddCommand(promote.NewCommand(cfg, opt))
cmd.AddCommand(version.NewCommand(cfg, opt))
cmd.AddCommand(promote.NewCommand(cfg, streams))
cmd.AddCommand(version.NewCommand(cfg, streams))
cmd.AddCommand(server.NewCommand())
cmd.AddCommand(
cobracompletefig.CreateCompletionSpecCommand(
cobracompletefig.Opts{
Use: "fig",
},
),
)
return cmd, nil
}

func buildRootContext(ctx context.Context) context.Context {
// TODO: Inject console printer or logger
return ctx
return cmd
}
20 changes: 13 additions & 7 deletions internal/cli/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,39 @@ import (

"connectrpc.com/connect"
"github.com/pkg/errors"
"github.com/spf13/pflag"

"github.com/akuity/kargo/internal/cli/config"
"github.com/akuity/kargo/internal/cli/option"
"github.com/akuity/kargo/pkg/api/service/v1alpha1/svcv1alpha1connect"
)

type Options struct {
InsecureTLS bool
}

// AddFlags adds the flags for the client options to the provided flag set.
func (o *Options) AddFlags(flags *pflag.FlagSet) {
option.InsecureTLS(flags, &o.InsecureTLS)
}

// GetClientFromConfig returns a new client for the Kargo API server located at
// the address specified in local configuration, using credentials also
// specified in local configuration UNLESS the specified options indicates that
// the local server should be used instead.
// specified in the local configuration.
func GetClientFromConfig(
ctx context.Context,
cfg config.CLIConfig,
opt *option.Option,
opts Options,
) (
svcv1alpha1connect.KargoServiceClient,
error,
) {
if opt.UseLocalServer {
return GetClient(opt.LocalServerAddress, "", opt.InsecureTLS), nil
}
if cfg.APIAddress == "" || cfg.BearerToken == "" {
return nil, errors.New(
"seems like you are not logged in; please use `kargo login` to authenticate",
)
}
skipTLSVerify := opt.InsecureTLS || cfg.InsecureSkipTLSVerify
skipTLSVerify := opts.InsecureTLS || cfg.InsecureSkipTLSVerify
cfg, err := newTokenRefresher().refreshToken(ctx, cfg, skipTLSVerify)
if err != nil {
return nil, errors.Wrap(err, "error refreshing token")
Expand Down
75 changes: 35 additions & 40 deletions internal/cli/cmd/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,35 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/utils/ptr"
sigyaml "sigs.k8s.io/yaml"

"github.com/akuity/kargo/internal/cli/client"
"github.com/akuity/kargo/internal/cli/config"
"github.com/akuity/kargo/internal/cli/io"
"github.com/akuity/kargo/internal/cli/kubernetes"
"github.com/akuity/kargo/internal/cli/option"
"github.com/akuity/kargo/internal/yaml"
kargosvcapi "github.com/akuity/kargo/pkg/api/service/v1alpha1"
)

type applyOptions struct {
*option.Option
Config config.CLIConfig
genericiooptions.IOStreams
*genericclioptions.PrintFlags

Config config.CLIConfig
ClientOptions client.Options

Filenames []string
}

func NewCommand(cfg config.CLIConfig, opt *option.Option) *cobra.Command {
func NewCommand(cfg config.CLIConfig, streams genericiooptions.IOStreams) *cobra.Command {
cmdOpts := &applyOptions{
Option: opt,
Config: cfg,
Config: cfg,
IOStreams: streams,
PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(kubernetes.GetScheme()),
}

cmd := &cobra.Command{
Expand All @@ -54,6 +60,9 @@ kargo apply -f stages/
},
}

// Set the input/output streams for the command.
io.SetIOStreams(cmd, cmdOpts.IOStreams)

// Register the option flags on the command.
cmdOpts.addFlags(cmd)

Expand All @@ -62,13 +71,9 @@ kargo apply -f stages/

// addFlags adds the flags for the apply options to the provided command.
func (o *applyOptions) addFlags(cmd *cobra.Command) {
o.ClientOptions.AddFlags(cmd.PersistentFlags())
o.PrintFlags.AddFlags(cmd)

// TODO: Factor out server flags to a higher level (root?) as they are
// common to almost all commands.
option.InsecureTLS(cmd.PersistentFlags(), o.Option)
option.LocalServer(cmd.PersistentFlags(), o.Option)

option.Filenames(cmd.Flags(), &o.Filenames, "Filename or directory to use to apply the resource(s)")

if err := cmd.MarkFlagRequired(option.FilenameFlag); err != nil {
Expand Down Expand Up @@ -101,15 +106,7 @@ func (o *applyOptions) run(ctx context.Context) error {
return errors.Wrap(err, "read manifests")
}

var printer printers.ResourcePrinter
if ptr.Deref(o.PrintFlags.OutputFormat, "") != "" {
printer, err = o.PrintFlags.ToPrinter()
if err != nil {
return errors.Wrap(err, "new printer")
}
}

kargoSvcCli, err := client.GetClientFromConfig(ctx, o.Config, o.Option)
kargoSvcCli, err := client.GetClientFromConfig(ctx, o.Config, o.ClientOptions)
if err != nil {
return errors.Wrap(err, "get client from config")
}
Expand Down Expand Up @@ -140,41 +137,39 @@ func (o *applyOptions) run(ctx context.Context) error {
}
}

printer, err := o.toPrinter("created")
if err != nil {
return errors.Wrap(err, "new printer")
}

for _, res := range createdRes {
var obj unstructured.Unstructured
if err := sigyaml.Unmarshal(res.CreatedResourceManifest, &obj); err != nil {
if err = sigyaml.Unmarshal(res.CreatedResourceManifest, &obj); err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "%s",
errors.Wrap(err, "Error: unmarshal created manifest"))
continue
}
if printer == nil {
name := types.NamespacedName{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
}.String()
fmt.Fprintf(o.IOStreams.Out, "%s Created: %q\n", obj.GetKind(), name)
continue
}
_ = printer.PrintObj(&obj, o.IOStreams.Out)
}

printer, err = o.toPrinter("updated")
if err != nil {
return errors.Wrap(err, "new printer")
}

for _, res := range updatedRes {
var obj unstructured.Unstructured
if err := sigyaml.Unmarshal(res.UpdatedResourceManifest, &obj); err != nil {
if err = sigyaml.Unmarshal(res.UpdatedResourceManifest, &obj); err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "%s",
errors.Wrap(err, "Error: unmarshal updated manifest"))
continue
}
if printer == nil {
name := types.NamespacedName{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
}.String()
fmt.Fprintf(o.IOStreams.Out, "%s Updated: %q\n", obj.GetKind(), name)
continue
}
_ = printer.PrintObj(&obj, o.IOStreams.Out)
}

return goerrors.Join(errs...)
}

func (o *applyOptions) toPrinter(operation string) (printers.ResourcePrinter, error) {
o.PrintFlags.NamePrintFlags.Operation = operation
return o.PrintFlags.ToPrinter()
}
Loading

0 comments on commit 34bcb82

Please sign in to comment.