Skip to content

Commit

Permalink
add: ls subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
nao1215 committed Dec 27, 2023
1 parent df25e67 commit 26b8111
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 89 deletions.
8 changes: 7 additions & 1 deletion app/di/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
type S3App struct {
// S3BucketCreator is the usecase for creating a new S3 bucket.
S3BucketCreator usecase.S3BucketCreator
// S3BucketLister is the usecase for listing S3 buckets.
S3BucketLister usecase.S3BucketLister
}

// NewS3App creates a new S3App.
Expand All @@ -26,14 +28,18 @@ func NewS3App(ctx context.Context, profile model.AWSProfile, region model.Region
model.NewAWSConfig,
external.NewS3Client,
external.S3BucketCreatorSet,
external.S3BucketListerSet,
external.S3BucketLocationGetterSet,
interactor.S3bucketCreatorSet,
interactor.S3bucketListerSet,
newS3App,
)
return nil, nil
}

func newS3App(s3bucketCreator usecase.S3BucketCreator) *S3App {
func newS3App(s3bucketCreator usecase.S3BucketCreator, s3bucketLister usecase.S3BucketLister) *S3App {
return &S3App{
S3BucketCreator: s3bucketCreator,
S3BucketLister: s3bucketLister,
}
}
10 changes: 8 additions & 2 deletions app/di/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions app/domain/model/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package model
import (
"fmt"
"strings"
"time"

"github.com/nao1215/rainbow/utils/errfmt"
"github.com/nao1215/rainbow/utils/xregex"
Expand Down Expand Up @@ -219,3 +220,27 @@ func (b Bucket) validateCharSequence() error {
}
return nil
}

// BucketSets is the set of the BucketSet.
type BucketSets []BucketSet

// BucketSet is the set of the Bucket and the Region.
type BucketSet struct {
// Bucket is the name of the S3 bucket.
Bucket Bucket
// Region is the name of the AWS region.
Region Region
// CreationDate is date the bucket was created.
// This date can change when making changes to your bucket, such as editing its bucket policy.
CreationDate time.Time
}

// Len returns the length of the BucketSets.
func (b BucketSets) Len() int {
return len(b)
}

// Empty returns true if the BucketSets is empty.
func (b BucketSets) Empty() bool {
return b.Len() == 0
}
30 changes: 30 additions & 0 deletions app/domain/service/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,33 @@ type S3BucketCreatorOutput struct{}
type S3BucketCreator interface {
CreateBucket(ctx context.Context, input *S3BucketCreatorInput) (*S3BucketCreatorOutput, error)
}

// S3BucketListerInput is the input of the ListBuckets method.
type S3BucketListerInput struct{}

// S3BucketListerOutput is the output of the ListBuckets method.
type S3BucketListerOutput struct {
// Buckets is the list of the buckets.
Buckets model.BucketSets
}

// S3BucketLister is the interface that wraps the basic ListBuckets method.
type S3BucketLister interface {
ListBuckets(ctx context.Context, input *S3BucketListerInput) (*S3BucketListerOutput, error)
}

// S3BucketLocationGetterInput is the input of the GetBucketLocation method.
type S3BucketLocationGetterInput struct {
Bucket model.Bucket
}

// S3BucketLocationGetterOutput is the output of the GetBucketLocation method.
type S3BucketLocationGetterOutput struct {
// Region is the region of the bucket.
Region model.Region
}

// S3BucketLocationGetter is the interface that wraps the basic GetBucketLocation method.
type S3BucketLocationGetter interface {
GetBucketLocation(ctx context.Context, input *S3BucketLocationGetterInput) (*S3BucketLocationGetterOutput, error)
}
76 changes: 76 additions & 0 deletions app/external/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,79 @@ func (c *S3BucketCreator) CreateBucket(ctx context.Context, input *service.S3Buc
}
return &service.S3BucketCreatorOutput{}, nil
}

// S3BucketLister implements the S3BucketLister interface.
type S3BucketLister struct {
client *s3.Client
}

// S3BucketListerSet is a provider set for S3BucketLister.
//
//nolint:gochecknoglobals
var S3BucketListerSet = wire.NewSet(
NewS3BucketLister,
wire.Bind(new(service.S3BucketLister), new(*S3BucketLister)),
)

var _ service.S3BucketLister = (*S3BucketLister)(nil)

// NewS3BucketLister creates a new S3BucketLister.
func NewS3BucketLister(client *s3.Client) *S3BucketLister {
return &S3BucketLister{client: client}
}

// ListBuckets lists the buckets.
func (c *S3BucketLister) ListBuckets(ctx context.Context, _ *service.S3BucketListerInput) (*service.S3BucketListerOutput, error) {
out, err := c.client.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
return nil, err
}

var buckets model.BucketSets
for _, b := range out.Buckets {
buckets = append(buckets, model.BucketSet{
Bucket: model.Bucket(*b.Name),
CreationDate: *b.CreationDate,
})
}
return &service.S3BucketListerOutput{Buckets: buckets}, nil
}

// S3BucketLocationGetter implements the S3BucketLocationGetter interface.
type S3BucketLocationGetter struct {
client *s3.Client
}

// S3BucketLocationGetterSet is a provider set for S3BucketLocationGetter.
//
//nolint:gochecknoglobals
var S3BucketLocationGetterSet = wire.NewSet(
NewS3BucketLocationGetter,
wire.Bind(new(service.S3BucketLocationGetter), new(*S3BucketLocationGetter)),
)

var _ service.S3BucketLocationGetter = (*S3BucketLocationGetter)(nil)

// NewS3BucketLocationGetter creates a new S3BucketLocationGetter.
func NewS3BucketLocationGetter(client *s3.Client) *S3BucketLocationGetter {
return &S3BucketLocationGetter{client: client}
}

