github.com/kolanos/fargate@v0.2.3/cmd/root.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/aws/session"
    12  	"github.com/jpignata/fargate/console"
    13  	ECS "github.com/jpignata/fargate/ecs"
    14  	"github.com/spf13/cobra"
    15  	"golang.org/x/crypto/ssh/terminal"
    16  )
    17  
    18  const (
    19  	version = "0.2.3"
    20  
    21  	defaultClusterName = "fargate"
    22  	defaultRegion      = "us-east-1"
    23  
    24  	typeApplication       string = "application"
    25  	typeNetwork           string = "network"
    26  	protocolHttp          string = "HTTP"
    27  	protocolHttps         string = "HTTPS"
    28  	protocolTcp           string = "TCP"
    29  	mebibytesInGibibyte   int64  = 1024
    30  	validProtocolsPattern string = "(?i)\\ATCP|HTTP(S)?\\z"
    31  	validRuleTypesPattern string = "(?i)^host|path$"
    32  )
    33  
    34  var InvalidCpuAndMemoryCombination = fmt.Errorf(`Invalid CPU and Memory settings
    35  
    36  CPU (CPU Units)    Memory (MiB)
    37  ---------------    ------------
    38  256                512, 1024, or 2048
    39  512                1024 through 4096 in 1GiB increments
    40  1024               2048 through 8192 in 1GiB increments
    41  2048               4096 through 16384 in 1GiB increments
    42  4096               8192 through 30720 in 1GiB increments
    43  `)
    44  
    45  var validRegions = []string{"us-east-1"}
    46  
    47  var (
    48  	clusterName string
    49  	noColor     bool
    50  	region      string
    51  	sess        *session.Session
    52  	verbose     bool
    53  )
    54  
    55  type Port struct {
    56  	Port     int64
    57  	Protocol string
    58  }
    59  
    60  func (p *Port) Empty() bool {
    61  	return p.Port == 0 && p.Protocol == ""
    62  }
    63  
    64  func (p *Port) String() string {
    65  	return fmt.Sprintf("%s:%d", p.Protocol, p.Port)
    66  }
    67  
    68  var rootCmd = &cobra.Command{
    69  	Use:   "fargate",
    70  	Short: "Deploy serverless containers onto the cloud from your command line",
    71  	Long: `Deploy serverless containers onto the cloud from your command line
    72  
    73  fargate is a command-line interface to deploy containers to AWS Fargate that
    74  makes it easy to run containers in AWS as one-off tasks or managed, highly
    75  available services secured by free TLS certificates. It bundles the power of AWS
    76  including Amazon Elastic Container Service (ECS), Amazon Elastic Container
    77  Registry (ECR), Elastic Load Balancing, AWS Certificate Manager, Amazon
    78  CloudWatch Logs, and Amazon Route 53 into an easy-to-use CLI.`,
    79  	PersistentPreRun: func(cmd *cobra.Command, args []string) {
    80  		if cmd.Parent().Name() == "fargate" {
    81  			return
    82  		}
    83  
    84  		if verbose {
    85  			verbose = true
    86  			console.Verbose = true
    87  		}
    88  
    89  		if noColor || !terminal.IsTerminal(int(os.Stdout.Fd())) {
    90  			console.Color = false
    91  		}
    92  
    93  		envAwsDefaultRegion := os.Getenv("AWS_DEFAULT_REGION")
    94  		envAwsRegion := os.Getenv("AWS_REGION")
    95  
    96  		if region == "" {
    97  			if envAwsDefaultRegion != "" {
    98  				region = envAwsDefaultRegion
    99  			} else if envAwsRegion != "" {
   100  				region = envAwsRegion
   101  			} else {
   102  				region = defaultRegion
   103  			}
   104  		}
   105  
   106  		for _, validRegion := range validRegions {
   107  			if region == validRegion {
   108  				break
   109  			}
   110  
   111  			console.IssueExit("Invalid region: %s [valid regions: %s]", region, strings.Join(validRegions, ", "))
   112  		}
   113  
   114  		sess = session.Must(
   115  			session.NewSession(
   116  				&aws.Config{
   117  					Region: aws.String(region),
   118  				},
   119  			),
   120  		)
   121  
   122  		_, err := sess.Config.Credentials.Get()
   123  
   124  		if aerr, ok := err.(awserr.Error); ok {
   125  			switch aerr.Code() {
   126  			case "NoCredentialProviders":
   127  				console.Issue("Could not find your AWS credentials")
   128  				console.Info("Your AWS credentials could not be found. Please configure your environment with your access key")
   129  				console.Info("   ID and secret access key using either the shared configuration file or environment variables.")
   130  				console.Info("   See http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials")
   131  				console.Info("   for more details.")
   132  				console.Exit(1)
   133  			default:
   134  				console.ErrorExit(err, "Could not create create AWS session")
   135  			}
   136  		}
   137  
   138  		if clusterName == "" {
   139  			clusterName = defaultClusterName
   140  			ecs := ECS.New(sess, clusterName)
   141  
   142  			ecs.CreateCluster()
   143  		}
   144  	},
   145  }
   146  
   147  func Execute() {
   148  	rootCmd.Version = version
   149  	rootCmd.Execute()
   150  }
   151  
   152  func init() {
   153  	rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output")
   154  	rootCmd.PersistentFlags().StringVar(&region, "region", "", `AWS region (default "us-east-1")`)
   155  	rootCmd.PersistentFlags().BoolVar(&noColor, "no-color", false, "Disable color output")
   156  	rootCmd.PersistentFlags().StringVar(&clusterName, "cluster", "", `ECS cluster name (default "fargate")`)
   157  }
   158  
   159  func inflatePort(src string) (port Port) {
   160  	ports := inflatePorts([]string{src})
   161  	return ports[0]
   162  }
   163  
   164  func inflatePorts(src []string) (ports []Port) {
   165  	for _, portRaw := range src {
   166  		if portRaw == "80" {
   167  			ports = append(ports,
   168  				Port{
   169  					Port:     80,
   170  					Protocol: "HTTP",
   171  				},
   172  			)
   173  		} else if portRaw == "443" {
   174  			ports = append(ports,
   175  				Port{
   176  					Port:     443,
   177  					Protocol: "HTTPS",
   178  				},
   179  			)
   180  		} else if strings.Index(portRaw, ":") > 1 {
   181  			portRawContents := strings.Split(portRaw, ":")
   182  			protocol := strings.ToUpper(portRawContents[0])
   183  			port, err := strconv.ParseInt(portRawContents[1], 10, 64)
   184  
   185  			if err != nil {
   186  				console.ErrorExit(err, "Invalid command line flags")
   187  			}
   188  
   189  			ports = append(ports,
   190  				Port{
   191  					Port:     port,
   192  					Protocol: protocol,
   193  				},
   194  			)
   195  		} else {
   196  			port, err := strconv.ParseInt(portRaw, 10, 64)
   197  
   198  			if err != nil {
   199  				console.ErrorExit(err, "Invalid command line flags")
   200  			}
   201  
   202  			ports = append(ports,
   203  				Port{
   204  					Port:     port,
   205  					Protocol: "TCP",
   206  				},
   207  			)
   208  		}
   209  	}
   210  
   211  	return
   212  }
   213  
   214  func extractEnvVars(inputEnvVars []string) []ECS.EnvVar {
   215  	var envVars []ECS.EnvVar
   216  
   217  	if len(inputEnvVars) == 0 {
   218  		return envVars
   219  	}
   220  
   221  	for _, inputEnvVar := range inputEnvVars {
   222  		splitInputEnvVar := strings.SplitN(inputEnvVar, "=", 2)
   223  
   224  		if len(splitInputEnvVar) != 2 {
   225  			console.ErrorExit(fmt.Errorf("%s must be in the form of KEY=value", inputEnvVar), "Invalid environment variable")
   226  		}
   227  
   228  		envVar := ECS.EnvVar{
   229  			Key:   strings.ToUpper(splitInputEnvVar[0]),
   230  			Value: splitInputEnvVar[1],
   231  		}
   232  
   233  		envVars = append(envVars, envVar)
   234  	}
   235  
   236  	return envVars
   237  }
   238  
   239  func validateCpuAndMemory(inputCpuUnits, inputMebibytes string) error {
   240  	cpuUnits, err := strconv.ParseInt(inputCpuUnits, 10, 16)
   241  
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	mebibytes, err := strconv.ParseInt(inputMebibytes, 10, 16)
   247  
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	switch cpuUnits {
   253  	case 256:
   254  		if mebibytes == 512 || validateMebibytes(mebibytes, 1024, 2048) {
   255  			return nil
   256  		}
   257  	case 512:
   258  		if validateMebibytes(mebibytes, 1024, 4096) {
   259  			return nil
   260  		}
   261  	case 1024:
   262  		if validateMebibytes(mebibytes, 2048, 8192) {
   263  			return nil
   264  		}
   265  	case 2048:
   266  		if validateMebibytes(mebibytes, 4096, 16384) {
   267  			return nil
   268  		}
   269  	case 4096:
   270  		if validateMebibytes(mebibytes, 8192, 30720) {
   271  			return nil
   272  		}
   273  	}
   274  
   275  	return InvalidCpuAndMemoryCombination
   276  }
   277  
   278  func validateMebibytes(mebibytes, min, max int64) bool {
   279  	return mebibytes >= min && mebibytes <= max && mebibytes%mebibytesInGibibyte == 0
   280  }