Skip to content

Commit

Permalink
Merge pull request #28 from flavioelawi/main
Browse files Browse the repository at this point in the history
Add support for ECS Clusters
  • Loading branch information
RamanaReddy0M committed Nov 27, 2023
2 parents c95c106 + cfc42b0 commit 63660b4
Show file tree
Hide file tree
Showing 25 changed files with 1,205 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -19,4 +19,4 @@ execs

#logs
*.log
tmp
tmp
5 changes: 5 additions & 0 deletions docs/README.md
Expand Up @@ -44,6 +44,7 @@ cloudlens
| [EC2](./#ec2) | view all instances and their associated metadata, including JSON data | `ec2` |
| [EC2 Snapshot](./#ec2-snapshot) | view a list of all EC2 snapshots | `ec2:s` |
| [EC2 Image](./#ec2-image) | See a list of all EC2 images | `ec2:i` |
| [ECS Clusters](./#ecs-clusters) | View all ECS Clusters | `ecs:c` |
| [VPC](./#vpc) | view all VPC's and their associated metadata, including JSON data | `vpc` |
| [Security Group](./#security-group) | Security Groups and their associated metadata | `sg` |
| [IAM users](./#iam-users) | view all IAM users and their associated metadata | `iam:u` |
Expand Down Expand Up @@ -109,6 +110,10 @@ To access the <mark style="color:blue;">EC2 image</mark> page, enter `ec2:i` in

<figure><img src=".gitbook/assets/image (21).png" alt="EC2 Image Details Page"><figcaption><p>EC2 Image Details Page</p></figcaption></figure>

### ECS Clusters

To acess the <mark style="color:blue;">ECS Clusters</mark> page, enter `ecs:c` in your prompt and press Enter. This will display a list of all deployed Clusetrs.

### VPC

To access the <mark style="color:green;">VPC</mark> management functionality, type `vpc` in the command prompt to display a list of available VPCs. Selecting a specific VPC and pressing enter will show a JSON file with its information. You can view the VPC's subnets by using the `s` command and navigate back to the previous page by pressing the escape key. Additionally, you can download a CSV file using the `z` command.
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/aws/aws-sdk-go v1.44.177
github.com/aws/aws-sdk-go-v2 v1.17.6
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.18
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/derailed/tview v0.7.2
Expand All @@ -20,6 +20,7 @@ require (
github.com/Masterminds/semver v1.5.0
github.com/adrg/xdg v0.4.0
github.com/atotto/clipboard v0.1.4
github.com/aws/aws-sdk-go-v2/service/ecs v1.30.1
github.com/charmbracelet/glamour v0.6.0
github.com/cheggaaa/pb/v3 v3.1.2
github.com/google/go-github/v50 v50.2.0
Expand Down Expand Up @@ -85,8 +86,8 @@ require (
github.com/aws/aws-sdk-go-v2/credentials v1.13.17
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.58
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect
github.com/aws/aws-sdk-go-v2/service/ec2 v1.90.0
github.com/aws/aws-sdk-go-v2/service/iam v1.19.6
Expand All @@ -97,7 +98,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.0
github.com/dustin/go-humanize v1.0.1
github.com/gdamore/encoding v1.0.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Expand Up @@ -36,6 +36,8 @@ github.com/aws/aws-sdk-go v1.44.177 h1:ckMJhU5Gj+4Rta+bJIUiUd7jvHom84aim3zkGPblq
github.com/aws/aws-sdk-go v1.44.177/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.17.6 h1:Y773UK7OBqhzi5VDXMi1zVGsoj+CVHs2eaC2bDsLwi0=
github.com/aws/aws-sdk-go-v2 v1.17.6/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.18.18 h1:/ePABXvXl3ESlzUGnkkvvNnRFw3Gh13dyqaq0Qo3JcU=
Expand All @@ -48,14 +50,20 @@ github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.58 h1:AFPYaPzlMno+YbnQGy+3
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.58/go.mod h1:fGEWh5NPS+2uQONSsIGIcbpJPIWoRu9unkcHgalx594=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 h1:y+8n9AGDjikyXoMBTRaHHHSaFEB8267ykmvyPodJfys=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30/go.mod h1:LUBAO3zNXQjoONBKn/kR1y0Q4cj/D02Ts0uHYjcCQLM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 h1:r+Kv+SEJquhAZXaJ7G4u44cIwXV3f8K+N482NNAzJZA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24/go.mod h1:gAuCezX/gob6BSMbItsSlMb6WZGV7K2+fWOvk8xBSto=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 h1:hf+Vhp5WtTdcSdE+yEcUz8L73sAzN0R+0jQv+Z51/mI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31/go.mod h1:5zUjguZfG5qjhG9/wqmuyHRyUftl2B5Cp6NNxNC6kRA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.22 h1:lTqBRUuy8oLhBsnnVZf14uRbIHPHCrGqg4Plc8gU/1U=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.22/go.mod h1:YsOa3tFriwWNvBPYHXM5ARiU2yqBNWPWeUiq+4i7Na0=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.90.0 h1:oRl2nzkuU/qMPvudU3qQ+GUAMV5POP3V/aJTJ7Q0lT0=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.90.0/go.mod h1:zDr1uSSLVYc6KqXvrmqYkeqnfbmOOrbVloz4Eqsc83k=
github.com/aws/aws-sdk-go-v2/service/ecs v1.30.1 h1:bOS7hAfvd8+glVAG88WnvRITe5N1vopGFHh10ORe/BI=
github.com/aws/aws-sdk-go-v2/service/ecs v1.30.1/go.mod h1:cxbA26Kf4UlTb40f5FON22ZPNMyEVmMS82KUJZC1E1w=
github.com/aws/aws-sdk-go-v2/service/iam v1.19.6 h1:5cwCVkREx62atl2qRLge5zyh8QmvIYtAgb2Fs7yKQ6k=
github.com/aws/aws-sdk-go-v2/service/iam v1.19.6/go.mod h1:sapsBrGFSqYB1rBHoPCQ3/wmExVPF896OSMwkO2rMWQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
Expand All @@ -80,6 +88,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 h1:rIFn5J3yDoeuKCE9sESXqM5POTAh
github.com/aws/aws-sdk-go-v2/service/sts v1.18.6/go.mod h1:48WJ9l3dwP0GSHWGc5sFGGlCkuA82Mc2xnw+T6Q8aDw=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
Expand Down
244 changes: 244 additions & 0 deletions internal/aws/ecs.go
@@ -0,0 +1,244 @@
package aws

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
)

// --- ECS Clusters ---

func ListEcsClusters(cfg aws.Config) ([]EcsClusterResp, error) {
ecsClient := ecs.NewFromConfig(cfg)
resultListClusters, err := ecsClient.ListClusters(context.TODO(), nil)
if err != nil {
return nil, err
}
var ecsClusterArns []string
ecsClusterArns = append(ecsClusterArns, resultListClusters.ClusterArns...)
describedClusters, err := DescribeEcsClusters(ecsClient, ecsClusterArns)
if err != nil {
return nil, err
}
var detailedClusters []EcsClusterResp
for _, cluster := range describedClusters.Clusters {
c := &EcsClusterResp{ClusterName: *cluster.ClusterName, Status: *cluster.Status, RunningTasksCount: fmt.Sprint(cluster.RunningTasksCount), ClusterArn: *cluster.ClusterArn}
detailedClusters = append(detailedClusters, *c)
}
return detailedClusters, nil

}

func DescribeEcsClusters(ecsClient *ecs.Client, clusters []string) (ecs.DescribeClustersOutput, error) {
detailedClusters, err := ecsClient.DescribeClusters(context.TODO(), &ecs.DescribeClustersInput{Clusters: clusters})
if err != nil {
return ecs.DescribeClustersOutput{}, err
}
return *detailedClusters, nil

}

func GetClusterJSONResponse(cfg aws.Config, clusterName string) (string, error) {
ecsClient := ecs.NewFromConfig(cfg)
// Describe the specific cluster
describeClustersInput := &ecs.DescribeClustersInput{
Clusters: []string{clusterName},
}
result, err := ecsClient.DescribeClusters(context.TODO(), describeClustersInput)
if err != nil {
return "", err
}
// Check if the cluster was found
if len(result.Clusters) == 0 {
errMessage := fmt.Sprintf("Cluster %s not found", clusterName)
return "", fmt.Errorf(errMessage)
}
// Marshal the cluster into a JSON string
jsonResponse, err := json.MarshalIndent(result.Clusters[0], "", " ")
if err != nil {
return "", err
}
return string(jsonResponse), nil
}

// --- ECS Services ---

func ListEcsServices(cfg aws.Config, clusterName string) ([]EcsServiceResp, error) {
ecsClient := ecs.NewFromConfig(cfg)
listServicesInput := &ecs.ListServicesInput{
Cluster: &clusterName,
}

result, err := ecsClient.ListServices(context.TODO(), listServicesInput)
if err != nil {
return nil, err
}
var ecsServiceArns []string
ecsServiceArns = append(ecsServiceArns, result.ServiceArns...)
describedServices, err := DescribeEcsServices(ecsClient, clusterName, ecsServiceArns)
if err != nil {
return nil, err
}

var detailedServices []EcsServiceResp
for _, service := range describedServices.Services {
s := &EcsServiceResp{
ServiceName: *service.ServiceName,
Status: *service.Status,
DesiredCount: fmt.Sprint(service.DesiredCount),
RunningCount: fmt.Sprint(service.RunningCount),
TaskDefinition: *service.TaskDefinition,
ServiceArn: *service.ServiceArn,
}
detailedServices = append(detailedServices, *s)
}

return detailedServices, nil
}

func DescribeEcsServices(ecsClient *ecs.Client, clusterName string, serviceArns []string) (ecs.DescribeServicesOutput, error) {
describeServicesInput := &ecs.DescribeServicesInput{
Cluster: &clusterName,
Services: serviceArns,
}
result, err := ecsClient.DescribeServices(context.TODO(), describeServicesInput)
if err != nil {
return ecs.DescribeServicesOutput{}, err
}
return *result, nil
}

func GetEcsServiceJSONResponse(cfg aws.Config, clusterName, serviceName string) (string, error) {
ecsClient := ecs.NewFromConfig(cfg)
// Describe the specific service within the cluster
describeServicesInput := &ecs.DescribeServicesInput{
Cluster: &clusterName,
Services: []string{serviceName},
}
result, err := ecsClient.DescribeServices(context.TODO(), describeServicesInput)
if err != nil {
return "", err
}
// Check if the service was found
if len(result.Services) == 0 {
errMessage := fmt.Sprintf("Service %s not found in cluster %s", serviceName, clusterName)
return "", fmt.Errorf(errMessage)
}
// Marshal the service into a JSON string
jsonResponse, err := json.MarshalIndent(result.Services[0], "", " ")
if err != nil {
return "", err
}
return string(jsonResponse), nil
}

// --- ECS Tasks ---

func ListEcsTasks(cfg aws.Config, clusterName, serviceName string) ([]EcsTaskResp, error) {
ecsClient := ecs.NewFromConfig(cfg)
taskDetails, err := DescribeEcsTasksForService(ecsClient, clusterName, serviceName)
if err != nil {
return nil, err
}
tasks := make([]EcsTaskResp, len(taskDetails.Tasks))
for i, task := range taskDetails.Tasks {
t := &EcsTaskResp{
TaskId: GetTaskIDFromArn(*task.TaskArn),
Task: &task,
}
tasks[i] = *t
}
return tasks, nil
}

func DescribeEcsTasksForService(ecsClient *ecs.Client, clusterName, serviceName string) (*ecs.DescribeTasksOutput, error) {
listTasksInput := &ecs.ListTasksInput{
Cluster: &clusterName,
ServiceName: &serviceName,
}

result, err := ecsClient.ListTasks(context.TODO(), listTasksInput)
if err != nil {
return nil, err
}

describeTasksInput := &ecs.DescribeTasksInput{
Cluster: &clusterName,
Tasks: result.TaskArns,
}

taskDetails, err := ecsClient.DescribeTasks(context.TODO(), describeTasksInput)
if err != nil {
return nil, err
}

return taskDetails, nil
}

func GetTaskJSONResponse(cfg aws.Config, clusterName, taskArn string) (string, error) {
ecsClient := ecs.NewFromConfig(cfg)
describeTasksInput := &ecs.DescribeTasksInput{
Cluster: &clusterName,
Tasks: []string{GetTaskIDFromArn(taskArn)},
}
taskDetails, err := ecsClient.DescribeTasks(context.TODO(), describeTasksInput)
if err != nil {
return "", err
}
// Convert containers to JSON
jsonResponse, err := json.MarshalIndent(taskDetails.Tasks[0], "", " ")
if err != nil {
return "", err
}
return string(jsonResponse), nil
}

// --- ECS Containers ---

func ListContainersForTask(cfg aws.Config, clusterName, taskId string) ([]types.Container, error) {
ecsClient := ecs.NewFromConfig(cfg)
describeTasksInput := &ecs.DescribeTasksInput{
Cluster: &clusterName,
Tasks: []string{taskId},
}
taskDetails, err := ecsClient.DescribeTasks(context.TODO(), describeTasksInput)
if err != nil {
return nil, err
}
if len(taskDetails.Tasks) == 0 {
return nil, fmt.Errorf("task with ID %s not found", taskId)
}
return taskDetails.Tasks[0].Containers, nil
}

func GetECSContainerJsonResponse(cfg aws.Config, clusterName, taskId, runtimeId string) (string, error) {
ecsClient := ecs.NewFromConfig(cfg)
describeTasksInput := &ecs.DescribeTasksInput{
Cluster: &clusterName,
Tasks: []string{taskId},
}
taskDetails, err := ecsClient.DescribeTasks(context.Background(), describeTasksInput)
if err != nil {
return "", err
}
for _, container := range taskDetails.Tasks[0].Containers {
if *container.RuntimeId == runtimeId {
jsonResponse, err := json.MarshalIndent(container, "", " ")
if err != nil {
return "", err
}
return string(jsonResponse), nil
}
}
return "", fmt.Errorf("container %s not found in task %s", runtimeId, taskId)
}

func GetTaskIDFromArn(taskArn string) string {
parts := strings.Split(taskArn, "/")
return parts[len(parts)-1]
}
24 changes: 23 additions & 1 deletion internal/aws/types.go
@@ -1,6 +1,7 @@
package aws

import (
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/aws-sdk-go/service/ec2"
)
Expand All @@ -17,7 +18,7 @@ type EC2Resp struct {
}

type S3Object struct {
SizeInBytes int64
SizeInBytes int64
Name, ObjectType, LastModified, Size, StorageClass string
}

Expand Down Expand Up @@ -131,3 +132,24 @@ type SGResp struct {
OwnerId string
VpcId string
}

type EcsClusterResp struct {
ClusterName string
Status string
ClusterArn string
RunningTasksCount string
}

type EcsServiceResp struct {
ServiceName string
Status string
DesiredCount string
RunningCount string
TaskDefinition string
ServiceArn string
}

type EcsTaskResp struct {
TaskId string
*ecsTypes.Task
}
1 change: 1 addition & 0 deletions internal/config/alias.go
Expand Up @@ -155,6 +155,7 @@ func (a *Aliases) loadDefaultAliases(cloud string) {
}
a.declare(internal.Help, internal.QuestionMark, internal.LowercaseH)
a.declare(internal.Quit, internal.LowercaseQ, internal.QFactorial, internal.UppercaseQ)
a.declare(internal.LowercaseEcsCluster, internal.UppercaseEcsCluster)
// a.declare(internal.Alias,internal.Aliases, internal.LowercaseA)
}

Expand Down

0 comments on commit 63660b4

Please sign in to comment.