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  }