From 253a033a1af0ddd2a33605950c5aac073ad590c8 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Tue, 10 Jan 2023 15:16:35 +0100 Subject: [PATCH 1/4] Remove status package to improve readability - A package can still be created if required, for now this improves the readability --- cmd/status.go | 16 ++++++++++++++-- internal/status/rss.go | 14 -------------- 2 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 internal/status/rss.go diff --git a/cmd/status.go b/cmd/status.go index 41ee3a6..6b5d845 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -3,7 +3,6 @@ package cmd import ( "encoding/xml" "fmt" - "github.com/NETWAYS/check_cloud_aws/internal/status" "github.com/NETWAYS/go-check" "github.com/spf13/cobra" "net/http" @@ -11,6 +10,19 @@ import ( "strings" ) +type rss struct { + Channel struct { + Title string `xml:"title"` + Link string `xml:"link"` + Desc string `xml:"description"` + Items []item `xml:"item"` + } `xml:"channel"` +} + +type item struct { + Title string `xml:"title"` +} + // To store the CLI parameters type StatusConfig struct { Url string @@ -47,7 +59,7 @@ var statusCmd = &cobra.Command{ CRITICAL - WARNING - Information available for iam (Global)`, Run: func(cmd *cobra.Command, args []string) { var ( - feed status.Rss + feed rss rc int output string feedUrl string diff --git a/internal/status/rss.go b/internal/status/rss.go deleted file mode 100644 index 3734e2d..0000000 --- a/internal/status/rss.go +++ /dev/null @@ -1,14 +0,0 @@ -package status - -type Rss struct { - Channel struct { - Title string `xml:"title"` - Link string `xml:"link"` - Desc string `xml:"description"` - Items []Item `xml:"item"` - } `xml:"channel"` -} - -type Item struct { - Title string `xml:"title"` -} From 5c8589300675a7b3fd1a264f84a0bec65fe8848b Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Tue, 10 Jan 2023 15:31:37 +0100 Subject: [PATCH 2/4] Normalize EC2 Client name --- cmd/ec2.go | 4 ++-- internal/ec2/client.go | 26 +++++++++++++------------- internal/ec2/client_test.go | 4 ++-- internal/ec2/filter_test.go | 34 ---------------------------------- 4 files changed, 17 insertions(+), 51 deletions(-) delete mode 100644 internal/ec2/filter_test.go diff --git a/cmd/ec2.go b/cmd/ec2.go index e94ec45..b1a02ad 100644 --- a/cmd/ec2.go +++ b/cmd/ec2.go @@ -14,11 +14,11 @@ var ec2Cmd = &cobra.Command{ Run: Help, } -func RequireEC2Client() *ec2.Client { +func RequireEC2Client() *ec2.EC2Client { session, err := common.CreateSession(CredentialsFile, Profile, Region) if err != nil { check.ExitError(fmt.Errorf("could not setup AWS API session: %w", err)) } - return ec2.NewClient(session) + return ec2.NewEC2Client(session) } diff --git a/internal/ec2/client.go b/internal/ec2/client.go index 7056c70..e4747aa 100644 --- a/internal/ec2/client.go +++ b/internal/ec2/client.go @@ -6,25 +6,25 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" ) -// Client implementation to offer various load functions for getting data from the API -type Client struct { - Client *ec2.EC2 +// EC2Client implementation to offer various load functions for getting data from the API +type EC2Client struct { + EC2Client *ec2.EC2 } // NewClient sets up a Client with a AWS session.Session -func NewClient(session *session.Session) *Client { - return &Client{ec2.New(session)} +func NewEC2Client(session *session.Session) *EC2Client { + return &EC2Client{ec2.New(session)} } // LoadInstance returns a single Instance looking for its id -func (c *Client) LoadInstance(id string) (instance *Instance, err error) { +func (c *EC2Client) LoadInstance(id string) (instance *Instance, err error) { return c.LoadInstanceByFilter(DescribeInstancesInput(Filter("instance-id", id))) } // LoadInstanceByName returns a single Instance looking for a name // // Name is not required to be unique, but our interface expects it is. -func (c *Client) LoadInstanceByName(name string) (instance *Instance, err error) { +func (c *EC2Client) LoadInstanceByName(name string) (instance *Instance, err error) { return c.LoadInstanceByFilter(DescribeInstancesInput(Filter("tag:Name", name))) } @@ -33,8 +33,8 @@ func (c *Client) LoadInstanceByName(name string) (instance *Instance, err error) // The function expects the result to have exactly one match. // // Also see our Filter and DescribeInstancesInput -func (c *Client) LoadInstanceByFilter(filter *ec2.DescribeInstancesInput) (instance *Instance, err error) { - instances, err := c.Client.DescribeInstances(filter) +func (c *EC2Client) LoadInstanceByFilter(filter *ec2.DescribeInstancesInput) (instance *Instance, err error) { + instances, err := c.EC2Client.DescribeInstances(filter) if err != nil { err = fmt.Errorf("could not load instances: %w", err) @@ -69,8 +69,8 @@ func (c *Client) LoadInstanceByFilter(filter *ec2.DescribeInstancesInput) (insta } // LoadInstanceStatus returns the ec2.Instance for an id -func (c *Client) LoadInstanceStatus(id string) (status *ec2.InstanceStatus, err error) { - d, err := c.Client.DescribeInstanceStatus(&ec2.DescribeInstanceStatusInput{ +func (c *EC2Client) LoadInstanceStatus(id string) (status *ec2.InstanceStatus, err error) { + d, err := c.EC2Client.DescribeInstanceStatus(&ec2.DescribeInstanceStatusInput{ InstanceIds: []*string{&id}, }) if err != nil { @@ -94,8 +94,8 @@ func (c *Client) LoadInstanceStatus(id string) (status *ec2.InstanceStatus, err // LoadAllInstancesByFilter returns Instances with a list of Instance to work with // // Also see our Filter and DescribeInstancesInput -func (c *Client) LoadAllInstancesByFilter(filter *ec2.DescribeInstancesInput) (instances *Instances, err error) { - d, err := c.Client.DescribeInstances(filter) +func (c *EC2Client) LoadAllInstancesByFilter(filter *ec2.DescribeInstancesInput) (instances *Instances, err error) { + d, err := c.EC2Client.DescribeInstances(filter) if err != nil { err = fmt.Errorf("could not load instances: %w", err) return nil, err diff --git a/internal/ec2/client_test.go b/internal/ec2/client_test.go index 5baa6d2..6d83e96 100644 --- a/internal/ec2/client_test.go +++ b/internal/ec2/client_test.go @@ -7,8 +7,8 @@ import ( "testing" ) -func createTestClient() (*ec2.Client, func()) { - return ec2.NewClient(common.CreateTestSession(TestRegion)), enableMocking() +func createTestClient() (*ec2.EC2Client, func()) { + return ec2.NewEC2Client(common.CreateTestSession(TestRegion)), enableMocking() } func TestClient_LoadInstance(t *testing.T) { diff --git a/internal/ec2/filter_test.go b/internal/ec2/filter_test.go deleted file mode 100644 index 980b94e..0000000 --- a/internal/ec2/filter_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package ec2_test - -import ( - "fmt" - "github.com/NETWAYS/check_cloud_aws/internal/ec2" -) - -func ExampleDescribeInstancesInput() { - input := ec2.DescribeInstancesInput( - ec2.Filter("tag:aws:autoscaling:groupName", "magic"), - ec2.Filter("tag:Name", "web*"), - ) - fmt.Println(input) - // Output: - // { - // Filters: [{ - // Name: "tag:aws:autoscaling:groupName", - // Values: ["magic"] - // },{ - // Name: "tag:Name", - // Values: ["web*"] - // }] - // } -} - -func ExampleFilter() { - filter := ec2.Filter("tag:Name", "vm1") - fmt.Println(filter) - // Output: - // { - // Name: "tag:Name", - // Values: ["vm1"] - // } -} From 426d6ca068a4613581b7c2cfd37efc95ded92684 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Tue, 10 Jan 2023 16:25:46 +0100 Subject: [PATCH 3/4] Fix S3 subcommand buckets flag --- README.md | 4 ++-- cmd/s3.go | 2 -- cmd/s3_buckets.go | 29 +++++++++++++++++++++-------- cmd/s3_objects.go | 43 +++++++++++++++++++++++++++++-------------- internal/s3/client.go | 18 ++++++++++-------- 5 files changed, 62 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index e78c744..80b2111 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Global Flags: ``` $ check_cloud_aws s3 bucket -C ~/.aws/credentials -w 100mb -c 200mb -OK - 2 Buckets: 0 Critical - 0 Warning - 2 Ok +OK - 2 Buckets: 0 Critical - 0 Warning - 2 OK \_[OK] my-aws-test-bucket1 - value: 50MiB \_[OK] my-aws-test-bucket2 - value: 20MiB | my-aws-test-bucket1=50MB;100;200 my-aws-test-bucket2=60MB;100;200 ``` @@ -149,7 +149,7 @@ Global Flags: ```` $ check_cloud_aws s3 object -C ~/.aws/credentials --perfdata --prefix 'test' -OK - 2 Objects: 0 Critical - 0 Warning - 3 Ok +OK - 2 Objects: 0 Critical - 0 Warning - 3 OK \_[y-aws-testbucket1]: \_[OK] test-file4.txt: 20MiB \_[y-aws-testbucket2]: diff --git a/cmd/s3.go b/cmd/s3.go index 40c95e4..452fb2f 100644 --- a/cmd/s3.go +++ b/cmd/s3.go @@ -9,8 +9,6 @@ import ( "github.com/spf13/cobra" ) -var BucketNames []string - var s3Cmd = &cobra.Command{ Use: "s3", Short: "Checks in the S3 context", diff --git a/cmd/s3_buckets.go b/cmd/s3_buckets.go index a73e9d5..a9fba2d 100644 --- a/cmd/s3_buckets.go +++ b/cmd/s3_buckets.go @@ -1,9 +1,10 @@ package cmd import ( + "errors" "fmt" "github.com/NETWAYS/check_cloud_aws/internal" - b "github.com/NETWAYS/check_cloud_aws/internal/s3" + s3i "github.com/NETWAYS/check_cloud_aws/internal/s3" "github.com/NETWAYS/go-check" "github.com/NETWAYS/go-check/perfdata" "github.com/NETWAYS/go-check/result" @@ -15,6 +16,7 @@ import ( var ( CriticalBucketSize string WarningBucketSize string + BucketNames []string ) var s3BucketCmd = &cobra.Command{ @@ -22,15 +24,15 @@ var s3BucketCmd = &cobra.Command{ Short: "Checks the size of a single bucket or multiple buckets", Example: ` check_cloud_aws s3 bucket - OK - 1 Buckets: 0 Critical - 0 Warning - 1 Ok + OK - 1 Buckets: 0 Critical - 0 Warning - 1 OK \_[OK] my-bucket - value: 100MiB | my-bucket=100MB;10240;20480 check_cloud_aws s3 bucket --crit-bucket-size 10 - CRITICAL - 1 Buckets: 1 Critical - 0 Warning - 0 Ok + CRITICAL - 1 Buckets: 1 Critical - 0 Warning - 0 OK \_[CRITICAL] my-bucket - value: 100MiB | my-bucket=100MB;10240;10 check_cloud_aws s3 bucket --crit-bucket-size 5GB - CRITICAL - 1 Buckets: 1 Critical - 0 Warning - 0 Ok + CRITICAL - 1 Buckets: 1 Critical - 0 Warning - 0 OK \_[CRITICAL] my-bucket - value: 100MiB | my-bucket=100MB;10240;10`, Run: func(cmd *cobra.Command, args []string) { var ( @@ -46,11 +48,12 @@ var s3BucketCmd = &cobra.Command{ ) buckets := s3.ListBucketsOutput{} - objectsOutput := b.V2Output{} + objectsOutput := s3i.V2Output{} client := RequireS3Client() - if BucketNames == nil { + // Load all buckets of if no buckets are speficied + if len(BucketNames) == 0 { bk, err := client.LoadAllBuckets() if err != nil { check.ExitError(err) @@ -58,11 +61,21 @@ var s3BucketCmd = &cobra.Command{ buckets = *bk } else { + // Load specific buckets for _, bucketName := range BucketNames { - buckets.Buckets = append(buckets.Buckets, client.LoadBucketByName(bucketName)) + b, err := client.LoadBucketByName(bucketName) + // Requested bucket does not exist, skip + if errors.Is(err, s3i.ErrBucketNotFound) { + continue + } + buckets.Buckets = append(buckets.Buckets, b) } } + if len(buckets.Buckets) == 0 { + check.ExitError(fmt.Errorf("No buckets available")) + } + critical, err := internal.ParseThreshold(CriticalBucketSize) if err != nil { check.ExitError(err) @@ -121,7 +134,7 @@ var s3BucketCmd = &cobra.Command{ perf.Add(&p) } - summary += fmt.Sprintf("%d Buckets: %d Critical - %d Warning - %d Ok\n", len(buckets.Buckets), totalCrit, totalWarn, totalOk) + summary += fmt.Sprintf("%d Buckets: %d Critical - %d Warning - %d OK\n", len(buckets.Buckets), totalCrit, totalWarn, totalOk) check.ExitRaw(result.WorstState(states...), summary+output, "|", perf.String()) }, diff --git a/cmd/s3_objects.go b/cmd/s3_objects.go index 6fb5d49..251437e 100644 --- a/cmd/s3_objects.go +++ b/cmd/s3_objects.go @@ -1,9 +1,10 @@ package cmd import ( + "errors" "fmt" "github.com/NETWAYS/check_cloud_aws/internal" - b "github.com/NETWAYS/check_cloud_aws/internal/s3" + s3i "github.com/NETWAYS/check_cloud_aws/internal/s3" "github.com/NETWAYS/go-check" "github.com/NETWAYS/go-check/perfdata" "github.com/NETWAYS/go-check/result" @@ -17,6 +18,7 @@ var ( WarningObjectSize string ShowPerfdata bool ObjectPrefix string + ObjectBucketNames []string ) var s3ObjectCmd = &cobra.Command{ @@ -24,21 +26,23 @@ var s3ObjectCmd = &cobra.Command{ Short: "Checks the size of objects, stored in a single bucket or multiple buckets", Example: ` check_cloud_aws s3 object - OK - 2 Objects: 0 Critical - 0 Warning - 2 Ok + OK - 2 Objects: 0 Critical - 0 Warning - 2 OK \_[my-bucket]: \_[OK] foo.fs: 100MiB \_[OK] bar.fs: 100MiB check_cloud_aws s3 object --prefix file - OK - 3 Objects: 0 Critical - 0 Warning - 3 Ok + OK - 3 Objects: 0 Critical - 0 Warning - 3 OK \_[my-bucket]: \_[OK] file_1.fs: 10MiB \_[OK] file_2.fs: 20MiB \_[OK] file_3.fs: 30MiB - check_cloud_aws s3 object --crit-object-size 10KB - CRITICAL - 1 Objects: 1 Critical - 0 Warning - 0 Ok - \_[my-bucket]: + check_cloud_aws s3 object --crit-object-size 10KB --buckets foo --buckets bar + CRITICAL - 2 Objects: 2 Critical - 0 Warning - 0 OK + \_[foo]: + \_[CRITICAL] file.fs: 100MiB + \_[bar]: \_[CRITICAL] file.fs: 100MiB`, Run: func(cmd *cobra.Command, args []string) { var ( @@ -55,11 +59,12 @@ var s3ObjectCmd = &cobra.Command{ ) buckets := s3.ListBucketsOutput{} - objectsOutput := b.V2Output{} + objectsOutput := s3i.V2Output{} client := RequireS3Client() - if BucketNames == nil { + // Load all buckets of if no buckets are speficied + if len(ObjectBucketNames) == 0 { bk, err := client.LoadAllBuckets() if err != nil { check.ExitError(err) @@ -67,11 +72,21 @@ var s3ObjectCmd = &cobra.Command{ buckets = *bk } else { - for _, bucketName := range BucketNames { - buckets.Buckets = append(buckets.Buckets, client.LoadBucketByName(bucketName)) + // Load specific buckets + for _, bucketName := range ObjectBucketNames { + b, err := client.LoadBucketByName(bucketName) + // Requested bucket does not exist, skip + if errors.Is(err, s3i.ErrBucketNotFound) { + continue + } + buckets.Buckets = append(buckets.Buckets, b) } } + if len(buckets.Buckets) == 0 { + check.ExitError(fmt.Errorf("No buckets available")) + } + critical, err := internal.ParseThreshold(CriticalObjectSize) if err != nil { check.ExitError(err) @@ -130,13 +145,13 @@ var s3ObjectCmd = &cobra.Command{ } } - summary += fmt.Sprintf("%d Objects: %d Critical - %d Warning - %d Ok\n", totalObjects, totalCrit, totalWarn, totalOk) + summary += fmt.Sprintf("%d Objects: %d Critical - %d Warning - %d OK\n", totalObjects, totalCrit, totalWarn, totalOk) if ShowPerfdata { check.ExitRaw(result.WorstState(states...), summary+output, "|", perf.String()) - } else { - check.ExitRaw(result.WorstState(states...), summary+output) } + + check.ExitRaw(result.WorstState(states...), summary+output) }, } @@ -144,7 +159,7 @@ func init() { s3Cmd.AddCommand(s3ObjectCmd) s3ObjectFlags := s3ObjectCmd.Flags() - s3ObjectFlags.StringSliceVarP(&BucketNames, "buckets", "b", nil, + s3ObjectFlags.StringSliceVarP(&ObjectBucketNames, "buckets", "b", nil, "Name of one or multiple S3 buckets. If '--buckets' is empty, all buckets will be evaluated.") s3ObjectFlags.StringVar(&ObjectPrefix, "prefix", "", "Limits the response to keys that begin with the specified prefix, e.G. '--prefix test' filters all objects which starts with 'test'.\n"+ diff --git a/internal/s3/client.go b/internal/s3/client.go index f283e2b..6f32119 100644 --- a/internal/s3/client.go +++ b/internal/s3/client.go @@ -1,13 +1,15 @@ package s3 import ( + "errors" "fmt" - "github.com/NETWAYS/go-check" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) +var ErrBucketNotFound = errors.New("No such Bucket") + type S3Client struct { S3Client *s3.S3 } @@ -19,26 +21,26 @@ func NewS3Client(session *session.Session) *S3Client { func (c *S3Client) LoadAllBuckets() (buckets *s3.ListBucketsOutput, err error) { buckets, err = c.S3Client.ListBuckets(&s3.ListBucketsInput{}) if err != nil { - err = fmt.Errorf("could not load all buckets: %w", err) - return nil, err + return nil, fmt.Errorf("could not load all buckets: %w", err) } - return + return buckets, nil } -func (c *S3Client) LoadBucketByName(name string) (bucket *s3.Bucket) { +func (c *S3Client) LoadBucketByName(name string) (bucket *s3.Bucket, err error) { buckets, err := c.LoadAllBuckets() + if err != nil { - check.ExitError(err) + return nil, err } for _, bucket = range buckets.Buckets { if *bucket.Name == name { - return bucket + return bucket, nil } } - return + return nil, ErrBucketNotFound } func (c *S3Client) LoadAllObjectsFromBucket(bucket, prefix string) (objects *s3.ListObjectsV2Output, err error) { From 8275eb00eb8197954c275710eeba3628a0476093 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Thu, 12 Jan 2023 15:18:11 +0100 Subject: [PATCH 4/4] [s3] Add flag to allow no buckets to be OK --- cmd/s3_buckets.go | 7 +++++++ cmd/s3_objects.go | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/cmd/s3_buckets.go b/cmd/s3_buckets.go index a9fba2d..71db6a0 100644 --- a/cmd/s3_buckets.go +++ b/cmd/s3_buckets.go @@ -17,6 +17,7 @@ var ( CriticalBucketSize string WarningBucketSize string BucketNames []string + EmptyOK bool ) var s3BucketCmd = &cobra.Command{ @@ -72,6 +73,10 @@ var s3BucketCmd = &cobra.Command{ } } + if len(buckets.Buckets) == 0 && EmptyOK { + check.ExitRaw(check.OK, "No buckets available") + } + if len(buckets.Buckets) == 0 { check.ExitError(fmt.Errorf("No buckets available")) } @@ -152,6 +157,8 @@ func init() { s3BucketFlags.StringVarP(&WarningBucketSize, "warn-bucket-size", "w", "10Gb", "Warning threshold for the size of the specified bucket. Alerts if the size is greater than the warning threshold.\n"+ "Possible units are MB, GB or TB (defaults to MB if none is specified).") + s3BucketFlags.BoolVar(&EmptyOK, "empty-ok", false, + "Return OK if there are no buckets") s3BucketFlags.SortFlags = false } diff --git a/cmd/s3_objects.go b/cmd/s3_objects.go index 251437e..1693074 100644 --- a/cmd/s3_objects.go +++ b/cmd/s3_objects.go @@ -19,6 +19,7 @@ var ( ShowPerfdata bool ObjectPrefix string ObjectBucketNames []string + ObjectEmptyOK bool ) var s3ObjectCmd = &cobra.Command{ @@ -83,6 +84,10 @@ var s3ObjectCmd = &cobra.Command{ } } + if len(buckets.Buckets) == 0 && ObjectEmptyOK { + check.ExitRaw(check.OK, "No buckets available") + } + if len(buckets.Buckets) == 0 { check.ExitError(fmt.Errorf("No buckets available")) } @@ -172,6 +177,8 @@ func init() { "Possible units are MB, GB or TB (defaults to MB if none is specified).") s3ObjectFlags.BoolVarP(&ShowPerfdata, "perfdata", "p", false, "Displays perfdata and lists all objects in the specified bucket.") + s3ObjectFlags.BoolVar(&ObjectEmptyOK, "empty-ok", false, + "Return OK if there are no buckets") s3ObjectFlags.SortFlags = false }