diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index a686d0d..d38a6e8 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -16,7 +16,7 @@ jobs: name: set up Go uses: actions/setup-go@v1 with: - go-version: 1.15.x + go-version: 1.21.x - name: cache modules uses: actions/cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2f42ad..27f667e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,12 +17,12 @@ jobs: name: set up Go uses: actions/setup-go@v1 with: - go-version: 1.17.x + go-version: 1.21.x - name: run GoReleaser uses: goreleaser/goreleaser-action@v1 with: version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} diff --git a/README.md b/README.md index 880db55..f12cddd 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,27 @@ So that `--cluster` can be set by `ECS_CLUSTER` environmental variable, or `--ta Also, `ecs-tool` exit code is the same as the container exit code. +### runFargate + +The runFargate function is a command that is integrated into the ecs-tool utility. This tool simplifies running commands on an AWS ECS (Elastic Container Service) cluster with Fargate. + +That normany use subnet with 'private' 'Tier' tag but if there is zero proivate subnets that will use 'public' + +``` +ecs-tool runFargate -e "preview" -- env +``` + +### EXEC + +ecs-tool exec Executes a specified command in a running container on an ECS Fargate cluster and get the output. +That function use existing container, so it's faster than runFargate +This command also could connect to fargate existing task: + +``` +ecs-tool exec -e "preview" /bin/sh +``` + + ### SSH 'SSH' access availabe to developers using `ecs-tool ssh` diff --git a/cmd/exec.go b/cmd/exec.go new file mode 100644 index 0000000..e6496a8 --- /dev/null +++ b/cmd/exec.go @@ -0,0 +1,45 @@ +package cmd + +import ( + //"os" + "strings" + + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/springload/ecs-tool/lib" +) + +var execCmd = &cobra.Command{ + Use: "exec", + Short: "Executes a command in an existing ECS Fargate container", + + Long: `Executes a specified command in a running container on an ECS Fargate cluster.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + viper.SetDefault("run.launch_type", "FARGATE") + //var containerName string + var commandArgs []string + if name := viper.GetString("container_name"); name == "" { + commandArgs = args[1:] + } else { + commandArgs = args + } + + // Join the commandArgs to form a single command string + commandString := strings.Join(commandArgs, " ") + + err := lib.ExecFargate( + viper.GetString("profile"), + viper.GetString("cluster"), + commandString, // Pass the combined command string + ) + if err != nil { + log.WithError(err).Error("Can't run task in Fargate mode") + } + }, +} + +func init() { + rootCmd.AddCommand(execCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 00d0860..138fe6e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,12 +51,17 @@ func init() { rootCmd.PersistentFlags().StringP("workdir", "w", "", "Set working directory") rootCmd.PersistentFlags().StringP("image_tag", "", "", "Overrides the docker image tag in all container definitions. Overrides \"--image-tags\" flag.") rootCmd.PersistentFlags().StringSliceP("image_tags", "", []string{}, "Modifies the docker image tags in container definitions. Can be specified several times, one for each container definition. Also takes comma-separated values in one tag. I.e. if there are 2 containers and --image-tags is set once to \"new\", then the image tag of the first container will be modified, leaving the second one untouched. Gets overridden by \"--image-tag\". If you have 3 container definitions and want to modify tags for the 1st and the 3rd, but leave the 2nd unchanged, specify it as \"--image_tags first_tag,,last_tag\".") + rootCmd.PersistentFlags().StringP("task_definition", "t", "", "Name of the ECS task definition to use (required)") + + viper.BindPFlag("profile", rootCmd.PersistentFlags().Lookup("profile")) viper.BindPFlag("cluster", rootCmd.PersistentFlags().Lookup("cluster")) viper.BindPFlag("workdir", rootCmd.PersistentFlags().Lookup("workdir")) viper.BindPFlag("image_tag", rootCmd.PersistentFlags().Lookup("image_tag")) viper.BindPFlag("image_tags", rootCmd.PersistentFlags().Lookup("image_tags")) + viper.BindPFlag("task_definition", rootCmd.PersistentFlags().Lookup("task_definition")) + } diff --git a/cmd/run.go b/cmd/run.go index cc94327..fc353f2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/springload/ecs-tool/lib" + "fmt" ) var runCmd = &cobra.Command{ @@ -18,6 +19,7 @@ It can modify the container command. `, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + viper.SetDefault("run.launch_type", "EC2") var containerName string var commandArgs []string if name := viper.GetString("container_name"); name == "" { @@ -52,9 +54,8 @@ func init() { rootCmd.AddCommand(runCmd) runCmd.PersistentFlags().StringP("log_group", "l", "", "Name of the log group to get output") runCmd.PersistentFlags().StringP("container_name", "", "", "Name of the container to modify parameters for") - runCmd.PersistentFlags().StringP("task_definition", "t", "", "name of task definition to use (required)") viper.BindPFlag("log_group", runCmd.PersistentFlags().Lookup("log_group")) viper.BindPFlag("container_name", runCmd.PersistentFlags().Lookup("container_name")) - viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) - viper.SetDefault("run.launch_type", "EC2") + //viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) + fmt.Println("Default launch_type set to:", viper.GetString("run.launch_type")) } diff --git a/cmd/runFargate.go b/cmd/runFargate.go new file mode 100644 index 0000000..36676ba --- /dev/null +++ b/cmd/runFargate.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "os" + + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/springload/ecs-tool/lib" +) + +var runFargateCmd = &cobra.Command{ + Use: "runFargate", + Short: "Runs a command in Fargate mode", + Long: `Runs the specified command on an ECS cluster, optionally catching its output. + +This command is specifically tailored for future Fargate-specific functionality.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + viper.SetDefault("run.launch_type", "FARGATE") + viper.SetDefault("run.security_group_filter", "ec2") + var containerName string + var commandArgs []string + if name := viper.GetString("container_name"); name == "" { + containerName = args[0] + commandArgs = args[1:] + } else { + containerName = name + commandArgs = args + } + + exitCode, err := lib.RunFargate( + viper.GetString("profile"), + viper.GetString("cluster"), + viper.GetString("run.service"), + viper.GetString("task_definition"), + viper.GetString("image_tag"), + viper.GetStringSlice("image_tags"), + viper.GetString("workdir"), + containerName, + viper.GetString("log_group"), + viper.GetString("run.launch_type"), + viper.GetString("run.security_group_filter"), + commandArgs, + ) + if err != nil { + log.WithError(err).Error("Can't run task in Fargate mode") + } + os.Exit(exitCode) + }, +} + +func init() { + rootCmd.AddCommand(runFargateCmd) +} diff --git a/go.mod b/go.mod index 04198dc..5e0f355 100644 --- a/go.mod +++ b/go.mod @@ -6,31 +6,69 @@ require ( github.com/Shopify/ejson v1.2.1 github.com/apex/log v1.0.0 github.com/aws/aws-sdk-go v1.43.24 + github.com/gorilla/websocket v1.5.1 github.com/imdario/mergo v0.3.11 github.com/spf13/cobra v0.0.3 github.com/spf13/viper v1.0.2 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/crypto v0.14.0 ) require ( github.com/BurntSushi/toml v0.3.1 // indirect + github.com/Songmu/flextime v0.1.0 // indirect + github.com/Songmu/prompter v0.5.1 // indirect + github.com/alecthomas/kong v0.7.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.26.3 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ecs v1.41.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.26.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.20.2 // indirect + github.com/creack/pty v1.1.20 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/fujiwara/ecsta v0.4.5 // indirect + github.com/fujiwara/tracer v1.0.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/itchyny/gojq v0.12.11 // indirect + github.com/itchyny/timefmt-go v0.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/magiconair/properties v1.8.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.3.4 // indirect + github.com/samber/lo v1.36.0 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/afero v1.1.1 // indirect github.com/spf13/cast v1.2.0 // indirect github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect github.com/spf13/pflag v1.0.1 // indirect - github.com/stretchr/testify v1.5.1 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + github.com/stretchr/testify v1.8.0 // indirect + github.com/tkuchiki/go-timezone v0.2.2 // indirect + github.com/tkuchiki/parsetime v0.3.0 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 0dea81f..2c2ef2c 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,55 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Shopify/ejson v1.2.1 h1:Dx0Ipn0mUgrZlzIa5oIUrH0rdSmBOyod/UJmQQK1KHo= github.com/Shopify/ejson v1.2.1/go.mod h1:J8cw5GOA0l/aMOPp+uDfwNYVbeqIaBhzRkv1+76UCvk= +github.com/Songmu/flextime v0.1.0 h1:sss5IALl84LbvU/cS5D1cKNd5ffT94N2BZwC+esgAJI= +github.com/Songmu/flextime v0.1.0/go.mod h1:ofUSZ/qj7f1BfQQ6rEH4ovewJ0SZmLOjBF1xa8iE87Q= +github.com/Songmu/prompter v0.5.1 h1:IAsttKsOZWSDw7bV1mtGn9TAmLFAjXbp9I/eYmUUogo= +github.com/Songmu/prompter v0.5.1/go.mod h1:CS3jEPD6h9IaLaG6afrl1orTgII9+uDWuw95dr6xHSw= +github.com/alecthomas/kong v0.7.0 h1:YIjJUiR7AcmHxL87UlbPn0gyIGwl4+nYND0OQ4ojP7k= +github.com/alecthomas/kong v0.7.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/apex/log v1.0.0 h1:5UWeZC54mWVtOGSCjtuvDPgY/o0QxmjQgvYZ27pLVGQ= github.com/apex/log v1.0.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY= github.com/aws/aws-sdk-go v1.43.24 h1:7c2PniJ0wpmWsIA6OtYBw6wS7DF0IjbhvPq+0ZQYNXw= github.com/aws/aws-sdk-go v1.43.24/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= +github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA= +github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0= +github.com/aws/aws-sdk-go-v2/credentials v1.16.14 h1:mMDTwwYO9A0/JbOCOG7EOZHtYM+o7OfGWfu0toa23VE= +github.com/aws/aws-sdk-go-v2/credentials v1.16.14/go.mod h1:cniAUh3ErQPHtCQGPT5ouvSAQ0od8caTO9OOuufZOAE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0 h1:Rk+Ft0Mu/eiNt2iJ2oS8Gf1h5m6q5crwS8cmlTylnvM= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0/go.mod h1:jZNaJEtn9TLi3pfxycLz79HVkKxP8ZdYm92iaNFgBsA= +github.com/aws/aws-sdk-go-v2/service/ecs v1.41.7 h1:aFdgmJ8G385PVC9mp8b9roGGHU/XbrKEQTbzl6V0GbE= +github.com/aws/aws-sdk-go-v2/service/ecs v1.41.7/go.mod h1:rcFIIrVk3NGCT3BV84HQM3ut+Dr1PO71UvvT8GeLAv4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/sns v1.26.7 h1:DylmW2c1Z7qGxN3Y02k+voPbtM1mh7Rp+gV+7maG5io= +github.com/aws/aws-sdk-go-v2/service/sns v1.26.7/go.mod h1:mLFiISZfiZAqZEfPWUsZBK8gD4dYCKuKAfapV+KrIVQ= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/crackcomm/go-clitable v0.0.0-20151121230230-53bcff2fea36/go.mod h1:XiV36mPegOHv+dlkCSCazuGdQR2BUTgIZ2FKqTTHles= +github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4= +github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,14 +58,26 @@ github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKB github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fujiwara/ecsta v0.4.5 h1:82M3oL6n+eNd3JgPiFOOKkIYq46ggnts2HbuHOtX3rI= +github.com/fujiwara/ecsta v0.4.5/go.mod h1:SwSlCJuhiVrWfpYSaaHUSZVrs6Ey6nJ9V3flWNX7ndk= +github.com/fujiwara/tracer v1.0.2 h1:ztstnson+QwOpO69Jir4nkUKlYgse3vJ28FO2eOUPk0= +github.com/fujiwara/tracer v1.0.2/go.mod h1:r2QzBEBNsW9OhmoVdmTANG+GEmxWNZk7317/mnW2yIw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/itchyny/gojq v0.12.11 h1:YhLueoHhHiN4mkfM+3AyJV6EPcCxKZsOnYf+aVSwaQw= +github.com/itchyny/gojq v0.12.11/go.mod h1:o3FT8Gkbg/geT4pLI0tF3hvip5F3Y/uskjRz9OYa38g= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -29,14 +86,28 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/goveralls v0.0.9/go.mod h1:FRbM1PS8oVsOe9JtdzAAXM+DsvDMMHcM1C7drGJD8HY= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 h1:/rdJjIiKG5rRdwG5yxHmSE/7ZREjpyC0kL7GxGT/qJw= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= +github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= +github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -54,28 +125,90 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q= +github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= +github.com/tkuchiki/parsetime v0.3.0 h1:cvblFQlPeAPJL8g6MgIGCHnnmHSZvluuY+hexoZCNqc= +github.com/tkuchiki/parsetime v0.3.0/go.mod h1:OJkQmIrf5Ao7R+WYIdITPOfDVj8LmnHGCfQ8DTs3LCA= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/exec.go b/lib/exec.go new file mode 100644 index 0000000..685da17 --- /dev/null +++ b/lib/exec.go @@ -0,0 +1,60 @@ +package lib + +import ( + "context" + "fmt" + "github.com/apex/log" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ecs" + "github.com/fujiwara/ecsta" + "os" +) + +var sessionInstance *ecs.Client +var sessionConfig aws.Config // Variable for session configuration + +// InitAWS initializes a new AWS session with the specified profile for Ecsta realization +func InitAWS(profile string) error { + if sessionInstance == nil { + cfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithSharedConfigProfile(profile), + ) + if err != nil { + return fmt.Errorf("failed to load configuration: %w", err) + } + os.Setenv("AWS_PROFILE", profile) //required for aws sdk + sessionInstance = ecs.NewFromConfig(cfg) + sessionConfig = cfg // Save session configuration + } + return nil +} + +// ExecFargate executes a command in a specified container on an ECS Fargate service +func ExecFargate(profile, cluster, command string) error { + + if err := InitAWS(profile); err != nil { + return fmt.Errorf("failed to initialize AWS session: %w", err) + } + + region := sessionConfig.Region // Use the saved region from session configuration + ecstaApp, err := ecsta.New(context.TODO(), region, cluster) + if err != nil { + return fmt.Errorf("failed to create ecsta application: %w", err) + } + + entrypoint := "/usr/bin/ssm-parent" + configPath := "/app/.ssm-parent.yaml" + fullCommand := fmt.Sprintf("%s -c %s run -- %s", entrypoint, configPath, command) + execOpt := ecsta.ExecOption{ + Command: fullCommand, + } + if err := ecstaApp.RunExec(context.Background(), &execOpt); err != nil { + return fmt.Errorf("failed to execute command: %w", err) + } + + log.Info("Command executed successfully") + return nil +} + + diff --git a/lib/run.go b/lib/run.go index f0768b8..4d6e548 100644 --- a/lib/run.go +++ b/lib/run.go @@ -10,12 +10,15 @@ import ( // RunTask runs the specified one-off task in the cluster using the task definition func RunTask(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, args []string) (exitCode int, err error) { + ctx := log.WithFields(log.Fields{ + "task_definition": taskDefinitionName, + "launch_type": launchType, + }) err = makeSession(profile) if err != nil { return 1, err } - ctx := log.WithFields(&log.Fields{"task_definition": taskDefinitionName}) - + svc := ecs.New(localSession) describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ diff --git a/lib/runFargate.go b/lib/runFargate.go new file mode 100644 index 0000000..bb6687a --- /dev/null +++ b/lib/runFargate.go @@ -0,0 +1,222 @@ +package lib + +import ( + "fmt" + "github.com/apex/log" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ec2" + "strings" +) + +// RunFargate runs the specified one-off task in the cluster using the task definition +func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, securityGroupFilter string, args []string) (exitCode int, err error) { + err = makeSession(profile) + if err != nil { + return 1, err + } + ctx := log.WithFields(log.Fields{"task_definition": taskDefinitionName}) + + + svc := ecs.New(localSession) + svcEC2 := ec2.New(localSession) // Assuming makeSession initializes localSession + + // Fetch subnets and security groups + subnets, err := fetchSubnetsByTag(svcEC2, "Tier", "private") + if err != nil { + log.WithError(err).Error("Failed to fetch subnets by private tag") + return 1, err + } + if len(subnets) == 0 { + subnets, err = fetchSubnetsByTag(svcEC2, "Tier", "public") + + if err != nil { + log.WithError(err).Error("Failed to fetch subnets by public tag") + return 1, err + } +} +securityGroups, err := fetchSecurityGroupsByName(svcEC2, securityGroupFilter) + if err != nil { + log.WithError(err).Error("Failed to fetch security groups by name") + return 1, err + } + // Set up network configuration + networkConfiguration := &ecs.NetworkConfiguration{ + AwsvpcConfiguration: &ecs.AwsVpcConfiguration{ + Subnets: subnets, + SecurityGroups: securityGroups, + AssignPublicIp: aws.String("ENABLED"), // or "ENABLED" if public IP is needed + }, + } + + + ctx.WithFields(log.Fields{ + "Cluster": aws.StringValue(aws.String(cluster)), + "TaskDefinition": aws.StringValue(aws.String(taskDefinitionName)), + "LaunchType": aws.StringValue(aws.String(launchType)), + "Subnets": fmt.Sprint(subnets), + "SecurityGroups": fmt.Sprint(securityGroups), + "AssignPublicIP": aws.StringValue(networkConfiguration.AwsvpcConfiguration.AssignPublicIp), +}).Info("Attempting to launch task") + + describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ + TaskDefinition: aws.String(taskDefinitionName), + }) + if err != nil { + ctx.WithError(err).Error("Can't get task definition") + return 1, err + } + taskDefinition := describeResult.TaskDefinition + + var foundContainerName bool + if err := modifyContainerDefinitionImages(imageTag, imageTags, workDir, taskDefinition.ContainerDefinitions, ctx); err != nil { + return 1, err + } + for n, containerDefinition := range taskDefinition.ContainerDefinitions { + if aws.StringValue(containerDefinition.Name) == containerName { + foundContainerName = true + // Use shell execution to interpret the command with any arguments + commandLine := strings.Join(args, " ") // Join args into a single command line + containerDefinition.Command = []*string{aws.String("sh"), aws.String("-c"), aws.String(commandLine)} + if awslogGroup != "" { + containerDefinition.LogConfiguration = &ecs.LogConfiguration{ + LogDriver: aws.String("awslogs"), + Options: map[string]*string{ + "awslogs-region": localSession.Config.Region, + "awslogs-group": aws.String(awslogGroup), + "awslogs-stream-prefix": aws.String(cluster), + }, + } + } + taskDefinition.ContainerDefinitions[n] = containerDefinition // Update the container definition + + } +} + if !foundContainerName { + err := fmt.Errorf("Can't find container with specified name in the task definition") + ctx.WithFields(log.Fields{"container_name": containerName}).Error(err.Error()) + return 1, err + } + + registerResult, err := svc.RegisterTaskDefinition(&ecs.RegisterTaskDefinitionInput{ + ContainerDefinitions: taskDefinition.ContainerDefinitions, + Cpu: taskDefinition.Cpu, + ExecutionRoleArn: taskDefinition.ExecutionRoleArn, + Family: taskDefinition.Family, + Memory: taskDefinition.Memory, + NetworkMode: taskDefinition.NetworkMode, + PlacementConstraints: taskDefinition.PlacementConstraints, + RequiresCompatibilities: taskDefinition.Compatibilities, + TaskRoleArn: taskDefinition.TaskRoleArn, + Volumes: taskDefinition.Volumes, + }) + if err != nil { + ctx.WithError(err).Error("Can't register task definition") + return 1, err + } + ctx.WithField("task_definition_arn", aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn)).Debug("Registered the task definition") + + // Deregister the task definition + defer func() { + _, err = svc.DeregisterTaskDefinition(&ecs.DeregisterTaskDefinitionInput{ + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, + }) + if err != nil { + ctx.WithError(err).Error("Can't deregister task definition") + } + }() + + // Run the task with network configuration + runTaskInput := ecs.RunTaskInput{ + Cluster: aws.String(cluster), + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, + Count: aws.Int64(1), + StartedBy: aws.String("go-deploy"), + LaunchType: aws.String(launchType), + NetworkConfiguration: networkConfiguration, + } + + runResult, err := svc.RunTask(&runTaskInput) + if err != nil { + ctx.WithError(err).Error("Can't run specified task") + return 1, err + } + if len(runResult.Tasks) == 0 { + ctx.Error("No tasks could be run. Please check if the ECS cluster has enough resources") + return 1, err + } + + ctx.Info("Waiting for the task to finish") + var tasks []*string + for _, task := range runResult.Tasks { + tasks = append(tasks, task.TaskArn) + ctx.WithField("task_arn", aws.StringValue(task.TaskArn)).Debug("Started task") + } + tasksInput := &ecs.DescribeTasksInput{ + Cluster: aws.String(cluster), + Tasks: tasks, + } + err = svc.WaitUntilTasksStopped(tasksInput) + if err != nil { + ctx.WithError(err).Error("The waiter has been finished with an error") + exitCode = 3 + return exitCode, err + } + + tasksOutput, err := svc.DescribeTasks(tasksInput) + if err != nil { + ctx.WithError(err).Error("Can't describe stopped tasks") + return 1, err + } + + + + + +for _, task := range tasksOutput.Tasks { + for _, container := range task.Containers { + ctx := log.WithFields(log.Fields{ + "container_name": aws.StringValue(container.Name), + }) + reason := aws.StringValue(container.Reason) + if len(reason) != 0 { + exitCode = 11 + ctx = ctx.WithField("reason", reason) + } else { + ctx = ctx.WithField("exit_code", aws.Int64Value(container.ExitCode)) + + } + if aws.Int64Value(container.ExitCode) == 0 && len(reason) == 0 { + ctx.Info("Container exited") + } else { + ctx.Error("Container exited") + } + + if aws.StringValue(container.Name) == containerName { + if len(reason) == 0 { + exitCode = int(aws.Int64Value(container.ExitCode)) + + if awslogGroup != "" { + // get log output + taskUUID, err := parseTaskUUID(container.TaskArn) + if err != nil { + log.WithFields(log.Fields{"task_arn": aws.StringValue(container.TaskArn)}).WithError(err).Error("Can't parse task uuid") + exitCode = 10 + continue + } + err = fetchCloudWatchLog(cluster, containerName, awslogGroup, taskUUID, false, ctx) + if err != nil { + log.WithError(err).Error("Can't fetch the logs") + exitCode = 10 + } + } + } + } + } + } + + return exitCode, nil +} + + + diff --git a/lib/util.go b/lib/util.go index 032d468..ad6a292 100644 --- a/lib/util.go +++ b/lib/util.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ec2" ) var localSession *session.Session @@ -133,3 +134,50 @@ func modifyContainerDefinitionImages(imageTag string, imageTags []string, workDi } return nil } + +// fetchSubnetsByTag fetches subnet IDs by a specific tag name and value +func fetchSubnetsByTag(svc *ec2.EC2, tagKey, tagValue string) ([]*string, error) { + input := &ec2.DescribeSubnetsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String(fmt.Sprintf("tag:%s", tagKey)), + Values: []*string{aws.String(tagValue)}, + }, + }, + } + + result, err := svc.DescribeSubnets(input) + if err != nil { + return nil, fmt.Errorf("error describing subnets: %w", err) + } + + var subnets []*string + for _, subnet := range result.Subnets { + subnets = append(subnets, subnet.SubnetId) + } + + return subnets, nil +} + + +func fetchSecurityGroupsByName(svc *ec2.EC2, securityGroupFilter string) ([]*string, error) { + // Describe all security groups + input := &ec2.DescribeSecurityGroupsInput{} + + result, err := svc.DescribeSecurityGroups(input) + if err != nil { + return nil, fmt.Errorf("error describing security groups: %w", err) + } + + var securityGroups []*string + // Loop through the security groups and add those that contain the filter in their name + for _, sg := range result.SecurityGroups { + if strings.Contains(*sg.GroupName, securityGroupFilter) { + securityGroups = append(securityGroups, sg.GroupId) + } + } + + // Return the filtered list of security group IDs + return securityGroups, nil +} +