diff --git a/cmd/secrethub/main.go b/cmd/secrethub/main.go index 3bd45810..b25d6031 100644 --- a/cmd/secrethub/main.go +++ b/cmd/secrethub/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - err := secrethub.NewApp().Version(secrethub.Version, secrethub.Commit).Run(os.Args[1:]) + err := secrethub.NewApp().Version(secrethub.Version, secrethub.Commit).Run() if err != nil { handleError(err) } diff --git a/go.mod b/go.mod index 227fefe6..7f38623b 100644 --- a/go.mod +++ b/go.mod @@ -18,13 +18,14 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/secrethub/demo-app v0.1.0 github.com/secrethub/secrethub-go v0.30.0 + github.com/spf13/cobra v1.0.0 + github.com/spf13/pflag v1.0.5 + github.com/xhit/go-str2duration v1.2.0 github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200501052902-10377860bb8e golang.org/x/text v0.3.2 google.golang.org/api v0.26.0 - google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 gopkg.in/yaml.v2 v2.2.2 gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index 4a4de5e0..cede34aa 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022 h1:y8Gs8CzNfDF5AZvjr+5UyGQvQEBL7pwo+v+wX6q9JI8= github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/kingpin v0.0.0-20190930021037-0a108b7f5563 h1:YT8l7Flq7VNXnjqwtjCF9bzffTPGgedBC+xyj88lVe4= github.com/alecthomas/kingpin v0.0.0-20190930021037-0a108b7f5563/go.mod h1:idxgS9pV6OOpAhZvx+gcoGRMX9/tt0iqkw/pNxI0C14= github.com/alecthomas/kingpin v1.3.8-0.20200323085623-b6657d9477a6 h1:nesv3dEn8GDv0ZMxkoCSvrxOE5KbzXXHtWEqJvYA/gw= @@ -39,6 +40,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5Vpd github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= @@ -47,18 +49,29 @@ github.com/aws/aws-sdk-go v1.19.38 h1:WKjobgPO4Ua1ww2NJJl2/zQNreUZxvqmEzwMlRjjm9 github.com/aws/aws-sdk-go v1.19.38/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.49 h1:j5R2Ey+g8qaiy2NJ9iH+KWzDWS4SjXRCjhc22EeQVE4= github.com/aws/aws-sdk-go v1.25.49/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +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/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= @@ -69,16 +82,25 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.1+incompatible h1:RSRC5qmFPtO90t7pTL0DBMNpZFsb/sHF3RXVlDgFisA= github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= @@ -119,14 +141,26 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -134,6 +168,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9 h1:SmVbOZFWAlyQshuMfOkiAx1f5oUTsOGG5IXplAEYeeM= github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/winrm v0.0.0-20190308153735-1d17eaf15943 h1:Bteu9XN1gkBePnKr0v1edkUo2LJRsmK5ne2FrC6yVW4= @@ -145,41 +180,57 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 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/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/secrethub/demo-app v0.1.0 h1:HwPPxuiSvx4TBE7Qppzu3A9eHqmsBrIz4Ko8u8pqMqw= github.com/secrethub/demo-app v0.1.0/go.mod h1:ymjm8+WXTSDTFqsGVBNVmHSnwtZMYi7KptHvpo/fLH4= github.com/secrethub/secrethub-cli v0.30.0/go.mod h1:dC0wd40v+iQdV83/0rUrOa01LYq+8Yj2AtJB1vzh2ao= github.com/secrethub/secrethub-go v0.21.0/go.mod h1:rc2IfKKBJ4L0wGec0u4XnF5/pe0FFPE4Q1MWfrFso7s= -github.com/secrethub/secrethub-go v0.29.1-0.20200626075900-f7c68f70dc36 h1:kRVdL7PRfR80xjpOxFy1O0JROVpILWc2FZWE7Ni2Z2M= -github.com/secrethub/secrethub-go v0.29.1-0.20200626075900-f7c68f70dc36/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= -github.com/secrethub/secrethub-go v0.29.1-0.20200630121846-9adfc0eb3add h1:+DzHsSjht15ycb7GFmyfmQ39gy8ZtA7FjWfJbWUPIYk= -github.com/secrethub/secrethub-go v0.29.1-0.20200630121846-9adfc0eb3add/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= -github.com/secrethub/secrethub-go v0.29.1-0.20200702094400-d465926a4a6a h1:rtFQLsSWGkdqd6LQFbgHsG/be60Cpqv8tc1w4XoKgKM= -github.com/secrethub/secrethub-go v0.29.1-0.20200702094400-d465926a4a6a/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= -github.com/secrethub/secrethub-go v0.29.1-0.20200702114848-1a3657310d91 h1:10KZJ3o7hodrTO1xAP1uNhDWSlLV9Bh9RqRFtiNCYJ4= -github.com/secrethub/secrethub-go v0.29.1-0.20200702114848-1a3657310d91/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= -github.com/secrethub/secrethub-go v0.29.1-0.20200703092019-9f5d3de9b0e4 h1:TszZ+u/DRpPjaAGwEFSQNHkWhG4QR3KBxQJ66NfTAMk= -github.com/secrethub/secrethub-go v0.29.1-0.20200703092019-9f5d3de9b0e4/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= -github.com/secrethub/secrethub-go v0.29.1-0.20200703150346-411544a71e9d h1:tADItWP+YXaGLD1ZMFocxDaKKVcu8wXgEulbcUmX4Ec= -github.com/secrethub/secrethub-go v0.29.1-0.20200703150346-411544a71e9d/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= -github.com/secrethub/secrethub-go v0.29.1-0.20200707154958-5e5602145597 h1:uC9ODMKaqBo1k8fxmFSWGkLr05TgEd3t4mHqJ8Jo9Gc= -github.com/secrethub/secrethub-go v0.29.1-0.20200707154958-5e5602145597/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= github.com/secrethub/secrethub-go v0.30.0 h1:Nh1twPDwPbYQj/cYc1NG+j7sv76LZiXLPovyV83tZj0= github.com/secrethub/secrethub-go v0.30.0/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= @@ -188,15 +239,26 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= +github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07 h1:U5I57s4ISLpeeLYl8b3MsainSSh9F+mRXauln37b50I= github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07/go.mod h1:XlXBIfkGawHNVOHlenOaBW7zlfCh8LovwjOgjamYnkQ= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= @@ -236,6 +298,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -269,6 +333,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= @@ -301,6 +368,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -378,6 +446,7 @@ google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYU google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -393,10 +462,14 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/internals/cli/env.go b/internals/cli/env.go index 21c16046..fdeae306 100644 --- a/internals/cli/env.go +++ b/internals/cli/env.go @@ -6,9 +6,11 @@ import ( "os" "strings" "text/tabwriter" + "time" "bitbucket.org/zombiezen/cardcpx/natsort" - "github.com/alecthomas/kingpin" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -21,55 +23,46 @@ var ( // App represents a command-line application that wraps the // kingpin library and adds additional functionality. type App struct { - *kingpin.Application - + Application cobra.Command name string delimiters []string separator string knownEnvVars map[string]struct{} - extraEnvVarFuncs [](func(key string) bool) + extraEnvVarFuncs []func(key string) bool } // NewApp defines a new command-line application. func NewApp(name, help string) *App { return &App{ - Application: kingpin.New(name, help), + Application: cobra.Command{Use: name, Short: help, SilenceErrors: true, SilenceUsage: true}, name: formatName(name, "", DefaultEnvSeparator, DefaultCommandDelimiters...), delimiters: DefaultCommandDelimiters, separator: DefaultEnvSeparator, knownEnvVars: make(map[string]struct{}), - extraEnvVarFuncs: [](func(string) bool){}, + extraEnvVarFuncs: []func(string) bool{}, } } // Command defines a new top-level command with the given name and help text. -func (a *App) Command(name, help string) *CommandClause { +func (a *App) CreateCommand(name, help string) *CommandClause { return &CommandClause{ - CmdClause: a.Application.Command(name, help), - name: name, - app: a, + Command: func() *cobra.Command { + newCommand := &cobra.Command{Use: name, Short: help, SilenceErrors: true, SilenceUsage: true} + a.Application.AddCommand(newCommand) + return newCommand + }(), + name: name, + App: a, } } +// // Version adds a flag for displaying the application version number. func (a *App) Version(version string) *App { - a.Application = a.Application.Version(version) + a.Application.Version = version return a } -// Flag defines a new flag with the given long name and help text, -// adding an environment variable default configurable by APP_FLAG_NAME. -func (a *App) Flag(name, help string) *Flag { - envVar := formatName(name, a.name, a.separator, a.delimiters...) - a.registerEnvVar(envVar) - flag := a.Application.Flag(name, help).Envar(envVar) - return &Flag{ - FlagClause: flag, - app: a, - envVar: envVar, - } -} - // registerEnvVar ensures the App recognizes an environment variable. func (a *App) registerEnvVar(name string) { a.knownEnvVars[strings.ToUpper(name)] = struct{}{} @@ -98,7 +91,7 @@ func (a *App) isExtraEnvVar(key string) bool { return false } -// PrintEnv reads all environment variables starting with the app name and writes +// PrintEnv reads all environment variables starting with the App name and writes // a table with the keys and their status: set, empty, unrecognized. The value // of environment variables are not printed out for security reasons. The list // is limited to variables that are actually set in the environment. Setting @@ -148,7 +141,7 @@ func (a *App) PrintEnv(w io.Writer, verbose bool, osEnv func() []string) error { return nil } -// CheckStrictEnv checks that every environment variable that starts with the app name is recognized by the application. +// CheckStrictEnv checks that every environment variable that starts with the App name is recognized by the application. func (a *App) CheckStrictEnv() error { for _, envVar := range os.Environ() { key, _, match := splitVar(a.name, a.separator, envVar) @@ -165,47 +158,211 @@ func (a *App) CheckStrictEnv() error { // CommandClause represents a command clause in a command0-line application. type CommandClause struct { - *kingpin.CmdClause + *cobra.Command name string - app *App + App *App +} + +func (cmd *CommandClause) BoolVarP(reference *bool, name, shorthand string, def bool, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().BoolVarP(reference, name, shorthand, def, usage) + } else { + cmd.Flags().BoolVarP(reference, name, shorthand, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) IntVarP(reference *int, name, shorthand string, def int, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().IntVarP(reference, name, shorthand, def, usage) + } else { + cmd.Flags().IntVarP(reference, name, shorthand, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) StringVarP(reference *string, name, shorthand string, def string, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().StringVarP(reference, name, shorthand, def, usage) + } else { + cmd.Flags().StringVarP(reference, name, shorthand, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) DurationVarP(reference *time.Duration, name, shorthand string, def time.Duration, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().DurationVarP(reference, name, shorthand, def, usage) + } else { + cmd.Flags().DurationVarP(reference, name, shorthand, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) BoolVar(reference *bool, name string, def bool, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().BoolVar(reference, name, def, usage) + } else { + cmd.Flags().BoolVar(reference, name, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) IntVar(reference *int, name string, def int, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().IntVar(reference, name, def, usage) + } else { + cmd.Flags().IntVar(reference, name, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) StringVar(reference *string, name string, def string, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().StringVar(reference, name, def, usage) + } else { + cmd.Flags().StringVar(reference, name, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) DurationVar(reference *time.Duration, name string, def time.Duration, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().DurationVar(reference, name, def, usage) + } else { + cmd.Flags().DurationVar(reference, name, def, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) VarP(reference pflag.Value, name string, shorthand string, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().VarP(reference, name, shorthand, usage) + } else { + cmd.Flags().VarP(reference, name, shorthand, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) Var(reference pflag.Value, name string, usage string, hasEnv bool, persistent bool) { + if persistent { + cmd.PersistentFlags().Var(reference, name, usage) + } else { + cmd.Flags().Var(reference, name, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } +} + +func (cmd *CommandClause) VarPF(reference pflag.Value, name string, shorthand string, usage string, hasEnv bool, persistent bool) *pflag.Flag { + var flag *pflag.Flag + if persistent { + flag = cmd.PersistentFlags().VarPF(reference, name, shorthand, usage) + } else { + flag = cmd.Flags().VarPF(reference, name, shorthand, usage) + } + + if hasEnv { + cmd.FlagRegister(name, usage) + } + return flag } // Command adds a new subcommand to this command. -func (cmd *CommandClause) Command(name, help string) *CommandClause { - return &CommandClause{ - CmdClause: cmd.CmdClause.Command(name, help), - name: name, - app: cmd.app, +func (cmd *CommandClause) CreateCommand(name, help string) *CommandClause { + clause := &CommandClause{ + Command: func() *cobra.Command { + newCommand := &cobra.Command{Use: name, Short: help} + return newCommand + }(), + name: name, + App: cmd.App, } + cmd.AddCommand(clause.Command) + return clause } // Hidden hides the command in help texts. func (cmd *CommandClause) Hidden() *CommandClause { - cmd.CmdClause = cmd.CmdClause.Hidden() + cmd.Command.Hidden = true return cmd } +func (cmd *CommandClause) FullCommand() string { + if cmd.Use == cmd.Root().Use { + return "" + } + out := []string{cmd.Use} + for p := cmd.Parent(); p != nil; p = p.Parent() { + if p.Use != cmd.Root().Use { + out = append([]string{p.Use}, out...) + } + } + return strings.Join(out, " ") +} + +func (cmd *CommandClause) HelpLong(helpLong string) { + cmd.Long = helpLong +} + +func (cmd *CommandClause) Alias(alias string) { + if cmd.Aliases == nil { + cmd.Aliases = []string{alias} + } else { + cmd.Aliases = append(cmd.Aliases, alias) + } +} + // Flag defines a new flag with the given long name and help text, // adding an environment variable default configurable by APP_COMMAND_FLAG_NAME. // The help text is suffixed with a description of secrthe environment variable default. -func (cmd *CommandClause) Flag(name, help string) *Flag { - fullCmd := strings.Replace(cmd.FullCommand(), " ", cmd.app.separator, -1) - prefix := formatName(fullCmd, cmd.app.name, cmd.app.separator, cmd.app.delimiters...) - envVar := formatName(name, prefix, cmd.app.separator, cmd.app.delimiters...) - - cmd.app.registerEnvVar(envVar) - flag := cmd.CmdClause.Flag(name, help).Envar(envVar) - return &Flag{ - FlagClause: flag, - app: cmd.app, - envVar: envVar, - } +func (cmd *CommandClause) FlagRegister(name, help string) *Flag { + fullCmd := strings.Replace(cmd.FullCommand(), " ", cmd.App.separator, -1) + prefix := formatName(fullCmd, cmd.App.name, cmd.App.separator, cmd.App.delimiters...) + envVar := formatName(name, prefix, cmd.App.separator, cmd.App.delimiters...) + + cmd.App.registerEnvVar(envVar) + flag := cmd.Flag(name) + return (&Flag{ + Flag: flag, + app: cmd.App, + envVar: envVar, + }).Envar(envVar) } // Flag represents a command-line flag. type Flag struct { - *kingpin.FlagClause + *pflag.Flag envVar string app *App @@ -220,29 +377,16 @@ func (f *Flag) Envar(name string) *Flag { } f.app.registerEnvVar(name) f.envVar = name - f.FlagClause = f.FlagClause.Envar(f.envVar) - return f -} - -// NoEnvar forces environment variable defaults to be disabled for this flag. -func (f *Flag) NoEnvar() *Flag { - if f.envVar != "" { - f.app.unregisterEnvVar(f.envVar) - } - f.envVar = "" - f.FlagClause = f.FlagClause.NoEnvar() - return f -} - -// Hidden hides the flag in help texts. -func (f *Flag) Hidden() *Flag { - f.FlagClause = f.FlagClause.Hidden() + f.Flag.DefValue = os.Getenv(f.envVar) return f } // formatName takes a name and converts it to an uppercased name, // joined by the given separator and prefixed with the given prefix. func formatName(name, prefix, separator string, delimiters ...string) string { + if name == "" { + return strings.ToUpper(prefix) + } for _, delim := range delimiters { name = strings.Replace(name, delim, separator, -1) } diff --git a/internals/cli/filemode/filemode.go b/internals/cli/filemode/filemode.go index c293405d..588907f9 100644 --- a/internals/cli/filemode/filemode.go +++ b/internals/cli/filemode/filemode.go @@ -17,6 +17,10 @@ var ( // so that the file mode can be parsed from a flag. type FileMode os.FileMode +func (m *FileMode) Type() string { + return "filemode" +} + // New creates a new FileMode. func New(fileMode os.FileMode) FileMode { return FileMode(fileMode) diff --git a/internals/demo/command.go b/internals/demo/command.go index 6cbed4ab..3d113feb 100644 --- a/internals/demo/command.go +++ b/internals/demo/command.go @@ -1,31 +1,31 @@ package demo -import ( - "github.com/secrethub/demo-app/cli" - - "github.com/secrethub/secrethub-cli/internals/cli/ui" - "github.com/secrethub/secrethub-cli/internals/secrethub/command" -) - -// Command is a command to run the secrethub example app. -type Command struct { - io ui.IO - newClient newClientFunc -} - -// NewCommand creates a new example app command. -func NewCommand(io ui.IO, newClient newClientFunc) *Command { - return &Command{ - io: io, - newClient: newClient, - } -} - -// Register registers the command, arguments and flags on the provided Registerer. -func (cmd *Command) Register(r command.Registerer) { - clause := r.Command("demo", "Manage the demo application.") - clause.Hidden() - - NewInitCommand(cmd.io, cmd.newClient).Register(clause) - cli.NewServeCommand(cmd.io).Register(clause) -} +//import ( +// "github.com/secrethub/demo-app/cli" +// +// "github.com/secrethub/secrethub-cli/internals/cli/ui" +// "github.com/secrethub/secrethub-cli/internals/secrethub/command" +//) +// +//// Command is a command to run the secrethub example app. +//type Command struct { +// io ui.IO +// newClient newClientFunc +//} +// +//// NewCommand creates a new example app command. +//func NewCommand(io ui.IO, newClient newClientFunc) *Command { +// return &Command{ +// io: io, +// newClient: newClient, +// } +//} +// +//// Register registers the command, arguments and flags on the provided Registerer. +//func (cmd *Command) Register(r command.Registerer) { +// clause := r.CreateCommand("demo", "Manage the demo application.") +// clause.Hidden() +// +// NewInitCommand(cmd.io, cmd.newClient).Register(clause) +// cli.NewServeCommand(cmd.io).Register(clause) +//} diff --git a/internals/demo/init.go b/internals/demo/init.go index 2efd5d02..14969374 100644 --- a/internals/demo/init.go +++ b/internals/demo/init.go @@ -19,8 +19,7 @@ type newClientFunc func() (secrethub.ClientInterface, error) const defaultDemoRepo = "demo" type InitCommand struct { - repo api.RepoPath - + repo pathValue io ui.IO newClient newClientFunc } @@ -34,12 +33,11 @@ func NewInitCommand(io ui.IO, newClient newClientFunc) *InitCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *InitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Create the secrets necessary to connect with the demo application.") + clause := r.CreateCommand("init", "Create the secrets necessary to connect with the demo application.") clause.HelpLong("demo init creates a repository with the username and password needed to connect to the demo API.") - clause.Flag("repo", "The path of the repository to create. Defaults to a "+defaultDemoRepo+" repo in your personal namespace.").SetValue(&cmd.repo) - - command.BindAction(clause, cmd.Run) + clause.VarPF(&cmd.repo, "repo", "", "The path of the repository to create. Defaults to a "+defaultDemoRepo+" repo in your personal namespace.", true, false) + command.BindAction(clause, nil, cmd.Run) } // Run handles the command with the options as specified in the command. @@ -51,7 +49,7 @@ func (cmd *InitCommand) Run() error { var repoPath string var username string - if cmd.repo == "" { + if cmd.repo.path == "" { me, err := client.Me().GetUser() if err != nil { return err @@ -59,8 +57,8 @@ func (cmd *InitCommand) Run() error { username = me.Username repoPath = secretpath.Join(me.Username, defaultDemoRepo) } else { - username = secretpath.Namespace(cmd.repo.Value()) - repoPath = cmd.repo.Value() + username = secretpath.Namespace(cmd.repo.path.Value()) + repoPath = cmd.repo.path.Value() } _, err = client.Repos().Create(repoPath) @@ -127,3 +125,24 @@ func (cmd *InitCommand) isDemoRepo(client secrethub.ClientInterface, repoPath st } return true, nil } + +type pathValue struct { + path api.RepoPath +} + +func (u *pathValue) String() string { + return u.path.String() +} + +func (u *pathValue) Set(s string) error { + var err error + u.path, err = api.NewRepoPath(s) + if err != nil { + return err + } + return nil +} + +func (u *pathValue) Type() string { + return "pathValue" +} diff --git a/internals/secrethub/account.go b/internals/secrethub/account.go index 31f65ce0..0614e539 100644 --- a/internals/secrethub/account.go +++ b/internals/secrethub/account.go @@ -23,8 +23,11 @@ func NewAccountCommand(io ui.IO, newClient newClientFunc, credentialStore Creden // Register registers the command and its sub-commands on the provided Registerer. func (cmd *AccountCommand) Register(r command.Registerer) { - clause := r.Command("account", "Manage your personal account.") + clause := r.CreateCommand("account", "Manage your personal account.") NewAccountInspectCommand(cmd.io, cmd.newClient).Register(clause) NewAccountInitCommand(cmd.io, cmd.newClient, cmd.credentialStore).Register(clause) NewAccountEmailVerifyCommand(cmd.io, cmd.newClient).Register(clause) + //command.BindAction(clause, nil, func() error { + // return nil + //}) } diff --git a/internals/secrethub/account_email_verify.go b/internals/secrethub/account_email_verify.go index 0181274c..a97f92a8 100644 --- a/internals/secrethub/account_email_verify.go +++ b/internals/secrethub/account_email_verify.go @@ -23,12 +23,12 @@ func NewAccountEmailVerifyCommand(io ui.IO, newClient newClientFunc) *AccountEma // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AccountEmailVerifyCommand) Register(r command.Registerer) { - clause := r.Command("verify-email", "Resend verification email to the registered email address.") + clause := r.CreateCommand("verify-email", "Resend verification email to the registered email address.") clause.HelpLong("When you create your account, a verification email is automatically sent to the email address you used to sign up. " + "In case anything goes wrong (e.g. the email ended up in your junk folder), this command lets you resend the verification email. " + "Once received, click the link in the verification email to verify your email address.") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run handles the command with the options as specified in the command. diff --git a/internals/secrethub/account_init.go b/internals/secrethub/account_init.go index 7b187b12..f97ad7a4 100644 --- a/internals/secrethub/account_init.go +++ b/internals/secrethub/account_init.go @@ -59,13 +59,13 @@ func NewAccountInitCommand(io ui.IO, newClient newClientFunc, credentialStore Cr // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AccountInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Connect a first device to your SecretHub account.") - clause.Flag("clip", "Copy the credential's public component to the clipboard instead of printing it to stdout.").Short('c').BoolVar(&cmd.useClipboard) - clause.Flag("no-wait", "Do not hang waiting for the credential's public component to be added to the account and instead exit after outputting the credential's public component. To finish initializing the account, use the --continue flag after adding the credential to the account.").BoolVar(&cmd.noWait) - clause.Flag("continue", "Continue initializing the account. Use this when a credential has already been generated by a previous execution of the command.").BoolVar(&cmd.isContinue) - registerForceFlag(clause).BoolVar(&cmd.force) + clause := r.CreateCommand("init", "Connect a first device to your SecretHub account.") + clause.BoolVarP(&cmd.useClipboard, "clip", "c", false, "Copy the credential's public component to the clipboard instead of printing it to stdout.", true, false) + clause.BoolVar(&cmd.noWait, "no-wait", false, "Do not hang waiting for the credential's public component to be added to the account and instead exit after outputting the credential's public component. To finish initializing the account, use the --continue flag after adding the credential to the account.", true, false) + clause.BoolVar(&cmd.isContinue, "continue", false, "Continue initializing the account. Use this when a credential has already been generated by a previous execution of the command.", true, false) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run creates a credential for this CLI and an account key for the credential. diff --git a/internals/secrethub/account_inspect.go b/internals/secrethub/account_inspect.go index bb81543c..cafc2da1 100644 --- a/internals/secrethub/account_inspect.go +++ b/internals/secrethub/account_inspect.go @@ -28,9 +28,9 @@ func NewAccountInspectCommand(io ui.IO, newClient newClientFunc) *AccountInspect // Register registers the command, arguments and flags on the provided Registerer. func (cmd *AccountInspectCommand) Register(r command.Registerer) { - clause := r.Command("inspect", "Show the details of your SecretHub account.") + clause := r.CreateCommand("inspect", "Show the details of your SecretHub account.") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run handles the command with the options as specified in the command. diff --git a/internals/secrethub/acl.go b/internals/secrethub/acl.go index 94d9d8fa..508f9328 100644 --- a/internals/secrethub/acl.go +++ b/internals/secrethub/acl.go @@ -21,7 +21,7 @@ func NewACLCommand(io ui.IO, newClient newClientFunc) *ACLCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ACLCommand) Register(r command.Registerer) { - clause := r.Command("acl", "Manage access rules on directories.") + clause := r.CreateCommand("acl", "Manage access rules on directories.") NewACLCheckCommand(cmd.io, cmd.newClient).Register(clause) NewACLListCommand(cmd.io, cmd.newClient).Register(clause) NewACLRmCommand(cmd.io, cmd.newClient).Register(clause) diff --git a/internals/secrethub/acl_check.go b/internals/secrethub/acl_check.go index 0e9df30a..d2298bf1 100644 --- a/internals/secrethub/acl_check.go +++ b/internals/secrethub/acl_check.go @@ -11,6 +11,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // ACLCheckCommand prints the access level(s) on a given directory. @@ -31,11 +33,18 @@ func NewACLCheckCommand(io ui.IO, newClient newClientFunc) *ACLCheckCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ACLCheckCommand) Register(r command.Registerer) { - clause := r.Command("check", "Checks the effective permission of accounts on a path.") - clause.Arg("dir-path", "The path of the directory to check the effective permission for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) - clause.Arg("account-name", "Check permissions of a specific account name (username or service name). When left empty, all accounts with permission on the path are printed out.").SetValue(&cmd.accountName) + clause := r.CreateCommand("check", "Checks the effective permission of accounts on a path.") + clause.Args = cobra.RangeArgs(1, 2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.DirectorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("dir-path", "The path of the directory to check the effective permission for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) + //clause.Arg("account-name", "Check permissions of a specific account name (username or service name). When left empty, all accounts with permission on the path are printed out.").SetValue(&cmd.accountName) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run prints the access level(s) on the given directory. @@ -106,3 +115,18 @@ func (cmd *ACLCheckCommand) listLevels() ([]*api.AccessLevel, error) { } return nil, listLevelsErr } + +func (cmd *ACLCheckCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewDirPath(args[0]) + if err != nil { + return err + } + if len(args) == 2 { + cmd.accountName, err = api.NewAccountName(args[1]) + if err != nil { + return err + } + } + return nil +} diff --git a/internals/secrethub/acl_list.go b/internals/secrethub/acl_list.go index 76f8ee8a..6e9f2ef5 100644 --- a/internals/secrethub/acl_list.go +++ b/internals/secrethub/acl_list.go @@ -5,12 +5,13 @@ import ( "sort" "text/tabwriter" + "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/api/uuid" "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" - "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // ACLListCommand prints access rules for the given directory. @@ -34,14 +35,16 @@ func NewACLListCommand(io ui.IO, newClient newClientFunc) *ACLListCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ACLListCommand) Register(r command.Registerer) { - clause := r.Command("ls", "List access rules of a directory and its children.") + clause := r.CreateCommand("ls", "List access rules of a directory and its children.") clause.Alias("list") - clause.Arg("dir-path", "The path of the directory to list the access rules for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) - clause.Flag("depth", "The maximum depth to which the rules of child directories should be displayed. Defaults to -1 (no limit).").Short('d').Default("-1").IntVar(&cmd.depth) - clause.Flag("all", "List all rules that apply on the directory, including rules on parent directories.").Short('a').BoolVar(&cmd.ancestors) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) - - command.BindAction(clause, cmd.Run) + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.DirectorySuggestions + //clause.Arg("dir-path", "The path of the directory to list the access rules for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) + clause.IntVarP(&cmd.depth, "depth", "d", -1, "The maximum depth to which the rules of child directories should be displayed. Defaults to -1 (no limit).", true, false) + clause.BoolVarP(&cmd.ancestors, "all", "a", false, "List all rules that apply on the directory, including rules on parent directories.", true, false) + registerTimestampFlag(clause, &cmd.useTimestamps) + + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run prints access rules for the given directory. @@ -50,6 +53,15 @@ func (cmd *ACLListCommand) Run() error { return cmd.run() } +func (cmd *ACLListCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewDirPath(args[0]) + if err != nil { + return err + } + return nil +} + // beforeRun configures the command using the flag values. func (cmd *ACLListCommand) beforeRun() { cmd.timeFormatter = NewTimeFormatter(cmd.useTimestamps) diff --git a/internals/secrethub/acl_rm.go b/internals/secrethub/acl_rm.go index 675eec7a..ecba4b4a 100644 --- a/internals/secrethub/acl_rm.go +++ b/internals/secrethub/acl_rm.go @@ -5,8 +5,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" - "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // ACLRmCommand handles removing an access rule. @@ -28,13 +28,20 @@ func NewACLRmCommand(io ui.IO, newClient newClientFunc) *ACLRmCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ACLRmCommand) Register(r command.Registerer) { - clause := r.Command("rm", "Remove an account's access rules on a given directory. Although the server will deny the account access afterwards, note that removing an access rule does not actually revoke an account and does NOT trigger secret rotation.") + clause := r.CreateCommand("rm", "Remove an account's access rules on a given directory. Although the server will deny the account access afterwards, note that removing an access rule does not actually revoke an account and does NOT trigger secret rotation.") clause.Alias("remove") - clause.Arg("dir-path", "The path of the directory to remove the access rule for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) - clause.Arg("account-name", "The account name (username or service name) whose rule to remove").Required().SetValue(&cmd.accountName) - registerForceFlag(clause).BoolVar(&cmd.force) + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.DirectorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("dir-path", "The path of the directory to remove the access rule for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) + //clause.Arg("account-name", "The account name (username or service name) whose rule to remove").Required().SetValue(&cmd.accountName) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run removes the access rule. @@ -75,3 +82,16 @@ func (cmd *ACLRmCommand) Run() error { return nil } + +func (cmd *ACLRmCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewDirPath(args[0]) + if err != nil { + return err + } + cmd.accountName, err = api.NewAccountName(args[1]) + if err != nil { + return err + } + return nil +} diff --git a/internals/secrethub/acl_set.go b/internals/secrethub/acl_set.go index 7d68866e..b8a28cfc 100644 --- a/internals/secrethub/acl_set.go +++ b/internals/secrethub/acl_set.go @@ -5,8 +5,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" - "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // ACLSetCommand is a command to set access rules. @@ -30,13 +30,22 @@ func NewACLSetCommand(io ui.IO, newClient newClientFunc) *ACLSetCommand { // Register adds a CommandClause and it's args and flags to a Registerer. // Register adds args and flags. func (cmd *ACLSetCommand) Register(r command.Registerer) { - clause := r.Command("set", "Set access rule for an user or service on a path.") - clause.Arg("dir-path", "The path of the directory to set the access rule for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) - clause.Arg("account-name", "The account name (username or service name) to set the access rule for").Required().SetValue(&cmd.accountName) - clause.Arg("permission", "The permission to set in the access rule.").Required().SetValue(&cmd.permission) - registerForceFlag(clause).BoolVar(&cmd.force) + clause := r.CreateCommand("set", "Set access rule for an user or service on a path.") + clause.Args = cobra.ExactValidArgs(3) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.DirectorySuggestions(cmd, args, toComplete) + } else if len(args) == 1 { + return []string{}, cobra.ShellCompDirectiveDefault + } + return []string{"read", "write", "admin"}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("dir-path", "The path of the directory to set the access rule for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) + //clause.Arg("account-name", "The account name (username or service name) to set the access rule for").Required().SetValue(&cmd.accountName) + //clause.Arg("permission", "The permission to set in the access rule.").Required().SetValue(&cmd.permission) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run handles the command with the options as specified in the command. @@ -78,5 +87,22 @@ func (cmd *ACLSetCommand) Run() error { fmt.Fprintln(cmd.io.Output(), "Access rule set!") return nil +} +func (cmd *ACLSetCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewDirPath(args[0]) + if err != nil { + return err + } + cmd.accountName, err = api.NewAccountName(args[1]) + if err != nil { + return err + } + cmd.permission = api.PermissionNone + err = cmd.permission.Set(args[2]) + if err != nil { + return err + } + return nil } diff --git a/internals/secrethub/app.go b/internals/secrethub/app.go index e95a9c8b..1d54926a 100644 --- a/internals/secrethub/app.go +++ b/internals/secrethub/app.go @@ -3,16 +3,11 @@ package secrethub import ( "fmt" "strings" - "text/template" "github.com/secrethub/secrethub-cli/internals/cli" "github.com/secrethub/secrethub-cli/internals/cli/ui" - "github.com/secrethub/secrethub-cli/internals/demo" - "github.com/secrethub/secrethub-go/internals/errio" "github.com/secrethub/secrethub-go/pkg/secrethub" - - "github.com/alecthomas/kingpin" ) const ( @@ -106,58 +101,58 @@ func NewApp() *App { app.clientFactory.Register(app.cli) app.registerCommands() - app.cli.UsageTemplate(DefaultUsageTemplate) - app.cli.UsageFuncs(template.FuncMap{ - "ManagementCommands": func(cmds []*kingpin.CmdModel) []*kingpin.CmdModel { - var res []*kingpin.CmdModel - for _, cmd := range cmds { - if len(cmd.Commands) > 0 { - res = append(res, cmd) - } - } - return res - }, - "RootCommands": func(cmds []*kingpin.CmdModel) []*kingpin.CmdModel { - var res []*kingpin.CmdModel - for _, cmd := range cmds { - if len(cmd.Commands) == 0 { - res = append(res, cmd) - } - } - return res - }, - "CommandsToTwoColumns": func(cmds []*kingpin.CmdModel) [][2]string { - var rows [][2]string - for _, cmd := range cmds { - if !cmd.Hidden { - rows = append(rows, [2]string{cmd.Name, cmd.Help}) - } - } - return rows - }, - }) + //app.cli.UsageTemplate(DefaultUsageTemplate) + //app.cli.UsageFuncs(template.FuncMap{ + // "ManagementCommands": func(cmds []*kingpin.CmdModel) []*kingpin.CmdModel { + // var res []*kingpin.CmdModel + // for _, cmd := range cmds { + // if len(cmd.Commands) > 0 { + // res = append(res, cmd) + // } + // } + // return res + // }, + // "RootCommands": func(cmds []*kingpin.CmdModel) []*kingpin.CmdModel { + // var res []*kingpin.CmdModel + // for _, cmd := range cmds { + // if len(cmd.Commands) == 0 { + // res = append(res, cmd) + // } + // } + // return res + // }, + // "CommandsToTwoColumns": func(cmds []*kingpin.CmdModel) [][2]string { + // var rows [][2]string + // for _, cmd := range cmds { + // if !cmd.Hidden { + // rows = append(rows, [2]string{cmd.Name, cmd.Help}) + // } + // } + // return rows + // }, + //}) return &app } // Version adds a flag for displaying the application version number. func (app *App) Version(version string, commit string) *App { - app.cli = app.cli.Version(ApplicationName + " version " + version + ", build " + commit) + app.cli = app.cli.Version(version + ", build " + commit) return app } // Run builds the command-line application, parses the arguments, // configures global behavior and executes the command given by the args. -func (app *App) Run(args []string) error { +func (app *App) Run() error { // Parse also executes the command when parsing is successful. - _, err := app.cli.Parse(args) + err := app.cli.Application.Execute() return err } -// Model returns the CLI application model containing all the SecretHub CLI commands, flags, and args. -func (app *App) Model() *kingpin.ApplicationModel { - return app.cli.Model() -} +//Model returns the CLI application model containing all the SecretHub CLI commands, flags, and args. +//func (app *App) Model() *kingpin.ApplicationModel { +// return app.cli.Model() +//} // registerCommands initializes all commands and registers them on the app. func (app *App) registerCommands() { @@ -193,6 +188,7 @@ func (app *App) registerCommands() { NewSetCommand(app.io, app.clientFactory.NewClient).Register(app.cli) NewClearClipboardCommand().Register(app.cli) NewKeyringClearCommand().Register(app.cli) + NewCompletionCommand().Register(app.cli) - demo.NewCommand(app.io, app.clientFactory.NewClient).Register(app.cli) + //demo.NewCommand(app.io, app.clientFactory.NewClient).Register(app.cli) } diff --git a/internals/secrethub/audit.go b/internals/secrethub/audit.go index 3871585d..b0ec1071 100644 --- a/internals/secrethub/audit.go +++ b/internals/secrethub/audit.go @@ -3,7 +3,6 @@ package secrethub import ( "fmt" "io" - "strconv" "github.com/secrethub/secrethub-go/internals/errio" @@ -18,11 +17,13 @@ import ( "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) var ( errAudit = errio.Namespace("audit") - errNoSuchFormat = errAudit.Code("invalid_format").ErrorPref("invalid format: %s") + errNoSuchFormat = errAudit.Code("invalid_format").ErrorPref("invalid se: %s") ) const ( @@ -66,14 +67,20 @@ func (cmd *AuditCommand) Register(r command.Registerer) { defaultLimit = pipedOutputLineLimit } - clause := r.Command("audit", "Show the audit log.") - clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) - clause.Flag("per-page", "Number of audit events shown per page").Default("20").Hidden().IntVar(&cmd.perPage) - clause.Flag("output-format", "Specify the format in which to output the log. Options are: table and json. If the output of the command is parsed by a script an alternative of the table format must be used.").HintOptions("table", "json").Default("table").StringVar(&cmd.format) - clause.Flag("max-results", "Specify the number of entries to list. If maxResults < 0 all entries are displayed. If the output of the command is piped, maxResults defaults to 1000.").Default(strconv.Itoa(defaultLimit)).IntVar(&cmd.maxResults) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) - - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("audit", "Show the audit log.") + clause.Args = cobra.MaximumNArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.SecretSuggestions + //clause.Arg("repo-path or secret-path", "Path to the repository or the secret to audit "+repoPathPlaceHolder+" or "+secretPathPlaceHolder).SetValue(&cmd.path) + clause.IntVar(&cmd.perPage, "per-page", 20, "Number of audit events shown per page", true, false) + clause.Flag("per-page").Hidden = true + clause.StringVar(&cmd.format, "output-format", "table", "Specify the format in which to output the log. Options are: table and json. If the output of the command is parsed by a script an alternative of the table format must be used.", true, false) + _ = clause.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"table", "json"}, cobra.ShellCompDirectiveDefault + }) + clause.IntVar(&cmd.maxResults, "max-results", defaultLimit, "Specify the number of entries to list. If maxResults < 0 all entries are displayed. If the output of the command is piped, maxResults defaults to 1000.", true, false) + registerTimestampFlag(clause, &cmd.useTimestamps) + + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run prints all audit events for the given repository or secret. @@ -82,6 +89,17 @@ func (cmd *AuditCommand) Run() error { return cmd.run() } +func (cmd *AuditCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + if len(args) != 0 { + cmd.path, err = api.NewPath(args[0]) + if err != nil { + return err + } + } + return nil +} + // beforeRun configures the command using the flag values. func (cmd *AuditCommand) beforeRun() { if cmd.format == formatJSON { diff --git a/internals/secrethub/clear.go b/internals/secrethub/clear.go index 83b1c33a..59585da5 100644 --- a/internals/secrethub/clear.go +++ b/internals/secrethub/clear.go @@ -25,10 +25,10 @@ func NewClearCommand(io ui.IO) *ClearCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ClearCommand) Register(r command.Registerer) { - clause := r.Command("clear", "Clear the secrets from your local environment. This reads and parses the secrets.yml file in the current working directory.").Hidden() - clause.Flag("in", "The path to a secrets.yml file to read").Short('i').Default("secrets.yml").ExistingFileVar(&cmd.in) + clause := r.CreateCommand("clear", "Clear the secrets from your local environment. This reads and parses the secrets.yml file in the current working directory.").Hidden() + clause.StringVarP(&cmd.in, "in", "i", "secrets.yml", "The path to a secrets.yml file to read", true, false) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run clears the secrets from the system. diff --git a/internals/secrethub/clear_clipboard.go b/internals/secrethub/clear_clipboard.go index 34902181..cf36b8ff 100644 --- a/internals/secrethub/clear_clipboard.go +++ b/internals/secrethub/clear_clipboard.go @@ -8,6 +8,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/cloneproc" "github.com/secrethub/secrethub-cli/internals/secrethub/command" + "github.com/spf13/cobra" "golang.org/x/crypto/bcrypt" ) @@ -30,11 +31,12 @@ func NewClearClipboardCommand() *ClearClipboardCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ClearClipboardCommand) Register(r command.Registerer) { - clause := r.Command("clipboard-clear", "Removes secret from clipboard.").Hidden() - clause.Arg("hash", "Hash from the secret to be cleared").Required().HexBytesVar(&cmd.hash) - clause.Flag("timeout", "Time to wait before clearing in seconds").DurationVar(&cmd.timeout) + clause := r.CreateCommand("clipboard-clear", "Removes secret from clipboard.").Hidden() + clause.Args = cobra.ExactValidArgs(1) + //clause.Arg("hash", "Hash from the secret to be cleared").Required().HexBytesVar(&cmd.hash) + clause.DurationVar(&cmd.timeout, "timeout", 0, "Time to wait before clearing in seconds", true, false) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run handles the command with the options as specified in the command. @@ -78,3 +80,7 @@ func WriteClipboardAutoClear(data []byte, timeout time.Duration, clipper clip.Cl return err } + +func (cmd *ClearClipboardCommand) argumentRegister(c *cobra.Command, args []string) error { + return nil +} diff --git a/internals/secrethub/client_factory.go b/internals/secrethub/client_factory.go index bd50bad2..eee3332c 100644 --- a/internals/secrethub/client_factory.go +++ b/internals/secrethub/client_factory.go @@ -5,9 +5,13 @@ import ( "net/url" "strings" + "github.com/secrethub/secrethub-cli/internals/cli" + "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-go/pkg/secrethub/configdir" "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" + + "github.com/spf13/cobra" ) // Errors @@ -21,7 +25,7 @@ type ClientFactory interface { NewClient() (secrethub.ClientInterface, error) NewClientWithCredentials(credentials.Provider) (secrethub.ClientInterface, error) NewUnauthenticatedClient() (secrethub.ClientInterface, error) - Register(FlagRegisterer) + Register(app *cli.App) } // NewClientFactory creates a new ClientFactory. @@ -33,17 +37,24 @@ func NewClientFactory(store CredentialConfig) ClientFactory { type clientFactory struct { client *secrethub.Client - ServerURL *url.URL + ServerURL urlValue identityProvider string - proxyAddress *url.URL + proxyAddress urlValue store CredentialConfig } // Register the flags for configuration on a cli application. -func (f *clientFactory) Register(r FlagRegisterer) { - r.Flag("api-remote", "The SecretHub API address, don't set this unless you know what you're doing.").Hidden().URLVar(&f.ServerURL) - r.Flag("identity-provider", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS), `gcp` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ").Default("key").StringVar(&f.identityProvider) - r.Flag("proxy-address", "Set to the address of a proxy to connect to the API through a proxy. The prepended scheme determines the proxy type (http, https and socks5 are supported). For example: `--proxy-address http://my-proxy:1234`").URLVar(&f.proxyAddress) +func (f *clientFactory) Register(app *cli.App) { + commandClause := cli.CommandClause{ + Command: &app.Application, + App: app, + } + commandClause.VarPF(&f.ServerURL, "api-remote", "", "The SecretHub API address, don't set this unless you know what you're doing.", true, false) + commandClause.StringVar(&f.identityProvider, "identity-provider", "key", "Enable native authentication with a trusted identity provider. Options are `aws` (IAM + KMS), `gcp` (IAM + KMS) and `key`. When you run the CLI on one of the platforms, you can leverage their respective identity providers to do native keyless authentication. Defaults to key, which uses the default credential sourced from a file, command-line flag, or environment variable. ", true, false) + _ = commandClause.RegisterFlagCompletionFunc("identity-provider", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"aws", "gcp", "key"}, cobra.ShellCompDirectiveDefault + }) + commandClause.VarPF(&f.proxyAddress, "proxy-address", "", "Set to the address of a proxy to connect to the API through a proxy. The prepended scheme determines the proxy type (http, https and socks5 are supported). For example: `--proxy-address http://my-proxy:1234`", true, false) } // NewClient returns a new client that is configured to use the remote that @@ -108,17 +119,41 @@ func (f *clientFactory) baseClientOptions() []secrethub.ClientOption { }), } - if f.proxyAddress != nil { + if f.proxyAddress.u != nil { transport := http.DefaultTransport.(*http.Transport) transport.Proxy = func(request *http.Request) (*url.URL, error) { - return f.proxyAddress, nil + return f.proxyAddress.u, nil } options = append(options, secrethub.WithTransport(transport)) } - if f.ServerURL != nil { + if f.ServerURL.u != nil { options = append(options, secrethub.WithServerURL(f.ServerURL.String())) } return options } + +type urlValue struct { + u *url.URL +} + +func (u *urlValue) String() string { + if u.u == nil { + return "" + } + return u.u.String() +} + +func (u *urlValue) Set(s string) error { + parsed, err := url.Parse(s) + u.u = parsed + if err != nil { + return err + } + return nil +} + +func (u *urlValue) Type() string { + return "urlValue" +} diff --git a/internals/secrethub/client_factory_test.go b/internals/secrethub/client_factory_test.go index b4899d91..e8b7c88f 100644 --- a/internals/secrethub/client_factory_test.go +++ b/internals/secrethub/client_factory_test.go @@ -18,7 +18,7 @@ func TestNewClientFactory_ProxyAddress(t *testing.T) { proxyReceivedRequest := false go func() { - err := http.ListenAndServe(proxyAddress.Hostname()+":"+proxyAddress.Port(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err = http.ListenAndServe(proxyAddress.Hostname()+":"+proxyAddress.Port(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { proxyReceivedRequest = true })) if err != http.ErrServerClosed && err != nil { @@ -38,8 +38,8 @@ func TestNewClientFactory_ProxyAddress(t *testing.T) { factory := clientFactory{ identityProvider: "key", store: store, - ServerURL: serverAddress, - proxyAddress: proxyAddress, + ServerURL: urlValue{serverAddress}, + proxyAddress: urlValue{proxyAddress}, } client, err := factory.NewUnauthenticatedClient() diff --git a/internals/secrethub/color.go b/internals/secrethub/color.go index 2d36fbf8..3ad66e17 100644 --- a/internals/secrethub/color.go +++ b/internals/secrethub/color.go @@ -4,20 +4,29 @@ import ( "strconv" "github.com/fatih/color" + "github.com/secrethub/secrethub-cli/internals/cli" ) // noColorFlag configures the global behaviour to disable colored output. type noColorFlag bool +func (f noColorFlag) Type() string { + return "noColorFlag" +} + // init disables colored output based on the value of the flag. func (f noColorFlag) init() { color.NoColor = bool(f) } // RegisterColorFlag registers a color flag that configures whether colored output is used. -func RegisterColorFlag(r FlagRegisterer) { +func RegisterColorFlag(app *cli.App) { + commandClause := cli.CommandClause{ + Command: &app.Application, + App: app, + } flag := noColorFlag(false) - r.Flag("no-color", "Disable colored output.").SetValue(&flag) + commandClause.Var(&flag, "no-color", "Disable colored output.", true, true) } // String implements the flag.Value interface. diff --git a/internals/secrethub/command/command.go b/internals/secrethub/command/command.go index 8596e949..5bc4bcc2 100644 --- a/internals/secrethub/command/command.go +++ b/internals/secrethub/command/command.go @@ -1,22 +1,25 @@ package command import ( - "github.com/alecthomas/kingpin" + "github.com/spf13/cobra" "github.com/secrethub/secrethub-cli/internals/cli" ) // Registerer allows others to register commands on it. type Registerer interface { - Command(cmd string, help string) *cli.CommandClause + CreateCommand(cmd string, help string) *cli.CommandClause } // BindAction binds a function to a command clause, so that // it is executed when the command is parsed. -func BindAction(clause *cli.CommandClause, fn func() error) { - clause.Action( - func(*kingpin.ParseContext) error { +func BindAction(clause *cli.CommandClause, argumentRegister func(c *cobra.Command, args []string) error, fn func() error) { + if argumentRegister != nil { + clause.PreRunE = argumentRegister + } + if fn != nil { + clause.RunE = func(cmd *cobra.Command, args []string) error { return fn() - }, - ) + } + } } diff --git a/internals/secrethub/completion.go b/internals/secrethub/completion.go new file mode 100644 index 00000000..67ae237a --- /dev/null +++ b/internals/secrethub/completion.go @@ -0,0 +1,49 @@ +package secrethub + +import ( + "os" + + "github.com/secrethub/secrethub-cli/internals/cli" + "github.com/secrethub/secrethub-cli/internals/secrethub/command" + "github.com/spf13/cobra" +) + +type CompletionCommand struct { + shell string + clause *cli.CommandClause +} + +// NewCompletionCommand is a command that, when executed, generates a completion script +// for a specific shell, based on the argument it is provided with. It is able to generate +// completions for Bash, ZSh, Fish and PowerShell. +func NewCompletionCommand() *CompletionCommand { + return &CompletionCommand{} +} + +// Register registers the command, arguments and flags on the provided Registerer. +func (cmd *CompletionCommand) Register(r command.Registerer) { + cmd.clause = r.CreateCommand("completion", "Generate completion script").Hidden() + cmd.clause.DisableFlagsInUseLine = true + cmd.clause.ValidArgs = []string{"bash", "zsh", "fish", "powershell"} + cmd.clause.Args = cobra.ExactValidArgs(1) + command.BindAction(cmd.clause, cmd.argumentRegister, cmd.run) +} + +func (cmd *CompletionCommand) run() error { + switch cmd.shell { + case "bash": + _ = cmd.clause.Root().GenBashCompletion(os.Stdout) + case "zsh": + _ = cmd.clause.Root().GenZshCompletion(os.Stdout) + case "fish": + _ = cmd.clause.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + _ = cmd.clause.Root().GenPowerShellCompletion(os.Stdout) + } + return nil +} + +func (cmd *CompletionCommand) argumentRegister(_ *cobra.Command, args []string) error { + cmd.shell = args[0] + return nil +} diff --git a/internals/secrethub/config.go b/internals/secrethub/config.go index 8b3e790f..13cb3b0f 100644 --- a/internals/secrethub/config.go +++ b/internals/secrethub/config.go @@ -21,7 +21,7 @@ func NewConfigCommand(io ui.IO, store CredentialConfig) *ConfigCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ConfigCommand) Register(r command.Registerer) { - clause := r.Command("config", "Manage your local configuration.") + clause := r.CreateCommand("config", "Manage your local configuration.") NewConfigUpdatePassphraseCommand(cmd.io, cmd.credentialStore).Register(clause) NewConfigUpgradeCommand().Register(clause) } diff --git a/internals/secrethub/config_dir.go b/internals/secrethub/config_dir.go index 22694ef2..4764a73f 100644 --- a/internals/secrethub/config_dir.go +++ b/internals/secrethub/config_dir.go @@ -6,6 +6,10 @@ type ConfigDir struct { configdir.Dir } +func (c *ConfigDir) Type() string { + return "configDir" +} + func (c *ConfigDir) String() string { return c.Dir.Path() } diff --git a/internals/secrethub/config_update_passphrase.go b/internals/secrethub/config_update_passphrase.go index c890b715..245650e9 100644 --- a/internals/secrethub/config_update_passphrase.go +++ b/internals/secrethub/config_update_passphrase.go @@ -24,9 +24,9 @@ func NewConfigUpdatePassphraseCommand(io ui.IO, credentialStore CredentialConfig // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ConfigUpdatePassphraseCommand) Register(r command.Registerer) { - clause := r.Command("update-passphrase", "Update the passphrase of your local key credential file.") + clause := r.CreateCommand("update-passphrase", "Update the passphrase of your local key credential file.") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run upgrades the configuration in the profile directory to the new version. diff --git a/internals/secrethub/config_upgrade.go b/internals/secrethub/config_upgrade.go index d2a98ada..2606c4bc 100644 --- a/internals/secrethub/config_upgrade.go +++ b/internals/secrethub/config_upgrade.go @@ -14,8 +14,8 @@ func NewConfigUpgradeCommand() *ConfigUpgradeCommand { } func (cmd *ConfigUpgradeCommand) Register(r command.Registerer) { - clause := r.Command("upgrade", "Upgrade your .secrethub configuration directory. This can be useful to migrate to a newer version of the configuration files.").Hidden() - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("upgrade", "Upgrade your .secrethub configuration directory. This can be useful to migrate to a newer version of the configuration files.").Hidden() + command.BindAction(clause, nil, cmd.Run) } func (cmd *ConfigUpgradeCommand) Run() error { diff --git a/internals/secrethub/credential.go b/internals/secrethub/credential.go index d0fc14d7..83de3507 100644 --- a/internals/secrethub/credential.go +++ b/internals/secrethub/credential.go @@ -23,7 +23,7 @@ func NewCredentialCommand(io ui.IO, clientFactory ClientFactory, credentialStore // Register registers the command and its sub-commands on the provided Registerer. func (cmd *CredentialCommand) Register(r command.Registerer) { - clause := r.Command("credential", "Manage your credentials.") + clause := r.CreateCommand("credential", "Manage your credentials.") NewCredentialListCommand(cmd.io, cmd.clientFactory.NewClient).Register(clause) NewCredentialBackupCommand(cmd.io, cmd.clientFactory.NewClient).Register(clause) NewCredentialDisableCommand(cmd.io, cmd.clientFactory.NewClient).Register(clause) diff --git a/internals/secrethub/credential_backup.go b/internals/secrethub/credential_backup.go index d4673edd..4bf620ab 100644 --- a/internals/secrethub/credential_backup.go +++ b/internals/secrethub/credential_backup.go @@ -24,9 +24,9 @@ func NewCredentialBackupCommand(io ui.IO, newClient newClientFunc) *CredentialBa // Register registers the command, arguments and flags on the provided Registerer. func (cmd *CredentialBackupCommand) Register(r command.Registerer) { - clause := r.Command("backup", "Create a backup code for restoring your account.") + clause := r.CreateCommand("backup", "Create a backup code for restoring your account.") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run creates a backup code for the currently authenticated account. diff --git a/internals/secrethub/credential_disable.go b/internals/secrethub/credential_disable.go index dfc6b247..9788e50d 100644 --- a/internals/secrethub/credential_disable.go +++ b/internals/secrethub/credential_disable.go @@ -7,6 +7,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // CredentialDisableCommand is a command that allows to disable an existing credential. @@ -27,14 +28,14 @@ func NewCredentialDisableCommand(io ui.IO, newClient newClientFunc) *CredentialD // Register registers the command, arguments and flags on the provided Registerer. func (cmd *CredentialDisableCommand) Register(r command.Registerer) { - clause := r.Command("disable", "Disable a credential for usage on SecretHub.") + clause := r.CreateCommand("disable", "Disable a credential for usage on SecretHub.") + clause.Args = cobra.MaximumNArgs(1) - fingerprintHelp := fmt.Sprintf("Fingerprint of the credential to disable. At least the first %d characters must be entered.", api.ShortCredentialFingerprintMinimumLength) - clause.Arg("fingerprint", fingerprintHelp).StringVar(&cmd.fingerprint) + //fingerprintHelp := fmt.Sprintf("Fingerprint of the credential to disable. At least the first %d characters must be entered.", api.ShortCredentialFingerprintMinimumLength) + //clause.Arg("fingerprint", fingerprintHelp).StringVar(&cmd.fingerprint) + registerForceFlag(clause, &cmd.force) - registerForceFlag(clause).BoolVar(&cmd.force) - - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run disables an existing credential. @@ -84,3 +85,10 @@ func (cmd *CredentialDisableCommand) Run() error { return nil } + +func (cmd *CredentialDisableCommand) argumentRegister(c *cobra.Command, args []string) error { + if len(args) != 0 { + cmd.fingerprint = args[0] + } + return nil +} diff --git a/internals/secrethub/credential_list.go b/internals/secrethub/credential_list.go index 31801f11..f8adcf05 100644 --- a/internals/secrethub/credential_list.go +++ b/internals/secrethub/credential_list.go @@ -29,12 +29,12 @@ func NewCredentialListCommand(io ui.IO, newClient newClientFunc) *CredentialList // Register registers the command, arguments and flags on the provided Registerer. func (cmd *CredentialListCommand) Register(r command.Registerer) { - clause := r.Command("ls", "List all your credentials.") + clause := r.CreateCommand("ls", "List all your credentials.") clause.Alias("list") - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) + registerTimestampFlag(clause, &cmd.useTimestamps) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run lists all the currently authenticated account's credentials. diff --git a/internals/secrethub/credential_store.go b/internals/secrethub/credential_store.go index 09f2ccf2..cb415b62 100644 --- a/internals/secrethub/credential_store.go +++ b/internals/secrethub/credential_store.go @@ -6,6 +6,7 @@ import ( "github.com/secrethub/secrethub-go/pkg/secrethub/configdir" "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" + "github.com/secrethub/secrethub-cli/internals/cli" "github.com/secrethub/secrethub-cli/internals/cli/ui" ) @@ -22,13 +23,16 @@ type CredentialConfig interface { ConfigDir() configdir.Dir PassphraseReader() credentials.Reader - Register(FlagRegisterer) + Register(app *cli.App) } // NewCredentialConfig creates a new CredentialConfig. func NewCredentialConfig(io ui.IO) CredentialConfig { + dir, _ := configdir.Default() + c := ConfigDir{Dir: *dir} return &credentialConfig{ - io: io, + configDir: c, + io: io, } } @@ -49,12 +53,17 @@ func (store *credentialConfig) IsPassphraseSet() bool { } // Register registers the flags for configuring the store on the provided Registerer. -func (store *credentialConfig) Register(r FlagRegisterer) { - r.Flag("config-dir", "The absolute path to a custom configuration directory. Defaults to $HOME/.secrethub").Default("").PlaceHolder("CONFIG-DIR").SetValue(&store.configDir) - r.Flag("credential", "Use a specific account credential to authenticate to the API. This overrides the credential stored in the configuration directory.").StringVar(&store.AccountCredential) - r.Flag("p", "").Short('p').Hidden().NoEnvar().StringVar(&store.credentialPassphrase) // Shorthand -p is deprecated. Use --credential-passphrase instead. - r.Flag("credential-passphrase", "The passphrase to unlock your credential file. When set, it will not prompt for the passphrase, nor cache it in the OS keyring. Please only use this if you know what you're doing and ensure your passphrase doesn't end up in bash history.").StringVar(&store.credentialPassphrase) - r.Flag("credential-passphrase-cache-ttl", "Cache the credential passphrase in the OS keyring for this duration. The cache is automatically cleared after the timer runs out. Each time the passphrase is read from the cache the timer is reset. Passphrase caching is turned on by default for 5 minutes. Turn it off by setting the duration to 0.").Default("5m").DurationVar(&store.CredentialPassphraseCacheTTL) +func (store *credentialConfig) Register(app *cli.App) { + commandClause := cli.CommandClause{ + Command: &app.Application, + App: app, + } + commandClause.Var(&store.configDir, "config-dir", "The absolute path to a custom configuration directory. Defaults to $HOME/.secrethub", true, true) + commandClause.StringVar(&store.AccountCredential, "credential", "", "Use a specific account credential to authenticate to the API. This overrides the credential stored in the configuration directory.", true, true) + commandClause.StringVarP(&store.credentialPassphrase, "p", "p", "", "", true, true) // Shorthand -p is deprecated. Use --credential-passphrase instead. + commandClause.Flag("p").Hidden = true + commandClause.StringVar(&store.credentialPassphrase, "credential-passphrase", "", "The passphrase to unlock your credential file. When set, it will not prompt for the passphrase, nor cache it in the OS keyring. Please only use this if you know what you're doing and ensure your passphrase doesn't end up in bash history.", false, true) + commandClause.DurationVar(&store.CredentialPassphraseCacheTTL, "credential-passphrase-cache-ttl", 5*time.Minute, "Cache the credential passphrase in the OS keyring for this duration. The cache is automatically cleared after the timer runs out. Each time the passphrase is read from the cache the timer is reset. Passphrase caching is turned on by default for 5 minutes. Turn it off by setting the duration to 0.", true, true) } // Provider retrieves a credential from the store. diff --git a/internals/secrethub/debug.go b/internals/secrethub/debug.go index e85aa9c1..f4564cf6 100644 --- a/internals/secrethub/debug.go +++ b/internals/secrethub/debug.go @@ -7,11 +7,15 @@ import ( ) // RegisterDebugFlag registers a debug flag that changes the log level of the given logger to DEBUG. -func RegisterDebugFlag(r FlagRegisterer, logger cli.Logger) { +func RegisterDebugFlag(app *cli.App, logger cli.Logger) { + commandClause := cli.CommandClause{ + Command: &app.Application, + App: app, + } flag := debugFlag{ logger: logger, } - r.Flag("debug", "Enable debug mode.").Short('D').SetValue(&flag) + commandClause.VarP(&flag, "debug", "D", "Enable debug mode.", true, true) } // debugFlag configures the debug level of a logger. @@ -20,6 +24,10 @@ type debugFlag struct { logger cli.Logger } +func (f debugFlag) Type() string { + return "debugFlag" +} + func (f debugFlag) init() { if f.debug { f.logger.EnableDebug() diff --git a/internals/secrethub/env.go b/internals/secrethub/env.go index 5e653311..8c9fdd50 100644 --- a/internals/secrethub/env.go +++ b/internals/secrethub/env.go @@ -21,7 +21,7 @@ func NewEnvCommand(io ui.IO, newClient newClientFunc) *EnvCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *EnvCommand) Register(r command.Registerer) { - clause := r.Command("env", "[BETA] Manage environment variables.").Hidden() + clause := r.CreateCommand("env", "[BETA] Manage environment variables.").Hidden() clause.HelpLong("This command is hidden because it is still in beta. Future versions may break.") NewEnvReadCommand(cmd.io, cmd.newClient).Register(clause) NewEnvListCommand(cmd.io, cmd.newClient).Register(clause) diff --git a/internals/secrethub/env_ls.go b/internals/secrethub/env_ls.go index c1cac212..8fcb7b87 100644 --- a/internals/secrethub/env_ls.go +++ b/internals/secrethub/env_ls.go @@ -23,13 +23,13 @@ func NewEnvListCommand(io ui.IO, newClient newClientFunc) *EnvListCommand { // Register adds a CommandClause and it's args and flags to a Registerer. func (cmd *EnvListCommand) Register(r command.Registerer) { - clause := r.Command("ls", "[BETA] List environment variable names that will be populated with secrets.") + clause := r.CreateCommand("ls", "[BETA] List environment variable names that will be populated with secrets.") clause.HelpLong("This command is hidden because it is still in beta. Future versions may break.") clause.Alias("list") cmd.environment.register(clause) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run executes the command. diff --git a/internals/secrethub/env_read.go b/internals/secrethub/env_read.go index af216e25..eb6c7eaa 100644 --- a/internals/secrethub/env_read.go +++ b/internals/secrethub/env_read.go @@ -5,6 +5,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" + "github.com/spf13/cobra" ) // EnvReadCommand is a command to read the value of a single environment variable. @@ -26,13 +27,14 @@ func NewEnvReadCommand(io ui.IO, newClient newClientFunc) *EnvReadCommand { // Register adds a CommandClause and it's args and flags to a Registerer. func (cmd *EnvReadCommand) Register(r command.Registerer) { - clause := r.Command("read", "[BETA] Read the value of a single environment variable.") + clause := r.CreateCommand("read", "[BETA] Read the value of a single environment variable.") clause.HelpLong("This command is hidden because it is still in beta. Future versions may break.") - clause.Arg("key", "the key of the environment variable to read").StringVar(&cmd.key) + clause.Args = cobra.MaximumNArgs(1) + //clause.Arg("key", "the key of the environment variable to read").StringVar(&cmd.key) cmd.environment.register(clause) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run executes the command. @@ -58,3 +60,10 @@ func (cmd *EnvReadCommand) Run() error { return nil } + +func (cmd *EnvReadCommand) argumentRegister(c *cobra.Command, args []string) error { + if len(args) != 0 { + cmd.key = args[0] + } + return nil +} diff --git a/internals/secrethub/env_source.go b/internals/secrethub/env_source.go index 242d4f59..d74a9c70 100644 --- a/internals/secrethub/env_source.go +++ b/internals/secrethub/env_source.go @@ -20,6 +20,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/validation" "github.com/secrethub/secrethub-cli/internals/secrethub/tpl" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" "gopkg.in/yaml.v2" ) @@ -39,9 +40,9 @@ type environment struct { osEnv []string readFile func(filename string) ([]byte, error) osStat func(filename string) (os.FileInfo, error) - envar map[string]string + envar MapValue envFile string - templateVars map[string]string + templateVars MapValue templateVersion string dontPromptMissingTemplateVar bool secretsDir string @@ -55,20 +56,25 @@ func newEnvironment(io ui.IO, newClient newClientFunc) *environment { osEnv: os.Environ(), readFile: ioutil.ReadFile, osStat: os.Stat, - templateVars: make(map[string]string), - envar: make(map[string]string), + templateVars: MapValue{stringMap: make(map[string]string)}, + envar: MapValue{stringMap: make(map[string]string)}, } } func (env *environment) register(clause *cli.CommandClause) { - clause.Flag("envar", "Source an environment variable from a secret at a given path with `NAME=`").Short('e').StringMapVar(&env.envar) - clause.Flag("env-file", "The path to a file with environment variable mappings of the form `NAME=value`. Template syntax can be used to inject secrets.").StringVar(&env.envFile) - clause.Flag("template", "").Hidden().StringVar(&env.envFile) - clause.Flag("var", "Define the value for a template variable with `VAR=VALUE`, e.g. --var env=prod").Short('v').StringMapVar(&env.templateVars) - clause.Flag("template-version", "The template syntax version to be used. The options are v1, v2, latest or auto to automatically detect the version.").Default("auto").StringVar(&env.templateVersion) - clause.Flag("no-prompt", "Do not prompt when a template variable is missing and return an error instead.").BoolVar(&env.dontPromptMissingTemplateVar) - clause.Flag("secrets-dir", "Recursively include all secrets from a directory. Environment variable names are derived from the path of the secret: `/` are replaced with `_` and the name is uppercased.").StringVar(&env.secretsDir) - clause.Flag("env", "The name of the environment prepared by the set command (default is `default`)").Default("default").Hidden().StringVar(&env.secretsEnvDir) + clause.VarP(&env.envar, "envar", "e", "Source an environment variable from a secret at a given path with `NAME=`", true, false) + clause.StringVar(&env.envFile, "env-file", "", "The path to a file with environment variable mappings of the form `NAME=value`. Template syntax can be used to inject secrets.", true, false) + clause.StringVar(&env.envFile, "template", "", "", true, false) + clause.Flag("template").Hidden = true + clause.VarP(&env.templateVars, "var", "v", "Define the value for a template variable with `VAR=VALUE`, e.g. --var env=prod", true, false) + clause.StringVar(&env.templateVersion, "template-version", "auto", "The template syntax version to be used. The options are v1, v2, latest or auto to automatically detect the version.", true, false) + _ = clause.RegisterFlagCompletionFunc("template-version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"v1", "v2", "latest", "auto"}, cobra.ShellCompDirectiveDefault + }) + clause.BoolVar(&env.dontPromptMissingTemplateVar, "no-prompt", false, "Do not prompt when a template variable is missing and return an error instead.", true, false) + clause.StringVar(&env.secretsDir, "secrets-dir", "", "Recursively include all secrets from a directory. Environment variable names are derived from the path of the secret: `/` are replaced with `_` and the name is uppercased.", true, false) + clause.StringVar(&env.secretsEnvDir, "env", "default", "The name of the environment prepared by the set command (default is `default`)", true, false) + clause.Flag("env").Hidden = true } func (env *environment) env() (map[string]value, error) { @@ -107,7 +113,7 @@ func (env *environment) env() (map[string]value, error) { } if env.envFile != "" { - templateVariableReader, err := newVariableReader(osEnvMap, env.templateVars) + templateVariableReader, err := newVariableReader(osEnvMap, env.templateVars.stringMap) if err != nil { return nil, err } @@ -139,7 +145,7 @@ func (env *environment) env() (map[string]value, error) { // --envar flag // TODO: Validate the flags when parsing by implementing the Flag interface for EnvFlags. - flagEnv, err := NewEnvFlags(env.envar) + flagEnv, err := NewEnvFlags(env.envar.stringMap) if err != nil { return nil, err } @@ -663,3 +669,26 @@ func (o *osEnv) env() (map[string]value, error) { } return res, nil } + +type MapValue struct { + stringMap map[string]string +} + +func (m MapValue) String() string { + textRepresentation := "" + for k, v := range m.stringMap { + textRepresentation += k + "=" + v + ";" + } + return textRepresentation +} + +//TODO treat the case when the array does not contain exactly 2 elements +func (m MapValue) Set(s string) error { + arr := strings.Split(s, "=") + m.stringMap[arr[0]] = arr[1] + return nil +} + +func (m MapValue) Type() string { + return "mapValue" +} diff --git a/internals/secrethub/flags.go b/internals/secrethub/flags.go index 7dba437b..1ae276d1 100644 --- a/internals/secrethub/flags.go +++ b/internals/secrethub/flags.go @@ -1,19 +1,13 @@ package secrethub import ( - "github.com/alecthomas/kingpin" "github.com/secrethub/secrethub-cli/internals/cli" ) -// FlagRegisterer allows others to register flags on it. -type FlagRegisterer interface { - Flag(name, help string) *cli.Flag +func registerTimestampFlag(r *cli.CommandClause, p *bool) { + r.BoolVarP(p, "timestamp", "T", false, "Show timestamps formatted to RFC3339 instead of human readable durations.", true, false) } -func registerTimestampFlag(r FlagRegisterer) *kingpin.FlagClause { - return r.Flag("timestamp", "Show timestamps formatted to RFC3339 instead of human readable durations.").Short('T') -} - -func registerForceFlag(r FlagRegisterer) *kingpin.FlagClause { - return r.Flag("force", "Ignore confirmation and fail instead of prompt for missing arguments.").Short('f') +func registerForceFlag(r *cli.CommandClause, p *bool) { + r.BoolVarP(p, "force", "f", false, "Ignore confirmation and fail instead of prompt for missing arguments.", true, false) } diff --git a/internals/secrethub/generate.go b/internals/secrethub/generate.go index 67fa949e..d734730b 100644 --- a/internals/secrethub/generate.go +++ b/internals/secrethub/generate.go @@ -16,6 +16,8 @@ import ( "github.com/secrethub/secrethub-go/pkg/randchar" "github.com/docker/go-units" + + "github.com/spf13/cobra" ) var ( @@ -60,17 +62,30 @@ func NewGenerateSecretCommand(io ui.IO, newClient newClientFunc) *GenerateSecret // Register registers the command, arguments and flags on the provided Registerer. func (cmd *GenerateSecretCommand) Register(r command.Registerer) { - clause := r.Command("generate", "Generate a random secret.") - clause.Arg("secret-path", "The path to write the generated secret to").Required().PlaceHolder(secretPathPlaceHolder).StringVar(&cmd.firstArg) - clause.Flag("length", "The length of the generated secret. Defaults to "+strconv.Itoa(defaultLength)).PlaceHolder(strconv.Itoa(defaultLength)).Short('l').SetValue(&cmd.lengthFlag) - clause.Flag("min", ": Ensure that the resulting password contains at least n characters from the given character set. Note that adding constraints reduces the strength of the secret. When possible, avoid any constraints.").SetValue(&cmd.mins) - clause.Flag("clip", "Copy the generated value to the clipboard. The clipboard is automatically cleared after "+units.HumanDuration(cmd.clearClipboardAfter)+".").Short('c').BoolVar(&cmd.copyToClipboard) - clause.Flag("charset", "Define the set of characters to randomly generate a password from. Options are all, alphanumeric, numeric, lowercase, uppercase, letters, symbols and human-readable. Multiple character sets can be combined by supplying them in a comma separated list. Defaults to alphanumeric.").Default("alphanumeric").HintOptions("all", "alphanumeric", "numeric", "lowercase", "uppercase", "letters", "symbols", "human-readable").SetValue(&cmd.charsetFlag) - clause.Flag("symbols", "Include symbols in secret.").Short('s').Hidden().SetValue(&cmd.symbolsFlag) - clause.Arg("rand-command", "").Hidden().StringVar(&cmd.secondArg) - clause.Arg("length", "").Hidden().SetValue(&cmd.lengthArg) - - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("generate", "Generate a random secret.") + clause.Args = cobra.RangeArgs(1, 3) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.SecretSuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("secret-path", "The path to write the generated secret to").Required().PlaceHolder(secretPathPlaceHolder).StringVar(&cmd.firstArg) + clause.VarP(&cmd.lengthFlag, "length", "l", "The length of the generated secret. Defaults to "+strconv.Itoa(defaultLength), true, false) //.PlaceHolder(strconv.Itoa(defaultLength)).Short('l').SetValue(&cmd.lengthFlag) + clause.Flag("length").DefValue = strconv.Itoa(defaultLength) + clause.Var(&cmd.mins, "min", ": Ensure that the resulting password contains at least n characters from the given character set. Note that adding constraints reduces the strength of the secret. When possible, avoid any constraints.", true, false) + clause.BoolVarP(&cmd.copyToClipboard, "clip", "c", false, "Copy the generated value to the clipboard. The clipboard is automatically cleared after "+units.HumanDuration(cmd.clearClipboardAfter)+".", true, false) + clause.Var(&cmd.charsetFlag, "charset", "Define the set of characters to randomly generate a password from. Options are all, alphanumeric, numeric, lowercase, uppercase, letters, symbols and human-readable. Multiple character sets can be combined by supplying them in a comma separated list. Defaults to alphanumeric.", true, false) //Default("alphanumeric").HintOptions("all", "alphanumeric", "numeric", "lowercase", "uppercase", "letters", "symbols", "human-readable") + clause.Flag("charset").DefValue = "alphanumeric" + _ = clause.RegisterFlagCompletionFunc("charset", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"all", "alphanumeric", "numeric", "lowercase", "uppercase", "letters", "symbols", "human-readable"}, cobra.ShellCompDirectiveDefault + }) + clause.VarP(&cmd.symbolsFlag, "symbols", "s", "Include symbols in secret.", true, false) //Short('s').Hidden().SetValue(&cmd.symbolsFlag) + clause.Flag("symbols").Hidden = true + //clause.Arg("rand-command", "").Hidden().StringVar(&cmd.secondArg) + //clause.Arg("length", "").Hidden().SetValue(&cmd.lengthArg) + + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // before configures the command using the flag values. @@ -102,6 +117,21 @@ func (cmd *GenerateSecretCommand) Run() error { return cmd.run() } +func (cmd *GenerateSecretCommand) argumentRegister(c *cobra.Command, args []string) error { + cmd.firstArg = args[0] + if len(args) >= 2 { + cmd.secondArg = args[1] + } + if len(args) == 3 { + len, err := strconv.Atoi(args[2]) + if err != nil { + return err + } + cmd.lengthArg = intValue{v: &len} + } + return nil +} + // run generates a new secret and writes to the output path. func (cmd *GenerateSecretCommand) run() error { length, err := cmd.length() @@ -198,6 +228,10 @@ type minRuleValue struct { v []randchar.Option } +func (ov *minRuleValue) Type() string { + return "minRuleValue" +} + func (ov *minRuleValue) String() string { return "" } @@ -230,6 +264,10 @@ type charsetValue struct { v randchar.Charset } +func (cv *charsetValue) Type() string { + return "charsetValue" +} + func (cv *charsetValue) String() string { return "" } @@ -254,6 +292,10 @@ type intValue struct { v *int } +func (iv *intValue) Type() string { + return "intValue" +} + func (iv *intValue) Get() int { if iv.v == nil { return 0 @@ -282,6 +324,10 @@ type boolValue struct { v *bool } +func (iv *boolValue) Type() string { + panic("boolValue") +} + func (iv *boolValue) Get() bool { if iv.v == nil { return false diff --git a/internals/secrethub/init.go b/internals/secrethub/init.go index dce0c9ac..82184810 100644 --- a/internals/secrethub/init.go +++ b/internals/secrethub/init.go @@ -38,11 +38,11 @@ func NewInitCommand(io ui.IO, newClient newClientFunc, newClientWithoutCredentia // Register registers the command, arguments and flags on the provided Registerer. func (cmd *InitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Initialize the SecretHub client for first use on this device.") - clause.Flag("backup-code", "The backup code used to restore an existing account to this device.").StringVar(&cmd.backupCode) - registerForceFlag(clause).BoolVar(&cmd.force) + clause := r.CreateCommand("init", "Initialize the SecretHub client for first use on this device.") + clause.StringVar(&cmd.backupCode, "backup-code", "", "The backup code used to restore an existing account to this device.", true, false) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } type InitMode int diff --git a/internals/secrethub/inject.go b/internals/secrethub/inject.go index e8718bd9..96621d27 100644 --- a/internals/secrethub/inject.go +++ b/internals/secrethub/inject.go @@ -36,7 +36,7 @@ type InjectCommand struct { clipper clip.Clipper osEnv []string newClient newClientFunc - templateVars map[string]string + templateVars MapValue templateVersion string dontPromptMissingTemplateVars bool } @@ -49,31 +49,32 @@ func NewInjectCommand(io ui.IO, newClient newClientFunc) *InjectCommand { clearClipboardAfter: defaultClearClipboardAfter, io: io, newClient: newClient, - templateVars: make(map[string]string), + templateVars: MapValue{stringMap: make(map[string]string)}, } } // Register adds a CommandClause and it's args and flags to a cli.App. // Register adds args and flags. func (cmd *InjectCommand) Register(r command.Registerer) { - clause := r.Command("inject", "Inject secrets into a template.") - clause.Flag( - "clip", + clause := r.CreateCommand("inject", "Inject secrets into a template.") + clause.BoolVarP(&cmd.useClipboard, + "clip", "c", false, fmt.Sprintf( "Copy the injected template to the clipboard instead of stdout. The clipboard is automatically cleared after %s.", units.HumanDuration(cmd.clearClipboardAfter), - ), - ).Short('c').BoolVar(&cmd.useClipboard) - clause.Flag("in-file", "The filename of a template file to inject.").Short('i').StringVar(&cmd.inFile) - clause.Flag("out-file", "Write the injected template to a file instead of stdout.").Short('o').StringVar(&cmd.outFile) - clause.Flag("file", "").Hidden().StringVar(&cmd.outFile) // Alias of --out-file (for backwards compatibility) - clause.Flag("file-mode", "Set filemode for the output file if it does not yet exist. Defaults to 0600 (read and write for current user) and is ignored without the --out-file flag.").Default("0600").SetValue(&cmd.fileMode) - clause.Flag("var", "Define the value for a template variable with `VAR=VALUE`, e.g. --var env=prod").Short('v').StringMapVar(&cmd.templateVars) - clause.Flag("template-version", "The template syntax version to be used. The options are v1, v2, latest or auto to automatically detect the version.").Default("auto").StringVar(&cmd.templateVersion) - clause.Flag("no-prompt", "Do not prompt when a template variable is missing and return an error instead.").BoolVar(&cmd.dontPromptMissingTemplateVars) - clause.Flag("force", "Overwrite the output file if it already exists, without prompting for confirmation. This flag is ignored if no --out-file is supplied.").Short('f').BoolVar(&cmd.force) - - command.BindAction(clause, cmd.Run) + ), true, false) + clause.StringVarP(&cmd.inFile, "in-file", "i", "", "The filename of a template file to inject.", true, false) + clause.StringVarP(&cmd.outFile, "out-file", "o", "", "Write the injected template to a file instead of stdout.", true, false) + clause.StringVar(&cmd.outFile, "file", "", "", true, false) // Alias of --out-file (for backwards compatibility) + clause.Flag("file").Hidden = true + clause.Var(&cmd.fileMode, "file-mode", "Set filemode for the output file if it does not yet exist. Defaults to 0600 (read and write for current user) and is ignored without the --out-file flag.", true, false) + clause.Flag("file-mode").DefValue = "0600" + clause.VarP(&cmd.templateVars, "var", "v", "Define the value for a template variable with `VAR=VALUE`, e.g. --var env=prod", true, false) + clause.StringVar(&cmd.templateVersion, "template-version", "auto", "Do not prompt when a template variable is missing and return an error instead.", true, false) + clause.BoolVar(&cmd.dontPromptMissingTemplateVars, "no-prompt", false, "Do not prompt when a template variable is missing and return an error instead.", true, false) + clause.BoolVarP(&cmd.force, "force", "f", false, "Overwrite the output file if it already exists, without prompting for confirmation. This flag is ignored if no --out-file is supplied.", true, false) + + command.BindAction(clause, nil, cmd.Run) } // Run handles the command with the options as specified in the command. @@ -104,7 +105,7 @@ func (cmd *InjectCommand) Run() error { osEnv, _ := parseKeyValueStringsToMap(cmd.osEnv) var templateVariableReader tpl.VariableReader - templateVariableReader, err = newVariableReader(osEnv, cmd.templateVars) + templateVariableReader, err = newVariableReader(osEnv, cmd.templateVars.stringMap) if err != nil { return err } diff --git a/internals/secrethub/inspect.go b/internals/secrethub/inspect.go index 22ff1ad1..6a357951 100644 --- a/internals/secrethub/inspect.go +++ b/internals/secrethub/inspect.go @@ -3,6 +3,7 @@ package secrethub import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" + "github.com/spf13/cobra" "github.com/secrethub/secrethub-go/internals/api" ) @@ -30,10 +31,12 @@ func NewInspectCommand(io ui.IO, newClient newClientFunc) *InspectCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *InspectCommand) Register(r command.Registerer) { - clause := r.Command("inspect", "Print details of a resource.") - clause.Arg("repo or secret-path", "Path to the repository or the secret to inspect "+repoPathPlaceHolder+" or "+secretPathOptionalVersionPlaceHolder).Required().SetValue(&cmd.path) + clause := r.CreateCommand("inspect", "Print details of a resource.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.SecretSuggestions + //clause.Arg("repo or secret-path", "Path to the repository or the secret to inspect "+repoPathPlaceHolder+" or "+secretPathOptionalVersionPlaceHolder).Required().SetValue(&cmd.path) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run inspects a repository or a secret @@ -67,3 +70,12 @@ func (cmd *InspectCommand) Run() error { return ErrInspectResourceNotSupported } + +func (cmd *InspectCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewPath(args[0]) + if err != nil { + return err + } + return nil +} diff --git a/internals/secrethub/keyring_clear.go b/internals/secrethub/keyring_clear.go index 40edb8cc..93e7f447 100644 --- a/internals/secrethub/keyring_clear.go +++ b/internals/secrethub/keyring_clear.go @@ -21,12 +21,12 @@ func NewKeyringClearCommand() *KeyringClearCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *KeyringClearCommand) Register(r command.Registerer) { - clause := r.Command("keyring-clear", "Clear the key passphrase from the keyring.").Hidden() + clause := r.CreateCommand("keyring-clear", "Clear the key passphrase from the keyring.").Hidden() // Alias for backwards compatibility with old name of command. clause.Alias("key-passphrase-clear") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run waits for the keyringItem store to expire and clears it. diff --git a/internals/secrethub/kingpin.go b/internals/secrethub/kingpin.go deleted file mode 100644 index 40f7fcdd..00000000 --- a/internals/secrethub/kingpin.go +++ /dev/null @@ -1 +0,0 @@ -package secrethub diff --git a/internals/secrethub/list.go b/internals/secrethub/list.go index 8a51f99e..44d3cd40 100644 --- a/internals/secrethub/list.go +++ b/internals/secrethub/list.go @@ -9,9 +9,9 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" - "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/internals/errio" + "github.com/spf13/cobra" ) // LsCommand lists a repo, secret or namespace. @@ -33,13 +33,15 @@ func NewLsCommand(io ui.IO, newClient newClientFunc) *LsCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *LsCommand) Register(r command.Registerer) { - clause := r.Command("ls", "List contents of a path.") + clause := r.CreateCommand("ls", "List contents of a path.") clause.Alias("list") - clause.Arg("path", "The path to list contents of").SetValue(&cmd.path) - clause.Flag("quiet", "Only print paths.").Short('q').BoolVar(&cmd.quiet) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) + clause.Args = cobra.MaximumNArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.DirectorySuggestions + //clause.Arg("path", "The path to list contents of").SetValue(&cmd.path) + clause.BoolVarP(&cmd.quiet, "quiet", "q", false, "Only print paths.", true, false) + registerTimestampFlag(clause, &cmd.useTimestamps) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run lists a repo, secret or namespace. @@ -132,6 +134,17 @@ func (cmd *LsCommand) Run() error { return errio.UnexpectedError(errors.New("invalid path argument")) } +func (cmd *LsCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + if len(args) != 0 { + cmd.path, err = api.NewPath(args[0]) + if err != nil { + return err + } + } + return nil +} + // printVersions prints out secret versions in long or short format. func printVersions(w io.Writer, quiet bool, timeFormatter TimeFormatter, versions ...*api.SecretVersion) error { if quiet { diff --git a/internals/secrethub/mkdir.go b/internals/secrethub/mkdir.go index 37e94438..f7e3a6d4 100644 --- a/internals/secrethub/mkdir.go +++ b/internals/secrethub/mkdir.go @@ -9,6 +9,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" + + "github.com/spf13/cobra" ) // Errors @@ -34,11 +36,13 @@ func NewMkDirCommand(io ui.IO, newClient newClientFunc) *MkDirCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *MkDirCommand) Register(r command.Registerer) { - clause := r.Command("mkdir", "Create a new directory.") - clause.Arg("dir-paths", "The paths to the directories").Required().PlaceHolder(dirPathsPlaceHolder).SetValue(&cmd.paths) - clause.Flag("parents", "Create parent directories if needed. Does not error when directories already exist.").BoolVar(&cmd.parents) + clause := r.CreateCommand("mkdir", "Create a new directory.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.DirectorySuggestions + //clause.Arg("dir-paths", "The paths to the directories").Required().PlaceHolder(dirPathsPlaceHolder).SetValue(&cmd.paths) + clause.BoolVar(&cmd.parents, "parents", false, "Create parent directories if needed. Does not error when directories already exist.", true, false) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run executes the command. @@ -59,6 +63,15 @@ func (cmd *MkDirCommand) Run() error { return nil } +func (cmd *MkDirCommand) argumentRegister(c *cobra.Command, args []string) error { + var list dirPathList + for _, arg := range args { + list = append(list, arg) + } + cmd.paths = list + return nil +} + // createDirectory validates the given path and creates a directory on it. func (cmd *MkDirCommand) createDirectory(client secrethub.ClientInterface, path string) error { dirPath, err := api.NewDirPath(path) diff --git a/internals/secrethub/mlock.go b/internals/secrethub/mlock.go index 153f3a07..494518e9 100644 --- a/internals/secrethub/mlock.go +++ b/internals/secrethub/mlock.go @@ -3,12 +3,17 @@ package secrethub import ( "strconv" + "github.com/secrethub/secrethub-cli/internals/cli" "github.com/secrethub/secrethub-cli/internals/cli/mlock" ) // mlockFlag configures locking memory. type mlockFlag bool +func (f mlockFlag) Type() string { + return "mlockFlag" +} + // init locks the memory based on the flag value if supported. func (f mlockFlag) init() error { if f { @@ -23,9 +28,13 @@ func (f mlockFlag) init() error { } // RegisterMlockFlag registers a mlock flag that enables memory locking when set to true. -func RegisterMlockFlag(r FlagRegisterer) { +func RegisterMlockFlag(app *cli.App) { + commandClause := cli.CommandClause{ + Command: &app.Application, + App: app, + } flag := mlockFlag(false) - r.Flag("mlock", "Enable memory locking").SetValue(&flag) + commandClause.Var(&flag, "mlock", "Enable memory locking", true, true) } // String implements the flag.Value interface. diff --git a/internals/secrethub/org.go b/internals/secrethub/org.go index 49637400..94bcf89e 100644 --- a/internals/secrethub/org.go +++ b/internals/secrethub/org.go @@ -21,7 +21,7 @@ func NewOrgCommand(io ui.IO, newClient newClientFunc) *OrgCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *OrgCommand) Register(r command.Registerer) { - clause := r.Command("org", "Manage shared organization workspaces.") + clause := r.CreateCommand("org", "Manage shared organization workspaces.") clause.Alias("organization") clause.Alias("organisation") clause.Alias("orgs") diff --git a/internals/secrethub/org_init.go b/internals/secrethub/org_init.go index b936b906..3e7db36b 100644 --- a/internals/secrethub/org_init.go +++ b/internals/secrethub/org_init.go @@ -11,7 +11,7 @@ import ( // OrgInitCommand handles creating an organization. type OrgInitCommand struct { - name api.OrgName + name orgNameValue description string force bool io ui.IO @@ -28,21 +28,24 @@ func NewOrgInitCommand(io ui.IO, newClient newClientFunc) *OrgInitCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Initialize a new organization account.") - clause.Flag("name", "The name you would like to use for your organization. If not set, you will be asked for it.").SetValue(&cmd.name) - clause.Flag("description", "A description (max 144 chars) for your organization so others will recognize it. If not set, you will be asked for it.").StringVar(&cmd.description) - clause.Flag("descr", "").Hidden().StringVar(&cmd.description) - clause.Flag("desc", "").Hidden().StringVar(&cmd.description) - registerForceFlag(clause).BoolVar(&cmd.force) - - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("init", "Initialize a new organization account.") + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + clause.Var(&cmd.name, "name", "The name you would like to use for your organization. If not set, you will be asked for it.", true, false) + clause.StringVar(&cmd.description, "description", "", "A description (max 144 chars) for your organization so others will recognize it. If not set, you will be asked for it.", true, false) + clause.StringVar(&cmd.description, "descr", "", "", true, false) + clause.Flag("descr").Hidden = true + clause.StringVar(&cmd.description, "desc", "", "", true, false) + clause.Flag("desc").Hidden = true + registerForceFlag(clause, &cmd.force) + + command.BindAction(clause, nil, cmd.Run) } // Run creates an organization. func (cmd *OrgInitCommand) Run() error { var err error - incompleteInput := cmd.name == "" || cmd.description == "" + incompleteInput := cmd.name.orgName == "" || cmd.description == "" if cmd.force && incompleteInput { return ErrMissingFlags } else if !cmd.force && incompleteInput { @@ -52,12 +55,12 @@ func (cmd *OrgInitCommand) Run() error { "Please answer the questions below, followed by an [ENTER]\n\n", ) - if cmd.name == "" { + if cmd.name.orgName == "" { name, err := ui.AskAndValidate(cmd.io, "The name you would like to use for your organization: ", 2, api.ValidateOrgName) if err != nil { return err } - cmd.name = api.OrgName(name) + cmd.name = orgNameValue{orgName: api.OrgName(name)} } if cmd.description == "" { @@ -78,7 +81,7 @@ func (cmd *OrgInitCommand) Run() error { fmt.Fprintf(cmd.io.Output(), "Creating organization...\n") - resp, err := client.Orgs().Create(cmd.name.Value(), cmd.description) + resp, err := client.Orgs().Create(cmd.name.orgName.Value(), cmd.description) if err != nil { return err } @@ -87,3 +90,19 @@ func (cmd *OrgInitCommand) Run() error { return nil } + +type orgNameValue struct { + orgName api.OrgName +} + +func (o orgNameValue) String() string { + return o.orgName.String() +} + +func (o orgNameValue) Set(s string) error { + return o.orgName.Set(s) +} + +func (o orgNameValue) Type() string { + return "orgNameValue" +} diff --git a/internals/secrethub/org_init_test.go b/internals/secrethub/org_init_test.go index c298d1d3..19ad5655 100644 --- a/internals/secrethub/org_init_test.go +++ b/internals/secrethub/org_init_test.go @@ -24,7 +24,7 @@ func TestOrgInitCommand_Run(t *testing.T) { }{ "success": { cmd: OrgInitCommand{ - name: "company", + name: orgNameValue{"company"}, description: "description", force: true, }, @@ -40,7 +40,7 @@ func TestOrgInitCommand_Run(t *testing.T) { }, "new client error": { cmd: OrgInitCommand{ - name: "company", + name: orgNameValue{"company"}, description: "description", force: true, }, @@ -49,7 +49,7 @@ func TestOrgInitCommand_Run(t *testing.T) { }, "create org error": { cmd: OrgInitCommand{ - name: "company", + name: orgNameValue{"company"}, description: "description", force: true, }, diff --git a/internals/secrethub/org_inspect.go b/internals/secrethub/org_inspect.go index 123d2df6..7c68f913 100644 --- a/internals/secrethub/org_inspect.go +++ b/internals/secrethub/org_inspect.go @@ -8,6 +8,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // OrgInspectCommand handles printing out the details of an organization in a JSON format. @@ -29,10 +31,12 @@ func NewOrgInspectCommand(io ui.IO, newClient newClientFunc) *OrgInspectCommand // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgInspectCommand) Register(r command.Registerer) { - clause := r.Command("inspect", "Show the details of an organization.") - clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.name) + clause := r.CreateCommand("inspect", "Show the details of an organization.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.name) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run prints out the details of an organization. @@ -67,6 +71,15 @@ func (cmd *OrgInspectCommand) Run() error { return nil } +func (cmd *OrgInspectCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.name = api.OrgName(args[0]) + return nil +} + // OrgInspectOutput is the json format to print out with all the details of an organization. type OrgInspectOutput struct { Name string diff --git a/internals/secrethub/org_invite.go b/internals/secrethub/org_invite.go index 459193ce..582d711b 100644 --- a/internals/secrethub/org_invite.go +++ b/internals/secrethub/org_invite.go @@ -7,6 +7,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // OrgInviteCommand handles inviting a user to an organization. @@ -29,13 +31,23 @@ func NewOrgInviteCommand(io ui.IO, newClient newClientFunc) *OrgInviteCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgInviteCommand) Register(r command.Registerer) { - clause := r.Command("invite", "Invite a user to join an organization.") - clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) - clause.Arg("username", "The username of the user to invite").Required().StringVar(&cmd.username) - clause.Flag("role", "Assign a role to the invited member. This can be either `admin` or `member`. It defaults to `member`.").Default("member").StringVar(&cmd.role) - registerForceFlag(clause).BoolVar(&cmd.force) + clause := r.CreateCommand("invite", "Invite a user to join an organization.") + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.RepositorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) + //clause.Arg("username", "The username of the user to invite").Required().StringVar(&cmd.username) + clause.StringVar(&cmd.role, "role", "member", "Assign a role to the invited member. This can be either `admin` or `member`. It defaults to `member`.", true, false) + _ = clause.RegisterFlagCompletionFunc("role", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"admin", "member"}, cobra.ShellCompDirectiveDefault + }) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run invites a user to an organization and gives them a certain role. @@ -72,3 +84,13 @@ func (cmd *OrgInviteCommand) Run() error { return nil } + +func (cmd *OrgInviteCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.orgName = api.OrgName(args[0]) + cmd.username = args[1] + return nil +} diff --git a/internals/secrethub/org_list_users.go b/internals/secrethub/org_list_users.go index 7bfbedc7..08198c78 100644 --- a/internals/secrethub/org_list_users.go +++ b/internals/secrethub/org_list_users.go @@ -9,6 +9,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // OrgListUsersCommand handles listing the users of an organization. @@ -30,12 +32,14 @@ func NewOrgListUsersCommand(io ui.IO, newClient newClientFunc) *OrgListUsersComm // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgListUsersCommand) Register(r command.Registerer) { - clause := r.Command("list-users", "List all members of an organization.") + clause := r.CreateCommand("list-users", "List all members of an organization.") clause.Alias("list-members") - clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) + registerTimestampFlag(clause, &cmd.useTimestamps) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run lists the users of an organization. @@ -44,6 +48,15 @@ func (cmd *OrgListUsersCommand) Run() error { return cmd.run() } +func (cmd *OrgListUsersCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.orgName = api.OrgName(args[0]) + return nil +} + // beforeRun configures the command using the flag values. func (cmd *OrgListUsersCommand) beforeRun() { cmd.timeFormatter = NewTimeFormatter(cmd.useTimestamps) diff --git a/internals/secrethub/org_ls.go b/internals/secrethub/org_ls.go index fccc24b4..546aeb23 100644 --- a/internals/secrethub/org_ls.go +++ b/internals/secrethub/org_ls.go @@ -30,12 +30,13 @@ func NewOrgLsCommand(io ui.IO, newClient newClientFunc) *OrgLsCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgLsCommand) Register(r command.Registerer) { - clause := r.Command("ls", "List all organizations you are a member of.") + clause := r.CreateCommand("ls", "List all organizations you are a member of.") clause.Alias("list") - clause.Flag("quiet", "Only print organization names.").Short('q').BoolVar(&cmd.quiet) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) + clause.BoolVarP(&cmd.quiet, "quiet", "q", false, "Only print organization names.", true, false) - command.BindAction(clause, cmd.Run) + registerTimestampFlag(clause, &cmd.useTimestamps) + + command.BindAction(clause, nil, cmd.Run) } // Run lists all organizations a user is a member of. diff --git a/internals/secrethub/org_purchase.go b/internals/secrethub/org_purchase.go index a7da9083..b2d2fbfc 100644 --- a/internals/secrethub/org_purchase.go +++ b/internals/secrethub/org_purchase.go @@ -21,9 +21,9 @@ func NewOrgPurchaseCommand(io ui.IO) *OrgPurchaseCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgPurchaseCommand) Register(r command.Registerer) { - clause := r.Command("purchase", "Purchase a SecretHub subscription.") + clause := r.CreateCommand("purchase", "Purchase a SecretHub subscription.") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run prints instructions on purchasing a SecretHub subscription. diff --git a/internals/secrethub/org_revoke.go b/internals/secrethub/org_revoke.go index dccfc0bf..cc504cdd 100644 --- a/internals/secrethub/org_revoke.go +++ b/internals/secrethub/org_revoke.go @@ -9,6 +9,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // OrgRevokeCommand handles revoking a member from an organization. @@ -29,11 +31,18 @@ func NewOrgRevokeCommand(io ui.IO, newClient newClientFunc) *OrgRevokeCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgRevokeCommand) Register(r command.Registerer) { - clause := r.Command("revoke", "Revoke a user from an organization. This automatically revokes the user from all of the organization's repositories. A list of repositories containing secrets that should be rotated will be printed out.") - clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) - clause.Arg("username", "The username of the user").Required().StringVar(&cmd.username) + clause := r.CreateCommand("revoke", "Revoke a user from an organization. This automatically revokes the user from all of the organization's repositories. A list of repositories containing secrets that should be rotated will be printed out.") + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.RepositorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) + //clause.Arg("username", "The username of the user").Required().StringVar(&cmd.username) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run revokes an organization member. @@ -130,6 +139,16 @@ func (cmd *OrgRevokeCommand) Run() error { return nil } +func (cmd *OrgRevokeCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.orgName = api.OrgName(args[0]) + cmd.username = args[1] + return nil +} + // writeOrgRevokeRepoList is a helper function that writes repos with a status. func writeOrgRevokeRepoList(w io.Writer, repos ...*api.RevokeRepoResponse) error { tw := tabwriter.NewWriter(w, 0, 2, 2, ' ', 0) diff --git a/internals/secrethub/org_rm.go b/internals/secrethub/org_rm.go index 12a651b0..31d55e54 100644 --- a/internals/secrethub/org_rm.go +++ b/internals/secrethub/org_rm.go @@ -7,6 +7,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // OrgRmCommand deletes an organization, prompting the user for confirmation. @@ -27,11 +29,13 @@ func NewOrgRmCommand(io ui.IO, newClient newClientFunc) *OrgRmCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgRmCommand) Register(r command.Registerer) { - clause := r.Command("rm", "Permanently delete an organization and all the repositories it owns.") + clause := r.CreateCommand("rm", "Permanently delete an organization and all the repositories it owns.") clause.Alias("remove") - clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.name) + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.name) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run deletes an organization, prompting the user for confirmation. @@ -71,3 +75,12 @@ func (cmd *OrgRmCommand) Run() error { return nil } + +func (cmd *OrgRmCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.name = api.OrgName(args[0]) + return nil +} diff --git a/internals/secrethub/org_set_role.go b/internals/secrethub/org_set_role.go index 65286a31..d0e5d66a 100644 --- a/internals/secrethub/org_set_role.go +++ b/internals/secrethub/org_set_role.go @@ -7,6 +7,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // OrgSetRoleCommand handles updating the role of an organization member. @@ -28,12 +30,21 @@ func NewOrgSetRoleCommand(io ui.IO, newClient newClientFunc) *OrgSetRoleCommand // Register registers the command, arguments and flags on the provided Registerer. func (cmd *OrgSetRoleCommand) Register(r command.Registerer) { - clause := r.Command("set-role", "Set a user's organization role.") - clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) - clause.Arg("username", "The username of the user").Required().StringVar(&cmd.username) - clause.Arg("role", "The role to assign to the user. Can be either `admin` or `member`.").Required().StringVar(&cmd.role) + clause := r.CreateCommand("set-role", "Set a user's organization role.") + clause.Args = cobra.ExactValidArgs(3) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.RepositorySuggestions(cmd, args, toComplete) + } else if len(args) == 1 { + return []string{}, cobra.ShellCompDirectiveDefault + } + return []string{"admin", "member"}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("org-name", "The organization name").Required().SetValue(&cmd.orgName) + //clause.Arg("username", "The username of the user").Required().StringVar(&cmd.username) + //clause.Arg("role", "The role to assign to the user. Can be either `admin` or `member`.").Required().StringVar(&cmd.role) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run updates the role of an organization member. @@ -54,3 +65,14 @@ func (cmd *OrgSetRoleCommand) Run() error { return nil } + +func (cmd *OrgSetRoleCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.orgName = api.OrgName(args[0]) + cmd.username = args[1] + cmd.role = args[2] + return nil +} diff --git a/internals/secrethub/path_autocompletion.go b/internals/secrethub/path_autocompletion.go new file mode 100644 index 00000000..d413a5e4 --- /dev/null +++ b/internals/secrethub/path_autocompletion.go @@ -0,0 +1,128 @@ +package secrethub + +import ( + "fmt" + "os" + "strings" + + "github.com/secrethub/secrethub-go/pkg/secrethub" + "github.com/secrethub/secrethub-go/pkg/secrethub/iterator" + "github.com/spf13/cobra" +) + +type AutoCompleter struct { + client *secrethub.Client +} + +// SecretSuggestions provides auto-completions for both arguments and flags +// that take as values paths to secrets SecretHub. +func (ac AutoCompleter) SecretSuggestions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getFullPaths(ac.client, toComplete, true), cobra.ShellCompDirectiveNoSpace +} + +// DirectorySuggestions provides auto-completions for both arguments and flags +// that take as values paths to directories in SecretHub. +func (ac AutoCompleter) DirectorySuggestions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getFullPaths(ac.client, toComplete, false), cobra.ShellCompDirectiveNoSpace +} + +// RepositorySuggestions provides auto-completions for both arguments and flags +// that take as values paths to repositories in SecretHub. +func (ac AutoCompleter) RepositorySuggestions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getNamespacesAndRepos(ac.client), cobra.ShellCompDirectiveNoSpace +} + +// NamespaceSuggestions provides auto-completions for both arguments and flags +// that take as values namespaces in SecretHub. +func (ac AutoCompleter) NamespaceSuggestions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return getNamespaces(ac.client), cobra.ShellCompDirectiveNoSpace +} + +func getNamespaces(client *secrethub.Client) []string { + var suggestions []string + iter := client.Orgs().Iterator(&secrethub.OrgIteratorParams{}) + for { + org, err := iter.Next() + if err == iterator.Done { + break + } else if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + return nil + } + suggestions = append(suggestions, org.Name) + } + me := client.Me() + user, _ := me.GetUser() + suggestions = append(suggestions, user.Username) + + return suggestions +} + +func getNamespacesAndRepos(client *secrethub.Client) []string { + var suggestions []string + iter := client.Me().RepoIterator(&secrethub.RepoIteratorParams{}) + for { + repo, err := iter.Next() + if err == iterator.Done { + break + } else if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + return nil + } + suggestions = append(suggestions, string(repo.Path()+"/")) + } + //TAKE PATHS FROM ORGS + //iter2 := client.Orgs().Iterator(&secrethub.OrgIteratorParams{}) + //for { + // org, err := iter2.Next() + // if err == iterator.Done { + // break + // } else if err != nil { + // _, _ = fmt.Fprintln(os.Stderr, err.Error()) + // return nil + // } + // suggestions = append(suggestions, org.) + //} + return suggestions +} + +func getFullPaths(client *secrethub.Client, toComplete string, includeSecrets bool) []string { + if len(toComplete) == 0 { + return getNamespacesAndRepos(client) + } + tree, err := client.Dirs().GetTree(toComplete, 1, false) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + path := toComplete[0:strings.LastIndex(toComplete, "/")] + _, err = client.Dirs().GetTree(path, 1, false) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + return getNamespacesAndRepos(client) + } + return getFullPaths(client, path, includeSecrets) + } + if strings.LastIndex(toComplete, "/") != len(toComplete)-1 { + toComplete += "/" + } + suggestions := make([]string, tree.DirCount()+tree.SecretCount()) + + for _, dir := range tree.RootDir.SubDirs { + suggestions = append(suggestions, toComplete+dir.Name+"/") + } + + if includeSecrets { + for _, secret := range tree.RootDir.Secrets { + suggestions = append(suggestions, toComplete+secret.Name) + } + } + return suggestions +} + +// GetClient returns a new SecretHub client. +func GetClient() *secrethub.Client { + client, err := secrethub.NewClient() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + } + return client +} diff --git a/internals/secrethub/path_placeholders.go b/internals/secrethub/path_placeholders.go index 1aead2d7..94dcdcab 100644 --- a/internals/secrethub/path_placeholders.go +++ b/internals/secrethub/path_placeholders.go @@ -1,10 +1,10 @@ package secrethub -const ( - repoPathPlaceHolder = "/" - dirPathPlaceHolder = repoPathPlaceHolder + "/[/ ...]" - dirPathsPlaceHolder = dirPathPlaceHolder + "..." - optionalDirPathPlaceHolder = repoPathPlaceHolder + "[/ ...]" - secretPathPlaceHolder = optionalDirPathPlaceHolder + "/" - secretPathOptionalVersionPlaceHolder = secretPathPlaceHolder + "[:]" -) +//const ( +// repoPathPlaceHolder = "/" +// dirPathPlaceHolder = repoPathPlaceHolder + "/[/ ...]" +// dirPathsPlaceHolder = dirPathPlaceHolder + "..." +// optionalDirPathPlaceHolder = repoPathPlaceHolder + "[/ ...]" +// secretPathPlaceHolder = optionalDirPathPlaceHolder + "/" +// secretPathOptionalVersionPlaceHolder = secretPathPlaceHolder + "[:]" +//) diff --git a/internals/secrethub/printenv.go b/internals/secrethub/printenv.go index 44f597d9..751a730a 100644 --- a/internals/secrethub/printenv.go +++ b/internals/secrethub/printenv.go @@ -36,8 +36,8 @@ func (cmd *PrintEnvCommand) Run() error { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *PrintEnvCommand) Register(r command.Registerer) { - clause := r.Command("printenv", "Print environment variables.") - clause.Flag("verbose", "Show all possible environment variables.").Short('v').BoolVar(&cmd.verbose) + clause := r.CreateCommand("printenv", "Print environment variables.") + clause.BoolVarP(&cmd.verbose, "verbose", "v", false, "Show all possible environment variables.", true, false) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } diff --git a/internals/secrethub/read.go b/internals/secrethub/read.go index 295a4ea1..41497fee 100644 --- a/internals/secrethub/read.go +++ b/internals/secrethub/read.go @@ -14,6 +14,7 @@ import ( "github.com/secrethub/secrethub-go/internals/api" "github.com/docker/go-units" + "github.com/spf13/cobra" ) // ReadCommand is a command to read a secret. @@ -41,20 +42,24 @@ func NewReadCommand(io ui.IO, newClient newClientFunc) *ReadCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ReadCommand) Register(r command.Registerer) { - clause := r.Command("read", "Read a secret.") - clause.Arg("secret-path", "The path to the secret").Required().PlaceHolder(secretPathOptionalVersionPlaceHolder).SetValue(&cmd.path) - clause.Flag( - "clip", + clause := r.CreateCommand("read", "Read a secret.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.SecretSuggestions + + clause.BoolVarP(&cmd.useClipboard, + "clip", "c", false, fmt.Sprintf( "Copy the secret value to the clipboard. The clipboard is automatically cleared after %s.", units.HumanDuration(cmd.clearClipboardAfter), - ), - ).Short('c').BoolVar(&cmd.useClipboard) - clause.Flag("out-file", "Write the secret value to this file.").Short('o').StringVar(&cmd.outFile) - clause.Flag("file-mode", "Set filemode for the output file. Defaults to 0600 (read and write for current user) and is ignored without the --out-file flag.").Default("0600").SetValue(&cmd.fileMode) - clause.Flag("no-newline", "Do not print a new line after the secret.").Short('n').BoolVar(&cmd.noNewLine) + ), true, false, + ) + clause.StringVarP(&cmd.outFile, "out-file", "o", "", "Write the secret value to this file.", true, false) + clause.BoolVarP(&cmd.noNewLine, "no-newline", "n", false, "Do not print a new line after the secret", true, false) + + fileModeFlag := clause.VarPF(&cmd.fileMode, "file-mode", "", "Set filemode for the output file. Defaults to 0600 (read and write for current user) and is ignored without the --out-file flag.", true, false) + fileModeFlag.DefValue = "0600" - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run handles the command with the options as specified in the command. @@ -75,7 +80,7 @@ func (cmd *ReadCommand) Run() error { return err } - fmt.Fprintf( + _, _ = fmt.Fprintf( cmd.io.Output(), "Copied %s to clipboard. It will be cleared after %s.\n", cmd.path, @@ -96,8 +101,17 @@ func (cmd *ReadCommand) Run() error { } if cmd.outFile == "" && !cmd.useClipboard { - fmt.Fprintf(cmd.io.Output(), "%s", string(secretData)) + _, _ = fmt.Fprintf(cmd.io.Output(), "%s", string(secretData)) } return nil } + +func (cmd *ReadCommand) argumentRegister(_ *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewSecretPath(args[0]) + if err != nil { + return err + } + return nil +} diff --git a/internals/secrethub/repo.go b/internals/secrethub/repo.go index 463d9e19..c5db46b2 100644 --- a/internals/secrethub/repo.go +++ b/internals/secrethub/repo.go @@ -21,7 +21,7 @@ func NewRepoCommand(io ui.IO, newClient newClientFunc) *RepoCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *RepoCommand) Register(r command.Registerer) { - clause := r.Command("repo", "Manage repositories.") + clause := r.CreateCommand("repo", "Manage repositories.") clause.Alias("repository") clause.Alias("repos") clause.Alias("repositories") diff --git a/internals/secrethub/repo_export.go b/internals/secrethub/repo_export.go index 23ffe444..69ef32fc 100644 --- a/internals/secrethub/repo_export.go +++ b/internals/secrethub/repo_export.go @@ -12,6 +12,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // Error @@ -37,11 +39,18 @@ func NewRepoExportCommand(io ui.IO, newClient newClientFunc) *RepoExportCommand // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RepoExportCommand) Register(r command.Registerer) { - clause := r.Command("export", "Export the repository to a zip file.") - clause.Arg("repo-path", "The repository to export").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) - clause.Arg("zip-file-name", "The file name to assign to the exported .zip file. Defaults to secrethub_export___.zip with the timestamp formatted as YYYYMMDD_HHMMSS").StringVar(&cmd.zipName) + clause := r.CreateCommand("export", "Export the repository to a zip file.") + clause.Args = cobra.RangeArgs(1, 2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.RepositorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("repo-path", "The repository to export").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) + //clause.Arg("zip-file-name", "The file name to assign to the exported .zip file. Defaults to secrethub_export___.zip with the timestamp formatted as YYYYMMDD_HHMMSS").StringVar(&cmd.zipName) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run exports a repo to a zip file @@ -139,3 +148,15 @@ func (cmd *RepoExportCommand) Run() error { return nil } + +func (cmd *RepoExportCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + if len(args) == 2 { + cmd.zipName = args[1] + } + return nil +} diff --git a/internals/secrethub/repo_init.go b/internals/secrethub/repo_init.go index df801140..cbe39509 100644 --- a/internals/secrethub/repo_init.go +++ b/internals/secrethub/repo_init.go @@ -7,6 +7,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // RepoInitCommand handles creating new repositories. @@ -26,10 +28,12 @@ func NewRepoInitCommand(io ui.IO, newClient newClientFunc) *RepoInitCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RepoInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Initialize a new repository.") - clause.Arg("repo-path", "Path to the new repository").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) + clause := r.CreateCommand("init", "Initialize a new repository.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo-path", "Path to the new repository").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run creates a new repository. @@ -50,3 +54,12 @@ func (cmd *RepoInitCommand) Run() error { return nil } + +func (cmd *RepoInitCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} diff --git a/internals/secrethub/repo_inspect.go b/internals/secrethub/repo_inspect.go index 66165648..82d2a69b 100644 --- a/internals/secrethub/repo_inspect.go +++ b/internals/secrethub/repo_inspect.go @@ -8,6 +8,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // RepoInspectCommand handles printing out the details of a repo in a JSON format. @@ -29,10 +31,12 @@ func NewRepoInspectCommand(io ui.IO, newClient newClientFunc) *RepoInspectComman // Register registers the command, args, and flags on the provided registerer. func (cmd *RepoInspectCommand) Register(r command.Registerer) { - clause := r.Command("inspect", "Show the details of a repository.") - clause.Arg("repo-path", "Path to the repository").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) + clause := r.CreateCommand("inspect", "Show the details of a repository.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo-path", "Path to the repository").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run prints out the details of a repo. @@ -67,6 +71,15 @@ func (cmd *RepoInspectCommand) Run() error { return nil } +func (cmd *RepoInspectCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} + func newInspectRepoOutput(repo *api.Repo, users []*api.User, services []*api.Service, timeFormatter TimeFormatter) inspectRepoOutput { out := inspectRepoOutput{ Name: repo.Name, diff --git a/internals/secrethub/repo_invite.go b/internals/secrethub/repo_invite.go index 317a86d3..2905fdfa 100644 --- a/internals/secrethub/repo_invite.go +++ b/internals/secrethub/repo_invite.go @@ -7,6 +7,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // RepoInviteCommand handles inviting a user to collaborate on a repository. @@ -28,12 +30,19 @@ func NewRepoInviteCommand(io ui.IO, newClient newClientFunc) *RepoInviteCommand // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RepoInviteCommand) Register(r command.Registerer) { - clause := r.Command("invite", "Invite a user to collaborate on a repository.") - clause.Arg("repo-path", "The repository to invite the user to").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) - clause.Arg("username", "username of the user").Required().StringVar(&cmd.username) - registerForceFlag(clause).BoolVar(&cmd.force) + clause := r.CreateCommand("invite", "Invite a user to collaborate on a repository.") + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.RepositorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("repo-path", "The repository to invite the user to").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) + //clause.Arg("username", "username of the user").Required().StringVar(&cmd.username) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run invites the configured user to collaborate on the repo. @@ -74,3 +83,13 @@ func (cmd *RepoInviteCommand) Run() error { return nil } + +func (cmd *RepoInviteCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + cmd.username = args[1] + return nil +} diff --git a/internals/secrethub/repo_ls.go b/internals/secrethub/repo_ls.go index f13ae0b2..b4508c0c 100644 --- a/internals/secrethub/repo_ls.go +++ b/internals/secrethub/repo_ls.go @@ -9,6 +9,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // RepoLSCommand lists repositories. @@ -31,13 +32,15 @@ func NewRepoLSCommand(io ui.IO, newClient newClientFunc) *RepoLSCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RepoLSCommand) Register(r command.Registerer) { - clause := r.Command("ls", "List all repositories you have access to.") + clause := r.CreateCommand("ls", "List all repositories you have access to.") clause.Alias("list") - clause.Flag("quiet", "Only print paths.").Short('q').BoolVar(&cmd.quiet) - clause.Arg("workspace", "When supplied, results are limited to repositories in this workspace.").SetValue(&cmd.workspace) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) + clause.Args = cobra.MaximumNArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.NamespaceSuggestions + clause.BoolVarP(&cmd.quiet, "quiet", "q", false, "Only print paths.", true, false) + //clause.Arg("workspace", "When supplied, results are limited to repositories in this workspace.").SetValue(&cmd.workspace) + registerTimestampFlag(clause, &cmd.useTimestamps) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run lists the repositories a user has access to. @@ -46,6 +49,18 @@ func (cmd *RepoLSCommand) Run() error { return cmd.run() } +func (cmd *RepoLSCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + if len(args) != 0 { + err = api.ValidateNamespace(args[0]) + if err != nil { + return err + } + cmd.workspace = api.Namespace(args[0]) + } + return nil +} + // beforeRun configures the command using the flag values. func (cmd *RepoLSCommand) beforeRun() { cmd.timeFormatter = NewTimeFormatter(cmd.useTimestamps) diff --git a/internals/secrethub/repo_revoke.go b/internals/secrethub/repo_revoke.go index 02b2f5cf..8488e69b 100644 --- a/internals/secrethub/repo_revoke.go +++ b/internals/secrethub/repo_revoke.go @@ -9,6 +9,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // RepoRevokeCommand handles revoking an account access to a repository. @@ -30,12 +31,19 @@ func NewRepoRevokeCommand(io ui.IO, newClient newClientFunc) *RepoRevokeCommand // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RepoRevokeCommand) Register(r command.Registerer) { - clause := r.Command("revoke", "Revoke an account's access to a repository. A list of secrets that should be rotated will be printed out.") - clause.Arg("repo-path", "The repository to revoke the account from").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) - clause.Arg("account-name", "The account name (username or service name) to revoke access for").Required().SetValue(&cmd.accountName) - registerForceFlag(clause).BoolVar(&cmd.force) + clause := r.CreateCommand("revoke", "Revoke an account's access to a repository. A list of secrets that should be rotated will be printed out.") + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.RepositorySuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("repo-path", "The repository to revoke the account from").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) + //clause.Arg("account-name", "The account name (username or service name) to revoke access for").Required().SetValue(&cmd.accountName) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run removes and revokes access to an account from a repo if possible. @@ -126,6 +134,19 @@ func (cmd *RepoRevokeCommand) Run() error { return nil } +func (cmd *RepoRevokeCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + cmd.accountName, err = api.NewAccountName(args[0]) + if err != nil { + return err + } + return nil +} + func printFlaggedSecrets(w io.Writer, dir *api.Dir, prePath string) (int, int) { var countUnaffected, countFlagged int if prePath != "" { diff --git a/internals/secrethub/repo_rm.go b/internals/secrethub/repo_rm.go index 9c33eef8..95008207 100644 --- a/internals/secrethub/repo_rm.go +++ b/internals/secrethub/repo_rm.go @@ -7,6 +7,8 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + + "github.com/spf13/cobra" ) // RepoRmCommand handles removing a repo. @@ -26,11 +28,13 @@ func NewRepoRmCommand(io ui.IO, newClient newClientFunc) *RepoRmCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RepoRmCommand) Register(r command.Registerer) { - clause := r.Command("rm", "Permanently delete a repository.") + clause := r.CreateCommand("rm", "Permanently delete a repository.") clause.Alias("remove") - clause.Arg("repo-path", "The repository to delete").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo-path", "The repository to delete").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.path) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run removes the repository. @@ -75,3 +79,12 @@ func (cmd *RepoRmCommand) Run() error { return nil } + +func (cmd *RepoRmCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} diff --git a/internals/secrethub/rm.go b/internals/secrethub/rm.go index 21b0c1d6..7cb6f59e 100644 --- a/internals/secrethub/rm.go +++ b/internals/secrethub/rm.go @@ -8,6 +8,7 @@ import ( "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/pkg/secrethub" + "github.com/spf13/cobra" ) // Errors @@ -37,13 +38,15 @@ func NewRmCommand(io ui.IO, newClient newClientFunc) *RmCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *RmCommand) Register(r command.Registerer) { - clause := r.Command("rm", "Remove a directory, secret or version.") + clause := r.CreateCommand("rm", "Remove a directory, secret or version.") clause.Alias("remove") - clause.Arg("path", "The path to the resource to remove (/[/])").Required().SetValue(&cmd.path) - clause.Flag("recursive", "Remove directories and their contents recursively.").Short('r').BoolVar(&cmd.recursive) - registerForceFlag(clause).BoolVar(&cmd.force) + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.SecretSuggestions + //clause.Arg("path", "The path to the resource to remove (/[/])").Required().SetValue(&cmd.path) + clause.BoolVarP(&cmd.recursive, "recursive", "r", false, "Remove directories and their contents recursively.", true, false) + registerForceFlag(clause, &cmd.force) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run removes the resource at the given path. @@ -94,6 +97,15 @@ func (cmd *RmCommand) Run() error { return rmSecret(client, secretPath, cmd.force, cmd.io) } +func (cmd *RmCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewPath(args[0]) + if err != nil { + return err + } + return nil +} + func rmSecretVersion(client secrethub.ClientInterface, secretPath api.SecretPath, force bool, io ui.IO) error { version, err := secretPath.GetVersion() if err != nil { diff --git a/internals/secrethub/run.go b/internals/secrethub/run.go index db770e20..29ac79b4 100644 --- a/internals/secrethub/run.go +++ b/internals/secrethub/run.go @@ -7,6 +7,7 @@ import ( "os/signal" "strings" "syscall" + "time" "github.com/secrethub/secrethub-cli/internals/cli/masker" "github.com/secrethub/secrethub-cli/internals/secrethub/tpl" @@ -17,6 +18,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/validation" "github.com/secrethub/secrethub-cli/internals/secrethub/command" + "github.com/spf13/cobra" ) // Errors @@ -75,16 +77,17 @@ func (cmd *RunCommand) Register(r command.Registerer) { "The output is buffered to scan for secrets and can be adjusted using the masking-buffer-period flag. " + "You should regard the masking as a best effort attempt and should always prevent secrets ending up on stdout and stderr in the first place." - clause := r.Command("run", helpShort) + clause := r.CreateCommand("run", helpShort) clause.HelpLong(helpLong) clause.Alias("exec") - clause.Arg("command", "The command to execute").Required().StringsVar(&cmd.command) - clause.Flag("no-masking", "Disable masking of secrets on stdout and stderr").BoolVar(&cmd.noMasking) - clause.Flag("no-output-buffering", "Disable output buffering. This increases output responsiveness, but decreases the probability that secrets get masked.").BoolVar(&cmd.maskerOptions.DisableBuffer) - clause.Flag("masking-buffer-period", "The time period for which output is buffered. A higher value increases the probability that secrets get masked but decreases output responsiveness.").Default("50ms").DurationVar(&cmd.maskerOptions.BufferDelay) - clause.Flag("ignore-missing-secrets", "Do not return an error when a secret does not exist and use an empty value instead.").BoolVar(&cmd.ignoreMissingSecrets) + clause.Args = cobra.MinimumNArgs(1) + //clause.Arg("command", "The command to execute").Required().StringsVar(&cmd.command) + clause.BoolVar(&cmd.noMasking, "no-masking", false, "Disable masking of secrets on stdout and stderr", true, false) + clause.BoolVar(&cmd.maskerOptions.DisableBuffer, "no-output-buffering", false, "Disable output buffering. This increases output responsiveness, but decreases the probability that secrets get masked.", true, false) + clause.DurationVar(&cmd.maskerOptions.BufferDelay, "masking-buffer-period", time.Millisecond*50, "The time period for which output is buffered. A higher value increases the probability that secrets get masked but decreases output responsiveness.", true, false) + clause.BoolVar(&cmd.ignoreMissingSecrets, "ignore-missing-secrets", false, "Do not return an error when a secret does not exist and use an empty value instead.", true, false) cmd.environment.register(clause) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run reads files from the .secretsenv/ directory, sets them as environment variables and runs the given command. @@ -173,6 +176,11 @@ func (cmd *RunCommand) Run() error { return nil } +func (cmd *RunCommand) argumentRegister(c *cobra.Command, args []string) error { + copy(cmd.command, args) + return nil +} + // sourceEnvironment returns the environment of the subcommand, with all the secrets sourced // and the secret values that need to be masked. func (cmd *RunCommand) sourceEnvironment() ([]string, []string, error) { diff --git a/internals/secrethub/run_test.go b/internals/secrethub/run_test.go index 87ed68a0..f3eefcae 100644 --- a/internals/secrethub/run_test.go +++ b/internals/secrethub/run_test.go @@ -490,9 +490,9 @@ func TestRunCommand_Run(t *testing.T) { command: RunCommand{ command: []string{"echo", "test"}, environment: &environment{ - envar: map[string]string{ + envar: MapValue{map[string]string{ "missing": "path/to/unexisting/secret", - }, + }}, osStat: osStatNotExist, }, newClient: func() (secrethub.ClientInterface, error) { @@ -515,9 +515,9 @@ func TestRunCommand_Run(t *testing.T) { command: []string{"echo", "test"}, environment: &environment{ osStat: osStatNotExist, - envar: map[string]string{ + envar: MapValue{map[string]string{ "missing": "path/to/unexisting/secret", - }, + }}, }, io: fakeui.NewIO(t), newClient: func() (secrethub.ClientInterface, error) { @@ -539,9 +539,9 @@ func TestRunCommand_Run(t *testing.T) { command: RunCommand{ command: []string{"echo", "test"}, environment: &environment{ - envar: map[string]string{ - "missing": "unexisting/repo/secret", - }, + envar: MapValue{map[string]string{ + "missing": "path/to/unexisting/secret", + }}, osStat: osStatNotExist, }, io: fakeui.NewIO(t), @@ -565,10 +565,10 @@ func TestRunCommand_Run(t *testing.T) { environment: &environment{ osStat: osStatNotExist, envFile: "secrethub.env", - templateVars: map[string]string{ + templateVars: MapValue{map[string]string{ "0foo": "value", - }, - envar: map[string]string{}, + }}, + envar: MapValue{map[string]string{}}, }, }, err: ErrInvalidTemplateVar("0foo"), @@ -578,10 +578,10 @@ func TestRunCommand_Run(t *testing.T) { environment: &environment{ osStat: osStatNotExist, envFile: "secrethub.env", - templateVars: map[string]string{ + templateVars: MapValue{map[string]string{ "foo@bar": "value", - }, - envar: map[string]string{}, + }}, + envar: MapValue{map[string]string{}}, }, }, err: ErrInvalidTemplateVar("foo@bar"), @@ -751,9 +751,9 @@ func TestRunCommand_environment(t *testing.T) { osStat: osStatFunc("secrethub.env", nil), readFile: readFileFunc("secrethub.env", "TEST=aaa"), envFile: "secrethub.env", - envar: map[string]string{ + envar: MapValue{map[string]string{ "TEST": "test/test/test", - }, + }}, templateVersion: "2", }, newClient: func() (secrethub.ClientInterface, error) { @@ -992,9 +992,9 @@ func TestRunCommand_environment(t *testing.T) { osStat: osStatFunc("secrethub.env", nil), envFile: "secrethub.env", readFile: readFileFunc("secrethub.env", ""), - envar: map[string]string{ + envar: MapValue{map[string]string{ "TEST": "test/test/test", - }, + }}, templateVersion: "2", }, newClient: func() (secrethub.ClientInterface, error) { @@ -1068,7 +1068,7 @@ func TestRunCommand_environment(t *testing.T) { readFile: readFileFunc("secrethub.env", "TEST = {{ test/$variable/test }}"), dontPromptMissingTemplateVar: true, templateVersion: "2", - templateVars: map[string]string{"variable": "test"}, + templateVars: MapValue{map[string]string{"variable": "test"}}, }, newClient: func() (secrethub.ClientInterface, error) { return fakeclient.Client{ @@ -1093,7 +1093,7 @@ func TestRunCommand_environment(t *testing.T) { readFile: readFileFunc("secrethub.env", "TEST=$variable"), dontPromptMissingTemplateVar: true, templateVersion: "2", - templateVars: map[string]string{"variable": "foo"}, + templateVars: MapValue{map[string]string{"variable": "foo"}}, }, osEnv: []string{"SECRETHUB_VAR_VARIABLE=bar"}, newClient: func() (secrethub.ClientInterface, error) { @@ -1180,9 +1180,9 @@ func TestRunCommand_RunWithFile(t *testing.T) { osStat: osStatOnlySecretHubEnv, readFile: readFileWithContent(""), envFile: "secrethub.env", - envar: map[string]string{ + envar: MapValue{map[string]string{ "TEST": "test/test/test", - }, + }}, templateVersion: "2", }, newClient: func() (secrethub.ClientInterface, error) { @@ -1207,9 +1207,9 @@ func TestRunCommand_RunWithFile(t *testing.T) { osStat: osStatOnlySecretHubEnv, envFile: "secrethub.env", readFile: readFileWithContent(""), - envar: map[string]string{ + envar: MapValue{map[string]string{ "TEST": "test/test/test", - }, + }}, templateVersion: "2", }, newClient: func() (secrethub.ClientInterface, error) { diff --git a/internals/secrethub/service.go b/internals/secrethub/service.go index 9a8d187a..750dad5d 100644 --- a/internals/secrethub/service.go +++ b/internals/secrethub/service.go @@ -21,7 +21,7 @@ func NewServiceCommand(io ui.IO, newClient newClientFunc) *ServiceCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ServiceCommand) Register(r command.Registerer) { - clause := r.Command("service", "Manage service accounts.") + clause := r.CreateCommand("service", "Manage service accounts.") NewServiceAWSCommand(cmd.io, cmd.newClient).Register(clause) NewServiceGCPCommand(cmd.io, cmd.newClient).Register(clause) NewServiceDeployCommand(cmd.io).Register(clause) diff --git a/internals/secrethub/service_aws.go b/internals/secrethub/service_aws.go index 45f5ed5d..1411b991 100644 --- a/internals/secrethub/service_aws.go +++ b/internals/secrethub/service_aws.go @@ -21,7 +21,7 @@ func NewServiceAWSCommand(io ui.IO, newClient newClientFunc) *ServiceAWSCommand // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ServiceAWSCommand) Register(r command.Registerer) { - clause := r.Command("aws", "Manage AWS service accounts.") + clause := r.CreateCommand("aws", "Manage AWS service accounts.") NewServiceAWSInitCommand(cmd.io, cmd.newClient).Register(clause) NewServiceAWSLsCommand(cmd.io, cmd.newClient).Register(clause) } diff --git a/internals/secrethub/service_aws_init.go b/internals/secrethub/service_aws_init.go index 11ac5127..b0f09525 100644 --- a/internals/secrethub/service_aws_init.go +++ b/internals/secrethub/service_aws_init.go @@ -21,6 +21,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/sts" + "github.com/spf13/cobra" ) // Errors @@ -152,17 +153,33 @@ func (cmd *ServiceAWSInitCommand) Run() error { return nil } +func (cmd *ServiceAWSInitCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.repo, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} + // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceAWSInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Create a new service account that is tied to an AWS IAM role.") - clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) - clause.Flag("kms-key", "The ID or ARN of the KMS-key to be used for encrypting the service's account key.").StringVar(&cmd.kmsKeyID) - clause.Flag("role", "The role name or ARN of the IAM role that should have access to this service account.").StringVar(&cmd.role) - clause.Flag("region", "The AWS region that should be used for KMS.").StringVar(&cmd.region) - clause.Flag("description", "A description for the service so others will recognize it. Defaults to the name of the role that is attached to the service.").StringVar(&cmd.description) - clause.Flag("descr", "").Hidden().StringVar(&cmd.description) - clause.Flag("desc", "").Hidden().StringVar(&cmd.description) - clause.Flag("permission", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.").StringVar(&cmd.permission) + clause := r.CreateCommand("init", "Create a new service account that is tied to an AWS IAM role.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) + clause.StringVar(&cmd.kmsKeyID, "kms-key", "", "The ID or ARN of the KMS-key to be used for encrypting the service's account key.", true, false) + clause.StringVar(&cmd.role, "role", "", "The role name or ARN of the IAM role that should have access to this service account.", true, false) + clause.StringVar(&cmd.region, "region", "", "The AWS region that should be used for KMS.", true, false) + clause.StringVar(&cmd.description, "description", "", "A description for the service so others will recognize it. Defaults to the name of the role that is attached to the service.", true, false) + clause.StringVar(&cmd.description, "descr", "", "", true, false) + clause.StringVar(&cmd.description, "desc", "", "", true, false) + clause.Flag("desc").Hidden = true + clause.Flag("descr").Hidden = true + clause.StringVar(&cmd.permission, "permission", "", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.", true, false) + _ = clause.RegisterFlagCompletionFunc("permission", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"read", "write", "admin"}, cobra.ShellCompDirectiveDefault + }) clause.HelpLong("The native AWS identity provider uses a combination of AWS IAM and AWS KMS to provide access to SecretHub for any service running on AWS (e.g. EC2, Lambda or ECS). For this to work, an IAM role and a KMS key are needed.\n" + "\n" + @@ -174,7 +191,7 @@ func (cmd *ServiceAWSInitCommand) Register(r command.Registerer) { "If no system-wide default for the AWS region is provided (e.g. with $AWS_REGION), the AWS-region where the KMS key resides should be explicitly provided to this command with the --region flag.", ) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } func newKMSKeyOptionsGetter(cfg *aws.Config) kmsKeyOptionsGetter { diff --git a/internals/secrethub/service_deploy.go b/internals/secrethub/service_deploy.go index 1417a2dc..764be59b 100644 --- a/internals/secrethub/service_deploy.go +++ b/internals/secrethub/service_deploy.go @@ -19,6 +19,6 @@ func NewServiceDeployCommand(io ui.IO) *ServiceDeployCommand { // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ServiceDeployCommand) Register(r command.Registerer) { - clause := r.Command("deploy", "Deploy a service account to a destination.") + clause := r.CreateCommand("deploy", "Deploy a service account to a destination.") NewServiceDeployWinRmCommand(cmd.io).Register(clause) } diff --git a/internals/secrethub/service_deploy_winrm.go b/internals/secrethub/service_deploy_winrm.go index 8c6c5d5c..5b6c12b7 100644 --- a/internals/secrethub/service_deploy_winrm.go +++ b/internals/secrethub/service_deploy_winrm.go @@ -13,6 +13,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/cli/ui" "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-cli/internals/winrm" + "github.com/spf13/cobra" ) // DefaultServiceConfigFilemode configures the filemode used for writing service configuration files. @@ -58,17 +59,21 @@ func NewServiceDeployWinRmCommand(io ui.IO) *ServiceDeployWinRmCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceDeployWinRmCommand) Register(r command.Registerer) { - clause := r.Command("winrm", "Read a service account configuration from stdin and deploy it to a running instance with WinRM. The instance needs to be reachable, have WinRM enabled, and have PowerShell installed.") - clause.Arg("resource-uri", "Hostname, optional connection protocol and port of the host ([http[s]://][:]). This defaults to https and port 5986.").Required().URLVar(&cmd.resourceURI) - clause.Flag("auth-type", "Authentication type (basic/cert)").HintOptions("basic", "cert").Default("basic").StringVar(&cmd.authType) - clause.Flag("username", "The username used for logging in when authentication type is basic. Is asked if not supplied.").StringVar(&cmd.username) - clause.Flag("password", "The password used for logging in when authentication type is basic. Is asked if not supplied.").StringVar(&cmd.password) - clause.Flag("client-cert", "Path to client certificate used for certificate authentication.").ExistingFileVar(&cmd.clientCert) - clause.Flag("client-key", "Path to client key used for certificate authentication.").ExistingFileVar(&cmd.clientKey) - clause.Flag("ca-cert", "Path to CA certificate used to verify server TLS certificate.").ExistingFileVar(&cmd.caCert) - clause.Flag("insecure-no-verify-cert", "Do not verify server TLS certificate (insecure).").BoolVar(&cmd.noVerify) - - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("winrm", "Read a service account configuration from stdin and deploy it to a running instance with WinRM. The instance needs to be reachable, have WinRM enabled, and have PowerShell installed.") + clause.Args = cobra.ExactValidArgs(1) + //clause.Arg("resource-uri", "Hostname, optional connection protocol and port of the host ([http[s]://][:]). This defaults to https and port 5986.").Required().URLVar(&cmd.resourceURI) + clause.StringVar(&cmd.authType, "auth-type", "basic", "Authentication type (basic/cert)", true, false) + _ = clause.RegisterFlagCompletionFunc("auth-type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"basic", "cert"}, cobra.ShellCompDirectiveDefault + }) + clause.StringVar(&cmd.username, "username", "", "The username used for logging in when authentication type is basic. Is asked if not supplied.", true, false) + clause.StringVar(&cmd.password, "password", "", "The password used for logging in when authentication type is basic. Is asked if not supplied.", true, false) + clause.StringVar(&cmd.clientCert, "client-cert", "", "Path to client certificate used for certificate authentication.", true, false) + clause.StringVar(&cmd.clientKey, "client-key", "", "Path to client key used for certificate authentication.", true, false) + clause.StringVar(&cmd.caCert, "ca-cert", "", "Path to CA certificate used to verify server TLS certificate.", true, false) + clause.BoolVar(&cmd.noVerify, "insecure-no-verify-cert", false, "Do not verify server TLS certificate (insecure).", true, false) + + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run creates a service and installs the configuration using WinRM. @@ -200,6 +205,15 @@ func (cmd *ServiceDeployWinRmCommand) Run() error { return nil } +func (cmd *ServiceDeployWinRmCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.resourceURI, err = url.Parse(args[0]) + if err != nil { + return err + } + return nil +} + // checkWinRMTLS checks if the given schema corresponds to the given CLI flags. func (cmd *ServiceDeployWinRmCommand) checkWinRMTLS() (bool, error) { if cmd.resourceURI.Scheme == "http" { diff --git a/internals/secrethub/service_gcp.go b/internals/secrethub/service_gcp.go index 67a7294b..f0ae3624 100644 --- a/internals/secrethub/service_gcp.go +++ b/internals/secrethub/service_gcp.go @@ -21,7 +21,7 @@ func NewServiceGCPCommand(io ui.IO, newClient newClientFunc) *ServiceGCPCommand // Register registers the command and its sub-commands on the provided Registerer. func (cmd *ServiceGCPCommand) Register(r command.Registerer) { - clause := r.Command("gcp", "Manage GCP service accounts.") + clause := r.CreateCommand("gcp", "Manage GCP service accounts.") NewServiceGCPInitCommand(cmd.io, cmd.newClient).Register(clause) NewServiceGCPLsCommand(cmd.io, cmd.newClient).Register(clause) NewServiceGCPLinkCommand(cmd.io, cmd.newClient).Register(clause) diff --git a/internals/secrethub/service_gcp_init.go b/internals/secrethub/service_gcp_init.go index a0ca47be..c2be0952 100644 --- a/internals/secrethub/service_gcp_init.go +++ b/internals/secrethub/service_gcp_init.go @@ -22,6 +22,8 @@ import ( "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" + + "github.com/spf13/cobra" ) // ServiceGCPInitCommand initializes a service for GCP. @@ -153,16 +155,32 @@ func (cmd *ServiceGCPInitCommand) Run() error { return nil } +func (cmd *ServiceGCPInitCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.repo, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} + // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Create a new service account that is tied to a GCP Service Account.") - clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) - clause.Flag("kms-key", "The Resource ID of the KMS-key to be used for encrypting the service's account key.").StringVar(&cmd.kmsKeyResourceID) - clause.Flag("service-account-email", "The email of the GCP Service Account that should have access to this service account.").StringVar(&cmd.serviceAccountEmail) - clause.Flag("description", "A description for the service so others will recognize it. Defaults to the name of the role that is attached to the service.").StringVar(&cmd.description) - clause.Flag("descr", "").Hidden().StringVar(&cmd.description) - clause.Flag("desc", "").Hidden().StringVar(&cmd.description) - clause.Flag("permission", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.").StringVar(&cmd.permission) + clause := r.CreateCommand("init", "Create a new service account that is tied to a GCP Service Account.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) + clause.StringVar(&cmd.kmsKeyResourceID, "kms-key", "", "The Resource ID of the KMS-key to be used for encrypting the service's account key.", true, false) + clause.StringVar(&cmd.serviceAccountEmail, "service-account-email", "", "The email of the GCP Service Account that should have access to this service account.", true, false) + clause.StringVar(&cmd.description, "description", "", "A description for the service so others will recognize it. Defaults to the name of the role that is attached to the service.", true, false) + clause.StringVar(&cmd.description, "descr", "", "", true, false) + clause.StringVar(&cmd.description, "desc", "", "", true, false) + clause.Flag("desc").Hidden = true + clause.Flag("descr").Hidden = true + clause.StringVar(&cmd.permission, "permission", "", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.", true, false) + _ = clause.RegisterFlagCompletionFunc("permission", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"read", "write", "admin"}, cobra.ShellCompDirectiveDefault + }) clause.HelpLong("The native GCP identity provider uses a combination of GCP IAM and GCP KMS to provide access to SecretHub for any service running on GCP. For this to work, a GCP Service Account and a KMS key are needed.\n" + "\n" + @@ -172,7 +190,7 @@ func (cmd *ServiceGCPInitCommand) Register(r command.Registerer) { "To create a new service that uses the GCP identity provider, the CLI must have encryption access to the KMS key that will be used by the service account. Therefore GCP application default credentials should be configured on this system. To achieve this, first install the Google Cloud SDK (https://cloud.google.com/sdk/docs/quickstarts) and then run `gcloud auth application-default login`.", ) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } type gcpProjectOptionLister struct { diff --git a/internals/secrethub/service_gcp_link.go b/internals/secrethub/service_gcp_link.go index 74022da3..420059b3 100644 --- a/internals/secrethub/service_gcp_link.go +++ b/internals/secrethub/service_gcp_link.go @@ -13,6 +13,7 @@ import ( "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-go/pkg/secrethub/iterator" + "github.com/spf13/cobra" ) // ServiceGCPLinkCommand create a new link between a SecretHub namespace and a GCP project. @@ -49,10 +50,31 @@ func (cmd *ServiceGCPLinkCommand) Run() error { return createGCPLink(client, cmd.io, cmd.namespace.String(), cmd.projectID.String()) } +func (cmd *ServiceGCPLinkCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + err = api.ValidateOrgName(args[0]) + if err != nil { + return err + } + cmd.namespace = api.OrgName(args[0]) + err = cmd.projectID.Set(args[1]) + if err != nil { + return err + } + return nil +} + func (cmd *ServiceGCPLinkCommand) Register(r command.Registerer) { - clause := r.Command("link", "Create a new link between a namespace and a GCP project to allow creating SecretHub service accounts for GCP Service Accounts in the GCP project.") - clause.Arg("namespace", "The SecretHub namespace to link.").Required().SetValue(&cmd.namespace) - clause.Arg("project-id", "The GCP project to link the namespace to.").Required().SetValue(&cmd.projectID) + clause := r.CreateCommand("link", "Create a new link between a namespace and a GCP project to allow creating SecretHub service accounts for GCP Service Accounts in the GCP project.") + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.NamespaceSuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("namespace", "The SecretHub namespace to link.").Required().SetValue(&cmd.namespace) + //clause.Arg("project-id", "The GCP project to link the namespace to.").Required().SetValue(&cmd.projectID) clause.HelpLong("Linking a GCP project to a namespace is required to create SecretHub service accounts that use a GCP Service Account within the project. " + "A SecretHub namespace can be linked to multiple GCP projects and a GCP project can be linked to multiple namespaces.\n" + @@ -69,7 +91,7 @@ func (cmd *ServiceGCPLinkCommand) Register(r command.Registerer) { "Any reference to SecretHub should automatically disappear within a few minutes. " + "If it does not, the access can safely be revoked manually.") - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // ServiceGCPListLinksCommand lists all existing links between the given namespace and GCP projects @@ -120,13 +142,23 @@ func (cmd *ServiceGCPListLinksCommand) Run() error { return nil } -func (cmd *ServiceGCPListLinksCommand) Register(r command.Registerer) { - clause := r.Command("list-links", "List all existing links between the given namespace and GCP projects.") - clause.Arg("namespace", "The namespace for which to list all existing links to GCP projects.").Required().SetValue(&cmd.namespace) +func (cmd *ServiceGCPListLinksCommand) argumentRegister(c *cobra.Command, args []string) error { + err := api.ValidateNamespace(args[0]) + if err != nil { + return err + } + cmd.namespace = api.Namespace(args[0]) + return nil +} - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) +func (cmd *ServiceGCPListLinksCommand) Register(r command.Registerer) { + clause := r.CreateCommand("list-links", "List all existing links between the given namespace and GCP projects.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.NamespaceSuggestions + //clause.Arg("namespace", "The namespace for which to list all existing links to GCP projects.").Required().SetValue(&cmd.namespace) + registerTimestampFlag(clause, &cmd.useTimestamps) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // ServiceGCPDeleteLinkCommand deletes the link between a SecretHub namespace and a GCP project. @@ -145,12 +177,19 @@ func NewServiceGCPDeleteLinkCommand(io ui.IO, newClient newClientFunc) *ServiceG } func (cmd *ServiceGCPDeleteLinkCommand) Register(r command.Registerer) { - clause := r.Command("delete-link", "Delete the link between a SecretHub namespace and a GCP project.") + clause := r.CreateCommand("delete-link", "Delete the link between a SecretHub namespace and a GCP project.") clause.HelpLong("After deleting the link you cannot create new GCP service accounts in the specified namespace and GCP project anymore. Exisiting service accounts will keep on working.") - clause.Arg("namespace", "The SecretHub namespace to delete the link from.").Required().SetValue(&cmd.namespace) - clause.Arg("project-id", "The GCP project to delete the link to.").Required().SetValue(&cmd.projectID) + clause.Args = cobra.ExactValidArgs(2) + clause.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return AutoCompleter{client: GetClient()}.NamespaceSuggestions(cmd, args, toComplete) + } + return []string{}, cobra.ShellCompDirectiveDefault + } + //clause.Arg("namespace", "The SecretHub namespace to delete the link from.").Required().SetValue(&cmd.namespace) + //clause.Arg("project-id", "The GCP project to delete the link to.").Required().SetValue(&cmd.projectID) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } func (cmd *ServiceGCPDeleteLinkCommand) Run() error { @@ -179,6 +218,20 @@ func (cmd *ServiceGCPDeleteLinkCommand) Run() error { return client.IDPLinks().GCP().Delete(cmd.namespace.String(), cmd.projectID.String()) } +func (cmd *ServiceGCPDeleteLinkCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + err = api.ValidateNamespace(args[0]) + if err != nil { + return err + } + cmd.namespace = api.Namespace(args[0]) + err = cmd.projectID.Set(args[1]) + if err != nil { + return err + } + return nil +} + type gcpProjectID string func (g *gcpProjectID) String() string { diff --git a/internals/secrethub/service_init.go b/internals/secrethub/service_init.go index 3213526e..8987c22c 100644 --- a/internals/secrethub/service_init.go +++ b/internals/secrethub/service_init.go @@ -15,6 +15,7 @@ import ( "github.com/secrethub/secrethub-go/internals/api" "github.com/secrethub/secrethub-go/pkg/secrethub" "github.com/secrethub/secrethub-go/pkg/secrethub/credentials" + "github.com/spf13/cobra" ) // ServiceInitCommand initializes a service and writes the generated config to stdout. @@ -104,19 +105,28 @@ func (cmd *ServiceInitCommand) Run() error { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceInitCommand) Register(r command.Registerer) { - clause := r.Command("init", "Create a new service account.") - clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) - clause.Flag("description", "A description for the service so others will recognize it.").StringVar(&cmd.description) - clause.Flag("descr", "").Hidden().StringVar(&cmd.description) - clause.Flag("desc", "").Hidden().StringVar(&cmd.description) - clause.Flag("permission", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.").StringVar(&cmd.permission) + clause := r.CreateCommand("init", "Create a new service account.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo", "The service account is attached to the repository in this path.").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repo) + clause.StringVar(&cmd.description, "description", "", "A description for the service so others will recognize it.", true, false) + clause.StringVar(&cmd.description, "descr", "", "", true, false) + clause.StringVar(&cmd.description, "desc", "", "", true, false) + clause.Flag("desc").Hidden = true + clause.Flag("descr").Hidden = true + clause.StringVar(&cmd.permission, "permission", "", "Create an access rule giving the service account permission on a directory. Accepted permissions are `read`, `write` and `admin`. Use `--permission ` to give permission on the root of the repo and `--permission [/ ...]:` to give permission on a subdirectory.", true, false) + _ = clause.RegisterFlagCompletionFunc("permission", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"read", "write", "admin"}, cobra.ShellCompDirectiveDefault + }) // TODO make 45 sec configurable - clause.Flag("clip", "Write the service account configuration to the clipboard instead of stdout. The clipboard is automatically cleared after 45 seconds.").Short('c').BoolVar(&cmd.clip) - clause.Flag("file", "Write the service account configuration to a file instead of stdout.").Hidden().StringVar(&cmd.file) - clause.Flag("out-file", "Write the service account configuration to a file instead of stdout.").StringVar(&cmd.file) - clause.Flag("file-mode", "Set filemode for the written file. Defaults to 0440 (read only) and is ignored without the --file flag.").Default("0440").SetValue(&cmd.fileMode) - - command.BindAction(clause, cmd.Run) + clause.BoolVarP(&cmd.clip, "clip", "c", false, "Write the service account configuration to the clipboard instead of stdout. The clipboard is automatically cleared after 45 seconds.", true, false) + clause.StringVar(&cmd.file, "file", "", "Write the service account configuration to a file instead of stdout.", true, false) + clause.Flag("file").Hidden = true + clause.StringVar(&cmd.file, "out-file", "", "Write the service account configuration to a file instead of stdout.", true, false) + clause.Var(&cmd.fileMode, "file-mode", "Set filemode for the written file. Defaults to 0440 (read only) and is ignored without the --file flag.", true, false) + clause.Flag("file-mode").DefValue = "0440" + + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // givePermission gives the service permission on the repository as defined in the permission flag. @@ -153,6 +163,15 @@ func givePermission(service *api.Service, repo api.RepoPath, permissionFlagValue return nil } +func (cmd *ServiceInitCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.repo, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} + // parsePermissionFlag parses a permission flag into a permission and a subdirectory to give // the permission on. func parsePermissionFlag(value string) (subdir string, permission string) { diff --git a/internals/secrethub/service_ls.go b/internals/secrethub/service_ls.go index 59bad3da..e226683b 100644 --- a/internals/secrethub/service_ls.go +++ b/internals/secrethub/service_ls.go @@ -9,6 +9,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // ServiceLsCommand lists all service accounts in a given repository. @@ -60,13 +61,15 @@ func NewServiceGCPLsCommand(io ui.IO, newClient newClientFunc) *ServiceLsCommand // Register registers the command, arguments and flags on the provided Registerer. func (cmd *ServiceLsCommand) Register(r command.Registerer) { - clause := r.Command("ls", cmd.help) + clause := r.CreateCommand("ls", cmd.help) clause.Alias("list") - clause.Arg("repo-path", "The path to the repository to list services for").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repoPath) - clause.Flag("quiet", "Only print service IDs.").Short('q').BoolVar(&cmd.quiet) - registerTimestampFlag(clause).BoolVar(&cmd.useTimestamps) + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.RepositorySuggestions + //clause.Arg("repo-path", "The path to the repository to list services for").Required().PlaceHolder(repoPathPlaceHolder).SetValue(&cmd.repoPath) + clause.BoolVarP(&cmd.quiet, "quiet", "q", false, "Only print service IDs.", true, false) + registerTimestampFlag(clause, &cmd.useTimestamps) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run lists all service accounts in a given repository. @@ -116,6 +119,15 @@ outer: return nil } +func (cmd *ServiceLsCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.repoPath, err = api.NewRepoPath(args[0]) + if err != nil { + return err + } + return nil +} + type serviceTable interface { header() []string row(service *api.Service) []string diff --git a/internals/secrethub/set.go b/internals/secrethub/set.go index 73059e41..d3747330 100644 --- a/internals/secrethub/set.go +++ b/internals/secrethub/set.go @@ -37,10 +37,10 @@ func NewSetCommand(io ui.IO, newClient newClientFunc) *SetCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *SetCommand) Register(r command.Registerer) { - clause := r.Command("set", "Set the secrets in your local environment. This reads and parses the secrets.yml file in the current working directory.").Hidden() - clause.Flag("in", "The path to a secrets.yml file to read").Short('i').Default("secrets.yml").ExistingFileVar(&cmd.in) + clause := r.CreateCommand("set", "Set the secrets in your local environment. This reads and parses the secrets.yml file in the current working directory.").Hidden() + clause.StringVarP(&cmd.in, "in", "i", "secrets.yml", "The path to a secrets.yml file to read", true, false) - command.BindAction(clause, cmd.Run) + command.BindAction(clause, nil, cmd.Run) } // Run parses a secret spec file and presents secrets on the system. diff --git a/internals/secrethub/signup.go b/internals/secrethub/signup.go index 2d8f3eca..331e7d5d 100644 --- a/internals/secrethub/signup.go +++ b/internals/secrethub/signup.go @@ -44,15 +44,15 @@ func NewSignUpCommand(io ui.IO, newClient newClientFunc, credentialStore Credent // Register registers the command, arguments and flags on the provided Registerer. func (cmd *SignUpCommand) Register(r command.Registerer) { - clause := r.Command("signup", "Create a free personal developer account.") - clause.Flag("username", "The username you would like to use on SecretHub.").StringVar(&cmd.username) - clause.Flag("full-name", "Your full name.").StringVar(&cmd.fullName) - clause.Flag("email", "Your (work) email address we will use for all correspondence.").StringVar(&cmd.email) - clause.Flag("org", "The name of your organization.").StringVar(&cmd.org) - clause.Flag("org-description", "A description (max 144 chars) for your organization so others will recognize it.").StringVar(&cmd.orgDescription) - registerForceFlag(clause).BoolVar(&cmd.force) - - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("signup", "Create a free personal developer account.") + clause.StringVar(&cmd.username, "username", "", "The username you would like to use on SecretHub.", true, false) + clause.StringVar(&cmd.fullName, "full-name", "", "Your full name.", true, false) + clause.StringVar(&cmd.email, "email", "", "Your (work) email address we will use for all correspondence.", true, false) + clause.StringVar(&cmd.org, "org", "", "The name of your organization.", true, false) + clause.StringVar(&cmd.orgDescription, "org-description", "", "A description (max 144 chars) for your organization so others will recognize it.", true, false) + registerForceFlag(clause, &cmd.force) + + command.BindAction(clause, nil, cmd.Run) } // Run signs up a new user and configures his account for use on this machine. diff --git a/internals/secrethub/tree.go b/internals/secrethub/tree.go index e026b27f..6bff8bed 100644 --- a/internals/secrethub/tree.go +++ b/internals/secrethub/tree.go @@ -9,6 +9,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) // TreeCommand lists the contents of a directory at a given path in a tree-like format. @@ -45,17 +46,30 @@ func (cmd *TreeCommand) Run() error { return nil } +func (cmd *TreeCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewDirPath(args[0]) + if err != nil { + return err + } + return nil +} + // Register registers the command, arguments and flags on the provided Registerer. func (cmd *TreeCommand) Register(r command.Registerer) { - clause := r.Command("tree", "List contents of a directory in a tree-like format.") - clause.Arg("dir-path", "The path to to show contents for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) + clause := r.CreateCommand("tree", "List contents of a directory in a tree-like format.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.DirectorySuggestions + //clause.Arg("dir-path", "The path to to show contents for").Required().PlaceHolder(optionalDirPathPlaceHolder).SetValue(&cmd.path) + + clause.BoolVarP(&cmd.fullPaths, "full-paths", "f", false, "Print the full path of each directory and secret.", true, false) + clause.BoolVarP(&cmd.noIndentation, "no-indentation", "n", false, "Do not use the standard indentation.", true, false) + clause.BoolVar(&cmd.noReport, "no-report", false, "Turn off secret/directory count at end of tree listing.", true, false) + clause.BoolVar(&cmd.noReport, "noreport", false, "Turn off secret/directory count at end of tree listing.", true, false) - clause.Flag("full-paths", "Print the full path of each directory and secret.").Short('f').BoolVar(&cmd.fullPaths) - clause.Flag("no-indentation", "Don't print indentation lines.").Short('i').BoolVar(&cmd.noIndentation) - clause.Flag("no-report", "Turn off secret/directory count at end of tree listing.").BoolVar(&cmd.noReport) - clause.Flag("noreport", "Turn off secret/directory count at end of tree listing.").Hidden().BoolVar(&cmd.noReport) + clause.Flag("noreport").Hidden = true - command.BindAction(clause, cmd.Run) + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // printTree recursively prints the tree's contents in a tree-like structure. diff --git a/internals/secrethub/write.go b/internals/secrethub/write.go index 316e48a6..52041255 100644 --- a/internals/secrethub/write.go +++ b/internals/secrethub/write.go @@ -10,6 +10,7 @@ import ( "github.com/secrethub/secrethub-cli/internals/secrethub/command" "github.com/secrethub/secrethub-go/internals/api" + "github.com/spf13/cobra" ) var ( @@ -42,14 +43,16 @@ func NewWriteCommand(io ui.IO, newClient newClientFunc) *WriteCommand { // Register registers the command, arguments and flags on the provided Registerer. func (cmd *WriteCommand) Register(r command.Registerer) { - clause := r.Command("write", "Write a secret.") - clause.Arg("secret-path", "The path to the secret").Required().PlaceHolder(secretPathPlaceHolder).SetValue(&cmd.path) - clause.Flag("clip", "Use clipboard content as input.").Short('c').BoolVar(&cmd.useClipboard) - clause.Flag("multiline", "Prompt for multiple lines of input, until an EOF is reached. On Linux/Mac, press CTRL-D to end input. On Windows, press CTRL-Z and then ENTER to end input.").Short('m').BoolVar(&cmd.multiline) - clause.Flag("no-trim", "Do not trim leading and trailing whitespace in the secret.").BoolVar(&cmd.noTrim) - clause.Flag("in-file", "Use the contents of this file as the value of the secret.").Short('i').StringVar(&cmd.inFile) - - command.BindAction(clause, cmd.Run) + clause := r.CreateCommand("write", "Write a secret.") + clause.Args = cobra.ExactValidArgs(1) + clause.ValidArgsFunction = AutoCompleter{client: GetClient()}.SecretSuggestions + //clause.Arg("secret-path", "The path to the secret").Required().PlaceHolder(secretPathPlaceHolder).SetValue(&cmd.path) + clause.BoolVarP(&cmd.useClipboard, "clip", "c", false, "Use clipboard content as input.", true, false) + clause.BoolVarP(&cmd.multiline, "multiline", "m", false, "Prompt for multiple lines of input, until an EOF is reached. On Linux/Mac, press CTRL-D to end input. On Windows, press CTRL-Z and then ENTER to end input.", true, false) + clause.BoolVar(&cmd.noTrim, "no-trim", false, "Do not trim leading and trailing whitespace in the secret.", true, false) + clause.StringVarP(&cmd.inFile, "in-file", "i", "", "Use the contents of this file as the value of the secret.", true, false) + + command.BindAction(clause, cmd.argumentRegister, cmd.Run) } // Run handles the command with the options as specified in the command. @@ -132,3 +135,12 @@ func (cmd *WriteCommand) Run() error { return nil } + +func (cmd *WriteCommand) argumentRegister(c *cobra.Command, args []string) error { + var err error + cmd.path, err = api.NewSecretPath(args[0]) + if err != nil { + return err + } + return nil +}