From 02095710fdea1516060bc2e659a29f4003a44b79 Mon Sep 17 00:00:00 2001 From: bilalcaliskan Date: Sun, 16 Jul 2023 20:32:16 +0300 Subject: [PATCH 1/5] docs: add method/function level comments for couple of methods/functions --- internal/aws/aws.go | 99 +++++++++++++++++++++++++++++++++---- internal/cleaner/cleaner.go | 26 ++++++++++ 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/internal/aws/aws.go b/internal/aws/aws.go index 8477dfb..e59e2da 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -35,10 +35,12 @@ import ( "github.com/rs/zerolog" ) -// createSession initializes session with provided credentials +// createSession initializes a new AWS SDK session using provided credentials. // -// It returns a pointer to session.Session along with the error that encountered during -// session initialization process. +// It accepts access key, secret key and region as arguments, then uses them +// to create an AWS Config, which is then used to initialize the session. +// It returns a pointer to session.Session along with any error encountered during +// session initialization process. If no error occurred during the process, the error is nil. func createSession(accessKey, secretKey, region string) (*session.Session, error) { return session.NewSession(&aws.Config{ Region: aws.String(region), @@ -46,6 +48,12 @@ func createSession(accessKey, secretKey, region string) (*session.Session, error }) } +// CreateAwsService creates a new Amazon Web Service S3 instance. +// +// It accepts a pointer to RootOptions as argument and uses them to create a new session +// and subsequently an S3 service instance. It checks for the presence of the +// required fields, and if any of them are empty, it returns an error. +// It returns the newly created S3 service along with any encountered error during the process. func CreateAwsService(opts *options.RootOptions) (svc *s3.S3, err error) { if opts.AccessKey == "" || opts.SecretKey == "" || opts.Region == "" { return svc, errors.New("missing required fields") @@ -60,7 +68,10 @@ func CreateAwsService(opts *options.RootOptions) (svc *s3.S3, err error) { return s3.New(sess), err } -// GetAllFiles gets all of the files in the target bucket as the function name indicates +// GetAllFiles retrieves all of the files in the target S3 bucket. +// +// It accepts an S3API interface, pointer to RootOptions, and prefix string as arguments. +// It returns a ListObjectsOutput, which contains all objects in the bucket, and any error encountered. func GetAllFiles(svc s3iface.S3API, opts *options.RootOptions, prefix string) (res *s3.ListObjectsOutput, err error) { // fetch all the objects in target bucket return svc.ListObjects(&s3.ListObjectsInput{ @@ -68,12 +79,21 @@ func GetAllFiles(svc s3iface.S3API, opts *options.RootOptions, prefix string) (r }) } +// GetBucketTags retrieves all tags attached to a specific S3 bucket. +// +// It accepts an S3API interface and pointer of TagOptions as arguments, and returns +// a GetBucketTaggingOutput, which contains all the bucket's tags, and any error encountered. func GetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.GetBucketTaggingOutput, err error) { return svc.GetBucketTagging(&s3.GetBucketTaggingInput{ Bucket: aws.String(opts.BucketName), }) } +// SetBucketTags attaches a set of tags to a specific S3 bucket. +// +// It accepts an S3API interface and TagOptions as arguments. +// For each tag in the provided TagOptions, a new tag is created and added to a slice of tags. +// It then attaches these tags to the bucket and returns a PutBucketTaggingOutput and any error encountered. func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.PutBucketTaggingOutput, err error) { // fetch all the objects in target bucket var tagsSet []*s3.Tag @@ -97,18 +117,32 @@ func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.PutBuc return res, nil } +// DeleteAllBucketTags removes all tags attached to a specific S3 bucket. +// +// It accepts an S3API interface and TagOptions as arguments, and returns +// a DeleteBucketTaggingOutput and any error encountered. func DeleteAllBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.DeleteBucketTaggingOutput, err error) { return svc.DeleteBucketTagging(&s3.DeleteBucketTaggingInput{ Bucket: aws.String(opts.BucketName), }) } +// GetTransferAcceleration retrieves the current transfer acceleration status of an S3 bucket. +// +// It accepts an S3API interface and TransferAccelerationOptions as arguments, +// and returns a GetBucketAccelerateConfigurationOutput and any error encountered. func GetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerationOptions) (res *s3.GetBucketAccelerateConfigurationOutput, err error) { return svc.GetBucketAccelerateConfiguration(&s3.GetBucketAccelerateConfigurationInput{ Bucket: aws.String(opts.BucketName), }) } +// SetTransferAcceleration sets the transfer acceleration status of an S3 bucket. +// +// It accepts an S3API interface, TransferAccelerationOptions, a PromptRunner, and a Logger as arguments. +// If the provided 'DryRun' or 'AutoApprove' options are set, the function will return early. +// If not, it will set the bucket's transfer acceleration status based on the provided desired state. +// It logs any errors encountered and returns them. func SetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerationOptions, confirmRunner prompt.PromptRunner, logger zerolog.Logger) error { if opts.DryRun { logger.Info().Msg("skipping operation since '--dry-run' flag is passed") @@ -170,12 +204,20 @@ func SetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerat return nil } +// GetBucketPolicy retrieves the current policy of an S3 bucket. +// +// It accepts an S3API interface and BucketPolicyOptions as arguments, +// and returns a GetBucketPolicyOutput and any error encountered. func GetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res *s3.GetBucketPolicyOutput, err error) { return svc.GetBucketPolicy(&s3.GetBucketPolicyInput{ Bucket: aws.String(opts.BucketName), }) } +// GetBucketPolicyString retrieves the current policy of an S3 bucket and beautifies it into a readable format. +// +// It accepts an S3API interface and BucketPolicyOptions as arguments, +// and returns the beautified policy as a string and any error encountered. func GetBucketPolicyString(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (out string, err error) { res, err := GetBucketPolicy(svc, opts) if err != nil { @@ -185,6 +227,10 @@ func GetBucketPolicyString(svc s3iface.S3API, opts *options5.BucketPolicyOptions return internalutil.BeautifyJSON(*res.Policy) } +// SetBucketPolicy sets the policy of an S3 bucket. +// +// It accepts an S3API interface and BucketPolicyOptions as arguments, +// and returns a PutBucketPolicyOutput and any error encountered. func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res *s3.PutBucketPolicyOutput, err error) { return svc.PutBucketPolicy(&s3.PutBucketPolicyInput{ Bucket: aws.String(opts.BucketName), @@ -192,20 +238,39 @@ func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res }) } +// DeleteBucketPolicy removes the existing policy from a specified S3 bucket. +// +// It requires an S3API interface and a BucketPolicyOptions object, which should +// include the name of the target bucket. The function will use these to generate +// and execute a DeleteBucketPolicyInput request via the provided S3 service. +// It returns a DeleteBucketPolicyOutput, which acknowledges the operation, +// along with any error encountered during the process. func DeleteBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res *s3.DeleteBucketPolicyOutput, err error) { return svc.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ Bucket: aws.String(opts.BucketName), }) } -// GetBucketVersioning gets the target bucket +// GetBucketVersioning retrieves the versioning state of the specified S3 bucket. +// +// The function accepts an S3API interface and RootOptions, which should include +// the name of the target bucket. It uses these to generate and execute a +// GetBucketVersioningInput request via the provided S3 service. +// The function returns a GetBucketVersioningOutput, which includes the bucket's +// versioning configuration, along with any error encountered during the process. func GetBucketVersioning(svc s3iface.S3API, opts *options.RootOptions) (res *s3.GetBucketVersioningOutput, err error) { return svc.GetBucketVersioning(&s3.GetBucketVersioningInput{ Bucket: aws.String(opts.BucketName), }) } -// SetBucketVersioning sets the target bucket +// SetBucketVersioning updates the versioning configuration of a specific S3 bucket. +// +// It accepts an S3API interface, VersioningOptions, a PromptRunner for user confirmations, +// and a Logger for logging events. The function uses these to check for dry-run or auto-approve +// flags, confirm versioning state changes with the user if needed, and execute a +// PutBucketVersioningInput request to set the bucket's versioning state. +// The function logs the process, including any errors encountered, and returns these errors. func SetBucketVersioning(svc s3iface.S3API, versioningOpts *options4.VersioningOptions, confirmRunner prompt.PromptRunner, logger zerolog.Logger) (err error) { if versioningOpts.DryRun { logger.Info().Msg("skipping operation since '--dry-run' flag is passed") @@ -267,7 +332,13 @@ func SetBucketVersioning(svc s3iface.S3API, versioningOpts *options4.VersioningO return nil } -// DeleteFiles deletes the slice of []*s3.Object objects in the target bucket +// DeleteFiles removes a specific list of objects from a specified S3 bucket. +// +// The function accepts an S3API interface, the name of the target bucket, an array of +// S3 Objects to delete, a dryRun boolean flag, and a Logger. It iterates over the array of +// objects, logging each one, and unless dryRun is set, it sends a DeleteObjectInput request +// for each object to the S3 service. The function logs each successful deletion and returns +// any errors encountered during the process. func DeleteFiles(svc s3iface.S3API, bucketName string, slice []*s3.Object, dryRun bool, logger zerolog.Logger) error { for _, v := range slice { logger.Debug().Str("key", *v.Key).Time("lastModifiedDate", *v.LastModified). @@ -288,6 +359,11 @@ func DeleteFiles(svc s3iface.S3API, bucketName string, slice []*s3.Object, dryRu return nil } +// GetDesiredObjects retrieves a list of objects in a specified S3 bucket that match a given +// +// regular expression. The function takes an S3API interface, the target bucket's name, and +// the regex as arguments. It fetches all objects in the target bucket and filters them using +// the regex. The function returns a list of matching S3 Object pointers and any error encountered. func GetDesiredObjects(svc s3iface.S3API, bucketName, regex string) (objects []*s3.Object, err error) { // fetch all the objects in target bucket listResult, err := svc.ListObjects(&s3.ListObjectsInput{ @@ -307,10 +383,13 @@ func GetDesiredObjects(svc s3iface.S3API, bucketName, regex string) (objects []* return objects, err } -// SearchString does the heavy lifting, communicates with the S3 and finds the files +// SearchString scans all objects in a specified S3 bucket for a given text string. // -// It returns the string array that contains keys of matched files, along with the error array -// that contains errors during search process for each individual file. +// The function accepts an S3API interface and SearchOptions, which include the bucket +// name, file name pattern, and search text. It first retrieves a list of objects that match +// the file name pattern, then concurrently checks each object's content for the search text. +// The function returns a list of object keys that contain the search text and a list of errors +// encountered during the search process. func SearchString(svc s3iface.S3API, opts *options2.SearchOptions) (matchedFiles []string, errs []error) { var wg sync.WaitGroup mu := &sync.Mutex{} diff --git a/internal/cleaner/cleaner.go b/internal/cleaner/cleaner.go index e4b0d39..465ef5c 100644 --- a/internal/cleaner/cleaner.go +++ b/internal/cleaner/cleaner.go @@ -16,6 +16,32 @@ import ( "github.com/rs/zerolog" ) +// StartCleaning is a function that performs a deletion operation on AWS S3 files based on specified parameters. +// +// The function requires an S3 service, a prompt runner, clean options, and a logger as parameters. +// The function first retrieves the list of desired objects (files) from the specified AWS S3 bucket that match the +// provided regular expression. If an error occurs during retrieval, it immediately returns the error. +// The retrieved objects are sorted according to the configuration specified in the CleanOptions. +// +// The function then calculates the border index in the sorted array from which deletion should start, which is +// determined by subtracting the number of files to keep from the total number of retrieved objects. If the border +// index is less than or equal to 0, it means there aren't enough files to delete; it logs a warning message and +// the function returns without deleting any files. +// +// Next, it prepares a list of target objects (files) to delete based on the border index calculated previously. +// The file names (keys) of these target objects are extracted and logged for information. +// +// If the --dryRun flag is set to true in the CleanOptions, the function skips the actual deletion process, +// logs a message, and returns. This is a way to simulate a cleaning operation without making actual deletions. +// +// If the --autoApprove flag is set to false, the function prompts the user for approval before proceeding +// with deletion. If the user input is 'n' (denoting No), the function returns an error indicating that the +// user terminated the process. If the user input is neither 'y' nor 'n', it returns an error indicating invalid user input. +// +// Finally, the function performs the deletion of the target files from the S3 bucket. If an error occurs during +// deletion, it logs the error and returns it. +// +// The function returns nil if it completes without encountering any errors. func StartCleaning(svc s3iface.S3API, runner prompt.PromptRunner, cleanOpts *start.CleanOptions, logger zerolog.Logger) error { res, err := aws.GetDesiredObjects(svc, cleanOpts.BucketName, cleanOpts.Regex) if err != nil { From a90b4eb74a7ce31a2fba70036e37e424f0cf7726 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:23:13 +0000 Subject: [PATCH 2/5] build(deps): bump github.com/aws/aws-sdk-go from 1.44.298 to 1.44.300 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.298 to 1.44.300. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.298...v1.44.300) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cd59fd5..d641681 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/bilalcaliskan/s3-manager go 1.20 require ( - github.com/aws/aws-sdk-go v1.44.298 + github.com/aws/aws-sdk-go v1.44.300 github.com/dimiro1/banner v1.1.0 github.com/manifoldco/promptui v0.9.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 248e534..7b5a18c 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= -github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.300 h1:Zn+3lqgYahIf9yfrwZ+g+hq/c3KzUBaQ8wqY/ZXiAbY= +github.com/aws/aws-sdk-go v1.44.300/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= From 1983da3d3cf20e00caa4ba69f4df945e0b59fd57 Mon Sep 17 00:00:00 2001 From: bilalcaliskan Date: Tue, 18 Jul 2023 00:08:43 +0300 Subject: [PATCH 3/5] refactoring: continue --- cmd/bucketpolicy/add/add.go | 20 +- cmd/bucketpolicy/add/add_test.go | 43 +- cmd/bucketpolicy/remove/remove.go | 20 +- cmd/tags/add/add.go | 20 +- cmd/tags/remove/remove.go | 22 +- internal/aws/aws.go | 66 ++- internal/aws/aws_test.go | 408 +++++++++++++++++- internal/cleaner/cleaner_test.go | 90 +--- internal/cleaner/utils.go | 12 + .../constants/{constants.go => errors.go} | 0 internal/prompt/types.go | 14 + 11 files changed, 504 insertions(+), 211 deletions(-) rename internal/constants/{constants.go => errors.go} (100%) diff --git a/cmd/bucketpolicy/add/add.go b/cmd/bucketpolicy/add/add.go index 3f14229..8508d8a 100644 --- a/cmd/bucketpolicy/add/add.go +++ b/cmd/bucketpolicy/add/add.go @@ -4,9 +4,6 @@ import ( "fmt" "io" "os" - "strings" - - "github.com/bilalcaliskan/s3-manager/internal/constants" rootopts "github.com/bilalcaliskan/s3-manager/cmd/root/options" @@ -72,24 +69,9 @@ s3-manager bucketpolicy add my_custom_policy.json logger.Info().Msg("will attempt to add below bucket policy") fmt.Println(bucketPolicyOpts.BucketPolicyContent) - if bucketPolicyOpts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") - return nil - } - - if !bucketPolicyOpts.AutoApprove { - var res string - if res, err = confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput - } - } logger.Info().Msg("trying to add bucket policy") - _, err = aws.SetBucketPolicy(svc, bucketPolicyOpts) + _, err = aws.SetBucketPolicy(svc, bucketPolicyOpts, confirmRunner, logger) if err != nil { logger.Error(). Str("error", err.Error()). diff --git a/cmd/bucketpolicy/add/add_test.go b/cmd/bucketpolicy/add/add_test.go index da89b98..c36a7ee 100644 --- a/cmd/bucketpolicy/add/add_test.go +++ b/cmd/bucketpolicy/add/add_test.go @@ -9,11 +9,9 @@ import ( "github.com/bilalcaliskan/s3-manager/internal/prompt" - internalaws "github.com/bilalcaliskan/s3-manager/internal/aws" - "github.com/bilalcaliskan/s3-manager/internal/constants" - "github.com/aws/aws-sdk-go/service/s3" "github.com/bilalcaliskan/s3-manager/cmd/root/options" + internalaws "github.com/bilalcaliskan/s3-manager/internal/aws" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -54,19 +52,6 @@ func TestExecuteAddCmd(t *testing.T) { false, false, }, - { - "Success with dry-run", - []string{"../../../testdata/bucketpolicy.json"}, - true, - nil, - &s3.PutBucketPolicyOutput{}, - prompt.PromptMock{ - Msg: "y", - Err: nil, - }, - true, - false, - }, { "Failure", []string{"../../../testdata/bucketpolicy.json"}, @@ -80,32 +65,6 @@ func TestExecuteAddCmd(t *testing.T) { false, false, }, - { - "Failure caused by user terminated process", - []string{"../../../testdata/bucketpolicy.json"}, - false, - nil, - &s3.PutBucketPolicyOutput{}, - prompt.PromptMock{ - Msg: "n", - Err: constants.ErrInjected, - }, - false, - false, - }, - { - "Failure caused by prompt error", - []string{"../../../testdata/bucketpolicy.json"}, - false, - nil, - &s3.PutBucketPolicyOutput{}, - prompt.PromptMock{ - Msg: "nasdasd", - Err: constants.ErrInjected, - }, - false, - false, - }, { "Failure caused by target file not found", []string{"../../../testdata/bucketpolicy.jsonnnn"}, diff --git a/cmd/bucketpolicy/remove/remove.go b/cmd/bucketpolicy/remove/remove.go index 27a56b2..bfff22a 100644 --- a/cmd/bucketpolicy/remove/remove.go +++ b/cmd/bucketpolicy/remove/remove.go @@ -2,7 +2,6 @@ package remove import ( "fmt" - "strings" rootopts "github.com/bilalcaliskan/s3-manager/cmd/root/options" @@ -10,8 +9,6 @@ import ( "github.com/bilalcaliskan/s3-manager/cmd/bucketpolicy/options" "github.com/bilalcaliskan/s3-manager/internal/utils" - "github.com/bilalcaliskan/s3-manager/internal/constants" - "github.com/bilalcaliskan/s3-manager/internal/aws" "github.com/bilalcaliskan/s3-manager/internal/prompt" "github.com/rs/zerolog" @@ -59,24 +56,9 @@ s3-manager bucketpolicy remove logger.Info().Msg("will attempt to delete below bucket policy") fmt.Println(bucketPolicyOpts.BucketPolicyContent) - if bucketPolicyOpts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") - return nil - } - - if !bucketPolicyOpts.AutoApprove { - var res string - if res, err = confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput - } - } logger.Info().Msg("trying to remove current bucket policy if exists") - _, err = aws.DeleteBucketPolicy(svc, bucketPolicyOpts) + _, err = aws.DeleteBucketPolicy(svc, bucketPolicyOpts, confirmRunner, logger) if err != nil { logger.Error(). Str("error", err.Error()). diff --git a/cmd/tags/add/add.go b/cmd/tags/add/add.go index ff383cd..24e9f25 100644 --- a/cmd/tags/add/add.go +++ b/cmd/tags/add/add.go @@ -5,8 +5,6 @@ import ( "fmt" "strings" - "github.com/bilalcaliskan/s3-manager/internal/constants" - rootopts "github.com/bilalcaliskan/s3-manager/cmd/root/options" "github.com/bilalcaliskan/s3-manager/internal/utils" @@ -84,23 +82,7 @@ s3-manager tags add foo1=bar1,foo2=bar2 fmt.Printf("%s=%s\n", i, v) } - if tagOpts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") - return nil - } - - if !tagOpts.AutoApprove { - var res string - if res, err = confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput - } - } - - if _, err := aws.SetBucketTags(svc, tagOpts); err != nil { + if err := aws.SetBucketTags(svc, tagOpts, confirmRunner, logger); err != nil { logger.Error(). Str("error", err.Error()). Msg("an error occurred while setting tags") diff --git a/cmd/tags/remove/remove.go b/cmd/tags/remove/remove.go index 9cd44b6..28e09f8 100644 --- a/cmd/tags/remove/remove.go +++ b/cmd/tags/remove/remove.go @@ -5,8 +5,6 @@ import ( "fmt" "strings" - "github.com/bilalcaliskan/s3-manager/internal/constants" - rootopts "github.com/bilalcaliskan/s3-manager/cmd/root/options" "github.com/bilalcaliskan/s3-manager/internal/prompt" @@ -99,25 +97,9 @@ s3-manager tags remove foo1=bar1,foo2=bar2 fmt.Printf(outputStr, i, v) } - if tagOpts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") - return nil - } - - if !tagOpts.AutoApprove { - var res string - if res, err = confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput - } - } - utils.RemoveMapElements(tagOpts.ActualTags, tagOpts.TagsToRemove) - if _, err := aws.DeleteAllBucketTags(svc, tagOpts); err != nil { + if _, err := aws.DeleteAllBucketTags(svc, tagOpts, confirmRunner, logger); err != nil { logger.Error(). Str("error", err.Error()). Msg("an error occurred while deleting all the tags") @@ -125,7 +107,7 @@ s3-manager tags remove foo1=bar1,foo2=bar2 } tagOpts.TagsToAdd = tagOpts.ActualTags - if _, err := aws.SetBucketTags(svc, tagOpts); err != nil { + if err := aws.SetBucketTags(svc, tagOpts, confirmRunner, logger); err != nil { logger.Error(). Str("error", err.Error()). Msg("an error occurred while setting desired tags") diff --git a/internal/aws/aws.go b/internal/aws/aws.go index e59e2da..77e9b43 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -94,7 +94,22 @@ func GetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.GetBuc // It accepts an S3API interface and TagOptions as arguments. // For each tag in the provided TagOptions, a new tag is created and added to a slice of tags. // It then attaches these tags to the bucket and returns a PutBucketTaggingOutput and any error encountered. -func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.PutBucketTaggingOutput, err error) { +func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions, confirmRunner prompt.PromptRunner, logger zerolog.Logger) error { + if opts.DryRun { + logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + return nil + } + + if !opts.AutoApprove { + if res, err := confirmRunner.Run(); err != nil { + if strings.ToLower(res) == "n" { + return constants.ErrUserTerminated + } + + return constants.ErrInvalidInput + } + } + // fetch all the objects in target bucket var tagsSet []*s3.Tag for i, v := range opts.TagsToAdd { @@ -105,23 +120,38 @@ func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.PutBuc tagsSet = append(tagsSet, tag) } - res, err = svc.PutBucketTagging(&s3.PutBucketTaggingInput{ + _, err := svc.PutBucketTagging(&s3.PutBucketTaggingInput{ Bucket: aws.String(opts.BucketName), Tagging: &s3.Tagging{TagSet: tagsSet}, }) if err != nil { - return res, err + return err } - return res, nil + return nil } // DeleteAllBucketTags removes all tags attached to a specific S3 bucket. // // It accepts an S3API interface and TagOptions as arguments, and returns // a DeleteBucketTaggingOutput and any error encountered. -func DeleteAllBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.DeleteBucketTaggingOutput, err error) { +func DeleteAllBucketTags(svc s3iface.S3API, opts *options3.TagOptions, runner prompt.PromptRunner, logger zerolog.Logger) (out *s3.DeleteBucketTaggingOutput, err error) { + if opts.DryRun { + logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + return out, nil + } + + if !opts.AutoApprove { + if res, err := runner.Run(); err != nil { + if strings.ToLower(res) == "n" { + return out, constants.ErrUserTerminated + } + + return out, constants.ErrInvalidInput + } + } + return svc.DeleteBucketTagging(&s3.DeleteBucketTaggingInput{ Bucket: aws.String(opts.BucketName), }) @@ -231,7 +261,18 @@ func GetBucketPolicyString(svc s3iface.S3API, opts *options5.BucketPolicyOptions // // It accepts an S3API interface and BucketPolicyOptions as arguments, // and returns a PutBucketPolicyOutput and any error encountered. -func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res *s3.PutBucketPolicyOutput, err error) { +func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions, runner prompt.PromptRunner, logger zerolog.Logger) (res *s3.PutBucketPolicyOutput, err error) { + if opts.DryRun { + logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + return res, nil + } + + if !opts.AutoApprove { + if err := prompt.AskForApproval(runner); err != nil { + return res, err + } + } + return svc.PutBucketPolicy(&s3.PutBucketPolicyInput{ Bucket: aws.String(opts.BucketName), Policy: aws.String(opts.BucketPolicyContent), @@ -245,7 +286,18 @@ func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res // and execute a DeleteBucketPolicyInput request via the provided S3 service. // It returns a DeleteBucketPolicyOutput, which acknowledges the operation, // along with any error encountered during the process. -func DeleteBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions) (res *s3.DeleteBucketPolicyOutput, err error) { +func DeleteBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions, runner prompt.PromptRunner, logger zerolog.Logger) (res *s3.DeleteBucketPolicyOutput, err error) { + if opts.DryRun { + logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + return res, nil + } + + if !opts.AutoApprove { + if err := prompt.AskForApproval(runner); err != nil { + return res, err + } + } + return svc.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ Bucket: aws.String(opts.BucketName), }) diff --git a/internal/aws/aws_test.go b/internal/aws/aws_test.go index 75f24f1..eafba98 100644 --- a/internal/aws/aws_test.go +++ b/internal/aws/aws_test.go @@ -53,6 +53,16 @@ var dummyBucketPolicyStr = ` } ` +// TestGetAllFiles is a test function that tests the behavior of the GetAllFiles function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and defines a list of objects to be returned. +// It expects GetAllFiles to return a nil error. +// For the failure case, it injects an error in the ListObjects operation of the mocked S3 client. +// It expects GetAllFiles to return a specific error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestGetAllFiles(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -104,6 +114,16 @@ func TestGetAllFiles(t *testing.T) { } } +// TestDeleteFiles is a test function that tests the behavior of the DeleteFiles function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and defines a list of objects to be deleted. +// It expects DeleteFiles to return a nil error. +// For the failure case, it injects an error in the DeleteObject operation of the mocked S3 client. +// It expects DeleteFiles to return a specific error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestDeleteFiles(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -183,6 +203,16 @@ func TestDeleteFiles(t *testing.T) { } } +// TestCreateAwsService is a test function that tests the behavior of the CreateAwsService function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a RootOptions object with all the required fields. +// It expects CreateAwsService to return a non-nil AWS service client and a nil error. +// For the failure case, it sets up a RootOptions object with a missing required field. +// It expects CreateAwsService to return a nil AWS service client and a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestCreateAwsService(t *testing.T) { cases := []struct { caseName string @@ -224,6 +254,16 @@ func TestCreateAwsService(t *testing.T) { } } +// TestSearchString is a test function that tests the behavior of the SearchString function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success cases, it sets up SearchOptions objects with specific search criteria and a mocked S3 client. +// It expects SearchString to return a non-nil result and a nil error, and it asserts the match count. +// For the failure cases, it either injects an error in the ListObjects operation or the GetObject operation of the mocked S3 client. +// It expects SearchString to return a nil result and a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestSearchString(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -364,6 +404,17 @@ func TestSearchString(t *testing.T) { } } +// TestSetBucketVersioning is a test function that tests the behavior of the SetBucketVersioning function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases cover various scenarios related to enabling or disabling bucket versioning. +// Each test case includes VersioningOptions, GetBucketVersioningOutput, and error parameters. +// The function tests different combinations of inputs, including success cases and failure cases. +// It sets up a mocked S3 client and mocks the GetBucketVersioning and PutBucketVersioning operations. +// The function asserts the expected error or nil value based on the scenario being tested. +// It also verifies that the function behaves correctly when dry-run or auto-approve options are enabled. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestSetBucketVersioning(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -567,6 +618,16 @@ func TestSetBucketVersioning(t *testing.T) { } } +// TestGetBucketVersioning is a test function that tests the behavior of the GetBucketVersioning function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and defines a GetBucketVersioningOutput object with a specific status. +// It expects GetBucketVersioning to return a non-nil output and a nil error. +// For the failure case, it injects an error in the GetBucketVersioning operation of the mocked S3 client. +// It expects GetBucketVersioning to return a nil output and a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestGetBucketVersioning(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -602,6 +663,16 @@ func TestGetBucketVersioning(t *testing.T) { } } +// TestGetBucketTags is a test function that tests the behavior of the GetBucketTags function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and defines a GetBucketTaggingOutput object with specific tags. +// It expects GetBucketTags to return the expected tags and a nil error. +// For the failure case, it injects an error in the GetBucketTagging operation of the mocked S3 client. +// It expects GetBucketTags to return a nil result and a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestGetBucketTags(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -653,14 +724,28 @@ func TestGetBucketTags(t *testing.T) { } } +// TestSetBucketTags is a test function that tests the behavior of the SetBucketTags function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and defines a list of tags to be added to the bucket. +// It expects SetBucketTags to return a nil error. +// For the failure case, it injects an error in the PutBucketTagging operation of the mocked S3 client. +// It expects SetBucketTags to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestSetBucketTags(t *testing.T) { rootOpts := options.GetMockedRootOptions() + logger := logging.GetLogger(rootOpts) cases := []struct { caseName string expected error *options4.TagOptions tags []*s3.Tag putBucketTaggingErr error + enableDryRun bool + enableAutoApprove bool + prompt.PromptRunner }{ { "Success", @@ -681,6 +766,35 @@ func TestSetBucketTags(t *testing.T) { }, }, nil, + false, + false, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + }, + { + "Success with dry run enabled", + nil, + &options4.TagOptions{ + RootOptions: rootOpts, + TagsToAdd: make(map[string]string), + TagsToRemove: make(map[string]string), + }, + []*s3.Tag{ + { + Key: aws.String("foo"), + Value: aws.String("bar"), + }, + { + Key: aws.String("foo2"), + Value: aws.String("bar2"), + }, + }, + nil, + true, + false, + nil, }, { "Failure", @@ -701,12 +815,73 @@ func TestSetBucketTags(t *testing.T) { }, }, constants.ErrInjected, + false, + false, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + }, + { + "Failure caused by user terminated the process", + constants.ErrUserTerminated, + &options4.TagOptions{ + RootOptions: rootOpts, + TagsToAdd: make(map[string]string), + TagsToRemove: make(map[string]string), + }, + []*s3.Tag{ + { + Key: aws.String("foo"), + Value: aws.String("bar"), + }, + { + Key: aws.String("foo2"), + Value: aws.String("bar2"), + }, + }, + constants.ErrInjected, + false, + false, + &prompt.PromptMock{ + Msg: "n", + Err: constants.ErrUserTerminated, + }, + }, + { + "Failure caused by invalid input", + constants.ErrInvalidInput, + &options4.TagOptions{ + RootOptions: rootOpts, + TagsToAdd: make(map[string]string), + TagsToRemove: make(map[string]string), + }, + []*s3.Tag{ + { + Key: aws.String("foo"), + Value: aws.String("bar"), + }, + { + Key: aws.String("foo2"), + Value: aws.String("bar2"), + }, + }, + constants.ErrInjected, + false, + false, + &prompt.PromptMock{ + Msg: "asdfasdfy", + Err: constants.ErrInvalidInput, + }, }, } for _, tc := range cases { t.Logf("starting case %s", tc.caseName) + tc.DryRun = tc.enableDryRun + tc.AutoApprove = tc.enableAutoApprove + mockS3 := new(MockS3Client) mockS3.On("PutBucketTagging", mock.AnythingOfType("*s3.PutBucketTaggingInput")).Return(&s3.PutBucketTaggingOutput{}, tc.putBucketTaggingErr) @@ -714,18 +889,31 @@ func TestSetBucketTags(t *testing.T) { tc.TagOptions.TagsToAdd[*v.Key] = *v.Value } - _, err := SetBucketTags(mockS3, tc.TagOptions) - assert.Equal(t, tc.expected, err) + assert.Equal(t, tc.expected, SetBucketTags(mockS3, tc.TagOptions, tc.PromptRunner, logger)) } } +// TestDeleteAllBucketTags is a test function that tests the behavior of the DeleteAllBucketTags function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and expects the DeleteBucketTagging operation to succeed. +// It expects DeleteAllBucketTags to return a nil error. +// For the failure case, it injects an error in the DeleteBucketTagging operation of the mocked S3 client. +// It expects DeleteAllBucketTags to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestDeleteAllBucketTags(t *testing.T) { rootOpts := options.GetMockedRootOptions() + logger := logging.GetLogger(rootOpts) cases := []struct { caseName string expected error *options4.TagOptions deleteBucketTaggingErr error + enableDryRun bool + enableAutoApprove bool + prompt.PromptRunner }{ { "Success", @@ -734,6 +922,23 @@ func TestDeleteAllBucketTags(t *testing.T) { RootOptions: rootOpts, }, nil, + false, + false, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + }, + { + "Success with dry-run enabled", + nil, + &options4.TagOptions{ + RootOptions: rootOpts, + }, + nil, + true, + false, + nil, }, { "Failure", @@ -742,20 +947,67 @@ func TestDeleteAllBucketTags(t *testing.T) { RootOptions: rootOpts, }, constants.ErrInjected, + false, + false, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + }, + { + "Failure caused by user terminated process", + constants.ErrUserTerminated, + &options4.TagOptions{ + RootOptions: rootOpts, + }, + nil, + false, + false, + &prompt.PromptMock{ + Msg: "n", + Err: constants.ErrUserTerminated, + }, + }, + { + "Failure caused by invalid input", + constants.ErrInvalidInput, + &options4.TagOptions{ + RootOptions: rootOpts, + }, + nil, + false, + false, + &prompt.PromptMock{ + Msg: "nasfassadads", + Err: constants.ErrInvalidInput, + }, }, } for _, tc := range cases { t.Logf("starting case %s", tc.caseName) + tc.DryRun = tc.enableDryRun + tc.AutoApprove = tc.enableAutoApprove + mockS3 := new(MockS3Client) mockS3.On("DeleteBucketTagging", mock.AnythingOfType("*s3.DeleteBucketTaggingInput")).Return(&s3.DeleteBucketTaggingOutput{}, tc.deleteBucketTaggingErr) - _, err := DeleteAllBucketTags(mockS3, tc.TagOptions) + _, err := DeleteAllBucketTags(mockS3, tc.TagOptions, tc.PromptRunner, logger) assert.Equal(t, tc.expected, err) } } +// TestGetBucketPolicy is a test function that tests the behavior of the GetBucketPolicy function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and expects the GetBucketPolicy operation to succeed. +// It expects GetBucketPolicy to return a nil error. +// For the failure case, it injects an error in the GetBucketPolicy operation of the mocked S3 client. +// It expects GetBucketPolicy to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestGetBucketPolicy(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -793,45 +1045,111 @@ func TestGetBucketPolicy(t *testing.T) { } } +// TestSetBucketPolicy is a test function that tests the behavior of the SetBucketPolicy function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and expects the PutBucketPolicy operation to succeed. +// It expects SetBucketPolicy to return a nil error. +// For the failure case, it injects an error in the PutBucketPolicy operation of the mocked S3 client. +// It expects SetBucketPolicy to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestSetBucketPolicy(t *testing.T) { rootOpts := options.GetMockedRootOptions() + logger := logging.GetLogger(rootOpts) cases := []struct { caseName string expected error + prompt.PromptRunner *options6.BucketPolicyOptions putBucketPolicyErr error + autoApprove bool + dryRun bool }{ { "Success", nil, + prompt.PromptMock{ + Msg: "y", + Err: nil, + }, &options6.BucketPolicyOptions{ RootOptions: rootOpts, BucketPolicyContent: dummyBucketPolicyStr, }, nil, + false, + false, + }, + { + "Success with dry-run enabled", + nil, + nil, + &options6.BucketPolicyOptions{ + RootOptions: rootOpts, + BucketPolicyContent: dummyBucketPolicyStr, + }, + nil, + false, + true, }, { "Failure", constants.ErrInjected, + prompt.PromptMock{ + Msg: "y", + Err: nil, + }, &options6.BucketPolicyOptions{ RootOptions: rootOpts, BucketPolicyContent: dummyBucketPolicyStr, }, constants.ErrInjected, + false, + false, + }, + { + "Failure caused by prompt error", + constants.ErrUserTerminated, + prompt.PromptMock{ + Msg: "n", + Err: constants.ErrUserTerminated, + }, + &options6.BucketPolicyOptions{ + RootOptions: rootOpts, + BucketPolicyContent: dummyBucketPolicyStr, + }, + nil, + false, + false, }, } for _, tc := range cases { t.Logf("starting case %s", tc.caseName) + tc.DryRun = tc.dryRun + tc.AutoApprove = tc.autoApprove + mockS3 := new(MockS3Client) mockS3.On("PutBucketPolicy", mock.AnythingOfType("*s3.PutBucketPolicyInput")).Return(&s3.PutBucketPolicyOutput{}, tc.putBucketPolicyErr) - _, err := SetBucketPolicy(mockS3, tc.BucketPolicyOptions) + _, err := SetBucketPolicy(mockS3, tc.BucketPolicyOptions, tc.PromptRunner, logger) assert.Equal(t, tc.expected, err) } } +// TestGetBucketPolicyString is a test function that tests the behavior of the GetBucketPolicyString function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and expects the GetBucketPolicy operation to succeed. +// It expects GetBucketPolicyString to return the expected bucket policy string and a nil error. +// For the failure case, it injects an error in the GetBucketPolicy operation of the mocked S3 client. +// It expects GetBucketPolicyString to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestGetBucketPolicyString(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -880,13 +1198,27 @@ func TestGetBucketPolicyString(t *testing.T) { } } +// TestDeleteBucketPolicy is a test function that tests the behavior of the DeleteBucketPolicy function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and expects the DeleteBucketPolicy operation to succeed. +// It expects DeleteBucketPolicy to return a nil error. +// For the failure case, it injects an error in the DeleteBucketPolicy operation of the mocked S3 client. +// It expects DeleteBucketPolicy to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestDeleteBucketPolicy(t *testing.T) { rootOpts := options.GetMockedRootOptions() + logger := logging.GetLogger(rootOpts) cases := []struct { caseName string expected error *options6.BucketPolicyOptions deleteBucketPolicyErr error + prompt.PromptRunner + enableAutoApprove bool + enableDryRun bool }{ { "Success", @@ -896,6 +1228,27 @@ func TestDeleteBucketPolicy(t *testing.T) { BucketPolicyContent: dummyBucketPolicyStr, }, nil, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + false, + false, + }, + { + "Success with dry-run enabled", + nil, + &options6.BucketPolicyOptions{ + RootOptions: rootOpts, + BucketPolicyContent: dummyBucketPolicyStr, + }, + nil, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + false, + true, }, { "Failure", @@ -905,20 +1258,54 @@ func TestDeleteBucketPolicy(t *testing.T) { BucketPolicyContent: dummyBucketPolicyStr, }, constants.ErrInjected, + &prompt.PromptMock{ + Msg: "y", + Err: nil, + }, + false, + false, + }, + { + "Failure caused by user terminated process", + constants.ErrUserTerminated, + &options6.BucketPolicyOptions{ + RootOptions: rootOpts, + BucketPolicyContent: dummyBucketPolicyStr, + }, + constants.ErrInjected, + &prompt.PromptMock{ + Msg: "n", + Err: constants.ErrUserTerminated, + }, + false, + false, }, } for _, tc := range cases { t.Logf("starting case %s", tc.caseName) + tc.DryRun = tc.enableDryRun + tc.AutoApprove = tc.enableAutoApprove + mockS3 := new(MockS3Client) mockS3.On("DeleteBucketPolicy", mock.AnythingOfType("*s3.DeleteBucketPolicyInput")).Return(&s3.DeleteBucketPolicyOutput{}, tc.deleteBucketPolicyErr) - _, err := DeleteBucketPolicy(mockS3, tc.BucketPolicyOptions) + _, err := DeleteBucketPolicy(mockS3, tc.BucketPolicyOptions, tc.PromptRunner, logger) assert.Equal(t, tc.expected, err) } } +// TestGetTransferAcceleration is a test function that tests the behavior of the GetTransferAcceleration function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success case, it sets up a mocked S3 client and expects the GetBucketAccelerateConfiguration operation to succeed. +// It expects GetTransferAcceleration to return a nil error. +// For the failure case, it injects an error in the GetBucketAccelerateConfiguration operation of the mocked S3 client. +// It expects GetTransferAcceleration to return a non-nil error. +// +// The test function iterates through all the test cases and performs the necessary assertions. func TestGetTransferAcceleration(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { @@ -956,6 +1343,17 @@ func TestGetTransferAcceleration(t *testing.T) { } } +// TestSetTransferAcceleration is a test function that tests the behavior of the SetTransferAcceleration function. +// +// It creates test cases with different scenarios and verifies the expected results. +// The test cases include both success and failure cases. +// For the success cases, it sets up a mocked S3 client and expects the GetBucketAccelerateConfiguration and PutBucketAccelerateConfiguration operations to succeed. +// It verifies that the function returns a nil error when the expected state is achieved. +// For the failure cases, it injects errors in the GetBucketAccelerateConfiguration and PutBucketAccelerateConfiguration operations of the mocked S3 client. +// It expects the function to return a non-nil error. +// The test function also includes cases where dry-run mode is enabled and prompts are involved. +// +// The function iterates through all the test cases and performs the necessary assertions. func TestSetTransferAcceleration(t *testing.T) { rootOpts := options.GetMockedRootOptions() cases := []struct { diff --git a/internal/cleaner/cleaner_test.go b/internal/cleaner/cleaner_test.go index 442929b..07feb71 100644 --- a/internal/cleaner/cleaner_test.go +++ b/internal/cleaner/cleaner_test.go @@ -3,16 +3,17 @@ package cleaner import ( - "os" "testing" "time" + internalaws "github.com/bilalcaliskan/s3-manager/internal/aws" + "github.com/stretchr/testify/mock" + "github.com/bilalcaliskan/s3-manager/internal/constants" "github.com/bilalcaliskan/s3-manager/internal/prompt" rootoptions "github.com/bilalcaliskan/s3-manager/cmd/root/options" - "github.com/aws/aws-sdk-go/service/s3/s3iface" "github.com/bilalcaliskan/s3-manager/internal/logging" "github.com/aws/aws-sdk-go/aws" @@ -21,76 +22,9 @@ import ( "github.com/stretchr/testify/assert" ) -var ( - defaultListObjectsErr error - defaultGetObjectErr error - defaultDeleteObjectErr error - defaultListObjectsOutput = &s3.ListObjectsOutput{ - Name: aws.String(""), - Marker: aws.String(""), - MaxKeys: aws.Int64(1000), - Prefix: aws.String(""), - IsTruncated: aws.Bool(false), - Contents: []*s3.Object{ - { - ETag: aws.String("03c0fe42b7efa3470fc99037a8e5449d"), - Key: aws.String("file4.txt"), - StorageClass: aws.String("STANDARD"), - Size: aws.Int64(1000), - LastModified: aws.Time(time.Now()), - }, - { - ETag: aws.String("03c0fe42b7efa3470fc99037a8e54122"), - Key: aws.String("file5.txt"), - StorageClass: aws.String("STANDARD"), - Size: aws.Int64(2000), - LastModified: aws.Time(time.Now()), - }, - { - ETag: aws.String("03c0fe42b7efa3470fc99037a8e5443d"), - Key: aws.String("file6.txt"), - StorageClass: aws.String("STANDARD"), - Size: aws.Int64(3000), - LastModified: aws.Time(time.Now()), - }, - }, - } - defaultDeleteObjectOutput = &s3.DeleteObjectOutput{ - DeleteMarker: nil, - RequestCharged: nil, - VersionId: nil, - } -) - -type mockS3Client struct { - s3iface.S3API -} - -// ListObjects mocks the S3API ListObjects method -func (m *mockS3Client) ListObjects(obj *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) { - return defaultListObjectsOutput, defaultListObjectsErr -} - -// GetObject mocks the S3API GetObject method -func (m *mockS3Client) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) { - bytes, err := os.Open(*input.Key) - if err != nil { - return nil, err - } - - return &s3.GetObjectOutput{ - AcceptRanges: aws.String("bytes"), - Body: bytes, - ContentLength: aws.Int64(1000), - ContentType: aws.String("text/plain"), - ETag: aws.String("d73a503d212d9279e6b2ed8ac6bb81f3"), - }, defaultGetObjectErr -} - -func (m *mockS3Client) DeleteObject(input *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) { - return defaultDeleteObjectOutput, defaultDeleteObjectErr -} - +// TestStartCleaning is a unit test function that tests the StartCleaning function. +// +// It tests various scenarios and expected outcomes of the StartCleaning function. func TestStartCleaning(t *testing.T) { cases := []struct { caseName string @@ -865,15 +799,11 @@ func TestStartCleaning(t *testing.T) { tc.DryRun = tc.dryRun tc.AutoApprove = tc.autoApprove - defaultListObjectsOutput = tc.ListObjectsOutput - defaultListObjectsErr = tc.listObjectsErr - - defaultDeleteObjectErr = tc.deleteObjectErr - - mockSvc := &mockS3Client{} - assert.NotNil(t, mockSvc) + mockS3 := new(internalaws.MockS3Client) + mockS3.On("ListObjects", mock.AnythingOfType("*s3.ListObjectsInput")).Return(tc.ListObjectsOutput, tc.listObjectsErr) + mockS3.On("DeleteObject", mock.AnythingOfType("*s3.DeleteObjectInput")).Return(&s3.DeleteObjectOutput{}, tc.deleteObjectErr) - err := StartCleaning(mockSvc, tc.PromptRunner, tc.CleanOptions, logging.GetLogger(tc.CleanOptions.RootOptions)) + err := StartCleaning(mockS3, tc.PromptRunner, tc.CleanOptions, logging.GetLogger(tc.CleanOptions.RootOptions)) assert.Equal(t, tc.expected, err) } } diff --git a/internal/cleaner/utils.go b/internal/cleaner/utils.go index 66b991b..5d017c3 100644 --- a/internal/cleaner/utils.go +++ b/internal/cleaner/utils.go @@ -7,6 +7,18 @@ import ( "github.com/bilalcaliskan/s3-manager/cmd/clean/options" ) +// sortObjects sorts a slice of *s3.Object based on the specified sorting criteria in the CleanOptions. +// +// The sorting criteria can be "lastModificationDate" or "size". +// For "lastModificationDate", the sorting order can be "ascending" or "descending". +// For "size", the sorting order can be "ascending" or "descending". +// +// Parameters: +// - slice: The slice of *s3.Object to be sorted. +// - opts: The CleanOptions struct containing the sorting criteria and order. +// +// Returns: +// - None func sortObjects(slice []*s3.Object, opts *options.CleanOptions) { switch opts.SortBy { case "lastModificationDate": diff --git a/internal/constants/constants.go b/internal/constants/errors.go similarity index 100% rename from internal/constants/constants.go rename to internal/constants/errors.go diff --git a/internal/prompt/types.go b/internal/prompt/types.go index 7626666..6bccc93 100644 --- a/internal/prompt/types.go +++ b/internal/prompt/types.go @@ -2,7 +2,9 @@ package prompt import ( "errors" + "strings" + "github.com/bilalcaliskan/s3-manager/internal/constants" "github.com/manifoldco/promptui" ) @@ -28,6 +30,18 @@ func GetConfirmRunner() *promptui.Prompt { }) } +func AskForApproval(runner PromptRunner) error { + if res, err := runner.Run(); err != nil { + if strings.ToLower(res) == "n" { + return constants.ErrUserTerminated + } + + return constants.ErrInvalidInput + } + + return nil +} + type PromptMock struct { Msg string Err error From 29e7c50da60c187b80079fbc13f2dcc4638ceee5 Mon Sep 17 00:00:00 2001 From: bilalcaliskan Date: Tue, 18 Jul 2023 00:32:10 +0300 Subject: [PATCH 4/5] test: increase more --- internal/aws/aws.go | 43 ++++++++----------------------- internal/prompt/types_test.go | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/internal/aws/aws.go b/internal/aws/aws.go index 77e9b43..c8a0e32 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -7,8 +7,6 @@ import ( "strings" "sync" - "github.com/bilalcaliskan/s3-manager/internal/constants" - "github.com/bilalcaliskan/s3-manager/internal/prompt" internalutil "github.com/bilalcaliskan/s3-manager/internal/utils" @@ -94,19 +92,15 @@ func GetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.GetBuc // It accepts an S3API interface and TagOptions as arguments. // For each tag in the provided TagOptions, a new tag is created and added to a slice of tags. // It then attaches these tags to the bucket and returns a PutBucketTaggingOutput and any error encountered. -func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions, confirmRunner prompt.PromptRunner, logger zerolog.Logger) error { +func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions, runner prompt.PromptRunner, logger zerolog.Logger) error { if opts.DryRun { logger.Info().Msg("skipping operation since '--dry-run' flag is passed") return nil } if !opts.AutoApprove { - if res, err := confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput + if err := prompt.AskForApproval(runner); err != nil { + return err } } @@ -143,12 +137,8 @@ func DeleteAllBucketTags(svc s3iface.S3API, opts *options3.TagOptions, runner pr } if !opts.AutoApprove { - if res, err := runner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return out, constants.ErrUserTerminated - } - - return out, constants.ErrInvalidInput + if err := prompt.AskForApproval(runner); err != nil { + return out, err } } @@ -173,21 +163,15 @@ func GetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerat // If the provided 'DryRun' or 'AutoApprove' options are set, the function will return early. // If not, it will set the bucket's transfer acceleration status based on the provided desired state. // It logs any errors encountered and returns them. -func SetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerationOptions, confirmRunner prompt.PromptRunner, logger zerolog.Logger) error { +func SetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerationOptions, runner prompt.PromptRunner, logger zerolog.Logger) error { if opts.DryRun { logger.Info().Msg("skipping operation since '--dry-run' flag is passed") return nil } - var err error if !opts.AutoApprove { - var res string - if res, err = confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput + if err := prompt.AskForApproval(runner); err != nil { + return err } } @@ -323,20 +307,15 @@ func GetBucketVersioning(svc s3iface.S3API, opts *options.RootOptions) (res *s3. // flags, confirm versioning state changes with the user if needed, and execute a // PutBucketVersioningInput request to set the bucket's versioning state. // The function logs the process, including any errors encountered, and returns these errors. -func SetBucketVersioning(svc s3iface.S3API, versioningOpts *options4.VersioningOptions, confirmRunner prompt.PromptRunner, logger zerolog.Logger) (err error) { +func SetBucketVersioning(svc s3iface.S3API, versioningOpts *options4.VersioningOptions, runner prompt.PromptRunner, logger zerolog.Logger) (err error) { if versioningOpts.DryRun { logger.Info().Msg("skipping operation since '--dry-run' flag is passed") return nil } if !versioningOpts.AutoApprove { - var res string - if res, err = confirmRunner.Run(); err != nil { - if strings.ToLower(res) == "n" { - return constants.ErrUserTerminated - } - - return constants.ErrInvalidInput + if err := prompt.AskForApproval(runner); err != nil { + return err } } diff --git a/internal/prompt/types_test.go b/internal/prompt/types_test.go index 72dc07a..2c9fa8f 100644 --- a/internal/prompt/types_test.go +++ b/internal/prompt/types_test.go @@ -5,6 +5,8 @@ package prompt import ( "testing" + "github.com/bilalcaliskan/s3-manager/internal/constants" + "github.com/stretchr/testify/assert" ) @@ -62,3 +64,49 @@ func TestPromptMock_Run(t *testing.T) { _, err := runner.Run() assert.Nil(t, err) } + +func TestAskForApproval(t *testing.T) { + testCases := []struct { + caseName string + mock *PromptMock + expectErr bool + }{ + { + caseName: "Approve", + mock: &PromptMock{ + Msg: "y", + Err: nil, + }, + expectErr: false, + }, + { + caseName: "Terminate", + mock: &PromptMock{ + Msg: "n", + Err: constants.ErrUserTerminated, + }, + expectErr: true, + }, + { + caseName: "Invalid input", + mock: &PromptMock{ + Msg: "adsjlkfasd", + Err: constants.ErrInvalidInput, + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.caseName, func(t *testing.T) { + t.Logf("starting case %s", tc.caseName) + + err := AskForApproval(tc.mock) + if tc.expectErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} From 99c3c5969198f11c1f1b5a501cad348957be42e9 Mon Sep 17 00:00:00 2001 From: bilalcaliskan Date: Tue, 18 Jul 2023 00:34:44 +0300 Subject: [PATCH 5/5] fix: fix code smell --- internal/aws/aws.go | 14 ++++++++------ internal/constants/infos.go | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 internal/constants/infos.go diff --git a/internal/aws/aws.go b/internal/aws/aws.go index c8a0e32..677969c 100644 --- a/internal/aws/aws.go +++ b/internal/aws/aws.go @@ -7,6 +7,8 @@ import ( "strings" "sync" + "github.com/bilalcaliskan/s3-manager/internal/constants" + "github.com/bilalcaliskan/s3-manager/internal/prompt" internalutil "github.com/bilalcaliskan/s3-manager/internal/utils" @@ -94,7 +96,7 @@ func GetBucketTags(svc s3iface.S3API, opts *options3.TagOptions) (res *s3.GetBuc // It then attaches these tags to the bucket and returns a PutBucketTaggingOutput and any error encountered. func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions, runner prompt.PromptRunner, logger zerolog.Logger) error { if opts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + logger.Info().Msg(constants.InfDryRun) return nil } @@ -132,7 +134,7 @@ func SetBucketTags(svc s3iface.S3API, opts *options3.TagOptions, runner prompt.P // a DeleteBucketTaggingOutput and any error encountered. func DeleteAllBucketTags(svc s3iface.S3API, opts *options3.TagOptions, runner prompt.PromptRunner, logger zerolog.Logger) (out *s3.DeleteBucketTaggingOutput, err error) { if opts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + logger.Info().Msg(constants.InfDryRun) return out, nil } @@ -165,7 +167,7 @@ func GetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerat // It logs any errors encountered and returns them. func SetTransferAcceleration(svc s3iface.S3API, opts *options6.TransferAccelerationOptions, runner prompt.PromptRunner, logger zerolog.Logger) error { if opts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + logger.Info().Msg(constants.InfDryRun) return nil } @@ -247,7 +249,7 @@ func GetBucketPolicyString(svc s3iface.S3API, opts *options5.BucketPolicyOptions // and returns a PutBucketPolicyOutput and any error encountered. func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions, runner prompt.PromptRunner, logger zerolog.Logger) (res *s3.PutBucketPolicyOutput, err error) { if opts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + logger.Info().Msg(constants.InfDryRun) return res, nil } @@ -272,7 +274,7 @@ func SetBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions, runn // along with any error encountered during the process. func DeleteBucketPolicy(svc s3iface.S3API, opts *options5.BucketPolicyOptions, runner prompt.PromptRunner, logger zerolog.Logger) (res *s3.DeleteBucketPolicyOutput, err error) { if opts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + logger.Info().Msg(constants.InfDryRun) return res, nil } @@ -309,7 +311,7 @@ func GetBucketVersioning(svc s3iface.S3API, opts *options.RootOptions) (res *s3. // The function logs the process, including any errors encountered, and returns these errors. func SetBucketVersioning(svc s3iface.S3API, versioningOpts *options4.VersioningOptions, runner prompt.PromptRunner, logger zerolog.Logger) (err error) { if versioningOpts.DryRun { - logger.Info().Msg("skipping operation since '--dry-run' flag is passed") + logger.Info().Msg(constants.InfDryRun) return nil } diff --git a/internal/constants/infos.go b/internal/constants/infos.go new file mode 100644 index 0000000..c9ca75b --- /dev/null +++ b/internal/constants/infos.go @@ -0,0 +1,3 @@ +package constants + +const InfDryRun = "skipping operation since '--dry-run' flag is passed"