Use docker/api progress writer

Signed-off-by: aiordache <anca.iordache@docker.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
aiordache 2020-08-11 10:43:11 +02:00 committed by Nicolas De Loof
parent 83d65c02a0
commit de99add26b
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
16 changed files with 491 additions and 236 deletions

View File

@ -12,6 +12,7 @@ import (
amazon "github.com/docker/ecs-plugin/pkg/amazon/backend" amazon "github.com/docker/ecs-plugin/pkg/amazon/backend"
"github.com/docker/ecs-plugin/pkg/amazon/cloudformation" "github.com/docker/ecs-plugin/pkg/amazon/cloudformation"
"github.com/docker/ecs-plugin/pkg/docker" "github.com/docker/ecs-plugin/pkg/docker"
"github.com/docker/ecs-plugin/pkg/progress"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -79,7 +80,11 @@ func UpCommand(dockerCli command.Cli, options *composeOptions) *cobra.Command {
if err != nil { if err != nil {
return err return err
} }
return backend.Up(context.Background(), opts)
return progress.Run(context.Background(), func(ctx context.Context) error {
backend.SetWriter(ctx)
return backend.Up(ctx, opts)
})
}), }),
} }
cmd.Flags().StringVar(&opts.loadBalancerArn, "load-balancer", "", "") cmd.Flags().StringVar(&opts.loadBalancerArn, "load-balancer", "", "")
@ -124,7 +129,10 @@ func DownCommand(dockerCli command.Cli, options *composeOptions) *cobra.Command
if err != nil { if err != nil {
return err return err
} }
return backend.Down(context.Background(), opts) return progress.Run(context.Background(), func(ctx context.Context) error {
backend.SetWriter(ctx)
return backend.Down(ctx, opts)
})
}), }),
} }
cmd.Flags().BoolVar(&opts.DeleteCluster, "delete-cluster", false, "Delete cluster") cmd.Flags().BoolVar(&opts.DeleteCluster, "delete-cluster", false, "Delete cluster")
@ -139,7 +147,7 @@ func LogsCommand(dockerCli command.Cli, options *composeOptions) *cobra.Command
if err != nil { if err != nil {
return err return err
} }
return backend.Logs(context.Background(), opts) return backend.Logs(context.Background(), opts, os.Stdout)
}), }),
} }
return cmd return cmd

View File