// GetBucketLocation gets the location of the bucket.
func (c *S3BucketLocationGetter) GetBucketLocation(ctx context.Context, input *service.S3BucketLocationGetterInput) (*service.S3BucketLocationGetterOutput, error) {
out, err := c.client.GetBucketLocation(ctx, &s3.GetBucketLocationInput{
Bucket: aws.String(input.Bucket.String()),
})
if err != nil {
return nil, err
}

region := model.Region(out.LocationConstraint)
if region == "" {
region = model.RegionUSEast1
}

return &service.S3BucketLocationGetterOutput{
Region: region,
}, nil
}
47 changes: 47 additions & 0 deletions app/interactor/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,50 @@ func (s *S3BucketCreator) CreateBucket(ctx context.Context, input *usecase.S3Buc
}
return &usecase.S3BucketCreatorOutput{}, nil
}

// S3bucketListerSet is a provider set for S3BucketLister.
//
//nolint:gochecknoglobals
var S3bucketListerSet = wire.NewSet(
NewS3BucketLister,
wire.Bind(new(usecase.S3BucketLister), new(*S3BucketLister)),
)

var _ usecase.S3BucketLister = (*S3BucketLister)(nil)

// S3BucketLister implements the S3BucketLister interface.
type S3BucketLister struct {
service.S3BucketLister
service.S3BucketLocationGetter
}

// NewS3BucketLister creates a new S3BucketLister.
func NewS3BucketLister(l service.S3BucketLister, g service.S3BucketLocationGetter) *S3BucketLister {
return &S3BucketLister{
S3BucketLister: l,
S3BucketLocationGetter: g,
}
}

// ListBuckets lists the buckets.
func (s *S3BucketLister) ListBuckets(ctx context.Context, _ *usecase.S3BucketListerInput) (*usecase.S3BucketListerOutput, error) {
out, err := s.S3BucketLister.ListBuckets(ctx, &service.S3BucketListerInput{})
if err != nil {
return nil, err
}

for i, b := range out.Buckets {
in := service.S3BucketLocationGetterInput{
Bucket: b.Bucket,
}
o, err := s.S3BucketLocationGetter.GetBucketLocation(ctx, &in)
if err != nil {
return nil, err
}
out.Buckets[i].Region = o.Region
}

return &usecase.S3BucketListerOutput{
Buckets: out.Buckets,
}, nil
}
14 changes: 14 additions & 0 deletions app/usecase/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,17 @@ type S3BucketCreatorOutput struct{}
type S3BucketCreator interface {
CreateBucket(ctx context.Context, input *S3BucketCreatorInput) (*S3BucketCreatorOutput, error)
}

// S3BucketListerInput is the input of the ListBuckets method.
type S3BucketListerInput struct{}

// S3BucketListerOutput is the output of the ListBuckets method.
type S3BucketListerOutput struct {
// Buckets is the list of the buckets.
Buckets model.BucketSets
}

// S3BucketLister is the interface that wraps the basic ListBuckets method.
type S3BucketLister interface {
ListBuckets(ctx context.Context, input *S3BucketListerInput) (*S3BucketListerOutput, error)
}
22 changes: 22 additions & 0 deletions cmd/subcmd/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package subcmd

import "github.com/spf13/cobra"

// Doer is an interface that represents the behavior of a command.
type Doer interface {
Do() error
}

// SubCommand is an interface that represents the behavior of a command.
type SubCommand interface {
Parse(cmd *cobra.Command, args []string) error
Doer
}

// Run runs the subcommand.
func Run(cmd *cobra.Command, args []string, subCmd SubCommand) error {
if err := subCmd.Parse(cmd, args); err != nil {
return err
}
return subCmd.Do()
}
75 changes: 75 additions & 0 deletions cmd/subcmd/s3hub/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package s3hub

import (
"context"

"github.com/nao1215/rainbow/app/di"
"github.com/nao1215/rainbow/app/domain/model"
"github.com/nao1215/rainbow/utils/errfmt"
"github.com/spf13/cobra"
)

// s3hub have common fields and methods for s3hub commands.
type s3hub struct {
// S3App is the application service for S3.
*di.S3App
// command is the cobra command.
command *cobra.Command
// ctx is the context of s3hub command.
ctx context.Context
// profile is the AWS profile name.
profile model.AWSProfile
// region is the AWS region name.
region model.Region
}

// newS3hub returns a new s3hub.
func newS3hub() *s3hub {
return &s3hub{}
}

// parse parses command line arguments.
func (s *s3hub) parse(cmd *cobra.Command) error {
s.command = cmd
s.ctx = context.Background()

p, err := cmd.Flags().GetString("profile")
if err != nil {
return err
}
s.profile = model.NewAWSProfile(p)

r, err := cmd.Flags().GetString("region")
if err != nil {
return err
}
s.region = model.Region(r)

cfg, err := model.NewAWSConfig(s.ctx, s.profile, s.region)
if err != nil {
return errfmt.Wrap(err, "can not get aws config")
}
if s.region == "" {
if cfg.Config.Region == "" {
s.region = model.RegionUSEast1
} else {
s.region = model.Region(cfg.Config.Region)
}
}

s.S3App, err = di.NewS3App(s.ctx, s.profile, s.region)
if err != nil {
return errfmt.Wrap(err, "can not create s3 application service")
}
return nil
}

// printf prints a formatted string.
func (s *s3hub) printf(format string, a ...interface{}) {
s.command.Printf(format, a...)
}

// commandName returns the s3hub command name.
func commandName() string {
return "s3hub"
}
Loading

0 comments on commit 26b8111

Please sign in to comment.