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(®ion, "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 }