@ -1,7 +1,6 @@
module github.com/docker/ecs-plugin module github.com/docker/ecs-plugin
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/hcsshim v0.8.7 // indirect github.com/Microsoft/hcsshim v0.8.7 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
@ -10,11 +9,13 @@ require (
github.com/bitly/go-hostpool v0.1.0 // indirect github.com/bitly/go-hostpool v0.1.0 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129
github.com/bugsnag/bugsnag-go v1.5.3 // indirect github.com/bugsnag/bugsnag-go v1.5.3 // indirect
github.com/bugsnag/panicwrap v1.2.0 // indirect github.com/bugsnag/panicwrap v1.2.0 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cloudflare/cfssl v1.4.1 // indirect github.com/cloudflare/cfssl v1.4.1 // indirect
github.com/compose-spec/compose-go v0.0.0-20200811091145-837f8f4de457 github.com/compose-spec/compose-go v0.0.0-20200811091145-837f8f4de457
github.com/containerd/console v1.0.0
github.com/containerd/containerd v1.3.2 // indirect github.com/containerd/containerd v1.3.2 // indirect
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492
@ -36,16 +37,17 @@ require (
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/mitchellh/mapstructure v1.3.3 github.com/mitchellh/mapstructure v1.3.3
github.com/morikuni/aec v1.0.0 // indirect github.com/moby/term v0.0.0-20200611042045-63b9a826fb74
github.com/morikuni/aec v1.0.0
github.com/onsi/ginkgo v1.11.0 // indirect github.com/onsi/ginkgo v1.11.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus v1.6.0
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/theupdateframework/notary v0.6.1 // indirect github.com/theupdateframework/notary v0.6.1 // indirect
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
google.golang.org/grpc v1.27.0 // indirect google.golang.org/grpc v1.27.0 // indirect
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect

View File

@ -34,6 +34,8 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 h1:gfAMKE626QEuKG3si0pdTRcr/YEbBoxY+3GOH3gWvl4=
github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKveP04= github.com/bugsnag/bugsnag-go v1.5.3 h1:yeRUT3mUE13jL1tGwvoQsKdVbAsQx9AJ+fqahKveP04=
github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA= github.com/bugsnag/panicwrap v1.2.0 h1:OzrKrRvXis8qEvOkfcxNcYbOd2O7xXS2nnKMEMABFQA=
@ -54,12 +56,13 @@ github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiK
github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo= github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
github.com/compose-spec/compose-go v0.0.0-20200716130117-e87e4f7839e3 h1:+ntlMTrEcScJjlnEOP8P1IIrusJaR93Eazr66YgUueA=
github.com/compose-spec/compose-go v0.0.0-20200716130117-e87e4f7839e3/go.mod h1:ArodJ6gsEB7iWKrbV3fSHZ08LlBvSVB0Oqg04fX86t4=
github.com/compose-spec/compose-go v0.0.0-20200811091145-837f8f4de457 h1:8ely1LF7H02sIWz6QjgU53YBCiRpYlM9F9u1MeE1ZPk= github.com/compose-spec/compose-go v0.0.0-20200811091145-837f8f4de457 h1:8ely1LF7H02sIWz6QjgU53YBCiRpYlM9F9u1MeE1ZPk=
github.com/compose-spec/compose-go v0.0.0-20200811091145-837f8f4de457/go.mod h1:cS0vAvM6u9yjJgKWIH2yiqYMWO7WGJb+c0Irw+RefqU= github.com/compose-spec/compose-go v0.0.0-20200811091145-837f8f4de457/go.mod h1:cS0vAvM6u9yjJgKWIH2yiqYMWO7WGJb+c0Irw+RefqU=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1 h1:uict5mhHFTzKLUCufdSLym7z/J0CbBJT59lYbP9wtbg=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/console v1.0.0 h1:fU3UuQapBs+zLJu82NhR11Rif1ny2zfMMAyPJzSN5tQ=
github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
@ -76,6 +79,8 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -137,8 +142,7 @@ github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -157,8 +161,6 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -229,11 +231,11 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs=
github.com/moby/term v0.0.0-20200611042045-63b9a826fb74 h1:kvRIeqJNICemq2UFLx8q/Pj+1IRNZS0XPTaMFkuNsvg=
github.com/moby/term v0.0.0-20200611042045-63b9a826fb74/go.mod h1:pJ0Ot5YGdTcMdxnPMyGCfAr6fKXe0g9cDlz16MuFEBE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -401,7 +403,9 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -419,8 +423,8 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdO
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

View File

@ -1,9 +1,12 @@
package backend package backend
import ( import (
"context"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/docker/ecs-plugin/pkg/amazon/sdk" "github.com/docker/ecs-plugin/pkg/amazon/sdk"
"github.com/docker/ecs-plugin/pkg/progress"
) )
func NewBackend(profile string, region string) (*Backend, error) { func NewBackend(profile string, region string) (*Backend, error) {
@ -17,6 +20,7 @@ func NewBackend(profile string, region string) (*Backend, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Backend{ return &Backend{
Region: region, Region: region,
api: sdk.NewAPI(sess), api: sdk.NewAPI(sess),
@ -26,4 +30,9 @@ func NewBackend(profile string, region string) (*Backend, error) {
type Backend struct { type Backend struct {
Region string Region string
api sdk.API api sdk.API
writer progress.Writer
}
func (b *Backend) SetWriter(context context.Context) {
b.writer = progress.ContextWriter(context)
} }

View File

@ -5,7 +5,6 @@ import (
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/cli"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
"github.com/docker/ecs-plugin/pkg/console"
) )
func (b *Backend) Down(ctx context.Context, options *cli.ProjectOptions) error { func (b *Backend) Down(ctx context.Context, options *cli.ProjectOptions) error {
@ -18,13 +17,7 @@ func (b *Backend) Down(ctx context.Context, options *cli.ProjectOptions) error {
if err != nil { if err != nil {
return err return err
} }
return b.WaitStackCompletion(ctx, name, compose.StackDelete)
w := console.NewProgressWriter()
err = b.WaitStackCompletion(ctx, name, compose.StackDelete, w)
if err != nil {
return err
}
return nil
} }
func (b *Backend) projectName(options *cli.ProjectOptions) (string, error) { func (b *Backend) projectName(options *cli.ProjectOptions) (string, error) {

View File

@ -1,8 +1,10 @@
package backend package backend
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
@ -13,7 +15,7 @@ import (
"github.com/docker/ecs-plugin/pkg/console" "github.com/docker/ecs-plugin/pkg/console"
) )
func (b *Backend) Logs(ctx context.Context, options *cli.ProjectOptions) error { func (b *Backend) Logs(ctx context.Context, options *cli.ProjectOptions, writer io.Writer) error {
name := options.Name name := options.Name
if name == "" { if name == "" {
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(options)
@ -26,6 +28,7 @@ func (b *Backend) Logs(ctx context.Context, options *cli.ProjectOptions) error {
err := b.api.GetLogs(ctx, name, &logConsumer{ err := b.api.GetLogs(ctx, name, &logConsumer{
colors: map[string]console.ColorFunc{}, colors: map[string]console.ColorFunc{},
width: 0, width: 0,
writer: writer,
}) })
if err != nil { if err != nil {
return err return err
@ -45,8 +48,10 @@ func (l *logConsumer) Log(service, container, message string) {
l.computeWidth() l.computeWidth()
} }
prefix := fmt.Sprintf("%-"+strconv.Itoa(l.width)+"s |", service) prefix := fmt.Sprintf("%-"+strconv.Itoa(l.width)+"s |", service)
for _, line := range strings.Split(message, "\n") { for _, line := range strings.Split(message, "\n") {
fmt.Printf("%s %s\n", cf(prefix), line) buf := bytes.NewBufferString(fmt.Sprintf("%s %s\n", cf(prefix), line))
l.writer.Write(buf.Bytes())
} }
} }
@ -63,4 +68,5 @@ func (l *logConsumer) computeWidth() {
type logConsumer struct { type logConsumer struct {
colors map[string]console.ColorFunc colors map[string]console.ColorFunc
width int width int
writer io.Writer
} }

View File

@ -5,12 +5,13 @@ import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
"github.com/docker/ecs-plugin/pkg/console" "github.com/docker/ecs-plugin/pkg/progress"
) )
func (b *Backend) Up(ctx context.Context, options *cli.ProjectOptions) error { func (b *Backend) Up(ctx context.Context, options *cli.ProjectOptions) error {
@ -82,10 +83,12 @@ func (b *Backend) Up(ctx context.Context, options *cli.ProjectOptions) error {
} }
} }
fmt.Println()
w := console.NewProgressWriter()
for k := range template.Resources { for k := range template.Resources {
w.ResourceEvent(k, "PENDING", "") b.writer.Event(progress.Event{
ID: k,
Status: progress.Working,
StatusText: "Pending",
})
} }
signalChan := make(chan os.Signal, 1) signalChan := make(chan os.Signal, 1)
@ -96,7 +99,29 @@ func (b *Backend) Up(ctx context.Context, options *cli.ProjectOptions) error {
b.Down(ctx, options) b.Down(ctx, options)
}() }()
return b.WaitStackCompletion(ctx, project.Name, operation, w) err = b.WaitStackCompletion(ctx, project.Name, operation)
// update status for external resources (LB and cluster)
loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name)))
for k := range template.Resources {
switch k {
case "Cluster":
if cluster == "" {
continue
}
case loadBalancerName:
if lb == "" {
continue
}
default:
continue
}
b.writer.Event(progress.Event{
ID: k,
Status: progress.Done,
StatusText: "",
})
}
return err
} }
func (b Backend) GetVPC(ctx context.Context, project *types.Project) (string, error) { func (b Backend) GetVPC(ctx context.Context, project *types.Project) (string, error) {

View File

@ -8,10 +8,11 @@ import (
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/docker/ecs-plugin/pkg/console" "github.com/docker/ecs-plugin/pkg/compose"
"github.com/docker/ecs-plugin/pkg/progress"
) )
func (b *Backend) WaitStackCompletion(ctx context.Context, name string, operation int, w console.ProgressWriter) error { func (b *Backend) WaitStackCompletion(ctx context.Context, name string, operation int) error {
knownEvents := map[string]struct{}{} knownEvents := map[string]struct{}{}
// Get the unique Stack ID so we can collect events without getting some from previous deployments with same name // Get the unique Stack ID so we can collect events without getting some from previous deployments with same name
@ -22,7 +23,6 @@ func (b *Backend) WaitStackCompletion(ctx context.Context, name string, operatio
ticker := time.NewTicker(1 * time.Second) ticker := time.NewTicker(1 * time.Second)
done := make(chan bool) done := make(chan bool)
go func() { go func() {
b.api.WaitStackComplete(ctx, stackID, operation) //nolint:errcheck b.api.WaitStackComplete(ctx, stackID, operation) //nolint:errcheck
ticker.Stop() ticker.Stop()
@ -55,11 +55,38 @@ func (b *Backend) WaitStackCompletion(ctx context.Context, name string, operatio
resource := aws.StringValue(event.LogicalResourceId) resource := aws.StringValue(event.LogicalResourceId)
reason := aws.StringValue(event.ResourceStatusReason) reason := aws.StringValue(event.ResourceStatusReason)
status := aws.StringValue(event.ResourceStatus) status := aws.StringValue(event.ResourceStatus)
w.ResourceEvent(resource, status, reason) progressStatus := progress.Working
if stackErr == nil && strings.HasSuffix(status, "_FAILED") {
stackErr = fmt.Errorf(reason) switch status {
case "CREATE_COMPLETE":
if operation == compose.StackCreate {
progressStatus = progress.Done
}
case "UPDATE_COMPLETE":
if operation == compose.StackUpdate {
progressStatus = progress.Done
}
case "DELETE_COMPLETE":
if operation == compose.StackDelete {
progressStatus = progress.Done
}
default:
if strings.HasSuffix(status, "_FAILED") {
progressStatus = progress.Error
if stackErr == nil {
operation = compose.StackDelete
stackErr = fmt.Errorf(reason)
}
}
} }
b.writer.Event(progress.Event{
ID: resource,
Status: progressStatus,
StatusText: status,
})
} }
} }
return stackErr return stackErr
} }

View File

@ -2,6 +2,7 @@ package compose
import ( import (
"context" "context"
"io"
"github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/cli"
@ -15,8 +16,8 @@ type API interface {
CreateContextData(ctx context.Context, params map[string]string) (contextData interface{}, description string, err error) CreateContextData(ctx context.Context, params map[string]string) (contextData interface{}, description string, err error)
Convert(project *types.Project) (*cloudformation.Template, error) Convert(project *types.Project) (*cloudformation.Template, error)
Logs(ctx context.Context, options *cli.ProjectOptions) error Logs(ctx context.Context, options *cli.ProjectOptions, writer io.Writer) error
Ps(background context.Context, options *cli.ProjectOptions) ([]ServiceStatus, error) Ps(ctx context.Context, options *cli.ProjectOptions) ([]ServiceStatus, error)
CreateSecret(ctx context.Context, secret Secret) (string, error) CreateSecret(ctx context.Context, secret Secret) (string, error)
InspectSecret(ctx context.Context, id string) (Secret, error) InspectSecret(ctx context.Context, id string) (Secret, error)

View File

@ -1,6 +1,7 @@
package console package console
import ( import (
"fmt"
"strconv" "strconv"
) )
@ -24,6 +25,14 @@ var Monochrome = func(s string) string {
return s return s
} }
func ansiColor(code, s string) string {
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
}
func ansi(code string) string {
return fmt.Sprintf("\033[%sm", code)
}
func makeColorFunc(code string) ColorFunc { func makeColorFunc(code string) ColorFunc {
return func(s string) string { return func(s string) string {
return ansiColor(code, s) return ansiColor(code, s)

View File

@ -1,132 +0,0 @@
package console
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
type resource struct {
name string
status string
details string
}
type progress struct {
console console
resources []*resource
}
type ProgressWriter interface {
ResourceEvent(name string, status string, details string)
}
func NewProgressWriter() ProgressWriter {
return &progress{
console: ansiConsole{os.Stdout},
}
}
const (
blue = "36;2"
red = "31;1"
green = "32;1"
)
func (p *progress) ResourceEvent(name string, status string, details string) {
if logrus.IsLevelEnabled(logrus.DebugLevel) {
logrus.Debugf("> %s : %s %s\n", name, status, details)
return
}
p.console.MoveUp(len(p.resources))
newResource := true
for _, r := range p.resources {
if r.name == name {
newResource = false
r.status = status
r.details = details
break
}
}
if newResource {
p.resources = append(p.resources, &resource{name, status, details})
}
var width int
for _, r := range p.resources {
l := len(r.name)
if width < l {
width = l
}
}
for _, r := range p.resources {
s := r.status
if strings.HasSuffix(s, "_IN_PROGRESS") {
s = p.console.WiP(s)
} else if strings.HasSuffix(s, "_COMPLETE") {
s = p.console.OK(s)
} else if strings.HasSuffix(s, "_FAILED") {
s = p.console.KO(s)
}
p.console.ClearLine()
p.console.Printf("%-"+strconv.Itoa(width)+"s ... %s %s", r.name, s, r.details) // nolint:errcheck
p.console.MoveDown(1)
}
}
type console interface {
Printf(format string, a ...interface{})
MoveUp(int)
MoveDown(int)
ClearLine()
OK(string) string
KO(string) string
WiP(string) string
}
type ansiConsole struct {
out io.Writer
}
func (c ansiConsole) Printf(format string, a ...interface{}) {
fmt.Fprintf(c.out, format, a...) // nolint:errcheck
fmt.Fprintf(c.out, "\r")
}
func (c ansiConsole) MoveUp(i int) {
fmt.Fprintf(c.out, "\033[%dA", i) // nolint:errcheck
}
func (c ansiConsole) MoveDown(i int) {
fmt.Fprintf(c.out, "\033[%dB", i) // nolint:errcheck
}
func (c ansiConsole) ClearLine() {
fmt.Fprint(c.out, "\033[2K\r") // nolint:errcheck
}
func (c ansiConsole) OK(s string) string {
return ansiColor(green, s)
}
func (c ansiConsole) KO(s string) string {
return ansiColor(red, s)
}
func (c ansiConsole) WiP(s string) string {
return ansiColor(blue, s)
}
func ansiColor(code, s string) string {
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
}
func ansi(code string) string {
return fmt.Sprintf("\033[%sm", code)
}

View File

@ -1,65 +0,0 @@
package console
import (
"fmt"
"testing"
"gotest.tools/v3/assert"
)
func TestProgressWriter(t *testing.T) {
c := &bufferConsole{}
p := progress{
console: c,
}
p.ResourceEvent("resource1", "CREATE_IN_PROGRESS", "")
assert.Equal(t, c.lines[0], "resource1 ... CREATE_IN_PROGRESS ")
p.ResourceEvent("resource2_long_name", "CREATE_IN_PROGRESS", "ok")
assert.Equal(t, c.lines[0], "resource1 ... CREATE_IN_PROGRESS ")
assert.Equal(t, c.lines[1], "resource2_long_name ... CREATE_IN_PROGRESS ok")
p.ResourceEvent("resource2_long_name", "CREATE_COMPLETE", "done")
assert.Equal(t, c.lines[0], "resource1 ... CREATE_IN_PROGRESS ")
assert.Equal(t, c.lines[1], "resource2_long_name ... CREATE_COMPLETE done")
p.ResourceEvent("resource1", "CREATE_FAILED", "oups")
assert.Equal(t, c.lines[0], "resource1 ... CREATE_FAILED oups")
assert.Equal(t, c.lines[1], "resource2_long_name ... CREATE_COMPLETE done")
}
type bufferConsole struct {
pos int
lines []string
}
func (b *bufferConsole) Printf(format string, a ...interface{}) {
b.lines[b.pos] = fmt.Sprintf(format, a...)
}
func (b *bufferConsole) MoveUp(i int) {
b.pos -= i
}
func (b *bufferConsole) MoveDown(i int) {
b.pos += i
}
func (b *bufferConsole) ClearLine() {
if len(b.lines) <= b.pos {
b.lines = append(b.lines, "")
}
b.lines[b.pos] = ""
}
func (b *bufferConsole) OK(s string) string {
return s
}
func (b *bufferConsole) KO(s string) string {
return s
}
func (b *bufferConsole) WiP(s string) string {
return s
}

29
ecs/pkg/progress/plain.go Normal file
View File

@ -0,0 +1,29 @@
package progress
import (
"context"
"fmt"
"io"
)
type plainWriter struct {
out io.Writer
done chan bool
}
func (p *plainWriter) Start(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-p.done:
return nil
}
}
func (p *plainWriter) Event(e Event) {
fmt.Println(e.ID, e.Text, e.StatusText)
}
func (p *plainWriter) Stop() {
p.done <- true
}

View File

@ -0,0 +1,50 @@
package progress
import (
"runtime"
"time"
)
type spinner struct {
time time.Time
index int
chars []string
stop bool
done string
}
func newSpinner() *spinner {
chars := []string{
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
}
done := "⠿"
if runtime.GOOS == "windows" {
chars = []string{"-"}
done = "-"
}
return &spinner{
index: 0,
time: time.Now(),
chars: chars,
done: done,
}
}
func (s *spinner) String() string {
if s.stop {
return s.done
}
d := time.Since(s.time)
if d.Milliseconds() > 100 {
s.index = (s.index + 1) % len(s.chars)
}
return s.chars[s.index]
}
func (s *spinner) Stop() {
s.stop = true
}

177
ecs/pkg/progress/tty.go Normal file
View File

@ -0,0 +1,177 @@
package progress
import (
"context"
"fmt"
"io"
"runtime"
"strings"
"sync"
"time"
"github.com/buger/goterm"
"github.com/morikuni/aec"
)
type ttyWriter struct {
out io.Writer
events map[string]Event
eventIDs []string
repeated bool
numLines int
done chan bool
mtx *sync.RWMutex
}
func (w *ttyWriter) Start(ctx context.Context) error {
ticker := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-ctx.Done():
w.print()
return ctx.Err()
case <-w.done:
w.print()
return nil
case <-ticker.C:
w.print()
}
}
}
func (w *ttyWriter) Stop() {
w.done <- true
}
func (w *ttyWriter) Event(e Event) {
w.mtx.Lock()
defer w.mtx.Unlock()
if !StringContains(w.eventIDs, e.ID) {
w.eventIDs = append(w.eventIDs, e.ID)
}
if _, ok := w.events[e.ID]; ok {
last := w.events[e.ID]
switch e.Status {
case Done, Error:
if last.Status != e.Status {
last.stop()
}
}
last.Status = e.Status
last.Text = e.Text
last.StatusText = e.StatusText
w.events[e.ID] = last
} else {
e.startTime = time.Now()
e.spinner = newSpinner()
w.events[e.ID] = e
}
}
func (w *ttyWriter) print() {
w.mtx.Lock()
defer w.mtx.Unlock()
if len(w.eventIDs) == 0 {
return
}
terminalWidth := goterm.Width()
b := aec.EmptyBuilder
for i := 0; i <= w.numLines; i++ {
b = b.Up(1)
}
if !w.repeated {
b = b.Down(1)
}
w.repeated = true
fmt.Fprint(w.out, b.Column(0).ANSI)
// Hide the cursor while we are printing
fmt.Fprint(w.out, aec.Hide)
defer fmt.Fprint(w.out, aec.Show)
firstLine := fmt.Sprintf("[+] Running %d/%d", numDone(w.events), w.numLines)
if w.numLines != 0 && numDone(w.events) == w.numLines {
firstLine = aec.Apply(firstLine, aec.BlueF)
}
fmt.Fprintln(w.out, firstLine)
var statusPadding int
for _, v := range w.eventIDs {
l := len(fmt.Sprintf("%s %s", w.events[v].ID, w.events[v].Text))
if statusPadding < l {
statusPadding = l
}
}
numLines := 0
for _, v := range w.eventIDs {
line := lineText(w.events[v], terminalWidth, statusPadding, runtime.GOOS != "windows")
// nolint: errcheck
fmt.Fprint(w.out, line)
numLines++
}
w.numLines = numLines
}
func lineText(event Event, terminalWidth, statusPadding int, color bool) string {
endTime := time.Now()
if event.Status != Working {
endTime = event.endTime
}
elapsed := endTime.Sub(event.startTime).Seconds()
textLen := len(fmt.Sprintf("%s %s", event.ID, event.Text))
padding := statusPadding - textLen
if padding < 0 {
padding = 0
}
text := fmt.Sprintf(" %s %s %s%s %s",
event.spinner.String(),
event.ID,
event.Text,
strings.Repeat(" ", padding),
event.StatusText,
)
timer := fmt.Sprintf("%.1fs\n", elapsed)
o := align(text, timer, terminalWidth)
if color {
color := aec.WhiteF
if event.Status == Done {
color = aec.BlueF
}
if event.Status == Error {
color = aec.RedF
}
return aec.Apply(o, color)
}
return o
}
func numDone(events map[string]Event) int {
i := 0
for _, e := range events {
if e.Status == Done {
i++
}
}
return i
}
func align(l, r string, w int) string {
return fmt.Sprintf("%-[2]*[1]s %[3]s", l, w-len(r)-1, r)
}
// StringContains check if an array contains a specific value
func StringContains(array []string, needle string) bool {
for _, val := range array {
if val == needle {
return true
}
}
return false
}

112
ecs/pkg/progress/writer.go Normal file
View File

@ -0,0 +1,112 @@
package progress
import (
"context"
"os"
"sync"
"time"
"github.com/containerd/console"
"github.com/moby/term"
"golang.org/x/sync/errgroup"
)
// EventStatus indicates the status of an action
type EventStatus int
const (
// Working means that the current task is working
Working EventStatus = iota
// Done means that the current task is done
Done
// Error means that the current task has errored
Error
)
// Event reprensents a progress event
type Event struct {
ID string
Text string
Status EventStatus
StatusText string
Done bool
startTime time.Time
endTime time.Time
spinner *spinner
}
func (e *Event) stop() {
e.endTime = time.Now()
e.spinner.Stop()
}
// Writer can write multiple progress events
type Writer interface {
Start(context.Context) error
Stop()
Event(Event)
}
type writerKey struct{}
// WithContextWriter adds the writer to the context
func WithContextWriter(ctx context.Context, writer Writer) context.Context {
return context.WithValue(ctx, writerKey{}, writer)
}
// ContextWriter returns the writer from the context
func ContextWriter(ctx context.Context) Writer {
s, _ := ctx.Value(writerKey{}).(Writer)
return s
}
type progressFunc func(context.Context) error
// Run will run a writer and the progress function
// in parallel
func Run(ctx context.Context, pf progressFunc) error {
eg, _ := errgroup.WithContext(ctx)
w, err := NewWriter(os.Stderr)
if err != nil {
return err
}
eg.Go(func() error {
return w.Start(context.Background())
})
ctx = WithContextWriter(ctx, w)
eg.Go(func() error {
defer w.Stop()
return pf(ctx)
})
return eg.Wait()
}
// NewWriter returns a new multi-progress writer
func NewWriter(out console.File) (Writer, error) {
_, isTerminal := term.GetFdInfo(out)
if isTerminal {
con, err := console.ConsoleFromFile(out)
if err != nil {
return nil, err
}
return &ttyWriter{
out: con,
eventIDs: []string{},
events: map[string]Event{},
repeated: false,
done: make(chan bool),
mtx: &sync.RWMutex{},
}, nil
}
return &plainWriter{
out: out,
done: make(chan bool),
}, nil
}