From 25b5df465a2a5594fa960f4d30b48ce002f6cdb6 Mon Sep 17 00:00:00 2001 From: Till Klampaeckel Date: Mon, 28 Oct 2024 19:47:21 +0100 Subject: [PATCH] Chore(ostor): refactor app setup --- cmd/ostor/main.go | 168 +++------------------------------------- internal/cmd/action.go | 21 +++++ internal/cmd/buckets.go | 14 ++-- internal/cmd/cmd.go | 142 +++++++++++++++++++++++++++++++++ internal/cmd/key.go | 57 ++++++++++++++ internal/cmd/stats.go | 4 +- internal/cmd/types.go | 2 +- internal/cmd/users.go | 103 ++++++------------------ internal/cmd/utils.go | 19 ++++- 9 files changed, 280 insertions(+), 250 deletions(-) create mode 100644 internal/cmd/action.go create mode 100644 internal/cmd/cmd.go create mode 100644 internal/cmd/key.go diff --git a/cmd/ostor/main.go b/cmd/ostor/main.go index c2ed313..ef14a08 100644 --- a/cmd/ostor/main.go +++ b/cmd/ostor/main.go @@ -1,13 +1,11 @@ package main import ( - "context" "fmt" "log/slog" "os" "github.com/Luzilla/acronis-s3-usage/internal/cmd" - "github.com/Luzilla/acronis-s3-usage/pkg/ostor" "github.com/urfave/cli/v2" ) @@ -22,21 +20,15 @@ func main() { slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))) app := &cli.App{ - Name: "ostor-client", + Name: "ostor-client", + Authors: []*cli.Author{ + { + Name: "Luzilla Capital GmbH", + }, + }, HelpName: "a program to interact with the s3 management APIs in ACI and VHI", Version: fmt.Sprintf("%s (%s, date: %s)", version, commit, date), - Before: func(cCtx *cli.Context) error { - client, err := ostor.New( - cCtx.String("s3-endpoint"), - cCtx.String("s3-system-key-id"), - cCtx.String("s3-system-secret")) - if err != nil { - return err - } - - cCtx.Context = context.WithValue(cCtx.Context, cmd.OstorClient, client) - return nil - }, + Before: cmd.Before, Flags: []cli.Flag{ &cli.StringFlag{ Name: "s3-endpoint", @@ -55,142 +47,9 @@ func main() { }, }, Commands: []*cli.Command{ - { - Name: "buckets", - Aliases: []string{"b"}, - Usage: "list buckets", - Action: cmd.ListBuckets, - Flags: []cli.Flag{ - emailFlag(), - }, - Subcommands: []*cli.Command{ - { - Name: "delete", - Aliases: []string{"d"}, - Action: cmd.DeleteBucket, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "bucket", - Required: true, - }, - &cli.BoolFlag{ - Name: "confirm", - Value: false, - }, - }, - }, - { - Name: "show", - Aliases: []string{"s"}, - Action: cmd.ShowBucket, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "bucket", - Required: true, - }, - }, - }, - }, - }, - { - Name: "stats", - Aliases: []string{"s"}, - Usage: "list stats", - Flags: []cli.Flag{ - // &cli.TimestampFlag{ - // Name: "from", - // Layout: "2006-01-02", - // Timezone: time.UTC, - // Required: false, - // }, - }, - Action: cmd.ShowStats, - }, - { - Name: "users", - Aliases: []string{"u"}, - Usage: "manage users", - Action: cmd.Users, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "usage", - Value: false, - }, - }, - Subcommands: []*cli.Command{ - { - Name: "create", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.CreateUser, - }, - { - Name: "delete", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.DeleteUser, - }, - { - Name: "lock", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.LockUser, - }, - { - Name: "unlock", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.UnlockUser, - }, - { - Name: "show", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.ShowUser, - }, - { - Name: "create-key", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.CreateKey, - }, - { - Name: "revoke-key", - Flags: []cli.Flag{ - emailFlag(), - &cli.StringFlag{ - Name: "key-id", - Required: true, - }, - }, - Action: cmd.RevokeKey, - }, - { - Name: "rotate-key", - Flags: []cli.Flag{ - emailFlag(), - &cli.StringFlag{ - Name: "key-id", - Required: true, - }, - }, - Action: cmd.RotateKey, - }, - { - Name: "limits", - Flags: []cli.Flag{ - emailFlag(), - }, - Action: cmd.UserLimits, - }, - }, - }, + cmd.BucketCommand(), + cmd.StatsCommand(), + cmd.UsersCommand(), }, } @@ -199,10 +58,3 @@ func main() { os.Exit(1) } } - -func emailFlag() *cli.StringFlag { - return &cli.StringFlag{ - Name: "email", - Required: true, - } -} diff --git a/internal/cmd/action.go b/internal/cmd/action.go new file mode 100644 index 0000000..7b6b5d5 --- /dev/null +++ b/internal/cmd/action.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "context" + + "github.com/Luzilla/acronis-s3-usage/pkg/ostor" + "github.com/urfave/cli/v2" +) + +func Before(cCtx *cli.Context) error { + client, err := ostor.New( + cCtx.String("s3-endpoint"), + cCtx.String("s3-system-key-id"), + cCtx.String("s3-system-secret")) + if err != nil { + return err + } + + cCtx.Context = context.WithValue(cCtx.Context, ostorClient, client) + return nil +} diff --git a/internal/cmd/buckets.go b/internal/cmd/buckets.go index 0f8751d..1055134 100644 --- a/internal/cmd/buckets.go +++ b/internal/cmd/buckets.go @@ -13,8 +13,8 @@ import ( // this executes the action on 'behalf' of the user by returning the account // and using the first credential pair to run the delete operations -func DeleteBucket(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func deleteBucket(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) s3, err := s3.NewS3(cCtx.String("s3-endpoint"), cCtx.String("email"), client) if err != nil { @@ -36,8 +36,8 @@ func DeleteBucket(cCtx *cli.Context) error { return nil } -func ListBuckets(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func listBuckets(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) buckets, _, err := client.GetBuckets(cCtx.String("email")) if err != nil { @@ -60,10 +60,10 @@ func ListBuckets(cCtx *cli.Context) error { return nil } -func ShowBucket(cCtx *cli.Context) error { - ListBuckets(cCtx) // display the filter view first +func showBucket(cCtx *cli.Context) error { + listBuckets(cCtx) // display the filter view first - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) s3, err := s3.NewS3(cCtx.String("s3-endpoint"), cCtx.String("email"), client) if err != nil { diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go new file mode 100644 index 0000000..d6bb6d5 --- /dev/null +++ b/internal/cmd/cmd.go @@ -0,0 +1,142 @@ +package cmd + +import "github.com/urfave/cli/v2" + +func BucketCommand() *cli.Command { + return &cli.Command{ + Name: "buckets", + Aliases: []string{"b"}, + Usage: "list buckets", + Action: listBuckets, + Flags: []cli.Flag{ + emailFlag(), + }, + Subcommands: []*cli.Command{ + { + Name: "delete", + Aliases: []string{"d"}, + Action: deleteBucket, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "bucket", + Required: true, + }, + &cli.BoolFlag{ + Name: "confirm", + Value: false, + }, + }, + }, + { + Name: "show", + Aliases: []string{"s"}, + Action: showBucket, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "bucket", + Required: true, + }, + }, + }, + }, + } +} + +func StatsCommand() *cli.Command { + return &cli.Command{ + Name: "stats", + Aliases: []string{"s"}, + Usage: "list stats", + Flags: []cli.Flag{ + // &cli.TimestampFlag{ + // Name: "from", + // Layout: "2006-01-02", + // Timezone: time.UTC, + // Required: false, + // }, + }, + Action: showStats, + } +} + +func UsersCommand() *cli.Command { + return &cli.Command{ + Name: "users", + Aliases: []string{"u"}, + Usage: "manage users", + Action: users, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "usage", + Value: false, + }, + }, + Subcommands: []*cli.Command{ + { + Name: "create", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: createUser, + }, + { + Name: "delete", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: deleteUser, + }, + { + Name: "lock", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: lockUser, + }, + { + Name: "unlock", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: unlockUser, + }, + { + Name: "show", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: showUser, + }, + { + Name: "create-key", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: createKey, + }, + { + Name: "revoke-key", + Flags: []cli.Flag{ + emailFlag(), + keyIDFlag(), + }, + Action: revokeKey, + }, + { + Name: "rotate-key", + Flags: []cli.Flag{ + emailFlag(), + keyIDFlag(), + }, + Action: rotateKey, + }, + { + Name: "limits", + Flags: []cli.Flag{ + emailFlag(), + }, + Action: userLimits, + }, + }, + } +} diff --git a/internal/cmd/key.go b/internal/cmd/key.go new file mode 100644 index 0000000..58b22ff --- /dev/null +++ b/internal/cmd/key.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "fmt" + "log/slog" + + "github.com/Luzilla/acronis-s3-usage/pkg/ostor" + "github.com/urfave/cli/v2" +) + +func revokeKey(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) + + email := cCtx.String("email") + keyID := cCtx.String("key-id") + + _, err := client.RevokeKey(email, keyID) + if err != nil { + return err + } + slog.Info("success") + + return nil +} + +func createKey(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) + + email := cCtx.String("email") + + _, _, err := client.GenerateCredentials(email) + if err != nil { + return err + } + slog.Info("success") + + return nil +} + +func rotateKey(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) + + email := cCtx.String("email") + keyID := cCtx.String("key-id") + + keyPair, _, err := client.RotateKey(email, keyID) + if err != nil { + return err + } + + fmt.Println("New key generated:") + fmt.Printf("Access Key ID: %s\n", keyPair.AccessKeyID) + fmt.Printf("Secret Access Key: %s\n", keyPair.SecretAccessKey) + fmt.Println("") + + return nil +} diff --git a/internal/cmd/stats.go b/internal/cmd/stats.go index baf0cba..2d9ac1e 100644 --- a/internal/cmd/stats.go +++ b/internal/cmd/stats.go @@ -24,8 +24,8 @@ type stat struct { // show stats is really expensive, it will (attempt to) crawl the entire `?ostor-usage` endpoint // and look up individual entries when returned, so for n pages returned from `?ostor-usage`, it // will make n * number of items returned requests to `?ostor-usage&obj=FOO` -func ShowStats(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func showStats(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) // fmt.Printf("from: %s\n", cCtx.Timestamp("from").String()) diff --git a/internal/cmd/types.go b/internal/cmd/types.go index cdc46ea..d6f6e83 100644 --- a/internal/cmd/types.go +++ b/internal/cmd/types.go @@ -1,5 +1,5 @@ package cmd const ( - OstorClient string = "xxclient" + ostorClient string = "xxclient" ) diff --git a/internal/cmd/users.go b/internal/cmd/users.go index ae991f5..0cba4e3 100644 --- a/internal/cmd/users.go +++ b/internal/cmd/users.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "log/slog" "github.com/Luzilla/acronis-s3-usage/internal/utils" "github.com/Luzilla/acronis-s3-usage/pkg/ostor" @@ -10,8 +9,8 @@ import ( "github.com/urfave/cli/v2" ) -func Users(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func users(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) users, _, err := client.ListUsers(cCtx.Bool("usage")) if err != nil { @@ -39,12 +38,10 @@ func Users(cCtx *cli.Context) error { return nil } -func CreateUser(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func createUser(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) - email := cCtx.String("email") - - user, _, err := client.CreateUser(email) + user, _, err := client.CreateUser(cCtx.String("email")) if err != nil { return err } @@ -60,12 +57,10 @@ func CreateUser(cCtx *cli.Context) error { return nil } -func DeleteUser(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) - - email := cCtx.String("email") +func deleteUser(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) - resp, err := client.DeleteUser(email) + resp, err := client.DeleteUser(cCtx.String("email")) if err != nil { fmt.Println(resp.Request.URL) @@ -76,11 +71,10 @@ func DeleteUser(cCtx *cli.Context) error { return nil } -func LockUser(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func lockUser(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) - email := cCtx.String("email") - err := lockUnLockUser(client, email, true) + err := lockUnLockUser(client, cCtx.String("email"), true) if err != nil { return err } @@ -89,11 +83,10 @@ func LockUser(cCtx *cli.Context) error { return nil } -func UnlockUser(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) +func unlockUser(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) - email := cCtx.String("email") - err := lockUnLockUser(client, email, false) + err := lockUnLockUser(client, cCtx.String("email"), false) if err != nil { return err } @@ -108,15 +101,13 @@ func lockUnLockUser(client *ostor.Ostor, email string, lock bool) error { return err } -func ShowUser(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) - - email := cCtx.String("email") +func showUser(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) - user, resp, err := client.GetUser(email) + user, resp, err := client.GetUser(cCtx.String("email")) if err != nil { if resp.StatusCode() == 404 { - return fmt.Errorf("no user with email %q found", email) + return fmt.Errorf("no user with email %q found", cCtx.String("email")) } return err } @@ -141,7 +132,7 @@ func ShowUser(cCtx *cli.Context) error { errorNoticeFmt("User does not have any keys.") } - buckets, _, err := client.GetBuckets(email) + buckets, _, err := client.GetBuckets(cCtx.String("email")) if err != nil { return err } @@ -161,60 +152,10 @@ func ShowUser(cCtx *cli.Context) error { return nil } -func RevokeKey(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) - - email := cCtx.String("email") - keyID := cCtx.String("key-id") - - _, err := client.RevokeKey(email, keyID) - if err != nil { - return err - } - slog.Info("success") - - return nil -} - -func CreateKey(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) - - email := cCtx.String("email") - - _, _, err := client.GenerateCredentials(email) - if err != nil { - return err - } - slog.Info("success") - - return nil -} - -func RotateKey(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) - - email := cCtx.String("email") - keyID := cCtx.String("key-id") - - keyPair, _, err := client.RotateKey(email, keyID) - if err != nil { - return err - } - - fmt.Println("New key generated:") - fmt.Printf("Access Key ID: %s\n", keyPair.AccessKeyID) - fmt.Printf("Secret Access Key: %s\n", keyPair.SecretAccessKey) - fmt.Println("") - - return nil -} - -func UserLimits(cCtx *cli.Context) error { - client := cCtx.Context.Value(OstorClient).(*ostor.Ostor) - - email := cCtx.String("email") +func userLimits(cCtx *cli.Context) error { + client := cCtx.Context.Value(ostorClient).(*ostor.Ostor) - limits, _, err := client.GetUserLimits(email) + limits, _, err := client.GetUserLimits(cCtx.String("email")) if err != nil { return nil } diff --git a/internal/cmd/utils.go b/internal/cmd/utils.go index 99c8423..b9029da 100644 --- a/internal/cmd/utils.go +++ b/internal/cmd/utils.go @@ -1,6 +1,9 @@ package cmd -import "github.com/fatih/color" +import ( + "github.com/fatih/color" + "github.com/urfave/cli/v2" +) func headerFmt() func(format string, a ...interface{}) string { return color.New(color.FgGreen, color.Underline).SprintfFunc() @@ -13,3 +16,17 @@ func columnFmt() func(format string, a ...interface{}) string { func errorNoticeFmt(msg string) (int, error) { return color.New(color.FgRed, color.BgHiWhite).Println(msg) } + +func emailFlag() *cli.StringFlag { + return &cli.StringFlag{ + Name: "email", + Required: true, + } +} + +func keyIDFlag() *cli.StringFlag { + return &cli.StringFlag{ + Name: "key-id", + Required: true, + } +}