Skip to content

Commit

Permalink
MET-328: Add EKS Nodes Count (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
laverion authored Apr 20, 2023
1 parent 6fadcdc commit 257c091
Show file tree
Hide file tree
Showing 17 changed files with 602 additions and 23 deletions.
43 changes: 39 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Activity
* Retrieving RDS instance counts...................OK (7)
* Retrieving Lightsail instance counts................OK (0)
* Retrieving S3 bucket counts...OK (13)
* Retrieving EKS Node counts....................OK (2)
* Writing to file...OK

Success.
Expand All @@ -127,9 +128,9 @@ As you can see above, no command line arguments were necessary: it used my defau
Here is what the CSV file looks like. It is important to mention that this tool was run TWICE to collect the results of two different accounts/profiles.

```csv
Account ID,Timestamp,Region,# of EC2 Instances,# of Spot Instances,# of EBS Volumes,# of Unique Containers,# of Lambda Functions,# of RDS Instances,# of Lightsail Instances,# of S3 Buckets
896149672290,2020-10-20T16:29:39-04:00,ALL_REGIONS,2,3,7,3,2,3,2,2
240520192079,2020-10-21T16:24:06-04:00,ALL_REGIONS,5,4,9,3,12,7,0,13
Account ID,Timestamp,Region,# of EC2 Instances,# of Spot Instances,# of EBS Volumes,# of Unique Containers,# of Lambda Functions,# of RDS Instances,# of Lightsail Instances,# of S3 Buckets,# of EKS Nodes
896149672290,2020-10-20T16:29:39-04:00,ALL_REGIONS,2,3,7,3,2,3,2,2,2
240520192079,2020-10-21T16:24:06-04:00,ALL_REGIONS,5,4,9,3,12,7,0,13,0
```

Here are some notes on specific columns:
Expand Down Expand Up @@ -221,7 +222,10 @@ To use this utility, this minimal IAM Profile can be associated with a bare user
"lightsail:GetInstances",
"lightsail:GetRegions",
"rds:DescribeDBInstances",
"s3:ListAllMyBuckets"
"s3:ListAllMyBuckets",
"eks:DescribeNodegroup",
"eks:ListNodegroups",
"eks:ListClusters"
],
"Resource": "*"
}
Expand Down Expand Up @@ -278,6 +282,11 @@ The `aws-resource-counter` examines the following resources:
* *NOTE:* We cannot currently count S3 buckets on a per-region basis (due to limitations with the AWS SDK).
* This is stored in the generated CSV file under the "# of S3 Buckets" column.
1. **EKS Nodes.** We count the number of nodes across all clusters in all regions.
* We do not qualify the type of EKS node.
* This is stored in the generated CSV file under the "# of EKS Nodes" column.
## Alternative Means of Resource Counting
If you do not wish to use the `aws-resource-counter` utility, you can use the AWS CLI to collect these same counts. For some of these counts, it will be easy to do. For others, the command line is a bit more complex.
Expand Down Expand Up @@ -568,3 +577,29 @@ $ aws s3api list-buckets $aws_p --query 'length(Buckets)'
```
Note that it is not possible through the AWS CLI to get S3 buckets on a per-region basis.
### EKS Nodes
To get a list of EKS nodes in a given region, we use the AWS CLI `eks` command, as in:
```bash
$ region=us-east-2
$ clusters=$(aws eks list-clusters $aws_p --no-paginate --region $region --output text --query='clusters')
$ for cluster in $clusters; do \
for node_pool in $(aws eks list-nodegroups $aws_p --no-paginate --region $region --cluster-name $cluster --query=nodegroups --output text); do \
aws eks describe-nodegroup $aws_p --no-paginate --region $region --cluster $cluster --nodegroup-name $node_pool --query="nodegroup.scalingConfig.desiredSize"; \
done; done | paste -s -d+ - | bc
1
```
To get a list of all EKS nodes across all regions use:
```bash
$ for reg in $ec2_r; do \
for cluster in $(aws eks list-clusters $aws_p --no-paginate --region $reg --output text --query='clusters'); do \
for node_pool in $(aws eks list-nodegroups $aws_p --no-paginate --cluster-name $cluster --region $reg --query=nodegroups --output text); do \
aws eks describe-nodegroup $aws_p --no-paginate --region $reg --cluster $cluster --nodegroup-name $node_pool --query="nodegroup.scalingConfig.desiredSize"; \
done; done; done | paste -s -d+ - | bc
5
```
11 changes: 9 additions & 2 deletions activityMonitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type ActivityMonitor interface {
StartAction(string, ...interface{})
CheckError(error) bool
ActionError(string, ...interface{})
SubResourceError(string, ...interface{})
EndAction(string, ...interface{})
Exit(int)
}
Expand All @@ -38,7 +39,7 @@ type TerminalActivityMonitor struct {
// Message constructs a simple message from the format string and arguments
// and sends it to the associated io.Writer.
func (tam *TerminalActivityMonitor) Message(format string, v ...interface{}) {
fmt.Fprint(tam.Writer, fmt.Sprintf(format, v...))
fmt.Fprintf(tam.Writer, format, v...)
}

// StartAction constructs a structured message to the associated Writer
Expand Down Expand Up @@ -66,7 +67,6 @@ func (tam *TerminalActivityMonitor) CheckError(err error) bool {
case "NoCredentialProviders":
// TODO Can we establish this failure earlier? When the session is created?
tam.ActionError("Either the profile does not exist, is misspelled or credentials are not stored there.")
break
case "AccessDeniedException":
// Construct a message by taking the first part of the string up to a newline character
tam.ActionError(parts[0])
Expand Down Expand Up @@ -94,6 +94,13 @@ func (tam *TerminalActivityMonitor) ActionError(format string, v ...interface{})
tam.Exit(1)
}

// SubResourceError formats the supplied format string (and associated parameters) in
// RED.
func (tam *TerminalActivityMonitor) SubResourceError(format string, v ...interface{}) {
// Display an error message (and newline)
fmt.Fprintln(tam.Writer, color.Red(fmt.Sprintf(fmt.Sprintf(" - [ERROR] %s", format), v...)))
}

// EndAction receives a format string (and arguments) and sends to the supplied
// Writer.
func (tam *TerminalActivityMonitor) EndAction(format string, v ...interface{}) {
Expand Down
47 changes: 47 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ecs/ecsiface"
"github.com/aws/aws-sdk-go/service/eks"
"github.com/aws/aws-sdk-go/service/eks/eksiface"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/aws/aws-sdk-go/service/lambda/lambdaiface"
"github.com/aws/aws-sdk-go/service/lightsail"
Expand Down Expand Up @@ -168,6 +170,33 @@ func (lss *LightsailService) InspectInstances(input *lightsail.GetInstancesInput
return lss.Client.GetInstances(input)
}

// EKSService is a struct that knows how to get a list of all EKS clusters and
// describes the clusters
type EKSService struct {
Client eksiface.EKSAPI
}

// ListClusters takes an input filter specification and a function
// to evaluate a ListClustersOutput struct. The supplied function
// can determine when to stop iterating through EKS clusters.
func (eksi *EKSService) ListClusters(input *eks.ListClustersInput,
fn func(*eks.ListClustersOutput, bool) bool) error {
return eksi.Client.ListClustersPages(input, fn)
}

// ListNodeGroups takes an input filter specification and a function
// to evaluate a ListNodeGroupsOutput struct. The supplied function
// can determine when to stop iterating through Nodegroups.
func (eksi *EKSService) ListNodeGroups(input *eks.ListNodegroupsInput,
fn func(*eks.ListNodegroupsOutput, bool) bool) error {
return eksi.Client.ListNodegroupsPages(input, fn)
}

// DescribeNodegroups returns a full description of a Nodegroup
func (eksi *EKSService) DescribeNodegroups(input *eks.DescribeNodegroupInput) (*eks.DescribeNodegroupOutput, error) {
return eksi.Client.DescribeNodegroup(input)
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Abstract Service Factory (provides access to all Abstract Services)
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Expand All @@ -178,6 +207,7 @@ type ServiceFactory interface {
GetCurrentRegion() string
GetAccountIDService() *AccountIDService
GetEC2InstanceService(string) *EC2InstanceService
GetEKSService(string) *EKSService
GetRDSInstanceService(string) *RDSInstanceService
GetS3Service() *S3Service
GetLambdaService(string) *LambdaService
Expand Down Expand Up @@ -362,3 +392,20 @@ func (awssf *AWSServiceFactory) GetLightsailService(regionName string) *Lightsai
Client: client,
}
}

// GetEKSService returns an instance of an EKSService associated
// with our session. The caller can supply an optional region name to contruct
// an instance associated with that region.
func (awssf *AWSServiceFactory) GetEKSService(regionName string) *EKSService {
// Construct our service client
var client eksiface.EKSAPI
if regionName == "" {
client = eks.New(awssf.Session)
} else {
client = eks.New(awssf.Session, aws.NewConfig().WithRegion(regionName))
}

return &EKSService{
Client: client,
}
}
52 changes: 52 additions & 0 deletions api_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"io"
"strings"
"testing"
Expand All @@ -9,6 +10,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/eks"
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/aws/aws-sdk-go/service/lightsail"
"github.com/aws/aws-sdk-go/service/rds"
Expand Down Expand Up @@ -386,3 +388,53 @@ func TestAwsServiceFactoryGetLightsailService(t *testing.T) {
}
}
}

func TestAwsServiceFactoryGetEKSService(t *testing.T) {
// Create our test cases
cases := []struct {
RegionName string
}{
{},
{
RegionName: "us-west-1",
},
}

// Loop through the test cases
for _, c := range cases {
// Create a config for the region?
var config = &aws.Config{}
if c.RegionName != "" {
config = config.WithRegion(c.RegionName)
}

// Create our test
session, err := session.NewSession(config)
if err != nil {
t.Errorf("Unexpected error while creating a new session: %v", err)
}

// Create an AWS Service Factory
sf := &AWSServiceFactory{
Session: session,
}

t.Run(fmt.Sprintf("testing with region name: %s", c.RegionName), func(t *testing.T) {
// Get the desired service
service := sf.GetEKSService(c.RegionName)

// Is the service nil?
if service == nil {
t.Errorf("No service returned for %s", "GetLightsailService")
} else if service.Client != nil {
// Convert to implementation type
implType, ok := service.Client.(*eks.EKS)
if !ok {
t.Errorf("Unexpected Client type: expected %v, actual %v", "*eks.EKS", implType)
} else if *implType.Config.Region != c.RegionName {
t.Errorf("Unexpected value for Client.Config.Region: expected %s, actual %s", c.RegionName, *implType.Config.Region)
}
}
})
}
}
5 changes: 5 additions & 0 deletions containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ func (fsf fakeCntrServiceFactory) GetLightsailService(string) *LightsailService
return nil
}

// Don't need to implement
func (fsf fakeCntrServiceFactory) GetEKSService(regionName string) *EKSService {
return nil
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Unit Test for UniqueContainerImages
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Expand Down
5 changes: 5 additions & 0 deletions ebs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ func (fsf fakeEBSServiceFactory) GetLightsailService(string) *LightsailService {
return nil
}

// Don't need to implement
func (fsf fakeEBSServiceFactory) GetEKSService(regionName string) *EKSService {
return nil
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Unit Test for EBSVolumes
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Expand Down
5 changes: 5 additions & 0 deletions ec2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,11 @@ func (fsf fakeEC2ServiceFactory) GetLightsailService(string) *LightsailService {
return nil
}

// Don't need to implement
func (fsf fakeEC2ServiceFactory) GetEKSService(regionName string) *EKSService {
return nil
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Unit Test for EC2Counts
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Expand Down
Loading

0 comments on commit 257c091

Please sign in to comment.