diff --git a/internal/versionsapi/cli/latest.go b/internal/versionsapi/cli/latest.go new file mode 100644 index 000000000..509d06ced --- /dev/null +++ b/internal/versionsapi/cli/latest.go @@ -0,0 +1,144 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package main + +import ( + "encoding/json" + "fmt" + + "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/versionsapi" + verclient "github.com/edgelesssys/constellation/v2/internal/versionsapi/client" + "github.com/spf13/cobra" + "go.uber.org/zap/zapcore" +) + +func newLatestCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "latest", + Short: "Find latest version", + Long: "Find latest version of a ref/stream. The returned version is in short format, if --json flag is not set.", + RunE: runLatest, + Args: cobra.ExactArgs(0), + } + + cmd.Flags().String("ref", "-", "Ref to query") + cmd.Flags().String("stream", "stable", "Stream to query") + cmd.Flags().Bool("json", false, "Whether to output the result as JSON") + + return cmd +} + +func runLatest(cmd *cobra.Command, args []string) error { + flags, err := parseLatestFlags(cmd) + if err != nil { + return err + } + log := logger.New(logger.PlainLog, flags.logLevel) + log.Debugf("Parsed flags: %+v", flags) + + log.Debugf("Validating flags.") + if err := flags.validate(); err != nil { + return err + } + + log.Debugf("Creating versions API client.") + client, err := verclient.NewClient(cmd.Context(), flags.region, flags.bucket, flags.distributionID, true, log) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + log.Debugf("Requesting latest version.") + latest := versionsapi.Latest{ + Ref: flags.ref, + Stream: flags.stream, + Kind: versionsapi.VersionKindImage, + } + latest, err = client.FetchVersionLatest(cmd.Context(), latest) + if err != nil { + return fmt.Errorf("fetching latest version: %w", err) + } + + if flags.json { + out, err := json.MarshalIndent(latest, "", " ") + if err != nil { + return fmt.Errorf("marshaling JSON: %w", err) + } + fmt.Fprintln(cmd.OutOrStdout(), string(out)) + return nil + } + + fmt.Fprintln(cmd.OutOrStdout(), latest.ShortPath()) + return nil +} + +type latestFlags struct { + ref string + stream string + json bool + region string + bucket string + distributionID string + logLevel zapcore.Level +} + +func (l *latestFlags) validate() error { + if err := versionsapi.ValidateRef(l.ref); err != nil { + return fmt.Errorf("invalid ref: %w", err) + } + if err := versionsapi.ValidateStream(l.ref, l.stream); err != nil { + return fmt.Errorf("invalid stream: %w", err) + } + + return nil +} + +func parseLatestFlags(cmd *cobra.Command) (latestFlags, error) { + ref, err := cmd.Flags().GetString("ref") + if err != nil { + return latestFlags{}, err + } + ref = versionsapi.CanonicalizeRef(ref) + stream, err := cmd.Flags().GetString("stream") + if err != nil { + return latestFlags{}, err + } + json, err := cmd.Flags().GetBool("json") + if err != nil { + return latestFlags{}, err + } + region, err := cmd.Flags().GetString("region") + if err != nil { + return latestFlags{}, err + } + bucket, err := cmd.Flags().GetString("bucket") + if err != nil { + return latestFlags{}, err + } + distributionID, err := cmd.Flags().GetString("distribution-id") + if err != nil { + return latestFlags{}, err + } + verbose, err := cmd.Flags().GetBool("verbose") + if err != nil { + return latestFlags{}, err + } + logLevel := zapcore.InfoLevel + if verbose { + logLevel = zapcore.DebugLevel + } + + return latestFlags{ + ref: ref, + stream: stream, + json: json, + region: region, + bucket: bucket, + distributionID: distributionID, + logLevel: logLevel, + }, nil +} diff --git a/internal/versionsapi/cli/main.go b/internal/versionsapi/cli/main.go index bd2cc2635..c8c1a5b61 100644 --- a/internal/versionsapi/cli/main.go +++ b/internal/versionsapi/cli/main.go @@ -44,6 +44,7 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().String("distribution-id", "E1H77EZTHC3NE4", "CloudFront distribution ID of the API") rootCmd.AddCommand(newAddCmd()) + rootCmd.AddCommand(newLatestCmd()) return rootCmd }