github.com/jenkins-x/jx/v2@v2.1.155/pkg/cloud/amazon/eks/eks.go (about) 1 package eks 2 3 import ( 4 "fmt" 5 "os/exec" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/aws/client" 9 10 session2 "github.com/jenkins-x/jx/v2/pkg/cloud/amazon/session" 11 12 "github.com/aws/aws-sdk-go/aws" 13 "github.com/aws/aws-sdk-go/service/cloudformation" 14 "github.com/aws/aws-sdk-go/service/eks" 15 "github.com/aws/aws-sdk-go/service/eks/eksiface" 16 "github.com/jenkins-x/jx/v2/pkg/cluster" 17 "github.com/pkg/errors" 18 ) 19 20 // eksAPIHandler contains some functions to interact with and serves as an abstraction of the EKS API 21 type eksAPIHandler struct { 22 eks eksiface.EKSAPI 23 } 24 25 // NewEKSAPIHandler will return an eksAPIHandler with configured credentials 26 func NewEKSAPIHandler(awsSession client.ConfigProvider, eksapi ...eksiface.EKSAPI) (*eksAPIHandler, error) { 27 if len(eksapi) == 1 { 28 return &eksAPIHandler{ 29 eks: eksapi[0], 30 }, nil 31 } 32 return &eksAPIHandler{ 33 eks: eks.New(awsSession), 34 }, nil 35 } 36 37 // EksClusterExists checks if EKS cluster with given name exists in given region. 38 func (e *eksAPIHandler) EksClusterExists(clusterName string, profile string, region string) (bool, error) { 39 region, err := session2.ResolveRegion(profile, region) 40 if err != nil { 41 return false, err 42 } 43 cmd := exec.Command("eksctl", "get", "cluster", "--region", region) //nolint:gosec 44 output, err := cmd.CombinedOutput() 45 if err != nil { 46 return false, err 47 } 48 for i, line := range strings.Split(string(output), "\n") { 49 if i == 0 { 50 continue 51 } 52 if strings.HasPrefix(line, clusterName+"\t") { 53 return true, nil 54 } 55 } 56 57 return false, nil 58 } 59 60 // DescribeCluster will attempt to describe the given cluster and return a simplified cluster.Cluster struct 61 func (e *eksAPIHandler) DescribeCluster(clusterName string) (*cluster.Cluster, string, error) { 62 output, err := e.eks.DescribeCluster(&eks.DescribeClusterInput{ 63 Name: aws.String(clusterName), 64 }) 65 if err != nil { 66 return nil, "", err 67 } 68 cl := &cluster.Cluster{ 69 Name: *output.Cluster.Name, 70 Labels: aws.StringValueMap(output.Cluster.Tags), 71 Status: *output.Cluster.Status, 72 } 73 74 if output.Cluster.Endpoint != nil { 75 cl.Location = *output.Cluster.Endpoint 76 } 77 78 return cl, *output.Cluster.Arn, err 79 } 80 81 // ListClusters will list all clusters existing in configured region and describe each one to return enhanced data 82 func (e *eksAPIHandler) ListClusters() ([]*cluster.Cluster, error) { 83 var nextToken *string = nil 84 var clusters []*cluster.Cluster 85 for { 86 output, err := e.eks.ListClusters(&eks.ListClustersInput{ 87 NextToken: nextToken, 88 }) 89 if err != nil { 90 return nil, err 91 } 92 93 for _, c := range output.Clusters { 94 describeClusters, _, err := e.DescribeCluster(*c) 95 if err != nil { 96 return nil, err 97 } 98 clusters = append(clusters, describeClusters) 99 } 100 101 if output.NextToken == nil { 102 return clusters, err 103 } 104 105 if output.NextToken != nil { 106 nextToken = output.NextToken 107 } 108 } 109 } 110 111 func (e eksAPIHandler) GetClusterAsEKSCluster(clusterName string) (*eks.Cluster, error) { 112 output, err := e.eks.DescribeCluster(&eks.DescribeClusterInput{ 113 Name: aws.String(clusterName), 114 }) 115 if err != nil { 116 return nil, errors.Wrapf(err, "error describing EKS cluster %s", clusterName) 117 } 118 return output.Cluster, nil 119 } 120 121 // AddTagsToCluster adds tags to an EKS cluster 122 func (e *eksAPIHandler) AddTagsToCluster(clusterName string, tags map[string]*string) error { 123 _, clusterARN, err := e.DescribeCluster(clusterName) 124 if err != nil { 125 return err 126 } 127 _, err = e.eks.TagResource(&eks.TagResourceInput{ 128 ResourceArn: aws.String(clusterARN), 129 Tags: tags, 130 }) 131 if err != nil { 132 return err 133 } 134 return nil 135 } 136 137 // EksClusterObsoleteStackExists detects if there is obsolete CloudFormation stack for given EKS cluster. 138 // 139 // If EKS cluster creation process is interrupted, there will be CloudFormation stack in ROLLBACK_COMPLETE state left. 140 // Such dead stack prevents eksctl from creating cluster with the same name. This is common activity then to remove stacks 141 // like this and this function performs this action. 142 func (e *eksAPIHandler) EksClusterObsoleteStackExists(clusterName string, profile string, region string) (bool, error) { 143 session, err := session2.NewAwsSession(profile, region) 144 if err != nil { 145 return false, err 146 } 147 cloudformationService := cloudformation.New(session) 148 stacks, err := cloudformationService.ListStacks(&cloudformation.ListStacksInput{ 149 StackStatusFilter: []*string{aws.String("ROLLBACK_COMPLETE")}, 150 }) 151 if err != nil { 152 return false, err 153 } 154 for _, stack := range stacks.StackSummaries { 155 if *stack.StackName == EksctlStackName(clusterName) { 156 return true, nil 157 } 158 } 159 160 return false, nil 161 } 162 163 // CleanUpObsoleteEksClusterStack removes dead eksctl CloudFormation stack associated with given EKS cluster name. 164 func (e *eksAPIHandler) CleanUpObsoleteEksClusterStack(clusterName string, profile string, region string) error { 165 session, err := session2.NewAwsSession(profile, region) 166 if err != nil { 167 return err 168 } 169 cloudformationService := cloudformation.New(session) 170 _, err = cloudformationService.DeleteStack(&cloudformation.DeleteStackInput{ 171 StackName: aws.String(EksctlStackName(clusterName)), 172 }) 173 174 return err 175 } 176 177 // EksctlStackName generates CloudFormation stack name for given EKS cluster name. This function follows eksctl 178 // naming convention. 179 func EksctlStackName(clusterName string) string { 180 return fmt.Sprintf("eksctl-%s-cluster", clusterName) 181 }