├── .github └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── cmd └── root.go ├── ecs ├── api.go └── ecs.go ├── go.mod ├── go.sum ├── main.go ├── print.gif └── write.gif /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - 21 | name: Fetch all tags 22 | run: git fetch --force --tags 23 | - 24 | name: Set up Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: 1.17 28 | - 29 | name: Run GoReleaser 30 | uses: goreleaser/goreleaser-action@v2 31 | with: 32 | distribution: goreleaser 33 | version: ${{ env.GITHUB_REF_NAME }} 34 | args: release --rm-dist 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: ecsnv 3 | goos: 4 | - darwin 5 | - linux 6 | goarch: 7 | - amd64 8 | - arm64 9 | env: 10 | - CGO_ENABLED=0 11 | 12 | release: 13 | prerelease: auto 14 | 15 | universal_binaries: 16 | - replace: true 17 | 18 | brews: 19 | - 20 | name: ecsnv 21 | homepage: https://github.com/dineshgowda24/ecsnv 22 | tap: 23 | owner: dineshgowda24 24 | name: homebrew-dineshgowda 25 | commit_author: 26 | name: dineshgowda24 27 | email: dinesh240894@gmail.com 28 | 29 | checksum: 30 | name_template: 'checksums.txt' 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dinesh Gowda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ecsnv 2 | 3 | [![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/gomods/athens.svg)](https://github.com/gomods/athens) 4 | 5 | A lightweight utility to dump AWS Fargate's ECS containers environment variables locally 6 | 7 | ## Motivation 8 | 9 | More often than not, during the development cycle. I keep looking at my AWS ECS cluster and services to check what all environment 10 | variables have been set. Some ENVs were not readily available in the project repository since they were configured as secrets in SSM's parameter store. 11 | 12 | While setting up an application or debugging some issue, I kept looking for env values for a set in different environments by logging in to the AWS console, which is painstakingly slow and hampers productivity. 13 | 14 | Hence the tool. 15 | 16 | ## Installation 17 | 18 | ### Homebrew 19 | 20 | ```shell 21 | brew tap dineshgowda24/dineshgowda 22 | brew install ecsnv 23 | ``` 24 | 25 | ### Go 26 | 27 | ```shell 28 | go install github.com/dineshgowda24/ecsnv@latest 29 | ``` 30 | 31 | ## Usage 32 | 33 | 1. Fetching ENVs for a specific cluster and service 34 | 35 | 1. Prints locally in the shell 36 | 37 | ```shell 38 | ecsnv --cluster --service 39 | ``` 40 | 41 | 2. Writes to a file 42 | 43 | ```shell 44 | ecsnv --cluster --service --file 45 | ``` 46 | 47 | 2. Fetching ENVs for a specific cluster and service from a particular profile 48 | 49 | If a profile is not passed, then the default profile is used. 50 | 51 | 1. Prints locally in the shell 52 | 53 | ```shell 54 | ecsnv --cluster --service --profile 55 | ``` 56 | 57 | 2. Writes to a file 58 | 59 | ```shell 60 | ecsnv --cluster --service --file --profile 61 | ``` 62 | 63 | 3. You can ignore the cluster or service name. The application will list all the clusters and services in the profile with an easy-to-use interactive shell. 64 | 65 | 1. Prints locally in the shell 66 | 67 | ```shell 68 | ecsnv 69 | ``` 70 | 71 | 2. Writes to a file 72 | 73 | ```shell 74 | ecsnv --file 75 | ``` 76 | 77 | ## Demo 78 | 79 | ### Printing ENVs in terminal 80 | 81 | ![](print.gif) 82 | 83 | ### Writing ENVs to file 84 | 85 | ![](write.gif) 86 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/dineshgowda24/ecsnv/ecs" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // rootCmd represents the base command when called without any subcommands 13 | var rootCmd = &cobra.Command{ 14 | Use: "ecsnv", 15 | Short: "Load AWS ECS envs locally", 16 | Long: `Application lets you download your AWS ECS envs locally. 17 | The envs can be downloaded into a file or can be exported in the current terminal 18 | session.`, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | cluster, err := cmd.Flags().GetString("cluster") 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | service, err := cmd.Flags().GetString("service") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | if len(service) > 0 && len(cluster) < 1 { 31 | fmt.Println(`Error: required flag(s) "cluster" not set. Service should be paired with cluster.`) 32 | cmd.Help() 33 | return 34 | } 35 | 36 | file, err := cmd.Flags().GetString("file") 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | profile, err := cmd.Flags().GetString("profile") 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | ecs.Run(cluster, service, file, profile) 47 | }, 48 | } 49 | 50 | // Execute adds all child commands to the root command and sets flags appropriately. 51 | // This is called by main.main(). It only needs to happen once to the rootCmd. 52 | func Execute() { 53 | err := rootCmd.Execute() 54 | if err != nil { 55 | os.Exit(1) 56 | } 57 | } 58 | 59 | func init() { 60 | rootCmd.PersistentFlags().StringP("cluster", "c", "", "ECS cluster name") 61 | rootCmd.PersistentFlags().StringP("service", "s", "", "ECS service name, service can be paired with cluster") 62 | rootCmd.PersistentFlags().StringP("file", "f", "", "File to export") 63 | rootCmd.PersistentFlags().StringP("profile", "p", "", "AWS profile(overrides the default profile set in terminal)") 64 | } 65 | -------------------------------------------------------------------------------- /ecs/api.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/ecs" 12 | ) 13 | 14 | // AWSClient encapsulates serveral helper method to retrive data from AWS cloud 15 | type AWSClient struct { 16 | profile string 17 | sn *session.Session 18 | } 19 | 20 | // NewAWSClient returns a new AWSClient 21 | func NewAWSClient(profile string) (*AWSClient, error) { 22 | sn, err := session.NewSessionWithOptions(session.Options{ 23 | Profile: profile, 24 | SharedConfigState: session.SharedConfigEnable, 25 | }) 26 | 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | return &AWSClient{ 32 | profile: profile, 33 | sn: sn, 34 | }, nil 35 | } 36 | 37 | // GetECSClusters returns a list of ecs clusters in a region 38 | func (a *AWSClient) GetECSClusters() ([]string, error) { 39 | svc := ecs.New(a.sn) 40 | maxResults := int64(100) 41 | 42 | input := &ecs.ListClustersInput{ 43 | MaxResults: &maxResults, 44 | } 45 | var clusters []string 46 | var nextToken string 47 | for { 48 | if nextToken != "" { 49 | input.NextToken = &nextToken 50 | } 51 | 52 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 53 | defer cancel() 54 | 55 | result, err := svc.ListClustersWithContext(ctx, input) 56 | if err != nil { 57 | return []string{}, err 58 | } 59 | 60 | for _, arn := range result.ClusterArns { 61 | cluster := strings.Split((*arn), "cluster/")[1] 62 | clusters = append(clusters, cluster) 63 | } 64 | 65 | if result.NextToken == nil { 66 | break 67 | } else { 68 | nextToken = *result.NextToken 69 | } 70 | 71 | } 72 | 73 | return clusters, nil 74 | } 75 | 76 | // GetECSServices returns a list of ecs services for a cluster in a region 77 | func (a *AWSClient) GetECSServices(cluster string) ([]string, error) { 78 | svc := ecs.New(a.sn) 79 | maxResults := int64(100) 80 | 81 | input := &ecs.ListServicesInput{ 82 | Cluster: &cluster, 83 | MaxResults: &maxResults, 84 | } 85 | var services []string 86 | var nextToken string 87 | for { 88 | if nextToken != "" { 89 | input.NextToken = &nextToken 90 | } 91 | 92 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 93 | defer cancel() 94 | 95 | result, err := svc.ListServicesWithContext(ctx, input) 96 | if err != nil { 97 | return []string{}, err 98 | } 99 | 100 | for _, arn := range result.ServiceArns { 101 | service := strings.Split((*arn), fmt.Sprintf("/%v/", cluster))[1] 102 | services = append(services, service) 103 | } 104 | 105 | if result.NextToken == nil { 106 | break 107 | } else { 108 | nextToken = *result.NextToken 109 | } 110 | 111 | } 112 | 113 | return services, nil 114 | } 115 | 116 | // GetECSTaskDef returns the current task definition for a given cluster and service 117 | func (a *AWSClient) GetECSTaskDef(cluster, service string) (string, error) { 118 | svc := ecs.New(a.sn) 119 | input := &ecs.DescribeServicesInput{ 120 | Cluster: &cluster, 121 | Services: []*string{&service}, 122 | } 123 | 124 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 125 | defer cancel() 126 | 127 | result, err := svc.DescribeServicesWithContext(ctx, input) 128 | if err != nil { 129 | return "", err 130 | } 131 | 132 | return *result.Services[0].TaskDefinition, nil 133 | } 134 | 135 | // GetENVsFromECSTaskDef returns enviroment variable in a task definition 136 | func (a *AWSClient) GetENVsFromECSTaskDef(taskDef string) (map[string]string, error) { 137 | svc := ecs.New(a.sn) 138 | 139 | input := &ecs.DescribeTaskDefinitionInput{ 140 | TaskDefinition: &taskDef, 141 | } 142 | 143 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 144 | defer cancel() 145 | 146 | result, err := svc.DescribeTaskDefinitionWithContext(ctx, input) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | if result.TaskDefinition == nil { 152 | return nil, errors.New("missing task definitions") 153 | } 154 | 155 | containerDefs := result.TaskDefinition.ContainerDefinitions 156 | if containerDefs == nil { 157 | return nil, errors.New("missing container definitions") 158 | } 159 | 160 | containerDef := containerDefs[0] 161 | envs := make(map[string]string) 162 | 163 | for _, kvPair := range containerDef.Environment { 164 | envs[*kvPair.Name] = *kvPair.Value 165 | } 166 | 167 | return envs, nil 168 | } 169 | -------------------------------------------------------------------------------- /ecs/ecs.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/manifoldco/promptui" 9 | ) 10 | 11 | func Run(cluster, service, file, profile string) { 12 | awsClient, err := NewAWSClient(profile) 13 | if err != nil { 14 | log.Fatalln(err) 15 | } 16 | 17 | if cluster == "" { 18 | clusters, err := awsClient.GetECSClusters() 19 | if err != nil { 20 | log.Fatalln(err) 21 | } 22 | 23 | if len(clusters) == 0 { 24 | log.Println("No clusters found") 25 | } 26 | 27 | cluster = prompt("Select cluster", clusters) 28 | } 29 | 30 | if service == "" { 31 | services, err := awsClient.GetECSServices(cluster) 32 | if err != nil { 33 | log.Fatalln(err) 34 | } 35 | 36 | if len(services) == 0 { 37 | log.Println("No services found") 38 | } 39 | 40 | service = prompt("Select service", services) 41 | } 42 | 43 | taskDef, err := awsClient.GetECSTaskDef(cluster, service) 44 | if err != nil { 45 | log.Fatalln(err) 46 | } 47 | 48 | envs, err := awsClient.GetENVsFromECSTaskDef(taskDef) 49 | if err != nil { 50 | log.Fatalln(err) 51 | } 52 | 53 | if file != "" { 54 | write(envs, file) 55 | } else { 56 | print(envs) 57 | } 58 | } 59 | 60 | // print prints to STDOUT 61 | func print(envs map[string]string) { 62 | for key, value := range envs { 63 | fmt.Printf("%v=%v\n", key, value) 64 | } 65 | } 66 | 67 | // writes the envs to file 68 | func write(envs map[string]string, file string) { 69 | f, err := os.Create(file) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | defer f.Close() 75 | 76 | for key, value := range envs { 77 | _, err := f.WriteString(fmt.Sprintf("%v=%v\n", key, value)) 78 | 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | } 83 | 84 | pwd, err := os.Getwd() 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | fmt.Printf("%v envs written to %v/%v\n", len(envs), pwd, file) 90 | } 91 | 92 | // prompt displays a terminal prompt 93 | func prompt(label string, items []string) string { 94 | pmt := promptui.Select{ 95 | Label: label, 96 | Items: items, 97 | Size: 25, 98 | } 99 | 100 | _, value, _ := pmt.Run() 101 | return value 102 | } 103 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dineshgowda24/ecsnv 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.44.209 7 | github.com/manifoldco/promptui v0.9.0 8 | github.com/spf13/cobra v1.6.1 9 | ) 10 | 11 | require ( 12 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect 13 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 14 | github.com/jmespath/go-jmespath v0.4.0 // indirect 15 | github.com/spf13/pflag v1.0.5 // indirect 16 | golang.org/x/sys v0.1.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.44.209 h1:wZuiaA4eaqYZmoZXqGgNHqVD7y7kUGFvACDGBgowTps= 2 | github.com/aws/aws-sdk-go v1.44.209/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 3 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 4 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 5 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 7 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 8 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 10 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 13 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 14 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 15 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 16 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 17 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 18 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= 19 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= 20 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 21 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 24 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 25 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 26 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 27 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 28 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 29 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 32 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 33 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 34 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 35 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 36 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 46 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 48 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 49 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 52 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 53 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 54 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 56 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 57 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 60 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 61 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 62 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/dineshgowda24/ecsnv/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /print.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineshgowda24/ecsnv/a5edff5f52fe5ddc00138a3800f1a04f8bf032ab/print.gif -------------------------------------------------------------------------------- /write.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dineshgowda24/ecsnv/a5edff5f52fe5ddc00138a3800f1a04f8bf032ab/write.gif --------------------------------------------------------------------------------