From 8e4a42aa429c6dec0d5face7c69554d8a0677e96 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 7 Oct 2020 16:58:53 +0200 Subject: short-name aliasing Add support for short-name aliasing. Signed-off-by: Valentin Rothberg --- docs/source/markdown/podman-build.1.md | 7 + go.mod | 4 +- go.sum | 28 +- libpod/image/image.go | 28 +- libpod/image/pull.go | 129 +-- libpod/image/pull_test.go | 67 +- vendor/github.com/chzyer/readline/.gitignore | 1 + vendor/github.com/chzyer/readline/.travis.yml | 8 + vendor/github.com/chzyer/readline/CHANGELOG.md | 58 ++ vendor/github.com/chzyer/readline/LICENSE | 22 + vendor/github.com/chzyer/readline/README.md | 114 +++ vendor/github.com/chzyer/readline/ansi_windows.go | 249 ++++++ vendor/github.com/chzyer/readline/complete.go | 285 +++++++ .../github.com/chzyer/readline/complete_helper.go | 165 ++++ .../github.com/chzyer/readline/complete_segment.go | 82 ++ vendor/github.com/chzyer/readline/history.go | 330 ++++++++ vendor/github.com/chzyer/readline/operation.go | 531 +++++++++++++ vendor/github.com/chzyer/readline/password.go | 33 + .../chzyer/readline/rawreader_windows.go | 125 +++ vendor/github.com/chzyer/readline/readline.go | 326 ++++++++ vendor/github.com/chzyer/readline/remote.go | 475 +++++++++++ vendor/github.com/chzyer/readline/runebuf.go | 629 +++++++++++++++ vendor/github.com/chzyer/readline/runes.go | 223 ++++++ vendor/github.com/chzyer/readline/search.go | 164 ++++ vendor/github.com/chzyer/readline/std.go | 197 +++++ vendor/github.com/chzyer/readline/std_windows.go | 9 + vendor/github.com/chzyer/readline/term.go | 123 +++ vendor/github.com/chzyer/readline/term_bsd.go | 29 + vendor/github.com/chzyer/readline/term_linux.go | 33 + vendor/github.com/chzyer/readline/term_solaris.go | 32 + vendor/github.com/chzyer/readline/term_unix.go | 24 + vendor/github.com/chzyer/readline/term_windows.go | 171 ++++ vendor/github.com/chzyer/readline/terminal.go | 238 ++++++ vendor/github.com/chzyer/readline/utils.go | 277 +++++++ vendor/github.com/chzyer/readline/utils_unix.go | 83 ++ vendor/github.com/chzyer/readline/utils_windows.go | 41 + vendor/github.com/chzyer/readline/vim.go | 176 ++++ vendor/github.com/chzyer/readline/windows_api.go | 152 ++++ vendor/github.com/containers/buildah/Makefile | 8 +- vendor/github.com/containers/buildah/add.go | 16 +- .../containers/buildah/btrfs_installed_tag.sh | 2 +- vendor/github.com/containers/buildah/btrfs_tag.sh | 2 +- vendor/github.com/containers/buildah/buildah.go | 2 +- .../github.com/containers/buildah/copier/copier.go | 10 + .../github.com/containers/buildah/copier/xattrs.go | 5 + vendor/github.com/containers/buildah/go.mod | 7 +- vendor/github.com/containers/buildah/go.sum | 30 +- .../containers/buildah/imagebuildah/build.go | 17 +- .../containers/buildah/imagebuildah/executor.go | 44 +- .../buildah/imagebuildah/stage_executor.go | 11 +- vendor/github.com/containers/buildah/libdm_tag.sh | 2 +- vendor/github.com/containers/buildah/new.go | 244 +++--- .../containers/buildah/pkg/cli/common.go | 59 +- .../containers/buildah/pkg/secrets/secrets.go | 2 +- vendor/github.com/containers/buildah/pull.go | 90 +-- vendor/github.com/containers/buildah/run_linux.go | 20 +- vendor/github.com/containers/buildah/util/util.go | 69 +- .../containers/buildah/util/util_linux.go | 9 - .../containers/buildah/util/util_unix.go | 8 + .../containers/buildah/util/util_unsupported.go | 12 - .../containers/buildah/util/util_windows.go | 8 + vendor/github.com/containers/image/v5/copy/copy.go | 17 +- .../containers/image/v5/docker/docker_image.go | 47 ++ .../image/v5/pkg/shortnames/shortnames.go | 458 +++++++++++ .../image/v5/pkg/sysregistriesv2/shortnames.go | 328 ++++++++ .../v5/pkg/sysregistriesv2/system_registries_v2.go | 252 ++++-- .../github.com/containers/image/v5/types/types.go | 34 + .../containers/image/v5/version/version.go | 2 +- vendor/github.com/juju/ansiterm/LICENSE | 191 +++++ vendor/github.com/juju/ansiterm/Makefile | 14 + vendor/github.com/juju/ansiterm/README.md | 323 ++++++++ vendor/github.com/juju/ansiterm/attribute.go | 50 ++ vendor/github.com/juju/ansiterm/color.go | 119 +++ vendor/github.com/juju/ansiterm/context.go | 95 +++ vendor/github.com/juju/ansiterm/doc.go | 6 + vendor/github.com/juju/ansiterm/style.go | 72 ++ vendor/github.com/juju/ansiterm/tabwriter.go | 64 ++ vendor/github.com/juju/ansiterm/tabwriter/LICENSE | 27 + .../juju/ansiterm/tabwriter/tabwriter.go | 587 ++++++++++++++ vendor/github.com/juju/ansiterm/terminal.go | 32 + vendor/github.com/juju/ansiterm/writer.go | 74 ++ vendor/github.com/lunixbochs/vtclean/.travis.yml | 9 + vendor/github.com/lunixbochs/vtclean/LICENSE | 19 + vendor/github.com/lunixbochs/vtclean/README.md | 46 ++ vendor/github.com/lunixbochs/vtclean/io.go | 93 +++ vendor/github.com/lunixbochs/vtclean/line.go | 113 +++ vendor/github.com/lunixbochs/vtclean/vtclean.go | 95 +++ vendor/github.com/manifoldco/promptui/.gitignore | 3 + .../github.com/manifoldco/promptui/.golangci.yml | 26 + vendor/github.com/manifoldco/promptui/.travis.yml | 14 + vendor/github.com/manifoldco/promptui/CHANGELOG.md | 123 +++ .../manifoldco/promptui/CODE_OF_CONDUCT.md | 73 ++ vendor/github.com/manifoldco/promptui/LICENSE.md | 29 + vendor/github.com/manifoldco/promptui/Makefile | 49 ++ vendor/github.com/manifoldco/promptui/README.md | 107 +++ vendor/github.com/manifoldco/promptui/codes.go | 120 +++ vendor/github.com/manifoldco/promptui/cursor.go | 232 ++++++ vendor/github.com/manifoldco/promptui/go.mod | 16 + vendor/github.com/manifoldco/promptui/go.sum | 23 + vendor/github.com/manifoldco/promptui/keycodes.go | 29 + .../manifoldco/promptui/keycodes_other.go | 10 + .../manifoldco/promptui/keycodes_windows.go | 10 + vendor/github.com/manifoldco/promptui/list/list.go | 237 ++++++ vendor/github.com/manifoldco/promptui/prompt.go | 341 ++++++++ vendor/github.com/manifoldco/promptui/promptui.go | 27 + .../manifoldco/promptui/screenbuf/screenbuf.go | 151 ++++ vendor/github.com/manifoldco/promptui/select.go | 637 +++++++++++++++ vendor/github.com/manifoldco/promptui/styles.go | 23 + .../manifoldco/promptui/styles_windows.go | 21 + vendor/github.com/mattn/go-colorable/.travis.yml | 9 + vendor/github.com/mattn/go-colorable/LICENSE | 21 + vendor/github.com/mattn/go-colorable/README.md | 48 ++ .../mattn/go-colorable/colorable_appengine.go | 29 + .../mattn/go-colorable/colorable_others.go | 30 + .../mattn/go-colorable/colorable_windows.go | 884 +++++++++++++++++++++ .../github.com/mattn/go-colorable/noncolorable.go | 55 ++ vendor/github.com/mattn/go-isatty/.travis.yml | 13 + vendor/github.com/mattn/go-isatty/LICENSE | 9 + vendor/github.com/mattn/go-isatty/README.md | 50 ++ vendor/github.com/mattn/go-isatty/doc.go | 2 + .../github.com/mattn/go-isatty/isatty_appengine.go | 15 + vendor/github.com/mattn/go-isatty/isatty_bsd.go | 18 + vendor/github.com/mattn/go-isatty/isatty_linux.go | 18 + .../mattn/go-isatty/isatty_linux_ppc64x.go | 19 + vendor/github.com/mattn/go-isatty/isatty_others.go | 10 + .../github.com/mattn/go-isatty/isatty_solaris.go | 16 + .../github.com/mattn/go-isatty/isatty_windows.go | 94 +++ vendor/modules.txt | 20 +- 128 files changed, 12682 insertions(+), 491 deletions(-) create mode 100644 vendor/github.com/chzyer/readline/.gitignore create mode 100644 vendor/github.com/chzyer/readline/.travis.yml create mode 100644 vendor/github.com/chzyer/readline/CHANGELOG.md create mode 100644 vendor/github.com/chzyer/readline/LICENSE create mode 100644 vendor/github.com/chzyer/readline/README.md create mode 100644 vendor/github.com/chzyer/readline/ansi_windows.go create mode 100644 vendor/github.com/chzyer/readline/complete.go create mode 100644 vendor/github.com/chzyer/readline/complete_helper.go create mode 100644 vendor/github.com/chzyer/readline/complete_segment.go create mode 100644 vendor/github.com/chzyer/readline/history.go create mode 100644 vendor/github.com/chzyer/readline/operation.go create mode 100644 vendor/github.com/chzyer/readline/password.go create mode 100644 vendor/github.com/chzyer/readline/rawreader_windows.go create mode 100644 vendor/github.com/chzyer/readline/readline.go create mode 100644 vendor/github.com/chzyer/readline/remote.go create mode 100644 vendor/github.com/chzyer/readline/runebuf.go create mode 100644 vendor/github.com/chzyer/readline/runes.go create mode 100644 vendor/github.com/chzyer/readline/search.go create mode 100644 vendor/github.com/chzyer/readline/std.go create mode 100644 vendor/github.com/chzyer/readline/std_windows.go create mode 100644 vendor/github.com/chzyer/readline/term.go create mode 100644 vendor/github.com/chzyer/readline/term_bsd.go create mode 100644 vendor/github.com/chzyer/readline/term_linux.go create mode 100644 vendor/github.com/chzyer/readline/term_solaris.go create mode 100644 vendor/github.com/chzyer/readline/term_unix.go create mode 100644 vendor/github.com/chzyer/readline/term_windows.go create mode 100644 vendor/github.com/chzyer/readline/terminal.go create mode 100644 vendor/github.com/chzyer/readline/utils.go create mode 100644 vendor/github.com/chzyer/readline/utils_unix.go create mode 100644 vendor/github.com/chzyer/readline/utils_windows.go create mode 100644 vendor/github.com/chzyer/readline/vim.go create mode 100644 vendor/github.com/chzyer/readline/windows_api.go create mode 100644 vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go create mode 100644 vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go create mode 100644 vendor/github.com/juju/ansiterm/LICENSE create mode 100644 vendor/github.com/juju/ansiterm/Makefile create mode 100644 vendor/github.com/juju/ansiterm/README.md create mode 100644 vendor/github.com/juju/ansiterm/attribute.go create mode 100644 vendor/github.com/juju/ansiterm/color.go create mode 100644 vendor/github.com/juju/ansiterm/context.go create mode 100644 vendor/github.com/juju/ansiterm/doc.go create mode 100644 vendor/github.com/juju/ansiterm/style.go create mode 100644 vendor/github.com/juju/ansiterm/tabwriter.go create mode 100644 vendor/github.com/juju/ansiterm/tabwriter/LICENSE create mode 100644 vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go create mode 100644 vendor/github.com/juju/ansiterm/terminal.go create mode 100644 vendor/github.com/juju/ansiterm/writer.go create mode 100644 vendor/github.com/lunixbochs/vtclean/.travis.yml create mode 100644 vendor/github.com/lunixbochs/vtclean/LICENSE create mode 100644 vendor/github.com/lunixbochs/vtclean/README.md create mode 100644 vendor/github.com/lunixbochs/vtclean/io.go create mode 100644 vendor/github.com/lunixbochs/vtclean/line.go create mode 100644 vendor/github.com/lunixbochs/vtclean/vtclean.go create mode 100644 vendor/github.com/manifoldco/promptui/.gitignore create mode 100644 vendor/github.com/manifoldco/promptui/.golangci.yml create mode 100644 vendor/github.com/manifoldco/promptui/.travis.yml create mode 100644 vendor/github.com/manifoldco/promptui/CHANGELOG.md create mode 100644 vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md create mode 100644 vendor/github.com/manifoldco/promptui/LICENSE.md create mode 100644 vendor/github.com/manifoldco/promptui/Makefile create mode 100644 vendor/github.com/manifoldco/promptui/README.md create mode 100644 vendor/github.com/manifoldco/promptui/codes.go create mode 100644 vendor/github.com/manifoldco/promptui/cursor.go create mode 100644 vendor/github.com/manifoldco/promptui/go.mod create mode 100644 vendor/github.com/manifoldco/promptui/go.sum create mode 100644 vendor/github.com/manifoldco/promptui/keycodes.go create mode 100644 vendor/github.com/manifoldco/promptui/keycodes_other.go create mode 100644 vendor/github.com/manifoldco/promptui/keycodes_windows.go create mode 100644 vendor/github.com/manifoldco/promptui/list/list.go create mode 100644 vendor/github.com/manifoldco/promptui/prompt.go create mode 100644 vendor/github.com/manifoldco/promptui/promptui.go create mode 100644 vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go create mode 100644 vendor/github.com/manifoldco/promptui/select.go create mode 100644 vendor/github.com/manifoldco/promptui/styles.go create mode 100644 vendor/github.com/manifoldco/promptui/styles_windows.go create mode 100644 vendor/github.com/mattn/go-colorable/.travis.yml create mode 100644 vendor/github.com/mattn/go-colorable/LICENSE create mode 100644 vendor/github.com/mattn/go-colorable/README.md create mode 100644 vendor/github.com/mattn/go-colorable/colorable_appengine.go create mode 100644 vendor/github.com/mattn/go-colorable/colorable_others.go create mode 100644 vendor/github.com/mattn/go-colorable/colorable_windows.go create mode 100644 vendor/github.com/mattn/go-colorable/noncolorable.go create mode 100644 vendor/github.com/mattn/go-isatty/.travis.yml create mode 100644 vendor/github.com/mattn/go-isatty/LICENSE create mode 100644 vendor/github.com/mattn/go-isatty/README.md create mode 100644 vendor/github.com/mattn/go-isatty/doc.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_appengine.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_bsd.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_linux.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_others.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_solaris.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_windows.go diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 130d3365e..5e538abbb 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -219,6 +219,13 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +#### **--decryption-key**=*key[:passphrase]* + +The [key[:passphrase]] to be used for decryption of images. Key can point to +keys and/or certificates. Decryption will be tried with all keys. If the key is +protected by a passphrase, it is required to be passed in the argument and +omitted otherwise. + #### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] Add a host device to the container. Optional *permissions* parameter diff --git a/go.mod b/go.mod index 28e577a39..67d13b2fd 100644 --- a/go.mod +++ b/go.mod @@ -10,10 +10,10 @@ require ( github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/containernetworking/cni v0.8.0 github.com/containernetworking/plugins v0.8.7 - github.com/containers/buildah v1.17.0 + github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2 github.com/containers/common v0.27.0 github.com/containers/conmon v2.0.20+incompatible - github.com/containers/image/v5 v5.7.0 + github.com/containers/image/v5 v5.8.0 github.com/containers/psgo v1.5.1 github.com/containers/storage v1.23.9 github.com/coreos/go-systemd/v22 v22.1.0 diff --git a/go.sum b/go.sum index ab8ed4246..d4583ca52 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,12 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b h1:T4nWG1TXIxeor8mAu5bFguPJgSIGhZqv/f0z55KCrJM= github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -83,15 +89,16 @@ github.com/containernetworking/cni v0.8.0 h1:BT9lpgGoH4jw3lFC7Odz2prU5ruiYKcgAjM github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.7 h1:bU7QieuAp+sACI2vCzESJ3FoT860urYP+lThyZkb/2M= github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0= -github.com/containers/buildah v1.17.0 h1:oaBIxKtW4kJ06vj4l0C9MZfFVapksf6F4qdQGOvZ2J4= -github.com/containers/buildah v1.17.0/go.mod h1:E6nOiMnF3uCAY3wAQK5lPR6w89SRp8iyIkjUfDKW+Eg= -github.com/containers/common v0.26.2/go.mod h1:igUeog5hx8rYhJk67rG6rGAh3zEcf0Uxuzm9KpXzo2E= +github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2 h1:sYOJ4xbCJTQEhjQax649sE+iy8ZohxmLGP8pCTrnypY= +github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2/go.mod h1:+GBrGojiBt2/IXxKYMCVD02kLIxfe5KYMvCwBjhJkFk= +github.com/containers/common v0.26.3/go.mod h1:hJWZIlrl5MsE2ELNRa+MPp6I1kPbXHauuj0Ym4BsLG4= github.com/containers/common v0.27.0 h1:+QlYEOitVYtU9/x8xebRgxdGqt4sLaIqV6MBOns+zLk= github.com/containers/common v0.27.0/go.mod h1:ZTswJJfu4aGF6Anyi2yON8Getda9NDYcdIzurOEHHXI= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= -github.com/containers/image/v5 v5.7.0 h1:fiTC8/Xbr+zEP6njGTZtPW/3UD7MC93nC9DbUoWdxkA= github.com/containers/image/v5 v5.7.0/go.mod h1:8aOy+YaItukxghRORkvhq5ibWttHErzDLy6egrKfKos= +github.com/containers/image/v5 v5.8.0 h1:B3FGHi0bdGXgg698kBIGOlHCXN5n+scJr6/5354GOPU= +github.com/containers/image/v5 v5.8.0/go.mod h1:jKxdRtyIDumVa56hdsZvV+gwx4zB50hRou6pIuCWLkg= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c= @@ -99,7 +106,6 @@ github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQ github.com/containers/psgo v1.5.1 h1:MQNb7FLbXqBdqz6u4lI2QWizVz4RSTzs1+Nk9XT1iVA= github.com/containers/psgo v1.5.1/go.mod h1:2ubh0SsreMZjSXW1Hif58JrEcFudQyIy9EzPUWfawVU= github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBDb5f8P5CAxJY= -github.com/containers/storage v1.23.7 h1:43ImvG/npvQSZXRjaudVvKISIuZSfI6qvtSNQQSGO/A= github.com/containers/storage v1.23.7/go.mod h1:cUT2zHjtx+WlVri30obWmM2gpqpi8jfPsmIzP1TVpEI= github.com/containers/storage v1.23.9 h1:qbgnTp76pLSyW3vYwY5GH4vk5cHYVXFJ+CsUEBp9TMw= github.com/containers/storage v1.23.9/go.mod h1:3b2ktpB6pw53SEeIoFfO0sQfP9+IoJJKPq5iJk74gxE= @@ -301,17 +307,19 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 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/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE= github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -320,10 +328,17 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= +github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -633,6 +648,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/libpod/image/image.go b/libpod/image/image.go index 301954703..cecd64eb7 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -24,6 +24,7 @@ import ( "github.com/containers/image/v5/manifest" ociarchive "github.com/containers/image/v5/oci/archive" "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/tarball" "github.com/containers/image/v5/transports" @@ -164,7 +165,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile } imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label) if err != nil { - return nil, errors.Wrapf(err, "unable to pull %s", name) + return nil, err } newImage, err := ir.NewFromLocal(imageName[0]) @@ -318,10 +319,8 @@ func (ir *Runtime) LoadAllImagesFromDockerArchive(ctx context.Context, fileName } goal := pullGoal{ - pullAllPairs: true, - usedSearchRegistries: false, - refPairs: refPairs, - searchedRegistries: nil, + pullAllPairs: true, + refPairs: refPairs, } defer goal.cleanUp() @@ -456,22 +455,19 @@ func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, erro return "", nil, errors.Wrapf(ErrNoSuchImage, imageError) } - // "Short-name image", so let's try out certain prefixes: - // 1) DefaultLocalRegistry (i.e., "localhost/) - // 2) Unqualified-search registries from registries.conf - unqualifiedSearchRegistries, err := registries.GetRegistries() + sys := &types.SystemContext{ + SystemRegistriesConfPath: registries.SystemRegistriesConfPath(), + } + + candidates, err := shortnames.ResolveLocally(sys, inputName) if err != nil { return "", nil, err } - for _, candidate := range append([]string{DefaultLocalRegistry}, unqualifiedSearchRegistries...) { - ref, err := decomposedImage.referenceWithRegistry(candidate) - if err != nil { - return "", nil, err - } - img, err := ir.store.Image(reference.TagNameOnly(ref).String()) + for _, candidate := range candidates { + img, err := ir.store.Image(candidate.String()) if err == nil { - return ref.String(), img, nil + return candidate.String(), img, nil } } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 65acdf427..2a2d16252 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "os" "path/filepath" "strings" @@ -15,13 +16,14 @@ import ( dockerarchive "github.com/containers/image/v5/docker/archive" ociarchive "github.com/containers/image/v5/oci/archive" oci "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/pkg/shortnames" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/events" + "github.com/containers/podman/v2/pkg/errorhandling" "github.com/containers/podman/v2/pkg/registries" - "github.com/hashicorp/go-multierror" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -56,9 +58,10 @@ var ( // pullRefPair records a pair of prepared image references to pull. type pullRefPair struct { - image string - srcRef types.ImageReference - dstRef types.ImageReference + image string + srcRef types.ImageReference + dstRef types.ImageReference + resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull } // cleanUpFunc is a function prototype for clean-up functions. @@ -66,11 +69,11 @@ type cleanUpFunc func() error // pullGoal represents the prepared image references and decided behavior to be executed by imagePull type pullGoal struct { - refPairs []pullRefPair - pullAllPairs bool // Pull all refPairs instead of stopping on first success. - usedSearchRegistries bool // refPairs construction has depended on registries.GetRegistries() - searchedRegistries []string // The list of search registries used; set only if usedSearchRegistries - cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader) + refPairs []pullRefPair + pullAllPairs bool // Pull all refPairs instead of stopping on first success. + cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader) + shortName string // Set when pulling a short name + resolved *shortnames.Resolved // Set when pulling a short name } // cleanUp invokes all cleanUpFuncs. Certain resources may not be available @@ -86,10 +89,8 @@ func (p *pullGoal) cleanUp() { // singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair. func singlePullRefPairGoal(rp pullRefPair) *pullGoal { return &pullGoal{ - refPairs: []pullRefPair{rp}, - pullAllPairs: false, // Does not really make a difference. - usedSearchRegistries: false, - searchedRegistries: nil, + refPairs: []pullRefPair{rp}, + pullAllPairs: false, // Does not really make a difference. } } @@ -193,11 +194,9 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types. } return &pullGoal{ - pullAllPairs: true, - usedSearchRegistries: false, - refPairs: pairs, - searchedRegistries: nil, - cleanUpFuncs: []cleanUpFunc{reader.Close}, + pullAllPairs: true, + refPairs: pairs, + cleanUpFuncs: []cleanUpFunc{reader.Close}, }, nil case OCIArchive: @@ -267,7 +266,7 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s if srcTransport != nil && srcTransport.Name() != DockerTransport { return nil, err } - goal, err = ir.pullGoalFromPossiblyUnqualifiedName(inputName) + goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName) if err != nil { return nil, errors.Wrap(err, "error getting default registries to try") } @@ -325,7 +324,7 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa var ( images []string - pullErrors *multierror.Error + pullErrors []error ) for _, imageInfo := range goal.refPairs { @@ -348,12 +347,17 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) return err }, retryOptions); err != nil { - pullErrors = multierror.Append(pullErrors, err) + pullErrors = append(pullErrors, err) logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) if writer != nil { _, _ = io.WriteString(writer, cleanErrorMessage(err)) } } else { + if imageInfo.resolvedShortname != nil { + if err := imageInfo.resolvedShortname.Record(); err != nil { + logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err) + } + } if !goal.pullAllPairs { ir.newImageEvent(events.Pull, "") return []string{imageInfo.image}, nil @@ -361,68 +365,75 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa images = append(images, imageInfo.image) } } - // If no image was found, we should handle. Lets be nicer to the user and see if we can figure out why. + // If no image was found, we should handle. Lets be nicer to the user + // and see if we can figure out why. if len(images) == 0 { - if goal.usedSearchRegistries && len(goal.searchedRegistries) == 0 { - return nil, errors.Errorf("image name provided is a short name and no search registries are defined in the registries config file.") - } - // If the image passed in was fully-qualified, we will have 1 refpair. Bc the image is fq'd, we don't need to yap about registries. - if !goal.usedSearchRegistries { - if pullErrors != nil && len(pullErrors.Errors) > 0 { // this should always be true - return nil, pullErrors.Errors[0] - } - return nil, errors.Errorf("unable to pull image, or you do not have pull access") + if goal.resolved != nil { + return nil, goal.resolved.FormatPullErrors(pullErrors) } - return nil, errors.Cause(pullErrors) - } - if len(images) > 0 { - ir.newImageEvent(events.Pull, images[0]) + return nil, errorhandling.JoinErrors(pullErrors) } + + ir.newImageEvent(events.Pull, images[0]) return images, nil } +// getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment +// variable. If it's "on", return `nil` to use the defaults from +// containers/image and the registries.conf files on the system. If it's +// "off", empty or unset, return types.ShortNameModeDisabled to turn off +// short-name aliasing by default. +// +// TODO: remove this function once we want to default to short-name aliasing. +func getShortNameMode() *types.ShortNameMode { + env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING") + if strings.ToLower(env) == "on" { + return nil // default to whatever registries.conf and c/image decide + } + mode := types.ShortNameModeDisabled + return &mode +} + // pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible // image references to try pulling in combination with the registries.conf file as well -func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullGoal, error) { - decomposedImage, err := decompose(inputName) +func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) { + if sys == nil { + sys = &types.SystemContext{} + } + sys.ShortNameMode = getShortNameMode() + + resolved, err := shortnames.Resolve(sys, inputName) if err != nil { return nil, err } - if decomposedImage.hasRegistry { - srcRef, err := docker.ParseReference("//" + inputName) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse '%s'", inputName) + if desc := resolved.Description(); len(desc) > 0 { + logrus.Debug(desc) + if writer != nil { + if _, err := writer.Write([]byte(desc + "\n")); err != nil { + return nil, err + } } - return ir.getSinglePullRefPairGoal(srcRef, inputName) } - searchRegistries, err := registries.GetRegistries() - if err != nil { - return nil, err - } - refPairs := make([]pullRefPair, 0, len(searchRegistries)) - for _, registry := range searchRegistries { - ref, err := decomposedImage.referenceWithRegistry(registry) + refPairs := []pullRefPair{} + for i, candidate := range resolved.PullCandidates { + srcRef, err := docker.NewReference(candidate.Value) if err != nil { return nil, err } - imageName := ref.String() - srcRef, err := docker.ParseReference("//" + imageName) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse '%s'", imageName) - } - ps, err := ir.getPullRefPair(srcRef, imageName) + ps, err := ir.getPullRefPair(srcRef, candidate.Value.String()) if err != nil { return nil, err } + ps.resolvedShortname = &resolved.PullCandidates[i] refPairs = append(refPairs, ps) } return &pullGoal{ - refPairs: refPairs, - pullAllPairs: false, - usedSearchRegistries: true, - searchedRegistries: searchRegistries, + refPairs: refPairs, + pullAllPairs: false, + shortName: inputName, + resolved: resolved, }, nil } diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go index 6cb80e8b5..2e1464ad3 100644 --- a/libpod/image/pull_test.go +++ b/libpod/image/pull_test.go @@ -278,15 +278,11 @@ func TestPullGoalFromImageReference(t *testing.T) { assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) } assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName) - assert.False(t, res.usedSearchRegistries, c.srcName) - assert.Nil(t, res.searchedRegistries, c.srcName) } } } -const registriesConfWithSearch = `[registries.search] -registries = ['example.com', 'docker.io'] -` +const registriesConfWithSearch = `unqualified-search-registries = ['example.com', 'docker.io']` func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" @@ -303,69 +299,58 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { ir, cleanup := newTestRuntime(t) defer cleanup() - // Environment is per-process, so this looks very unsafe; actually it seems fine because tests are not - // run in parallel unless they opt in by calling t.Parallel(). So don’t do that. - oldRCP, hasRCP := os.LookupEnv("REGISTRIES_CONFIG_PATH") - defer func() { - if hasRCP { - os.Setenv("REGISTRIES_CONFIG_PATH", oldRCP) - } else { - os.Unsetenv("REGISTRIES_CONFIG_PATH") - } - }() - os.Setenv("REGISTRIES_CONFIG_PATH", registriesConf.Name()) + sc := GetSystemContext("", "", false) + + aliasesConf, err := ioutil.TempFile("", "short-name-aliases.conf") + require.NoError(t, err) + defer aliasesConf.Close() + defer os.Remove(aliasesConf.Name()) + sc.UserShortNameAliasConfPath = aliasesConf.Name() + sc.SystemRegistriesConfPath = registriesConf.Name() for _, c := range []struct { - input string - expected []pullRefStrings - expectedUsedSearchRegistries bool + input string + expected []pullRefStrings }{ - {"#", nil, false}, // Clearly invalid. + {"#", nil}, // Clearly invalid. { // Fully-explicit docker.io, name-only. "docker.io/library/busybox", // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, - false, + []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, }, { // docker.io with implied /library/, name-only. "docker.io/busybox", // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, - false, + []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, }, { // Qualified example.com, name-only. "example.com/ns/busybox", - []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}}, - false, + []pullRefStrings{{"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}}, }, { // Qualified example.com, name:tag. "example.com/ns/busybox:notlatest", []pullRefStrings{{"example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}}, - false, }, { // Qualified example.com, name@digest. "example.com/ns/busybox" + digestSuffix, []pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}}, - false, }, // Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. - {"example.com/ns/busybox:notlatest" + digestSuffix, nil, false}, + {"example.com/ns/busybox:notlatest" + digestSuffix, nil}, { // Unqualified, single-name, name-only "busybox", []pullRefStrings{ - {"example.com/busybox", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, + {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}, + {"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}, }, - true, }, { // Unqualified, namespaced, name-only "ns/busybox", []pullRefStrings{ - {"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, + {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}, }, - true, }, { // Unqualified, name:tag "busybox:notlatest", @@ -374,7 +359,6 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) {"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"}, }, - true, }, { // Unqualified, name@digest "busybox" + digestSuffix, @@ -383,29 +367,22 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) {"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix}, }, - true, }, // Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. - {"busybox:notlatest" + digestSuffix, nil, false}, + {"busybox:notlatest" + digestSuffix, nil}, } { - res, err := ir.pullGoalFromPossiblyUnqualifiedName(c.input) + res, err := ir.pullGoalFromPossiblyUnqualifiedName(sc, nil, c.input) if len(c.expected) == 0 { assert.Error(t, err, c.input) } else { assert.NoError(t, err, c.input) for i, e := range c.expected { - testDescription := fmt.Sprintf("%s #%d", c.input, i) + testDescription := fmt.Sprintf("%s #%d (%v)", c.input, i, res.refPairs) assert.Equal(t, e.image, res.refPairs[i].image, testDescription) assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription) assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) } assert.False(t, res.pullAllPairs, c.input) - assert.Equal(t, c.expectedUsedSearchRegistries, res.usedSearchRegistries, c.input) - if !c.expectedUsedSearchRegistries { - assert.Nil(t, res.searchedRegistries, c.input) - } else { - assert.Equal(t, []string{"example.com", "docker.io"}, res.searchedRegistries, c.input) - } } } } diff --git a/vendor/github.com/chzyer/readline/.gitignore b/vendor/github.com/chzyer/readline/.gitignore new file mode 100644 index 000000000..a3062beae --- /dev/null +++ b/vendor/github.com/chzyer/readline/.gitignore @@ -0,0 +1 @@ +.vscode/* diff --git a/vendor/github.com/chzyer/readline/.travis.yml b/vendor/github.com/chzyer/readline/.travis.yml new file mode 100644 index 000000000..9c3595543 --- /dev/null +++ b/vendor/github.com/chzyer/readline/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.x +script: + - GOOS=windows go install github.com/chzyer/readline/example/... + - GOOS=linux go install github.com/chzyer/readline/example/... + - GOOS=darwin go install github.com/chzyer/readline/example/... + - go test -race -v diff --git a/vendor/github.com/chzyer/readline/CHANGELOG.md b/vendor/github.com/chzyer/readline/CHANGELOG.md new file mode 100644 index 000000000..14ff5be13 --- /dev/null +++ b/vendor/github.com/chzyer/readline/CHANGELOG.md @@ -0,0 +1,58 @@ +# ChangeLog + +### 1.4 - 2016-07-25 + +* [#60][60] Support dynamic autocompletion +* Fix ANSI parser on Windows +* Fix wrong column width in complete mode on Windows +* Remove dependent package "golang.org/x/crypto/ssh/terminal" + +### 1.3 - 2016-05-09 + +* [#38][38] add SetChildren for prefix completer interface +* [#42][42] improve multiple lines compatibility +* [#43][43] remove sub-package(runes) for gopkg compatibility +* [#46][46] Auto complete with space prefixed line +* [#48][48] support suspend process (ctrl+Z) +* [#49][49] fix bug that check equals with previous command +* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty + +### 1.2 - 2016-03-05 + +* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib) +* [#23][23], support stdin remapping +* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM. +* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines. +* Supports performs even stdin/stdout is not a tty. +* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api. +* [#28][28], fixes the history is not working as expected. +* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)` + +### 1.1 - 2015-11-20 + +* [#12][12] Add support for key ``/``/`` +* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`. +* Bugs fixed for `PrefixCompleter` +* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience. +* Customable Interrupt/EOF prompt in `Config` +* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices +* Provides a new password user experience(`readline.ReadPasswordEx()`). + +### 1.0 - 2015-10-14 + +* Initial public release. + +[12]: https://github.com/chzyer/readline/pull/12 +[17]: https://github.com/chzyer/readline/pull/17 +[23]: https://github.com/chzyer/readline/pull/23 +[27]: https://github.com/chzyer/readline/pull/27 +[28]: https://github.com/chzyer/readline/pull/28 +[33]: https://github.com/chzyer/readline/pull/33 +[38]: https://github.com/chzyer/readline/pull/38 +[42]: https://github.com/chzyer/readline/pull/42 +[43]: https://github.com/chzyer/readline/pull/43 +[46]: https://github.com/chzyer/readline/pull/46 +[48]: https://github.com/chzyer/readline/pull/48 +[49]: https://github.com/chzyer/readline/pull/49 +[53]: https://github.com/chzyer/readline/pull/53 +[60]: https://github.com/chzyer/readline/pull/60 diff --git a/vendor/github.com/chzyer/readline/LICENSE b/vendor/github.com/chzyer/readline/LICENSE new file mode 100644 index 000000000..c9afab3dc --- /dev/null +++ b/vendor/github.com/chzyer/readline/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Chzyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/chzyer/readline/README.md b/vendor/github.com/chzyer/readline/README.md new file mode 100644 index 000000000..fab974b7f --- /dev/null +++ b/vendor/github.com/chzyer/readline/README.md @@ -0,0 +1,114 @@ +[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) +[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases) +[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline) +[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) +[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors) + +

+ + + +

+ +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` + +## Guide + +* [Demo](example/readline-demo/readline-demo.go) +* [Shortcut](doc/shortcut.md) + +## Repos using readline + +[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach) +[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto) +[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire) +[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg) +[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql) +[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman) +[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp) +[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell) +[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001) +[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p) + + +## Feedback + +If you have any questions, please submit a github issue and any pull requests is welcomed :) + +* [https://twitter.com/chzyer](https://twitter.com/chzyer) +* [http://weibo.com/2145262190](http://weibo.com/2145262190) + + +## Backers + +Love Readline? Help me keep it alive by donating funds to cover project expenses!
+[[Become a backer](https://opencollective.com/readline#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Sponsors + +Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/chzyer/readline/ansi_windows.go b/vendor/github.com/chzyer/readline/ansi_windows.go new file mode 100644 index 000000000..63b908c18 --- /dev/null +++ b/vendor/github.com/chzyer/readline/ansi_windows.go @@ -0,0 +1,249 @@ +// +build windows + +package readline + +import ( + "bufio" + "io" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" +) + +const ( + _ = uint16(0) + COLOR_FBLUE = 0x0001 + COLOR_FGREEN = 0x0002 + COLOR_FRED = 0x0004 + COLOR_FINTENSITY = 0x0008 + + COLOR_BBLUE = 0x0010 + COLOR_BGREEN = 0x0020 + COLOR_BRED = 0x0040 + COLOR_BINTENSITY = 0x0080 + + COMMON_LVB_UNDERSCORE = 0x8000 + COMMON_LVB_BOLD = 0x0007 +) + +var ColorTableFg = []word{ + 0, // 30: Black + COLOR_FRED, // 31: Red + COLOR_FGREEN, // 32: Green + COLOR_FRED | COLOR_FGREEN, // 33: Yellow + COLOR_FBLUE, // 34: Blue + COLOR_FRED | COLOR_FBLUE, // 35: Magenta + COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan + COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White +} + +var ColorTableBg = []word{ + 0, // 40: Black + COLOR_BRED, // 41: Red + COLOR_BGREEN, // 42: Green + COLOR_BRED | COLOR_BGREEN, // 43: Yellow + COLOR_BBLUE, // 44: Blue + COLOR_BRED | COLOR_BBLUE, // 45: Magenta + COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan + COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White +} + +type ANSIWriter struct { + target io.Writer + wg sync.WaitGroup + ctx *ANSIWriterCtx + sync.Mutex +} + +func NewANSIWriter(w io.Writer) *ANSIWriter { + a := &ANSIWriter{ + target: w, + ctx: NewANSIWriterCtx(w), + } + return a +} + +func (a *ANSIWriter) Close() error { + a.wg.Wait() + return nil +} + +type ANSIWriterCtx struct { + isEsc bool + isEscSeq bool + arg []string + target *bufio.Writer + wantFlush bool +} + +func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { + return &ANSIWriterCtx{ + target: bufio.NewWriter(target), + } +} + +func (a *ANSIWriterCtx) Flush() { + a.target.Flush() +} + +func (a *ANSIWriterCtx) process(r rune) bool { + if a.wantFlush { + if r == 0 || r == CharEsc { + a.wantFlush = false + a.target.Flush() + } + } + if a.isEscSeq { + a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) + return true + } + + switch r { + case CharEsc: + a.isEsc = true + case '[': + if a.isEsc { + a.arg = nil + a.isEscSeq = true + a.isEsc = false + break + } + fallthrough + default: + a.target.WriteRune(r) + a.wantFlush = true + } + return true +} + +func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { + arg := *argptr + var err error + + if r >= 'A' && r <= 'D' { + count := short(GetInt(arg, 1)) + info, err := GetConsoleScreenBufferInfo() + if err != nil { + return false + } + switch r { + case 'A': // up + info.dwCursorPosition.y -= count + case 'B': // down + info.dwCursorPosition.y += count + case 'C': // right + info.dwCursorPosition.x += count + case 'D': // left + info.dwCursorPosition.x -= count + } + SetConsoleCursorPosition(&info.dwCursorPosition) + return false + } + + switch r { + case 'J': + killLines() + case 'K': + eraseLine() + case 'm': + color := word(0) + for _, item := range arg { + var c int + c, err = strconv.Atoi(item) + if err != nil { + w.WriteString("[" + strings.Join(arg, ";") + "m") + break + } + if c >= 30 && c < 40 { + color ^= COLOR_FINTENSITY + color |= ColorTableFg[c-30] + } else if c >= 40 && c < 50 { + color ^= COLOR_BINTENSITY + color |= ColorTableBg[c-40] + } else if c == 4 { + color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7] + } else if c == 1 { + color |= COMMON_LVB_BOLD | COLOR_FINTENSITY + } else { // unknown code treat as reset + color = ColorTableFg[7] + } + } + if err != nil { + break + } + kernel.SetConsoleTextAttribute(stdout, uintptr(color)) + case '\007': // set title + case ';': + if len(arg) == 0 || arg[len(arg)-1] != "" { + arg = append(arg, "") + *argptr = arg + } + return true + default: + if len(arg) == 0 { + arg = append(arg, "") + } + arg[len(arg)-1] += string(r) + *argptr = arg + return true + } + *argptr = nil + return false +} + +func (a *ANSIWriter) Write(b []byte) (int, error) { + a.Lock() + defer a.Unlock() + + off := 0 + for len(b) > off { + r, size := utf8.DecodeRune(b[off:]) + if size == 0 { + return off, io.ErrShortWrite + } + off += size + a.ctx.process(r) + } + a.ctx.Flush() + return off, nil +} + +func killLines() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x + size += sbi.dwCursorPosition.x + + var written int + kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} + +func eraseLine() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := sbi.dwSize.x + sbi.dwCursorPosition.x = 0 + var written int + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} diff --git a/vendor/github.com/chzyer/readline/complete.go b/vendor/github.com/chzyer/readline/complete.go new file mode 100644 index 000000000..c08c99414 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete.go @@ -0,0 +1,285 @@ +package readline + +import ( + "bufio" + "bytes" + "fmt" + "io" +) + +type AutoCompleter interface { + // Readline will pass the whole line and current offset to it + // Completer need to pass all the candidates, and how long they shared the same characters in line + // Example: + // [go, git, git-shell, grep] + // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 + // Do("gi", 2) => ["t", "t-shell"], 2 + // Do("git", 3) => ["", "-shell"], 3 + Do(line []rune, pos int) (newLine [][]rune, length int) +} + +type TabCompleter struct{} + +func (t *TabCompleter) Do([]rune, int) ([][]rune, int) { + return [][]rune{[]rune("\t")}, 0 +} + +type opCompleter struct { + w io.Writer + op *Operation + width int + + inCompleteMode bool + inSelectMode bool + candidate [][]rune + candidateSource []rune + candidateOff int + candidateChoise int + candidateColNum int +} + +func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { + return &opCompleter{ + w: w, + op: op, + width: width, + } +} + +func (o *opCompleter) doSelect() { + if len(o.candidate) == 1 { + o.op.buf.WriteRunes(o.candidate[0]) + o.ExitCompleteMode(false) + return + } + o.nextCandidate(1) + o.CompleteRefresh() +} + +func (o *opCompleter) nextCandidate(i int) { + o.candidateChoise += i + o.candidateChoise = o.candidateChoise % len(o.candidate) + if o.candidateChoise < 0 { + o.candidateChoise = len(o.candidate) + o.candidateChoise + } +} + +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } + if o.IsInCompleteSelectMode() { + o.doSelect() + return true + } + + buf := o.op.buf + rs := buf.Runes() + + if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { + o.EnterCompleteSelectMode() + o.doSelect() + return true + } + + o.ExitCompleteSelectMode() + o.candidateSource = rs + newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) + if len(newLines) == 0 { + o.ExitCompleteMode(false) + return true + } + + // only Aggregate candidates in non-complete mode + if !o.IsInCompleteMode() { + if len(newLines) == 1 { + buf.WriteRunes(newLines[0]) + o.ExitCompleteMode(false) + return true + } + + same, size := runes.Aggregate(newLines) + if size > 0 { + buf.WriteRunes(same) + o.ExitCompleteMode(false) + return true + } + } + + o.EnterCompleteMode(offset, newLines) + return true +} + +func (o *opCompleter) IsInCompleteSelectMode() bool { + return o.inSelectMode +} + +func (o *opCompleter) IsInCompleteMode() bool { + return o.inCompleteMode +} + +func (o *opCompleter) HandleCompleteSelect(r rune) bool { + next := true + switch r { + case CharEnter, CharCtrlJ: + next = false + o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise]) + o.ExitCompleteMode(false) + case CharLineStart: + num := o.candidateChoise % o.candidateColNum + o.nextCandidate(-num) + case CharLineEnd: + num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1 + o.candidateChoise += num + if o.candidateChoise >= len(o.candidate) { + o.candidateChoise = len(o.candidate) - 1 + } + case CharBackspace: + o.ExitCompleteSelectMode() + next = false + case CharTab, CharForward: + o.doSelect() + case CharBell, CharInterrupt: + o.ExitCompleteMode(true) + next = false + case CharNext: + tmpChoise := o.candidateChoise + o.candidateColNum + if tmpChoise >= o.getMatrixSize() { + tmpChoise -= o.getMatrixSize() + } else if tmpChoise >= len(o.candidate) { + tmpChoise += o.candidateColNum + tmpChoise -= o.getMatrixSize() + } + o.candidateChoise = tmpChoise + case CharBackward: + o.nextCandidate(-1) + case CharPrev: + tmpChoise := o.candidateChoise - o.candidateColNum + if tmpChoise < 0 { + tmpChoise += o.getMatrixSize() + if tmpChoise >= len(o.candidate) { + tmpChoise -= o.candidateColNum + } + } + o.candidateChoise = tmpChoise + default: + next = false + o.ExitCompleteSelectMode() + } + if next { + o.CompleteRefresh() + return true + } + return false +} + +func (o *opCompleter) getMatrixSize() int { + line := len(o.candidate) / o.candidateColNum + if len(o.candidate)%o.candidateColNum != 0 { + line++ + } + return line * o.candidateColNum +} + +func (o *opCompleter) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opCompleter) CompleteRefresh() { + if !o.inCompleteMode { + return + } + lineCnt := o.op.buf.CursorLineCount() + colWidth := 0 + for _, c := range o.candidate { + w := runes.WidthAll(c) + if w > colWidth { + colWidth = w + } + } + colWidth += o.candidateOff + 1 + same := o.op.buf.RuneSlice(-o.candidateOff) + + // -1 to avoid reach the end of line + width := o.width - 1 + colNum := width / colWidth + if colNum != 0 { + colWidth += (width - (colWidth * colNum)) / colNum + } + + o.candidateColNum = colNum + buf := bufio.NewWriter(o.w) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + + colIdx := 0 + lines := 1 + buf.WriteString("\033[J") + for idx, c := range o.candidate { + inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() + if inSelect { + buf.WriteString("\033[30;47m") + } + buf.WriteString(string(same)) + buf.WriteString(string(c)) + buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same))) + + if inSelect { + buf.WriteString("\033[0m") + } + + colIdx++ + if colIdx == colNum { + buf.WriteString("\n") + lines++ + colIdx = 0 + } + } + + // move back + fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) + fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) + buf.Flush() +} + +func (o *opCompleter) aggCandidate(candidate [][]rune) int { + offset := 0 + for i := 0; i < len(candidate[0]); i++ { + for j := 0; j < len(candidate)-1; j++ { + if i > len(candidate[j]) { + goto aggregate + } + if candidate[j][i] != candidate[j+1][i] { + goto aggregate + } + } + offset = i + } +aggregate: + return offset +} + +func (o *opCompleter) EnterCompleteSelectMode() { + o.inSelectMode = true + o.candidateChoise = -1 + o.CompleteRefresh() +} + +func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) { + o.inCompleteMode = true + o.candidate = candidate + o.candidateOff = offset + o.CompleteRefresh() +} + +func (o *opCompleter) ExitCompleteSelectMode() { + o.inSelectMode = false + o.candidate = nil + o.candidateChoise = -1 + o.candidateOff = -1 + o.candidateSource = nil +} + +func (o *opCompleter) ExitCompleteMode(revent bool) { + o.inCompleteMode = false + o.ExitCompleteSelectMode() +} diff --git a/vendor/github.com/chzyer/readline/complete_helper.go b/vendor/github.com/chzyer/readline/complete_helper.go new file mode 100644 index 000000000..58d724872 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_helper.go @@ -0,0 +1,165 @@ +package readline + +import ( + "bytes" + "strings" +) + +// Caller type for dynamic completion +type DynamicCompleteFunc func(string) []string + +type PrefixCompleterInterface interface { + Print(prefix string, level int, buf *bytes.Buffer) + Do(line []rune, pos int) (newLine [][]rune, length int) + GetName() []rune + GetChildren() []PrefixCompleterInterface + SetChildren(children []PrefixCompleterInterface) +} + +type DynamicPrefixCompleterInterface interface { + PrefixCompleterInterface + IsDynamic() bool + GetDynamicNames(line []rune) [][]rune +} + +type PrefixCompleter struct { + Name []rune + Dynamic bool + Callback DynamicCompleteFunc + Children []PrefixCompleterInterface +} + +func (p *PrefixCompleter) Tree(prefix string) string { + buf := bytes.NewBuffer(nil) + p.Print(prefix, 0, buf) + return buf.String() +} + +func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.GetName())) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.GetName()) + "\n") + level++ + } + for _, ch := range p.GetChildren() { + ch.Print(prefix, level, buf) + } +} + +func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { + Print(p, prefix, level, buf) +} + +func (p *PrefixCompleter) IsDynamic() bool { + return p.Dynamic +} + +func (p *PrefixCompleter) GetName() []rune { + return p.Name +} + +func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { + var names = [][]rune{} + for _, name := range p.Callback(string(line)) { + names = append(names, []rune(name+" ")) + } + return names +} + +func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { + return p.Children +} + +func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) { + p.Children = children +} + +func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter { + return PcItem("", pc...) +} + +func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { + name += " " + return &PrefixCompleter{ + Name: []rune(name), + Dynamic: false, + Children: pc, + } +} + +func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { + return &PrefixCompleter{ + Callback: callback, + Dynamic: true, + Children: pc, + } +} + +func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { + line = runes.TrimSpaceLeft(line[:pos]) + goNext := false + var lineCompleter PrefixCompleterInterface + for _, child := range p.GetChildren() { + childNames := make([][]rune, 1) + + childDynamic, ok := child.(DynamicPrefixCompleterInterface) + if ok && childDynamic.IsDynamic() { + childNames = childDynamic.GetDynamicNames(origLine) + } else { + childNames[0] = child.GetName() + } + + for _, childName := range childNames { + if len(line) >= len(childName) { + if runes.HasPrefix(line, childName) { + if len(line) == len(childName) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, childName) + } + offset = len(childName) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[len(line):]) + offset = len(line) + lineCompleter = child + } + } + } + } + + if len(newLine) != 1 { + return + } + + tmpLine := make([]rune, 0, len(line)) + for i := offset; i < len(line); i++ { + if line[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) + } + + if goNext { + return doInternal(lineCompleter, nil, 0, origLine) + } + return +} diff --git a/vendor/github.com/chzyer/readline/complete_segment.go b/vendor/github.com/chzyer/readline/complete_segment.go new file mode 100644 index 000000000..5ceadd80f --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_segment.go @@ -0,0 +1,82 @@ +package readline + +type SegmentCompleter interface { + // a + // |- a1 + // |--- a11 + // |- a2 + // b + // input: + // DoTree([], 0) [a, b] + // DoTree([a], 1) [a] + // DoTree([a, ], 0) [a1, a2] + // DoTree([a, a], 1) [a1, a2] + // DoTree([a, a1], 2) [a1] + // DoTree([a, a1, ], 0) [a11] + // DoTree([a, a1, a], 1) [a11] + DoSegment([][]rune, int) [][]rune +} + +type dumpSegmentCompleter struct { + f func([][]rune, int) [][]rune +} + +func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { + return d.f(segment, n) +} + +func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { + return &SegmentComplete{&dumpSegmentCompleter{f}} +} + +func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete { + return &SegmentComplete{ + SegmentCompleter: completer, + } +} + +type SegmentComplete struct { + SegmentCompleter +} + +func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { + ret := make([][]rune, 0, len(cands)) + lastSegment := segments[len(segments)-1] + for _, cand := range cands { + if !runes.HasPrefix(cand, lastSegment) { + continue + } + ret = append(ret, cand[len(lastSegment):]) + } + return ret, idx +} + +func SplitSegment(line []rune, pos int) ([][]rune, int) { + segs := [][]rune{} + lastIdx := -1 + line = line[:pos] + pos = 0 + for idx, l := range line { + if l == ' ' { + pos = 0 + segs = append(segs, line[lastIdx+1:idx]) + lastIdx = idx + } else { + pos++ + } + } + segs = append(segs, line[lastIdx+1:]) + return segs, pos +} + +func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { + + segment, idx := SplitSegment(line, pos) + + cands := c.DoSegment(segment, idx) + newLine, offset = RetSegment(segment, cands, idx) + for idx := range newLine { + newLine[idx] = append(newLine[idx], ' ') + } + return newLine, offset +} diff --git a/vendor/github.com/chzyer/readline/history.go b/vendor/github.com/chzyer/readline/history.go new file mode 100644 index 000000000..6b17c464b --- /dev/null +++ b/vendor/github.com/chzyer/readline/history.go @@ -0,0 +1,330 @@ +package readline + +import ( + "bufio" + "container/list" + "fmt" + "os" + "strings" + "sync" +) + +type hisItem struct { + Source []rune + Version int64 + Tmp []rune +} + +func (h *hisItem) Clean() { + h.Source = nil + h.Tmp = nil +} + +type opHistory struct { + cfg *Config + history *list.List + historyVer int64 + current *list.Element + fd *os.File + fdLock sync.Mutex + enable bool +} + +func newOpHistory(cfg *Config) (o *opHistory) { + o = &opHistory{ + cfg: cfg, + history: list.New(), + enable: true, + } + return o +} + +func (o *opHistory) Reset() { + o.history = list.New() + o.current = nil +} + +func (o *opHistory) IsHistoryClosed() bool { + o.fdLock.Lock() + defer o.fdLock.Unlock() + return o.fd.Fd() == ^(uintptr(0)) +} + +func (o *opHistory) Init() { + if o.IsHistoryClosed() { + o.initHistory() + } +} + +func (o *opHistory) initHistory() { + if o.cfg.HistoryFile != "" { + o.historyUpdatePath(o.cfg.HistoryFile) + } +} + +// only called by newOpHistory +func (o *opHistory) historyUpdatePath(path string) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return + } + o.fd = f + r := bufio.NewReader(o.fd) + total := 0 + for ; ; total++ { + line, err := r.ReadString('\n') + if err != nil { + break + } + // ignore the empty line + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + o.Push([]rune(line)) + o.Compact() + } + if total > o.cfg.HistoryLimit { + o.rewriteLocked() + } + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Compact() { + for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { + o.history.Remove(o.history.Front()) + } +} + +func (o *opHistory) Rewrite() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + o.rewriteLocked() +} + +func (o *opHistory) rewriteLocked() { + if o.cfg.HistoryFile == "" { + return + } + + tmpFile := o.cfg.HistoryFile + ".tmp" + fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) + if err != nil { + return + } + + buf := bufio.NewWriter(fd) + for elem := o.history.Front(); elem != nil; elem = elem.Next() { + buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") + } + buf.Flush() + + // replace history file + if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { + fd.Close() + return + } + + if o.fd != nil { + o.fd.Close() + } + // fd is write only, just satisfy what we need. + o.fd = fd +} + +func (o *opHistory) Close() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + if o.fd != nil { + o.fd.Close() + } +} + +func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Prev() { + item := o.showItem(elem.Value) + if isNewSearch { + start += len(rs) + } + if elem == o.current { + if len(item) >= start { + item = item[:start] + } + } + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Next() { + item := o.showItem(elem.Value) + if isNewSearch { + start -= len(rs) + if start < 0 { + start = 0 + } + } + if elem == o.current { + if len(item)-1 >= start { + item = item[start:] + } else { + continue + } + } + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + if elem == o.current { + idx += start + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) showItem(obj interface{}) []rune { + item := obj.(*hisItem) + if item.Version == o.historyVer { + return item.Tmp + } + return item.Source +} + +func (o *opHistory) Prev() []rune { + if o.current == nil { + return nil + } + current := o.current.Prev() + if current == nil { + return nil + } + o.current = current + return runes.Copy(o.showItem(current.Value)) +} + +func (o *opHistory) Next() ([]rune, bool) { + if o.current == nil { + return nil, false + } + current := o.current.Next() + if current == nil { + return nil, false + } + + o.current = current + return runes.Copy(o.showItem(current.Value)), true +} + +// Disable the current history +func (o *opHistory) Disable() { + o.enable = false +} + +// Enable the current history +func (o *opHistory) Enable() { + o.enable = true +} + +func (o *opHistory) debug() { + Debug("-------") + for item := o.history.Front(); item != nil; item = item.Next() { + Debug(fmt.Sprintf("%+v", item.Value)) + } +} + +// save history +func (o *opHistory) New(current []rune) (err error) { + + // history deactivated + if !o.enable { + return nil + } + + current = runes.Copy(current) + + // if just use last command without modify + // just clean lastest history + if back := o.history.Back(); back != nil { + prev := back.Prev() + if prev != nil { + if runes.Equal(current, prev.Value.(*hisItem).Source) { + o.current = o.history.Back() + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + } + + if len(current) == 0 { + o.current = o.history.Back() + if o.current != nil { + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + + if o.current != o.history.Back() { + // move history item to current command + currentItem := o.current.Value.(*hisItem) + // set current to last item + o.current = o.history.Back() + + current = runes.Copy(currentItem.Tmp) + } + + // err only can be a IO error, just report + err = o.Update(current, true) + + // push a new one to commit current command + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Revert() { + o.historyVer++ + o.current = o.history.Back() +} + +func (o *opHistory) Update(s []rune, commit bool) (err error) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + s = runes.Copy(s) + if o.current == nil { + o.Push(s) + o.Compact() + return + } + r := o.current.Value.(*hisItem) + r.Version = o.historyVer + if commit { + r.Source = s + if o.fd != nil { + // just report the error + _, err = o.fd.Write([]byte(string(r.Source) + "\n")) + } + } else { + r.Tmp = append(r.Tmp[:0], s...) + } + o.current.Value = r + o.Compact() + return +} + +func (o *opHistory) Push(s []rune) { + s = runes.Copy(s) + elem := o.history.PushBack(&hisItem{Source: s}) + o.current = elem +} diff --git a/vendor/github.com/chzyer/readline/operation.go b/vendor/github.com/chzyer/readline/operation.go new file mode 100644 index 000000000..4c31624f8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/operation.go @@ -0,0 +1,531 @@ +package readline + +import ( + "errors" + "io" + "sync" +) + +var ( + ErrInterrupt = errors.New("Interrupt") +) + +type InterruptError struct { + Line []rune +} + +func (*InterruptError) Error() string { + return "Interrupted" +} + +type Operation struct { + m sync.Mutex + cfg *Config + t *Terminal + buf *RuneBuffer + outchan chan []rune + errchan chan error + w io.Writer + + history *opHistory + *opSearch + *opCompleter + *opPassword + *opVim +} + +func (o *Operation) SetBuffer(what string) { + o.buf.Set([]rune(what)) +} + +type wrapWriter struct { + r *Operation + t *Terminal + target io.Writer +} + +func (w *wrapWriter) Write(b []byte) (int, error) { + if !w.t.IsReading() { + return w.target.Write(b) + } + + var ( + n int + err error + ) + w.r.buf.Refresh(func() { + n, err = w.target.Write(b) + }) + + if w.r.IsSearchMode() { + w.r.SearchRefresh(-1) + } + if w.r.IsInCompleteMode() { + w.r.CompleteRefresh() + } + return n, err +} + +func NewOperation(t *Terminal, cfg *Config) *Operation { + width := cfg.FuncGetWidth() + op := &Operation{ + t: t, + buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), + outchan: make(chan []rune), + errchan: make(chan error, 1), + } + op.w = op.buf.w + op.SetConfig(cfg) + op.opVim = newVimMode(op) + op.opCompleter = newOpCompleter(op.buf.w, op, width) + op.opPassword = newOpPassword(op) + op.cfg.FuncOnWidthChanged(func() { + newWidth := cfg.FuncGetWidth() + op.opCompleter.OnWidthChange(newWidth) + op.opSearch.OnWidthChange(newWidth) + op.buf.OnWidthChange(newWidth) + }) + go op.ioloop() + return op +} + +func (o *Operation) SetPrompt(s string) { + o.buf.SetPrompt(s) +} + +func (o *Operation) SetMaskRune(r rune) { + o.buf.SetMask(r) +} + +func (o *Operation) GetConfig() *Config { + o.m.Lock() + cfg := *o.cfg + o.m.Unlock() + return &cfg +} + +func (o *Operation) ioloop() { + for { + keepInSearchMode := false + keepInCompleteMode := false + r := o.t.ReadRune() + if o.GetConfig().FuncFilterInputRune != nil { + var process bool + r, process = o.GetConfig().FuncFilterInputRune(r) + if !process { + o.buf.Refresh(nil) // to refresh the line + continue // ignore this rune + } + } + + if r == 0 { // io.EOF + if o.buf.Len() == 0 { + o.buf.Clean() + select { + case o.errchan <- io.EOF: + } + break + } else { + // if stdin got io.EOF and there is something left in buffer, + // let's flush them by sending CharEnter. + // And we will got io.EOF int next loop. + r = CharEnter + } + } + isUpdateHistory := true + + if o.IsInCompleteSelectMode() { + keepInCompleteMode = o.HandleCompleteSelect(r) + if keepInCompleteMode { + continue + } + + o.buf.Refresh(nil) + switch r { + case CharEnter, CharCtrlJ: + o.history.Update(o.buf.Runes(), false) + fallthrough + case CharInterrupt: + o.t.KickRead() + fallthrough + case CharBell: + continue + } + } + + if o.IsEnableVimMode() { + r = o.HandleVim(r, o.t.ReadRune) + if r == 0 { + continue + } + } + + switch r { + case CharBell: + if o.IsSearchMode() { + o.ExitSearchMode(true) + o.buf.Refresh(nil) + } + if o.IsInCompleteMode() { + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + } + case CharTab: + if o.GetConfig().AutoComplete == nil { + o.t.Bell() + break + } + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + + case CharBckSearch: + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharCtrlU: + o.buf.KillFront() + case CharFwdSearch: + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharKill: + o.buf.Kill() + keepInCompleteMode = true + case MetaForward: + o.buf.MoveToNextWord() + case CharTranspose: + o.buf.Transpose() + case MetaBackward: + o.buf.MoveToPrevWord() + case MetaDelete: + o.buf.DeleteWord() + case CharLineStart: + o.buf.MoveToLineStart() + case CharLineEnd: + o.buf.MoveToLineEnd() + case CharBackspace, CharCtrlH: + if o.IsSearchMode() { + o.SearchBackspace() + keepInSearchMode = true + break + } + + if o.buf.Len() == 0 { + o.t.Bell() + break + } + o.buf.Backspace() + if o.IsInCompleteMode() { + o.OnComplete() + } + case CharCtrlZ: + o.buf.Clean() + o.t.SleepToResume() + o.Refresh() + case CharCtrlL: + ClearScreen(o.w) + o.Refresh() + case MetaBackspace, CharCtrlW: + o.buf.BackEscapeWord() + case CharCtrlY: + o.buf.Yank() + case CharEnter, CharCtrlJ: + if o.IsSearchMode() { + o.ExitSearchMode(false) + } + o.buf.MoveToLineEnd() + var data []rune + if !o.GetConfig().UniqueEditLine { + o.buf.WriteRune('\n') + data = o.buf.Reset() + data = data[:len(data)-1] // trim \n + } else { + o.buf.Clean() + data = o.buf.Reset() + } + o.outchan <- data + if !o.GetConfig().DisableAutoSaveHistory { + // ignore IO error + _ = o.history.New(data) + } else { + isUpdateHistory = false + } + case CharBackward: + o.buf.MoveBackward() + case CharForward: + o.buf.MoveForward() + case CharPrev: + buf := o.history.Prev() + if buf != nil { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharNext: + buf, ok := o.history.Next() + if ok { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharDelete: + if o.buf.Len() > 0 || !o.IsNormalMode() { + o.t.KickRead() + if !o.buf.Delete() { + o.t.Bell() + } + break + } + + // treat as EOF + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(o.GetConfig().EOFPrompt + "\n") + } + o.buf.Reset() + isUpdateHistory = false + o.history.Revert() + o.errchan <- io.EOF + if o.GetConfig().UniqueEditLine { + o.buf.Clean() + } + case CharInterrupt: + if o.IsSearchMode() { + o.t.KickRead() + o.ExitSearchMode(true) + break + } + if o.IsInCompleteMode() { + o.t.KickRead() + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + break + } + o.buf.MoveToLineEnd() + o.buf.Refresh(nil) + hint := o.GetConfig().InterruptPrompt + "\n" + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(hint) + } + remain := o.buf.Reset() + if !o.GetConfig().UniqueEditLine { + remain = remain[:len(remain)-len([]rune(hint))] + } + isUpdateHistory = false + o.history.Revert() + o.errchan <- &InterruptError{remain} + default: + if o.IsSearchMode() { + o.SearchChar(r) + keepInSearchMode = true + break + } + o.buf.WriteRune(r) + if o.IsInCompleteMode() { + o.OnComplete() + keepInCompleteMode = true + } + } + + listener := o.GetConfig().Listener + if listener != nil { + newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r) + if ok { + o.buf.SetWithIdx(newPos, newLine) + } + } + + o.m.Lock() + if !keepInSearchMode && o.IsSearchMode() { + o.ExitSearchMode(false) + o.buf.Refresh(nil) + } else if o.IsInCompleteMode() { + if !keepInCompleteMode { + o.ExitCompleteMode(false) + o.Refresh() + } else { + o.buf.Refresh(nil) + o.CompleteRefresh() + } + } + if isUpdateHistory && !o.IsSearchMode() { + // it will cause null history + o.history.Update(o.buf.Runes(), false) + } + o.m.Unlock() + } +} + +func (o *Operation) Stderr() io.Writer { + return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t} +} + +func (o *Operation) Stdout() io.Writer { + return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} +} + +func (o *Operation) String() (string, error) { + r, err := o.Runes() + return string(r), err +} + +func (o *Operation) Runes() ([]rune, error) { + o.t.EnterRawMode() + defer o.t.ExitRawMode() + + listener := o.GetConfig().Listener + if listener != nil { + listener.OnChange(nil, 0, 0) + } + + o.buf.Refresh(nil) // print prompt + o.t.KickRead() + select { + case r := <-o.outchan: + return r, nil + case err := <-o.errchan: + if e, ok := err.(*InterruptError); ok { + return e.Line, ErrInterrupt + } + return nil, err + } +} + +func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { + cfg := o.GenPasswordConfig() + cfg.Prompt = prompt + cfg.Listener = l + return o.PasswordWithConfig(cfg) +} + +func (o *Operation) GenPasswordConfig() *Config { + return o.opPassword.PasswordConfig() +} + +func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { + if err := o.opPassword.EnterPasswordMode(cfg); err != nil { + return nil, err + } + defer o.opPassword.ExitPasswordMode() + return o.Slice() +} + +func (o *Operation) Password(prompt string) ([]byte, error) { + return o.PasswordEx(prompt, nil) +} + +func (o *Operation) SetTitle(t string) { + o.w.Write([]byte("\033[2;" + t + "\007")) +} + +func (o *Operation) Slice() ([]byte, error) { + r, err := o.Runes() + if err != nil { + return nil, err + } + return []byte(string(r)), nil +} + +func (o *Operation) Close() { + o.history.Close() +} + +func (o *Operation) SetHistoryPath(path string) { + if o.history != nil { + o.history.Close() + } + o.cfg.HistoryFile = path + o.history = newOpHistory(o.cfg) +} + +func (o *Operation) IsNormalMode() bool { + return !o.IsInCompleteMode() && !o.IsSearchMode() +} + +func (op *Operation) SetConfig(cfg *Config) (*Config, error) { + op.m.Lock() + defer op.m.Unlock() + if op.cfg == cfg { + return op.cfg, nil + } + if err := cfg.Init(); err != nil { + return op.cfg, err + } + old := op.cfg + op.cfg = cfg + op.SetPrompt(cfg.Prompt) + op.SetMaskRune(cfg.MaskRune) + op.buf.SetConfig(cfg) + width := op.cfg.FuncGetWidth() + + if cfg.opHistory == nil { + op.SetHistoryPath(cfg.HistoryFile) + cfg.opHistory = op.history + cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) + } + op.history = cfg.opHistory + + // SetHistoryPath will close opHistory which already exists + // so if we use it next time, we need to reopen it by `InitHistory()` + op.history.Init() + + if op.cfg.AutoComplete != nil { + op.opCompleter = newOpCompleter(op.buf.w, op, width) + } + + op.opSearch = cfg.opSearch + return old, nil +} + +func (o *Operation) ResetHistory() { + o.history.Reset() +} + +// if err is not nil, it just mean it fail to write to file +// other things goes fine. +func (o *Operation) SaveHistory(content string) error { + return o.history.New([]rune(content)) +} + +func (o *Operation) Refresh() { + if o.t.IsReading() { + o.buf.Refresh(nil) + } +} + +func (o *Operation) Clean() { + o.buf.Clean() +} + +func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { + return &DumpListener{f: f} +} + +type DumpListener struct { + f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + return d.f(line, pos, key) +} + +type Listener interface { + OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +type Painter interface { + Paint(line []rune, pos int) []rune +} + +type defaultPainter struct{} + +func (p *defaultPainter) Paint(line []rune, _ int) []rune { + return line +} diff --git a/vendor/github.com/chzyer/readline/password.go b/vendor/github.com/chzyer/readline/password.go new file mode 100644 index 000000000..414288c2a --- /dev/null +++ b/vendor/github.com/chzyer/readline/password.go @@ -0,0 +1,33 @@ +package readline + +type opPassword struct { + o *Operation + backupCfg *Config +} + +func newOpPassword(o *Operation) *opPassword { + return &opPassword{o: o} +} + +func (o *opPassword) ExitPasswordMode() { + o.o.SetConfig(o.backupCfg) + o.backupCfg = nil +} + +func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) { + o.backupCfg, err = o.o.SetConfig(cfg) + return +} + +func (o *opPassword) PasswordConfig() *Config { + return &Config{ + EnableMask: true, + InterruptPrompt: "\n", + EOFPrompt: "\n", + HistoryLimit: -1, + Painter: &defaultPainter{}, + + Stdout: o.o.cfg.Stdout, + Stderr: o.o.cfg.Stderr, + } +} diff --git a/vendor/github.com/chzyer/readline/rawreader_windows.go b/vendor/github.com/chzyer/readline/rawreader_windows.go new file mode 100644 index 000000000..073ef150a --- /dev/null +++ b/vendor/github.com/chzyer/readline/rawreader_windows.go @@ -0,0 +1,125 @@ +// +build windows + +package readline + +import "unsafe" + +const ( + VK_CANCEL = 0x03 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_ESCAPE = 0x1B + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_DELETE = 0x2E + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 +) + +// RawReader translate input record to ANSI escape sequence. +// To provides same behavior as unix terminal. +type RawReader struct { + ctrlKey bool + altKey bool +} + +func NewRawReader() *RawReader { + r := new(RawReader) + return r +} + +// only process one action in one read +func (r *RawReader) Read(buf []byte) (int, error) { + ir := new(_INPUT_RECORD) + var read int + var err error +next: + err = kernel.ReadConsoleInputW(stdin, + uintptr(unsafe.Pointer(ir)), + 1, + uintptr(unsafe.Pointer(&read)), + ) + if err != nil { + return 0, err + } + if ir.EventType != EVENT_KEY { + goto next + } + ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0])) + if ker.bKeyDown == 0 { // keyup + if r.ctrlKey || r.altKey { + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = false + case VK_MENU: //alt + r.altKey = false + } + } + goto next + } + + if ker.unicodeChar == 0 { + var target rune + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = true + case VK_MENU: //alt + r.altKey = true + case VK_LEFT: + target = CharBackward + case VK_RIGHT: + target = CharForward + case VK_UP: + target = CharPrev + case VK_DOWN: + target = CharNext + } + if target != 0 { + return r.write(buf, target) + } + goto next + } + char := rune(ker.unicodeChar) + if r.ctrlKey { + switch char { + case 'A': + char = CharLineStart + case 'E': + char = CharLineEnd + case 'R': + char = CharBckSearch + case 'S': + char = CharFwdSearch + } + } else if r.altKey { + switch char { + case VK_BACK: + char = CharBackspace + } + return r.writeEsc(buf, char) + } + return r.write(buf, char) +} + +func (r *RawReader) writeEsc(b []byte, char rune) (int, error) { + b[0] = '\033' + n := copy(b[1:], []byte(string(char))) + return n + 1, nil +} + +func (r *RawReader) write(b []byte, char rune) (int, error) { + n := copy(b, []byte(string(char))) + return n, nil +} + +func (r *RawReader) Close() error { + return nil +} diff --git a/vendor/github.com/chzyer/readline/readline.go b/vendor/github.com/chzyer/readline/readline.go new file mode 100644 index 000000000..0e7aca06d --- /dev/null +++ b/vendor/github.com/chzyer/readline/readline.go @@ -0,0 +1,326 @@ +// Readline is a pure go implementation for GNU-Readline kind library. +// +// example: +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() +// +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// println(line) +// } +// +package readline + +import "io" + +type Instance struct { + Config *Config + Terminal *Terminal + Operation *Operation +} + +type Config struct { + // prompt supports ANSI escape sequence, so we can color some characters even in windows + Prompt string + + // readline will persist historys to file where HistoryFile specified + HistoryFile string + // specify the max length of historys, it's 500 by default, set it to -1 to disable history + HistoryLimit int + DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool + + // AutoCompleter will called once user press TAB + AutoComplete AutoCompleter + + // Any key press will pass to Listener + // NOTE: Listener will be triggered by (nil, 0, 0) immediately + Listener Listener + + Painter Painter + + // If VimMode is true, readline will in vim.insert mode by default + VimMode bool + + InterruptPrompt string + EOFPrompt string + + FuncGetWidth func() int + + Stdin io.ReadCloser + StdinWriter io.Writer + Stdout io.Writer + Stderr io.Writer + + EnableMask bool + MaskRune rune + + // erase the editing line after user submited it + // it use in IM usually. + UniqueEditLine bool + + // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) + // -> output = new (translated) rune and true/false if continue with processing this one + FuncFilterInputRune func(rune) (rune, bool) + + // force use interactive even stdout is not a tty + FuncIsTerminal func() bool + FuncMakeRaw func() error + FuncExitRaw func() error + FuncOnWidthChanged func(func()) + ForceUseInteractive bool + + // private fields + inited bool + opHistory *opHistory + opSearch *opSearch +} + +func (c *Config) useInteractive() bool { + if c.ForceUseInteractive { + return true + } + return c.FuncIsTerminal() +} + +func (c *Config) Init() error { + if c.inited { + return nil + } + c.inited = true + if c.Stdin == nil { + c.Stdin = NewCancelableStdin(Stdin) + } + + c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) + + if c.Stdout == nil { + c.Stdout = Stdout + } + if c.Stderr == nil { + c.Stderr = Stderr + } + if c.HistoryLimit == 0 { + c.HistoryLimit = 500 + } + + if c.InterruptPrompt == "" { + c.InterruptPrompt = "^C" + } else if c.InterruptPrompt == "\n" { + c.InterruptPrompt = "" + } + if c.EOFPrompt == "" { + c.EOFPrompt = "^D" + } else if c.EOFPrompt == "\n" { + c.EOFPrompt = "" + } + + if c.AutoComplete == nil { + c.AutoComplete = &TabCompleter{} + } + if c.FuncGetWidth == nil { + c.FuncGetWidth = GetScreenWidth + } + if c.FuncIsTerminal == nil { + c.FuncIsTerminal = DefaultIsTerminal + } + rm := new(RawMode) + if c.FuncMakeRaw == nil { + c.FuncMakeRaw = rm.Enter + } + if c.FuncExitRaw == nil { + c.FuncExitRaw = rm.Exit + } + if c.FuncOnWidthChanged == nil { + c.FuncOnWidthChanged = DefaultOnWidthChanged + } + + return nil +} + +func (c Config) Clone() *Config { + c.opHistory = nil + c.opSearch = nil + return &c +} + +func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { + c.Listener = FuncListener(f) +} + +func (c *Config) SetPainter(p Painter) { + c.Painter = p +} + +func NewEx(cfg *Config) (*Instance, error) { + t, err := NewTerminal(cfg) + if err != nil { + return nil, err + } + rl := t.Readline() + if cfg.Painter == nil { + cfg.Painter = &defaultPainter{} + } + return &Instance{ + Config: cfg, + Terminal: t, + Operation: rl, + }, nil +} + +func New(prompt string) (*Instance, error) { + return NewEx(&Config{Prompt: prompt}) +} + +func (i *Instance) ResetHistory() { + i.Operation.ResetHistory() +} + +func (i *Instance) SetPrompt(s string) { + i.Operation.SetPrompt(s) +} + +func (i *Instance) SetMaskRune(r rune) { + i.Operation.SetMaskRune(r) +} + +// change history persistence in runtime +func (i *Instance) SetHistoryPath(p string) { + i.Operation.SetHistoryPath(p) +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stdout() io.Writer { + return i.Operation.Stdout() +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stderr() io.Writer { + return i.Operation.Stderr() +} + +// switch VimMode in runtime +func (i *Instance) SetVimMode(on bool) { + i.Operation.SetVimMode(on) +} + +func (i *Instance) IsVimMode() bool { + return i.Operation.IsEnableVimMode() +} + +func (i *Instance) GenPasswordConfig() *Config { + return i.Operation.GenPasswordConfig() +} + +// we can generate a config by `i.GenPasswordConfig()` +func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { + return i.Operation.PasswordWithConfig(cfg) +} + +func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { + return i.Operation.PasswordEx(prompt, l) +} + +func (i *Instance) ReadPassword(prompt string) ([]byte, error) { + return i.Operation.Password(prompt) +} + +type Result struct { + Line string + Error error +} + +func (l *Result) CanContinue() bool { + return len(l.Line) != 0 && l.Error == ErrInterrupt +} + +func (l *Result) CanBreak() bool { + return !l.CanContinue() && l.Error != nil +} + +func (i *Instance) Line() *Result { + ret, err := i.Readline() + return &Result{ret, err} +} + +// err is one of (nil, io.EOF, readline.ErrInterrupt) +func (i *Instance) Readline() (string, error) { + return i.Operation.String() +} + +func (i *Instance) ReadlineWithDefault(what string) (string, error) { + i.Operation.SetBuffer(what) + return i.Operation.String() +} + +func (i *Instance) SaveHistory(content string) error { + return i.Operation.SaveHistory(content) +} + +// same as readline +func (i *Instance) ReadSlice() ([]byte, error) { + return i.Operation.Slice() +} + +// we must make sure that call Close() before process exit. +func (i *Instance) Close() error { + if err := i.Terminal.Close(); err != nil { + return err + } + i.Config.Stdin.Close() + i.Operation.Close() + return nil +} +func (i *Instance) Clean() { + i.Operation.Clean() +} + +func (i *Instance) Write(b []byte) (int, error) { + return i.Stdout().Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +// ie : +// i := readline.New() +// i.WriteStdin([]byte("test")) +// _, _= i.Readline() +// +// gives +// +// > test[cursor] +func (i *Instance) WriteStdin(val []byte) (int, error) { + return i.Terminal.WriteStdin(val) +} + +func (i *Instance) SetConfig(cfg *Config) *Config { + if i.Config == cfg { + return cfg + } + old := i.Config + i.Config = cfg + i.Operation.SetConfig(cfg) + i.Terminal.SetConfig(cfg) + return old +} + +func (i *Instance) Refresh() { + i.Operation.Refresh() +} + +// HistoryDisable the save of the commands into the history +func (i *Instance) HistoryDisable() { + i.Operation.history.Disable() +} + +// HistoryEnable the save of the commands into the history (default on) +func (i *Instance) HistoryEnable() { + i.Operation.history.Enable() +} diff --git a/vendor/github.com/chzyer/readline/remote.go b/vendor/github.com/chzyer/readline/remote.go new file mode 100644 index 000000000..74dbf5690 --- /dev/null +++ b/vendor/github.com/chzyer/readline/remote.go @@ -0,0 +1,475 @@ +package readline + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "os" + "sync" + "sync/atomic" +) + +type MsgType int16 + +const ( + T_DATA = MsgType(iota) + T_WIDTH + T_WIDTH_REPORT + T_ISTTY_REPORT + T_RAW + T_ERAW // exit raw + T_EOF +) + +type RemoteSvr struct { + eof int32 + closed int32 + width int32 + reciveChan chan struct{} + writeChan chan *writeCtx + conn net.Conn + isTerminal bool + funcWidthChan func() + stopChan chan struct{} + + dataBufM sync.Mutex + dataBuf bytes.Buffer +} + +type writeReply struct { + n int + err error +} + +type writeCtx struct { + msg *Message + reply chan *writeReply +} + +func newWriteCtx(msg *Message) *writeCtx { + return &writeCtx{ + msg: msg, + reply: make(chan *writeReply), + } +} + +func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) { + rs := &RemoteSvr{ + width: -1, + conn: conn, + writeChan: make(chan *writeCtx), + reciveChan: make(chan struct{}), + stopChan: make(chan struct{}), + } + buf := bufio.NewReader(rs.conn) + + if err := rs.init(buf); err != nil { + return nil, err + } + + go rs.readLoop(buf) + go rs.writeLoop() + return rs, nil +} + +func (r *RemoteSvr) init(buf *bufio.Reader) error { + m, err := ReadMessage(buf) + if err != nil { + return err + } + // receive isTerminal + if m.Type != T_ISTTY_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotIsTerminal(m.Data) + + // receive width + m, err = ReadMessage(buf) + if err != nil { + return err + } + if m.Type != T_WIDTH_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotReportWidth(m.Data) + + return nil +} + +func (r *RemoteSvr) HandleConfig(cfg *Config) { + cfg.Stderr = r + cfg.Stdout = r + cfg.Stdin = r + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncIsTerminal = r.IsTerminal + cfg.FuncMakeRaw = r.EnterRawMode + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncGetWidth = r.GetWidth + cfg.FuncOnWidthChanged = func(f func()) { + r.funcWidthChan = f + } +} + +func (r *RemoteSvr) IsTerminal() bool { + return r.isTerminal +} + +func (r *RemoteSvr) checkEOF() error { + if atomic.LoadInt32(&r.eof) == 1 { + return io.EOF + } + return nil +} + +func (r *RemoteSvr) Read(b []byte) (int, error) { + r.dataBufM.Lock() + n, err := r.dataBuf.Read(b) + r.dataBufM.Unlock() + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + if n == 0 && err == io.EOF { + <-r.reciveChan + r.dataBufM.Lock() + n, err = r.dataBuf.Read(b) + r.dataBufM.Unlock() + } + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + return n, err +} + +func (r *RemoteSvr) writeMsg(m *Message) error { + ctx := newWriteCtx(m) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.err +} + +func (r *RemoteSvr) Write(b []byte) (int, error) { + ctx := newWriteCtx(NewMessage(T_DATA, b)) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.n, reply.err +} + +func (r *RemoteSvr) EnterRawMode() error { + return r.writeMsg(NewMessage(T_RAW, nil)) +} + +func (r *RemoteSvr) ExitRawMode() error { + return r.writeMsg(NewMessage(T_ERAW, nil)) +} + +func (r *RemoteSvr) writeLoop() { + defer r.Close() + +loop: + for { + select { + case ctx, ok := <-r.writeChan: + if !ok { + break + } + n, err := ctx.msg.WriteTo(r.conn) + ctx.reply <- &writeReply{n, err} + case <-r.stopChan: + break loop + } + } +} + +func (r *RemoteSvr) Close() error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + close(r.stopChan) + r.conn.Close() + } + return nil +} + +func (r *RemoteSvr) readLoop(buf *bufio.Reader) { + defer r.Close() + for { + m, err := ReadMessage(buf) + if err != nil { + break + } + switch m.Type { + case T_EOF: + atomic.StoreInt32(&r.eof, 1) + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_DATA: + r.dataBufM.Lock() + r.dataBuf.Write(m.Data) + r.dataBufM.Unlock() + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_WIDTH_REPORT: + r.GotReportWidth(m.Data) + case T_ISTTY_REPORT: + r.GotIsTerminal(m.Data) + } + } +} + +func (r *RemoteSvr) GotIsTerminal(data []byte) { + if binary.BigEndian.Uint16(data) == 0 { + r.isTerminal = false + } else { + r.isTerminal = true + } +} + +func (r *RemoteSvr) GotReportWidth(data []byte) { + atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data))) + if r.funcWidthChan != nil { + r.funcWidthChan() + } +} + +func (r *RemoteSvr) GetWidth() int { + return int(atomic.LoadInt32(&r.width)) +} + +// ----------------------------------------------------------------------------- + +type Message struct { + Type MsgType + Data []byte +} + +func ReadMessage(r io.Reader) (*Message, error) { + m := new(Message) + var length int32 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil { + return nil, err + } + m.Data = make([]byte, int(length)-2) + if _, err := io.ReadFull(r, m.Data); err != nil { + return nil, err + } + return m, nil +} + +func NewMessage(t MsgType, data []byte) *Message { + return &Message{t, data} +} + +func (m *Message) WriteTo(w io.Writer) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4)) + binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2)) + binary.Write(buf, binary.BigEndian, m.Type) + buf.Write(m.Data) + n, err := buf.WriteTo(w) + return int(n), err +} + +// ----------------------------------------------------------------------------- + +type RemoteCli struct { + conn net.Conn + raw RawMode + receiveChan chan struct{} + inited int32 + isTerminal *bool + + data bytes.Buffer + dataM sync.Mutex +} + +func NewRemoteCli(conn net.Conn) (*RemoteCli, error) { + r := &RemoteCli{ + conn: conn, + receiveChan: make(chan struct{}), + } + return r, nil +} + +func (r *RemoteCli) MarkIsTerminal(is bool) { + r.isTerminal = &is +} + +func (r *RemoteCli) init() error { + if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) { + return nil + } + + if err := r.reportIsTerminal(); err != nil { + return err + } + + if err := r.reportWidth(); err != nil { + return err + } + + // register sig for width changed + DefaultOnWidthChanged(func() { + r.reportWidth() + }) + return nil +} + +func (r *RemoteCli) writeMsg(m *Message) error { + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return err +} + +func (r *RemoteCli) Write(b []byte) (int, error) { + m := NewMessage(T_DATA, b) + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return len(b), err +} + +func (r *RemoteCli) reportWidth() error { + screenWidth := GetScreenWidth() + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(screenWidth)) + msg := NewMessage(T_WIDTH_REPORT, data) + + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) reportIsTerminal() error { + var isTerminal bool + if r.isTerminal != nil { + isTerminal = *r.isTerminal + } else { + isTerminal = DefaultIsTerminal() + } + data := make([]byte, 2) + if isTerminal { + binary.BigEndian.PutUint16(data, 1) + } else { + binary.BigEndian.PutUint16(data, 0) + } + msg := NewMessage(T_ISTTY_REPORT, data) + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) readLoop() { + buf := bufio.NewReader(r.conn) + for { + msg, err := ReadMessage(buf) + if err != nil { + break + } + switch msg.Type { + case T_ERAW: + r.raw.Exit() + case T_RAW: + r.raw.Enter() + case T_DATA: + os.Stdout.Write(msg.Data) + } + } +} + +func (r *RemoteCli) ServeBy(source io.Reader) error { + if err := r.init(); err != nil { + return err + } + + go func() { + defer r.Close() + for { + n, _ := io.Copy(r, source) + if n == 0 { + break + } + } + }() + defer r.raw.Exit() + r.readLoop() + return nil +} + +func (r *RemoteCli) Close() { + r.writeMsg(NewMessage(T_EOF, nil)) +} + +func (r *RemoteCli) Serve() error { + return r.ServeBy(os.Stdin) +} + +func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error { + ln, err := net.Listen(n, addr) + if err != nil { + return err + } + if len(onListen) > 0 { + if err := onListen[0](ln); err != nil { + return err + } + } + for { + conn, err := ln.Accept() + if err != nil { + break + } + go func() { + defer conn.Close() + rl, err := HandleConn(*cfg, conn) + if err != nil { + return + } + h(rl) + }() + } + return nil +} + +func HandleConn(cfg Config, conn net.Conn) (*Instance, error) { + r, err := NewRemoteSvr(conn) + if err != nil { + return nil, err + } + r.HandleConfig(&cfg) + + rl, err := NewEx(&cfg) + if err != nil { + return nil, err + } + return rl, nil +} + +func DialRemote(n, addr string) error { + conn, err := net.Dial(n, addr) + if err != nil { + return err + } + defer conn.Close() + + cli, err := NewRemoteCli(conn) + if err != nil { + return err + } + return cli.Serve() +} diff --git a/vendor/github.com/chzyer/readline/runebuf.go b/vendor/github.com/chzyer/readline/runebuf.go new file mode 100644 index 000000000..81d2da50c --- /dev/null +++ b/vendor/github.com/chzyer/readline/runebuf.go @@ -0,0 +1,629 @@ +package readline + +import ( + "bufio" + "bytes" + "io" + "strconv" + "strings" + "sync" +) + +type runeBufferBck struct { + buf []rune + idx int +} + +type RuneBuffer struct { + buf []rune + idx int + prompt []rune + w io.Writer + + hadClean bool + interactive bool + cfg *Config + + width int + + bck *runeBufferBck + + offset string + + lastKill []rune + + sync.Mutex +} + +func (r* RuneBuffer) pushKill(text []rune) { + r.lastKill = append([]rune{}, text...) +} + +func (r *RuneBuffer) OnWidthChange(newWidth int) { + r.Lock() + r.width = newWidth + r.Unlock() +} + +func (r *RuneBuffer) Backup() { + r.Lock() + r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() +} + +func (r *RuneBuffer) Restore() { + r.Refresh(func() { + if r.bck == nil { + return + } + r.buf = r.bck.buf + r.idx = r.bck.idx + }) +} + +func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { + rb := &RuneBuffer{ + w: w, + interactive: cfg.useInteractive(), + cfg: cfg, + width: width, + } + rb.SetPrompt(prompt) + return rb +} + +func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() + r.cfg = cfg + r.interactive = cfg.useInteractive() + r.Unlock() +} + +func (r *RuneBuffer) SetMask(m rune) { + r.Lock() + r.cfg.MaskRune = m + r.Unlock() +} + +func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() + return runes.WidthAll(r.buf[:x]) +} + +func (r *RuneBuffer) PromptLen() int { + r.Lock() + width := r.promptLen() + r.Unlock() + return width +} + +func (r *RuneBuffer) promptLen() int { + return runes.WidthAll(runes.ColorFilter(r.prompt)) +} + +func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + + if i > 0 { + rs := make([]rune, i) + copy(rs, r.buf[r.idx:r.idx+i]) + return rs + } + rs := make([]rune, -i) + copy(rs, r.buf[r.idx+i:r.idx]) + return rs +} + +func (r *RuneBuffer) Runes() []rune { + r.Lock() + newr := make([]rune, len(r.buf)) + copy(newr, r.buf) + r.Unlock() + return newr +} + +func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() + return r.idx +} + +func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() + return len(r.buf) +} + +func (r *RuneBuffer) MoveToLineStart() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx = 0 + }) +} + +func (r *RuneBuffer) MoveBackward() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx-- + }) +} + +func (r *RuneBuffer) WriteString(s string) { + r.WriteRunes([]rune(s)) +} + +func (r *RuneBuffer) WriteRune(s rune) { + r.WriteRunes([]rune{s}) +} + +func (r *RuneBuffer) WriteRunes(s []rune) { + r.Refresh(func() { + tail := append(s, r.buf[r.idx:]...) + r.buf = append(r.buf[:r.idx], tail...) + r.idx += len(s) + }) +} + +func (r *RuneBuffer) MoveForward() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.idx++ + }) +} + +func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() + return r.idx == len(r.buf) +} + +func (r *RuneBuffer) Replace(ch rune) { + r.Refresh(func() { + r.buf[r.idx] = ch + }) +} + +func (r *RuneBuffer) Erase() { + r.Refresh(func() { + r.idx = 0 + r.pushKill(r.buf[:]) + r.buf = r.buf[:0] + }) +} + +func (r *RuneBuffer) Delete() (success bool) { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.pushKill(r.buf[r.idx : r.idx+1]) + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + success = true + }) + return +} + +func (r *RuneBuffer) DeleteWord() { + if r.idx == len(r.buf) { + return + } + init := r.idx + for init < len(r.buf) && IsWordBreak(r.buf[init]) { + init++ + } + for i := init + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[r.idx:i-1]) + r.Refresh(func() { + r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) + }) + return + } + } + r.Kill() +} + +func (r *RuneBuffer) MoveToPrevWord() (success bool) { + r.Refresh(func() { + if r.idx == 0 { + return + } + + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + success = true + return + } + } + r.idx = 0 + success = true + }) + return +} + +func (r *RuneBuffer) KillFront() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + length := len(r.buf) - r.idx + r.pushKill(r.buf[:r.idx]) + copy(r.buf[:length], r.buf[r.idx:]) + r.idx = 0 + r.buf = r.buf[:length] + }) +} + +func (r *RuneBuffer) Kill() { + r.Refresh(func() { + r.pushKill(r.buf[r.idx:]) + r.buf = r.buf[:r.idx] + }) +} + +func (r *RuneBuffer) Transpose() { + r.Refresh(func() { + if len(r.buf) == 1 { + r.idx++ + } + + if len(r.buf) < 2 { + return + } + + if r.idx == 0 { + r.idx = 1 + } else if r.idx >= len(r.buf) { + r.idx = len(r.buf) - 1 + } + r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] + r.idx++ + }) +} + +func (r *RuneBuffer) MoveToNextWord() { + r.Refresh(func() { + for i := r.idx + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + return + } + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) MoveToEndWord() { + r.Refresh(func() { + // already at the end, so do nothing + if r.idx == len(r.buf) { + return + } + // if we are at the end of a word already, go to next + if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { + r.idx++ + } + + // keep going until at the end of a word + for i := r.idx + 1; i < len(r.buf); i++ { + if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { + r.idx = i - 1 + return + } + } + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) BackEscapeWord() { + r.Refresh(func() { + if r.idx == 0 { + return + } + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[i:r.idx]) + r.buf = append(r.buf[:i], r.buf[r.idx:]...) + r.idx = i + return + } + } + + r.buf = r.buf[:0] + r.idx = 0 + }) +} + +func (r *RuneBuffer) Yank() { + if len(r.lastKill) == 0 { + return + } + r.Refresh(func() { + buf := make([]rune, 0, len(r.buf) + len(r.lastKill)) + buf = append(buf, r.buf[:r.idx]...) + buf = append(buf, r.lastKill...) + buf = append(buf, r.buf[r.idx:]...) + r.buf = buf + r.idx += len(r.lastKill) + }) +} + +func (r *RuneBuffer) Backspace() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + r.idx-- + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + }) +} + +func (r *RuneBuffer) MoveToLineEnd() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) LineCount(width int) int { + if width == -1 { + width = r.width + } + return LineCount(width, + runes.WidthAll(r.buf)+r.PromptLen()) +} + +func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { + r.Refresh(func() { + if reverse { + for i := r.idx - 1; i >= 0; i-- { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx++ + } + success = true + return + } + } + return + } + for i := r.idx + 1; i < len(r.buf); i++ { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx-- + } + success = true + return + } + } + }) + return +} + +func (r *RuneBuffer) isInLineEdge() bool { + if isWindows { + return false + } + sp := r.getSplitByLine(r.buf) + return len(sp[len(sp)-1]) == 0 +} + +func (r *RuneBuffer) getSplitByLine(rs []rune) []string { + return SplitByLine(r.promptLen(), r.width, rs) +} + +func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { + if width == 0 { + return 0 + } + sp := r.getSplitByLine(r.buf[:r.idx]) + return len(sp) - 1 +} + +func (r *RuneBuffer) CursorLineCount() int { + return r.LineCount(r.width) - r.IdxLine(r.width) +} + +func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + + if !r.interactive { + if f != nil { + f() + } + return + } + + r.clean() + if f != nil { + f() + } + r.print() +} + +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + +func (r *RuneBuffer) print() { + r.w.Write(r.output()) + r.hadClean = false +} + +func (r *RuneBuffer) output() []byte { + buf := bytes.NewBuffer(nil) + buf.WriteString(string(r.prompt)) + if r.cfg.EnableMask && len(r.buf) > 0 { + buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) + if r.buf[len(r.buf)-1] == '\n' { + buf.Write([]byte{'\n'}) + } else { + buf.Write([]byte(string(r.cfg.MaskRune))) + } + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + + } else { + for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { + if e == '\t' { + buf.WriteString(strings.Repeat(" ", TabWidth)) + } else { + buf.WriteRune(e) + } + } + if r.isInLineEdge() { + buf.Write([]byte(" \b")) + } + } + // cursor position + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + return buf.Bytes() +} + +func (r *RuneBuffer) getBackspaceSequence() []byte { + var sep = map[int]bool{} + + var i int + for { + if i >= runes.WidthAll(r.buf) { + break + } + + if i == 0 { + i -= r.promptLen() + } + i += r.width + + sep[i] = true + } + var buf []byte + for i := len(r.buf); i > r.idx; i-- { + // move input to the left of one + buf = append(buf, '\b') + if sep[i] { + // up one line, go to the start of the line and move cursor right to the end (r.width) + buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) + } + } + + return buf + +} + +func (r *RuneBuffer) Reset() []rune { + ret := runes.Copy(r.buf) + r.buf = r.buf[:0] + r.idx = 0 + return ret +} + +func (r *RuneBuffer) calWidth(m int) int { + if m > 0 { + return runes.WidthAll(r.buf[r.idx : r.idx+m]) + } + return runes.WidthAll(r.buf[r.idx+m : r.idx]) +} + +func (r *RuneBuffer) SetStyle(start, end int, style string) { + if end < start { + panic("end < start") + } + + // goto start + move := start - r.idx + if move > 0 { + r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) + } else { + r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) + } + r.w.Write([]byte("\033[" + style + "m")) + r.w.Write([]byte(string(r.buf[start:end]))) + r.w.Write([]byte("\033[0m")) + // TODO: move back +} + +func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { + r.Refresh(func() { + r.buf = buf + r.idx = idx + }) +} + +func (r *RuneBuffer) Set(buf []rune) { + r.SetWithIdx(len(buf), buf) +} + +func (r *RuneBuffer) SetPrompt(prompt string) { + r.Lock() + r.prompt = []rune(prompt) + r.Unlock() +} + +func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { + buf := bufio.NewWriter(w) + + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) + buf.Write([]byte("\033[J")) + } else { + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") + } + } + buf.Flush() + return +} + +func (r *RuneBuffer) Clean() { + r.Lock() + r.clean() + r.Unlock() +} + +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) +} + +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { + if r.hadClean || !r.interactive { + return + } + r.hadClean = true + r.cleanOutput(r.w, idxLine) +} diff --git a/vendor/github.com/chzyer/readline/runes.go b/vendor/github.com/chzyer/readline/runes.go new file mode 100644 index 000000000..a669bc48c --- /dev/null +++ b/vendor/github.com/chzyer/readline/runes.go @@ -0,0 +1,223 @@ +package readline + +import ( + "bytes" + "unicode" + "unicode/utf8" +) + +var runes = Runes{} +var TabWidth = 4 + +type Runes struct{} + +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + +func (Runes) Equal(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { + for i := len(r) - len(sub); i >= 0; i-- { + found := true + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + +// Search in runes from front to end +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { + for i := 0; i < len(r); i++ { + found := true + if len(r[i:]) < len(sub) { + return -1 + } + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +func (Runes) Index(r rune, rs []rune) int { + for i := 0; i < len(rs); i++ { + if rs[i] == r { + return i + } + } + return -1 +} + +func (Runes) ColorFilter(r []rune) []rune { + newr := make([]rune, 0, len(r)) + for pos := 0; pos < len(r); pos++ { + if r[pos] == '\033' && r[pos+1] == '[' { + idx := runes.Index('m', r[pos+2:]) + if idx == -1 { + continue + } + pos += idx + 2 + continue + } + newr = append(newr, r[pos]) + } + return newr +} + +var zeroWidth = []*unicode.RangeTable{ + unicode.Mn, + unicode.Me, + unicode.Cc, + unicode.Cf, +} + +var doubleWidth = []*unicode.RangeTable{ + unicode.Han, + unicode.Hangul, + unicode.Hiragana, + unicode.Katakana, +} + +func (Runes) Width(r rune) int { + if r == '\t' { + return TabWidth + } + if unicode.IsOneOf(zeroWidth, r) { + return 0 + } + if unicode.IsOneOf(doubleWidth, r) { + return 2 + } + return 1 +} + +func (Runes) WidthAll(r []rune) (length int) { + for i := 0; i < len(r); i++ { + length += runes.Width(r[i]) + } + return +} + +func (Runes) Backspace(r []rune) []byte { + return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r)) +} + +func (Runes) Copy(r []rune) []rune { + n := make([]rune, len(r)) + copy(n, r) + return n +} + +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + +func (Runes) HasPrefix(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.Equal(r[:len(prefix)], prefix) +} + +func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) { + for i := 0; i < len(candicate[0]); i++ { + for j := 0; j < len(candicate)-1; j++ { + if i >= len(candicate[j]) || i >= len(candicate[j+1]) { + goto aggregate + } + if candicate[j][i] != candicate[j+1][i] { + goto aggregate + } + } + size = i + 1 + } +aggregate: + if size > 0 { + same = runes.Copy(candicate[0][:size]) + for i := 0; i < len(candicate); i++ { + n := runes.Copy(candicate[i]) + copy(n, n[size:]) + candicate[i] = n[:len(n)-size] + } + } + return +} + +func (Runes) TrimSpaceLeft(in []rune) []rune { + firstIndex := len(in) + for i, r := range in { + if unicode.IsSpace(r) == false { + firstIndex = i + break + } + } + return in[firstIndex:] +} diff --git a/vendor/github.com/chzyer/readline/search.go b/vendor/github.com/chzyer/readline/search.go new file mode 100644 index 000000000..52e8ff099 --- /dev/null +++ b/vendor/github.com/chzyer/readline/search.go @@ -0,0 +1,164 @@ +package readline + +import ( + "bytes" + "container/list" + "fmt" + "io" +) + +const ( + S_STATE_FOUND = iota + S_STATE_FAILING +) + +const ( + S_DIR_BCK = iota + S_DIR_FWD +) + +type opSearch struct { + inMode bool + state int + dir int + source *list.Element + w io.Writer + buf *RuneBuffer + data []rune + history *opHistory + cfg *Config + markStart int + markEnd int + width int +} + +func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { + return &opSearch{ + w: w, + buf: buf, + cfg: cfg, + history: history, + width: width, + } +} + +func (o *opSearch) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opSearch) IsSearchMode() bool { + return o.inMode +} + +func (o *opSearch) SearchBackspace() { + if len(o.data) > 0 { + o.data = o.data[:len(o.data)-1] + o.search(true) + } +} + +func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { + if o.dir == S_DIR_BCK { + return o.history.FindBck(isNewSearch, o.data, o.buf.idx) + } + return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) +} + +func (o *opSearch) search(isChange bool) bool { + if len(o.data) == 0 { + o.state = S_STATE_FOUND + o.SearchRefresh(-1) + return true + } + idx, elem := o.findHistoryBy(isChange) + if elem == nil { + o.SearchRefresh(-2) + return false + } + o.history.current = elem + + item := o.history.showItem(o.history.current.Value) + start, end := 0, 0 + if o.dir == S_DIR_BCK { + start, end = idx, idx+len(o.data) + } else { + start, end = idx, idx+len(o.data) + idx += len(o.data) + } + o.buf.SetWithIdx(idx, item) + o.markStart, o.markEnd = start, end + o.SearchRefresh(idx) + return true +} + +func (o *opSearch) SearchChar(r rune) { + o.data = append(o.data, r) + o.search(true) +} + +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } + alreadyInMode := o.inMode + o.inMode = true + o.dir = dir + o.source = o.history.current + if alreadyInMode { + o.search(false) + } else { + o.SearchRefresh(-1) + } + return true +} + +func (o *opSearch) ExitSearchMode(revert bool) { + if revert { + o.history.current = o.source + o.buf.Set(o.history.showItem(o.history.current.Value)) + } + o.markStart, o.markEnd = 0, 0 + o.state = S_STATE_FOUND + o.inMode = false + o.source = nil + o.data = nil +} + +func (o *opSearch) SearchRefresh(x int) { + if x == -2 { + o.state = S_STATE_FAILING + } else if x >= 0 { + o.state = S_STATE_FOUND + } + if x < 0 { + x = o.buf.idx + } + x = o.buf.CurrentWidth(x) + x += o.buf.PromptLen() + x = x % o.width + + if o.markStart > 0 { + o.buf.SetStyle(o.markStart, o.markEnd, "4") + } + + lineCnt := o.buf.CursorLineCount() + buf := bytes.NewBuffer(nil) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + buf.WriteString("\033[J") + if o.state == S_STATE_FAILING { + buf.WriteString("failing ") + } + if o.dir == S_DIR_BCK { + buf.WriteString("bck") + } else if o.dir == S_DIR_FWD { + buf.WriteString("fwd") + } + buf.WriteString("-i-search: ") + buf.WriteString(string(o.data)) // keyword + buf.WriteString("\033[4m \033[0m") // _ + fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev + if x > 0 { + fmt.Fprintf(buf, "\033[%dC", x) // move forward + } + o.w.Write(buf.Bytes()) +} diff --git a/vendor/github.com/chzyer/readline/std.go b/vendor/github.com/chzyer/readline/std.go new file mode 100644 index 000000000..61d44b759 --- /dev/null +++ b/vendor/github.com/chzyer/readline/std.go @@ -0,0 +1,197 @@ +package readline + +import ( + "io" + "os" + "sync" + "sync/atomic" +) + +var ( + Stdin io.ReadCloser = os.Stdin + Stdout io.WriteCloser = os.Stdout + Stderr io.WriteCloser = os.Stderr +) + +var ( + std *Instance + stdOnce sync.Once +) + +// global instance will not submit history automatic +func getInstance() *Instance { + stdOnce.Do(func() { + std, _ = NewEx(&Config{ + DisableAutoSaveHistory: true, + }) + }) + return std +} + +// let readline load history from filepath +// and try to persist history into disk +// set fp to "" to prevent readline persisting history to disk +// so the `AddHistory` will return nil error forever. +func SetHistoryPath(fp string) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.HistoryFile = fp + ins.SetConfig(cfg) +} + +// set auto completer to global instance +func SetAutoComplete(completer AutoCompleter) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.AutoComplete = completer + ins.SetConfig(cfg) +} + +// add history to global instance manually +// raise error only if `SetHistoryPath` is set with a non-empty path +func AddHistory(content string) error { + ins := getInstance() + return ins.SaveHistory(content) +} + +func Password(prompt string) ([]byte, error) { + ins := getInstance() + return ins.ReadPassword(prompt) +} + +// readline with global configs +func Line(prompt string) (string, error) { + ins := getInstance() + ins.SetPrompt(prompt) + return ins.Readline() +} + +type CancelableStdin struct { + r io.Reader + mutex sync.Mutex + stop chan struct{} + closed int32 + notify chan struct{} + data []byte + read int + err error +} + +func NewCancelableStdin(r io.Reader) *CancelableStdin { + c := &CancelableStdin{ + r: r, + notify: make(chan struct{}), + stop: make(chan struct{}), + } + go c.ioloop() + return c +} + +func (c *CancelableStdin) ioloop() { +loop: + for { + select { + case <-c.notify: + c.read, c.err = c.r.Read(c.data) + select { + case c.notify <- struct{}{}: + case <-c.stop: + break loop + } + case <-c.stop: + break loop + } + } +} + +func (c *CancelableStdin) Read(b []byte) (n int, err error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if atomic.LoadInt32(&c.closed) == 1 { + return 0, io.EOF + } + + c.data = b + select { + case c.notify <- struct{}{}: + case <-c.stop: + return 0, io.EOF + } + select { + case <-c.notify: + return c.read, c.err + case <-c.stop: + return 0, io.EOF + } +} + +func (c *CancelableStdin) Close() error { + if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { + close(c.stop) + } + return nil +} + +// FillableStdin is a stdin reader which can prepend some data before +// reading into the real stdin +type FillableStdin struct { + sync.Mutex + stdin io.Reader + stdinBuffer io.ReadCloser + buf []byte + bufErr error +} + +// NewFillableStdin gives you FillableStdin +func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) { + r, w := io.Pipe() + s := &FillableStdin{ + stdinBuffer: r, + stdin: stdin, + } + s.ioloop() + return s, w +} + +func (s *FillableStdin) ioloop() { + go func() { + for { + bufR := make([]byte, 100) + var n int + n, s.bufErr = s.stdinBuffer.Read(bufR) + if s.bufErr != nil { + if s.bufErr == io.ErrClosedPipe { + break + } + } + s.Lock() + s.buf = append(s.buf, bufR[:n]...) + s.Unlock() + } + }() +} + +// Read will read from the local buffer and if no data, read from stdin +func (s *FillableStdin) Read(p []byte) (n int, err error) { + s.Lock() + i := len(s.buf) + if len(p) < i { + i = len(p) + } + if i > 0 { + n := copy(p, s.buf) + s.buf = s.buf[:0] + cerr := s.bufErr + s.bufErr = nil + s.Unlock() + return n, cerr + } + s.Unlock() + n, err = s.stdin.Read(p) + return n, err +} + +func (s *FillableStdin) Close() error { + s.stdinBuffer.Close() + return nil +} diff --git a/vendor/github.com/chzyer/readline/std_windows.go b/vendor/github.com/chzyer/readline/std_windows.go new file mode 100644 index 000000000..b10f91bcb --- /dev/null +++ b/vendor/github.com/chzyer/readline/std_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package readline + +func init() { + Stdin = NewRawReader() + Stdout = NewANSIWriter(Stdout) + Stderr = NewANSIWriter(Stderr) +} diff --git a/vendor/github.com/chzyer/readline/term.go b/vendor/github.com/chzyer/readline/term.go new file mode 100644 index 000000000..133993ca8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term.go @@ -0,0 +1,123 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + termios Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := getTermios(fd) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + + if termios, err := getTermios(fd); err != nil { + return nil, err + } else { + oldState.termios = *termios + } + + newState := oldState.termios + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + // newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + return &oldState, setTermios(fd, &newState) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := getTermios(fd) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + return setTermios(fd, &state.termios) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + oldState, err := getTermios(fd) + if err != nil { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if err := setTermios(fd, newState); err != nil { + return nil, err + } + + defer func() { + setTermios(fd, oldState) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/term_bsd.go b/vendor/github.com/chzyer/readline/term_bsd.go new file mode 100644 index 000000000..68b56ea6b --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_bsd.go @@ -0,0 +1,29 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_linux.go b/vendor/github.com/chzyer/readline/term_linux.go new file mode 100644 index 000000000..e3392b4ac --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_linux.go @@ -0,0 +1,33 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package readline + +import ( + "syscall" + "unsafe" +) + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_solaris.go b/vendor/github.com/chzyer/readline/term_solaris.go new file mode 100644 index 000000000..4c27273c7 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_solaris.go @@ -0,0 +1,32 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package readline + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} + +type Termios unix.Termios + +func getTermios(fd int) (*Termios, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + return (*Termios)(termios), nil +} + +func setTermios(fd int, termios *Termios) error { + return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios)) +} diff --git a/vendor/github.com/chzyer/readline/term_unix.go b/vendor/github.com/chzyer/readline/term_unix.go new file mode 100644 index 000000000..d3ea24244 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_unix.go @@ -0,0 +1,24 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +type Termios syscall.Termios + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + var dimensions [4]uint16 + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0) + if err != 0 { + return 0, 0, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} diff --git a/vendor/github.com/chzyer/readline/term_windows.go b/vendor/github.com/chzyer/readline/term_windows.go new file mode 100644 index 000000000..1290e00bc --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_windows.go @@ -0,0 +1,171 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/terminal.go b/vendor/github.com/chzyer/readline/terminal.go new file mode 100644 index 000000000..1078631c1 --- /dev/null +++ b/vendor/github.com/chzyer/readline/terminal.go @@ -0,0 +1,238 @@ +package readline + +import ( + "bufio" + "fmt" + "io" + "strings" + "sync" + "sync/atomic" +) + +type Terminal struct { + m sync.Mutex + cfg *Config + outchan chan rune + closed int32 + stopChan chan struct{} + kickChan chan struct{} + wg sync.WaitGroup + isReading int32 + sleeping int32 + + sizeChan chan string +} + +func NewTerminal(cfg *Config) (*Terminal, error) { + if err := cfg.Init(); err != nil { + return nil, err + } + t := &Terminal{ + cfg: cfg, + kickChan: make(chan struct{}, 1), + outchan: make(chan rune), + stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), + } + + go t.ioloop() + return t, nil +} + +// SleepToResume will sleep myself, and return only if I'm resumed. +func (t *Terminal) SleepToResume() { + if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { + return + } + defer atomic.StoreInt32(&t.sleeping, 0) + + t.ExitRawMode() + ch := WaitForResume() + SuspendMe() + <-ch + t.EnterRawMode() +} + +func (t *Terminal) EnterRawMode() (err error) { + return t.cfg.FuncMakeRaw() +} + +func (t *Terminal) ExitRawMode() (err error) { + return t.cfg.FuncExitRaw() +} + +func (t *Terminal) Write(b []byte) (int, error) { + return t.cfg.Stdout.Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +func (t *Terminal) WriteStdin(b []byte) (int, error) { + return t.cfg.StdinWriter.Write(b) +} + +type termSize struct { + left int + top int +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + +func (t *Terminal) Print(s string) { + fmt.Fprintf(t.cfg.Stdout, "%s", s) +} + +func (t *Terminal) PrintRune(r rune) { + fmt.Fprintf(t.cfg.Stdout, "%c", r) +} + +func (t *Terminal) Readline() *Operation { + return NewOperation(t, t.cfg) +} + +// return rune(0) if meet EOF +func (t *Terminal) ReadRune() rune { + ch, ok := <-t.outchan + if !ok { + return rune(0) + } + return ch +} + +func (t *Terminal) IsReading() bool { + return atomic.LoadInt32(&t.isReading) == 1 +} + +func (t *Terminal) KickRead() { + select { + case t.kickChan <- struct{}{}: + default: + } +} + +func (t *Terminal) ioloop() { + t.wg.Add(1) + defer func() { + t.wg.Done() + close(t.outchan) + }() + + var ( + isEscape bool + isEscapeEx bool + expectNextChar bool + ) + + buf := bufio.NewReader(t.getStdin()) + for { + if !expectNextChar { + atomic.StoreInt32(&t.isReading, 0) + select { + case <-t.kickChan: + atomic.StoreInt32(&t.isReading, 1) + case <-t.stopChan: + return + } + } + expectNextChar = false + r, _, err := buf.ReadRune() + if err != nil { + if strings.Contains(err.Error(), "interrupted system call") { + expectNextChar = true + continue + } + break + } + + if isEscape { + isEscape = false + if r == CharEscapeEx { + expectNextChar = true + isEscapeEx = true + continue + } + r = escapeKey(r, buf) + } else if isEscapeEx { + isEscapeEx = false + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } + } + + expectNextChar = true + switch r { + case CharEsc: + if t.cfg.VimMode { + t.outchan <- r + break + } + isEscape = true + case CharInterrupt, CharEnter, CharCtrlJ, CharDelete: + expectNextChar = false + fallthrough + default: + t.outchan <- r + } + } + +} + +func (t *Terminal) Bell() { + fmt.Fprintf(t, "%c", CharBell) +} + +func (t *Terminal) Close() error { + if atomic.SwapInt32(&t.closed, 1) != 0 { + return nil + } + if closer, ok := t.cfg.Stdin.(io.Closer); ok { + closer.Close() + } + close(t.stopChan) + t.wg.Wait() + return t.ExitRawMode() +} + +func (t *Terminal) GetConfig() *Config { + t.m.Lock() + cfg := *t.cfg + t.m.Unlock() + return &cfg +} + +func (t *Terminal) getStdin() io.Reader { + t.m.Lock() + r := t.cfg.Stdin + t.m.Unlock() + return r +} + +func (t *Terminal) SetConfig(c *Config) error { + if err := c.Init(); err != nil { + return err + } + t.m.Lock() + t.cfg = c + t.m.Unlock() + return nil +} diff --git a/vendor/github.com/chzyer/readline/utils.go b/vendor/github.com/chzyer/readline/utils.go new file mode 100644 index 000000000..af4e00521 --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils.go @@ -0,0 +1,277 @@ +package readline + +import ( + "bufio" + "bytes" + "container/list" + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode" +) + +var ( + isWindows = false +) + +const ( + CharLineStart = 1 + CharBackward = 2 + CharInterrupt = 3 + CharDelete = 4 + CharLineEnd = 5 + CharForward = 6 + CharBell = 7 + CharCtrlH = 8 + CharTab = 9 + CharCtrlJ = 10 + CharKill = 11 + CharCtrlL = 12 + CharEnter = 13 + CharNext = 14 + CharPrev = 16 + CharBckSearch = 18 + CharFwdSearch = 19 + CharTranspose = 20 + CharCtrlU = 21 + CharCtrlW = 23 + CharCtrlY = 25 + CharCtrlZ = 26 + CharEsc = 27 + CharEscapeEx = 91 + CharBackspace = 127 +) + +const ( + MetaBackward rune = -iota - 1 + MetaForward + MetaDelete + MetaBackspace + MetaTranspose +) + +// WaitForResume need to call before current process got suspend. +// It will run a ticker until a long duration is occurs, +// which means this process is resumed. +func WaitForResume() chan struct{} { + ch := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + ticker := time.NewTicker(10 * time.Millisecond) + t := time.Now() + wg.Done() + for { + now := <-ticker.C + if now.Sub(t) > 100*time.Millisecond { + break + } + t = now + } + ticker.Stop() + ch <- struct{}{} + }() + wg.Wait() + return ch +} + +func Restore(fd int, state *State) error { + err := restoreTerm(fd, state) + if err != nil { + // errno 0 means everything is ok :) + if err.Error() == "errno 0" { + return nil + } else { + return err + } + } + return nil +} + +func IsPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// translate Esc[X +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { + case 'D': + r = CharBackward + case 'C': + r = CharForward + case 'A': + r = CharPrev + case 'B': + r = CharNext + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + case '~': + if key.attr == "3" { + r = CharDelete + } + default: + } + return r +} + +type escapeKeyPair struct { + attr string + typ rune +} + +func (e *escapeKeyPair) Get2() (int, int, bool) { + sp := strings.Split(e.attr, ";") + if len(sp) < 2 { + return -1, -1, false + } + s1, err := strconv.Atoi(sp[0]) + if err != nil { + return -1, -1, false + } + s2, err := strconv.Atoi(sp[1]) + if err != nil { + return -1, -1, false + } + return s1, s2, true +} + +func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { + p := escapeKeyPair{} + buf := bytes.NewBuffer(nil) + for { + if r == ';' { + } else if unicode.IsNumber(r) { + } else { + p.typ = r + break + } + buf.WriteRune(r) + r, _, _ = reader.ReadRune() + } + p.attr = buf.String() + return &p +} + +// translate EscX to Meta+X +func escapeKey(r rune, reader *bufio.Reader) rune { + switch r { + case 'b': + r = MetaBackward + case 'f': + r = MetaForward + case 'd': + r = MetaDelete + case CharTranspose: + r = MetaTranspose + case CharBackspace: + r = MetaBackspace + case 'O': + d, _, _ := reader.ReadRune() + switch d { + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + default: + reader.UnreadRune() + } + case CharEsc: + + } + return r +} + +func SplitByLine(start, screenWidth int, rs []rune) []string { + var ret []string + buf := bytes.NewBuffer(nil) + currentWidth := start + for _, r := range rs { + w := runes.Width(r) + currentWidth += w + buf.WriteRune(r) + if currentWidth >= screenWidth { + ret = append(ret, buf.String()) + buf.Reset() + currentWidth = 0 + } + } + ret = append(ret, buf.String()) + return ret +} + +// calculate how many lines for N character +func LineCount(screenWidth, w int) int { + r := w / screenWidth + if w%screenWidth != 0 { + r++ + } + return r +} + +func IsWordBreak(i rune) bool { + switch { + case i >= 'a' && i <= 'z': + case i >= 'A' && i <= 'Z': + case i >= '0' && i <= '9': + default: + return true + } + return false +} + +func GetInt(s []string, def int) int { + if len(s) == 0 { + return def + } + c, err := strconv.Atoi(s[0]) + if err != nil { + return def + } + return c +} + +type RawMode struct { + state *State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = MakeRaw(GetStdin()) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + return Restore(GetStdin(), r.state) +} + +// ----------------------------------------------------------------------------- + +func sleep(n int) { + Debug(n) + time.Sleep(2000 * time.Millisecond) +} + +// print a linked list to Debug() +func debugList(l *list.List) { + idx := 0 + for e := l.Front(); e != nil; e = e.Next() { + Debug(idx, fmt.Sprintf("%+v", e.Value)) + idx++ + } +} + +// append log info to another file +func Debug(o ...interface{}) { + f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fmt.Fprintln(f, o...) + f.Close() +} diff --git a/vendor/github.com/chzyer/readline/utils_unix.go b/vendor/github.com/chzyer/readline/utils_unix.go new file mode 100644 index 000000000..f88dac97b --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_unix.go @@ -0,0 +1,83 @@ +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +package readline + +import ( + "io" + "os" + "os/signal" + "sync" + "syscall" +) + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// SuspendMe use to send suspend signal to myself, when we in the raw mode. +// For OSX it need to send to parent's pid +// For Linux it need to send to myself +func SuspendMe() { + p, _ := os.FindProcess(os.Getppid()) + p.Signal(syscall.SIGTSTP) + p, _ = os.FindProcess(os.Getpid()) + p.Signal(syscall.SIGTSTP) +} + +// get width of the terminal +func getWidth(stdoutFd int) int { + cols, _, err := GetSize(stdoutFd) + if err != nil { + return -1 + } + return cols +} + +func GetScreenWidth() int { + w := getWidth(syscall.Stdout) + if w < 0 { + w = getWidth(syscall.Stderr) + } + return w +} + +// ClearScreen clears the console screen +func ClearScreen(w io.Writer) (int, error) { + return w.Write([]byte("\033[H")) +} + +func DefaultIsTerminal() bool { + return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr)) +} + +func GetStdin() int { + return syscall.Stdin +} + +// ----------------------------------------------------------------------------- + +var ( + widthChange sync.Once + widthChangeCallback func() +) + +func DefaultOnWidthChanged(f func()) { + widthChangeCallback = f + widthChange.Do(func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + + go func() { + for { + _, ok := <-ch + if !ok { + break + } + widthChangeCallback() + } + }() + }) +} diff --git a/vendor/github.com/chzyer/readline/utils_windows.go b/vendor/github.com/chzyer/readline/utils_windows.go new file mode 100644 index 000000000..5bfa55dcc --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package readline + +import ( + "io" + "syscall" +) + +func SuspendMe() { +} + +func GetStdin() int { + return int(syscall.Stdin) +} + +func init() { + isWindows = true +} + +// get width of the terminal +func GetScreenWidth() int { + info, _ := GetConsoleScreenBufferInfo() + if info == nil { + return -1 + } + return int(info.dwSize.x) +} + +// ClearScreen clears the console screen +func ClearScreen(_ io.Writer) error { + return SetConsoleCursorPosition(&_COORD{0, 0}) +} + +func DefaultIsTerminal() bool { + return true +} + +func DefaultOnWidthChanged(func()) { + +} diff --git a/vendor/github.com/chzyer/readline/vim.go b/vendor/github.com/chzyer/readline/vim.go new file mode 100644 index 000000000..bedf2c1a6 --- /dev/null +++ b/vendor/github.com/chzyer/readline/vim.go @@ -0,0 +1,176 @@ +package readline + +const ( + VIM_NORMAL = iota + VIM_INSERT + VIM_VISUAL +) + +type opVim struct { + cfg *Config + op *Operation + vimMode int +} + +func newVimMode(op *Operation) *opVim { + ov := &opVim{ + cfg: op.cfg, + op: op, + } + ov.SetVimMode(ov.cfg.VimMode) + return ov +} + +func (o *opVim) SetVimMode(on bool) { + if o.cfg.VimMode && !on { // turn off + o.ExitVimMode() + } + o.cfg.VimMode = on + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) IsEnableVimMode() bool { + return o.cfg.VimMode +} + +func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'h': + t = CharBackward + case 'j': + t = CharNext + case 'k': + t = CharPrev + case 'l': + t = CharForward + case '0', '^': + rb.MoveToLineStart() + case '$': + rb.MoveToLineEnd() + case 'x': + rb.Delete() + if rb.IsCursorInEnd() { + rb.MoveBackward() + } + case 'r': + rb.Replace(readNext()) + case 'd': + next := readNext() + switch next { + case 'd': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + case 'p': + rb.Yank() + case 'b', 'B': + rb.MoveToPrevWord() + case 'w', 'W': + rb.MoveToNextWord() + case 'e', 'E': + rb.MoveToEndWord() + case 'f', 'F', 't', 'T': + next := readNext() + prevChar := r == 't' || r == 'T' + reverse := r == 'F' || r == 'T' + switch next { + case CharEsc: + default: + rb.MoveTo(next, prevChar, reverse) + } + default: + return r, false + } + return t, true +} + +func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'i': + case 'I': + rb.MoveToLineStart() + case 'a': + rb.MoveForward() + case 'A': + rb.MoveToLineEnd() + case 's': + rb.Delete() + case 'S': + rb.Erase() + case 'c': + next := readNext() + switch next { + case 'c': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + default: + return r, false + } + + o.EnterVimInsertMode() + return +} + +func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) { + switch r { + case CharEnter, CharInterrupt: + o.ExitVimMode() + return r + } + + if r, handled := o.handleVimNormalMovement(r, readNext); handled { + return r + } + + if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled { + return r + } + + // invalid operation + o.op.t.Bell() + return 0 +} + +func (o *opVim) EnterVimInsertMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimInsertMode() { + o.vimMode = VIM_NORMAL +} + +func (o *opVim) HandleVim(r rune, readNext func() rune) rune { + if o.vimMode == VIM_NORMAL { + return o.HandleVimNormal(r, readNext) + } + if r == CharEsc { + o.ExitVimInsertMode() + return 0 + } + + switch o.vimMode { + case VIM_INSERT: + return r + case VIM_VISUAL: + } + return r +} diff --git a/vendor/github.com/chzyer/readline/windows_api.go b/vendor/github.com/chzyer/readline/windows_api.go new file mode 100644 index 000000000..63f4f7b78 --- /dev/null +++ b/vendor/github.com/chzyer/readline/windows_api.go @@ -0,0 +1,152 @@ +// +build windows + +package readline + +import ( + "reflect" + "syscall" + "unsafe" +) + +var ( + kernel = NewKernel() + stdout = uintptr(syscall.Stdout) + stdin = uintptr(syscall.Stdin) +) + +type Kernel struct { + SetConsoleCursorPosition, + SetConsoleTextAttribute, + FillConsoleOutputCharacterW, + FillConsoleOutputAttribute, + ReadConsoleInputW, + GetConsoleScreenBufferInfo, + GetConsoleCursorInfo, + GetStdHandle CallFunc +} + +type short int16 +type word uint16 +type dword uint32 +type wchar uint16 + +type _COORD struct { + x short + y short +} + +func (c *_COORD) ptr() uintptr { + return uintptr(*(*int32)(unsafe.Pointer(c))) +} + +const ( + EVENT_KEY = 0x0001 + EVENT_MOUSE = 0x0002 + EVENT_WINDOW_BUFFER_SIZE = 0x0004 + EVENT_MENU = 0x0008 + EVENT_FOCUS = 0x0010 +) + +type _KEY_EVENT_RECORD struct { + bKeyDown int32 + wRepeatCount word + wVirtualKeyCode word + wVirtualScanCode word + unicodeChar wchar + dwControlKeyState dword +} + +// KEY_EVENT_RECORD KeyEvent; +// MOUSE_EVENT_RECORD MouseEvent; +// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; +// MENU_EVENT_RECORD MenuEvent; +// FOCUS_EVENT_RECORD FocusEvent; +type _INPUT_RECORD struct { + EventType word + Padding uint16 + Event [16]byte +} + +type _CONSOLE_SCREEN_BUFFER_INFO struct { + dwSize _COORD + dwCursorPosition _COORD + wAttributes word + srWindow _SMALL_RECT + dwMaximumWindowSize _COORD +} + +type _SMALL_RECT struct { + left short + top short + right short + bottom short +} + +type _CONSOLE_CURSOR_INFO struct { + dwSize dword + bVisible bool +} + +type CallFunc func(u ...uintptr) error + +func NewKernel() *Kernel { + k := &Kernel{} + kernel32 := syscall.NewLazyDLL("kernel32.dll") + v := reflect.ValueOf(k).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + name := t.Field(i).Name + f := kernel32.NewProc(name) + v.Field(i).Set(reflect.ValueOf(k.Wrap(f))) + } + return k +} + +func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc { + return func(args ...uintptr) error { + var r0 uintptr + var e1 syscall.Errno + size := uintptr(len(args)) + if len(args) <= 3 { + buf := make([]uintptr, 3) + copy(buf, args) + r0, _, e1 = syscall.Syscall(p.Addr(), size, + buf[0], buf[1], buf[2]) + } else { + buf := make([]uintptr, 6) + copy(buf, args) + r0, _, e1 = syscall.Syscall6(p.Addr(), size, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + ) + } + + if int(r0) == 0 { + if e1 != 0 { + return error(e1) + } else { + return syscall.EINVAL + } + } + return nil + } + +} + +func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { + t := new(_CONSOLE_SCREEN_BUFFER_INFO) + err := kernel.GetConsoleScreenBufferInfo( + stdout, + uintptr(unsafe.Pointer(t)), + ) + return t, err +} + +func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { + t := new(_CONSOLE_CURSOR_INFO) + err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) + return t, err +} + +func SetConsoleCursorPosition(c *_COORD) error { + return kernel.SetConsoleCursorPosition(stdout, c.ptr()) +} diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile index 7b2cfcf81..e70dd161d 100644 --- a/vendor/github.com/containers/buildah/Makefile +++ b/vendor/github.com/containers/buildah/Makefile @@ -34,7 +34,7 @@ RUNC_COMMIT := v1.0.0-rc8 LIBSECCOMP_COMMIT := release-2.3 EXTRA_LDFLAGS ?= -LDFLAGS := -ldflags '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(EXTRA_LDFLAGS)' +BUILDAH_LDFLAGS := -ldflags '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(EXTRA_LDFLAGS)' SOURCES=*.go imagebuildah/*.go bind/*.go chroot/*.go cmd/buildah/*.go copier/*.go docker/*.go pkg/blobcache/*.go pkg/cli/*.go pkg/parse/*.go util/*.go LINTFLAGS ?= @@ -56,7 +56,7 @@ static: .PHONY: bin/buildah bin/buildah: $(SOURCES) - $(GO_BUILD) $(LDFLAGS) -o $@ $(BUILDFLAGS) ./cmd/buildah + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./cmd/buildah .PHONY: buildah buildah: bin/buildah @@ -67,11 +67,11 @@ cross: bin/buildah.darwin.amd64 bin/buildah.linux.386 bin/buildah.linux.amd64 bi .PHONY: bin/buildah.% bin/buildah.%: mkdir -p ./bin - GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah + GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah .PHONY: bin/imgtype bin/imgtype: *.go docker/*.go util/*.go tests/imgtype/imgtype.go - $(GO_BUILD) $(LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go .PHONY: clean clean: diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go index 80ee0d912..6cfd6a09f 100644 --- a/vendor/github.com/containers/buildah/add.go +++ b/vendor/github.com/containers/buildah/add.go @@ -71,7 +71,7 @@ func sourceIsRemote(source string) bool { } // getURL writes a tar archive containing the named content -func getURL(src, mountpoint, renameTarget string, writer io.Writer) error { +func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer) error { url, err := url.Parse(src) if err != nil { return err @@ -122,10 +122,18 @@ func getURL(src, mountpoint, renameTarget string, writer io.Writer) error { // Write the output archive. Set permissions for compatibility. tw := tar.NewWriter(writer) defer tw.Close() + uid := 0 + gid := 0 + if chown != nil { + uid = chown.UID + gid = chown.GID + } hdr := tar.Header{ Typeflag: tar.TypeReg, Name: name, Size: size, + Uid: uid, + Gid: gid, Mode: 0600, ModTime: date, } @@ -323,7 +331,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption pipeReader, pipeWriter := io.Pipe() wg.Add(1) go func() { - getErr = getURL(src, mountPoint, renameTarget, pipeWriter) + getErr = getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter) pipeWriter.Close() wg.Done() }() @@ -341,9 +349,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption putOptions := copier.PutOptions{ UIDMap: destUIDMap, GIDMap: destGIDMap, - ChownDirs: chownDirs, + ChownDirs: nil, ChmodDirs: nil, - ChownFiles: chownFiles, + ChownFiles: nil, ChmodFiles: nil, } putErr = copier.Put(mountPoint, extractDirectory, putOptions, io.TeeReader(pipeReader, hasher)) diff --git a/vendor/github.com/containers/buildah/btrfs_installed_tag.sh b/vendor/github.com/containers/buildah/btrfs_installed_tag.sh index c4d99f377..f2f2b33c8 100644 --- a/vendor/github.com/containers/buildah/btrfs_installed_tag.sh +++ b/vendor/github.com/containers/buildah/btrfs_installed_tag.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -cc -E - > /dev/null 2> /dev/null << EOF +${CPP:-${CC:-cc} -E} ${CPPFLAGS} - > /dev/null 2> /dev/null << EOF #include EOF if test $? -ne 0 ; then diff --git a/vendor/github.com/containers/buildah/btrfs_tag.sh b/vendor/github.com/containers/buildah/btrfs_tag.sh index 59cb969ad..ea753d4d0 100644 --- a/vendor/github.com/containers/buildah/btrfs_tag.sh +++ b/vendor/github.com/containers/buildah/btrfs_tag.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -cc -E - > /dev/null 2> /dev/null << EOF +${CPP:-${CC:-cc} -E} ${CPPFLAGS} - > /dev/null 2> /dev/null << EOF #include EOF if test $? -ne 0 ; then diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 86695508c..96e8619a8 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -28,7 +28,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.17.0" + Version = "1.18.0-dev" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/copier/copier.go b/vendor/github.com/containers/buildah/copier/copier.go index 9ebc8e2a3..84b636202 100644 --- a/vendor/github.com/containers/buildah/copier/copier.go +++ b/vendor/github.com/containers/buildah/copier/copier.go @@ -7,7 +7,9 @@ import ( "fmt" "io" "io/ioutil" + "net" "os" + "os/user" "path/filepath" "strconv" "strings" @@ -35,6 +37,14 @@ const ( func init() { reexec.Register(copierCommand, copierMain) + // Attempt a user and host lookup to force libc (glibc, and possibly others that use dynamic + // modules to handle looking up user and host information) to load modules that match the libc + // our binary is currently using. Hopefully they're loaded on first use, so that they won't + // need to be loaded after we've chrooted into the rootfs, which could include modules that + // don't match our libc and which can't be loaded, or modules which we don't want to execute + // because we don't trust their code. + _, _ = user.Lookup("buildah") + _, _ = net.LookupHost("localhost") } // isArchivePath returns true if the specified path can be read like a (possibly diff --git a/vendor/github.com/containers/buildah/copier/xattrs.go b/vendor/github.com/containers/buildah/copier/xattrs.go index 71769989c..c757adcc8 100644 --- a/vendor/github.com/containers/buildah/copier/xattrs.go +++ b/vendor/github.com/containers/buildah/copier/xattrs.go @@ -45,6 +45,11 @@ func Lgetxattrs(path string) (map[string]string, error) { listSize *= 2 continue } + if (unwrapError(err) == syscall.ENOTSUP) || (unwrapError(err) == syscall.ENOSYS) { + // treat these errors listing xattrs as equivalent to "no xattrs" + list = list[:0] + break + } return nil, errors.Wrapf(err, "error listing extended attributes of %q", path) } list = list[:size] diff --git a/vendor/github.com/containers/buildah/go.mod b/vendor/github.com/containers/buildah/go.mod index 6fe683f4b..2bc71f948 100644 --- a/vendor/github.com/containers/buildah/go.mod +++ b/vendor/github.com/containers/buildah/go.mod @@ -5,10 +5,10 @@ go 1.12 require ( github.com/containerd/containerd v1.4.1 // indirect github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 - github.com/containers/common v0.26.2 - github.com/containers/image/v5 v5.7.0 + github.com/containers/common v0.26.3 + github.com/containers/image/v5 v5.8.0 github.com/containers/ocicrypt v1.0.3 - github.com/containers/storage v1.23.7 + github.com/containers/storage v1.23.9 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20201020191947-73dc6a680cdd+incompatible // indirect github.com/docker/go-units v0.4.0 @@ -17,6 +17,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/hashicorp/go-multierror v1.1.0 github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mattn/go-shellwords v1.0.10 github.com/moby/sys/mount v0.1.1 // indirect github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2 // indirect diff --git a/vendor/github.com/containers/buildah/go.sum b/vendor/github.com/containers/buildah/go.sum index 65268af4e..1952ace1a 100644 --- a/vendor/github.com/containers/buildah/go.sum +++ b/vendor/github.com/containers/buildah/go.sum @@ -45,6 +45,12 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb 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/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= 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= @@ -65,17 +71,20 @@ github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDG github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784 h1:rqUVLD8I859xRgUx/WMC3v7QAFqbLKZbs+0kqYboRJc= github.com/containernetworking/cni v0.7.2-0.20190904153231-83439463f784/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containers/common v0.26.2 h1:TysMCBpzq3gDFD9GzM0TKTGjtq/9HySWevKtlrvVGRU= -github.com/containers/common v0.26.2/go.mod h1:igUeog5hx8rYhJk67rG6rGAh3zEcf0Uxuzm9KpXzo2E= +github.com/containers/common v0.26.3 h1:5Kb5fMmJ7/xMiJ+iEbPA+5pQpl/FGxCgJex4nml4Slo= +github.com/containers/common v0.26.3/go.mod h1:hJWZIlrl5MsE2ELNRa+MPp6I1kPbXHauuj0Ym4BsLG4= github.com/containers/image/v5 v5.7.0 h1:fiTC8/Xbr+zEP6njGTZtPW/3UD7MC93nC9DbUoWdxkA= github.com/containers/image/v5 v5.7.0/go.mod h1:8aOy+YaItukxghRORkvhq5ibWttHErzDLy6egrKfKos= +github.com/containers/image/v5 v5.8.0 h1:B3FGHi0bdGXgg698kBIGOlHCXN5n+scJr6/5354GOPU= +github.com/containers/image/v5 v5.8.0/go.mod h1:jKxdRtyIDumVa56hdsZvV+gwx4zB50hRou6pIuCWLkg= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.3 h1:vYgl+RZ9Q3DPMuTfxmN+qp0X2Bj52uuY2vnt6GzVe1c= github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBDb5f8P5CAxJY= -github.com/containers/storage v1.23.7 h1:43ImvG/npvQSZXRjaudVvKISIuZSfI6qvtSNQQSGO/A= github.com/containers/storage v1.23.7/go.mod h1:cUT2zHjtx+WlVri30obWmM2gpqpi8jfPsmIzP1TVpEI= +github.com/containers/storage v1.23.9 h1:qbgnTp76pLSyW3vYwY5GH4vk5cHYVXFJ+CsUEBp9TMw= +github.com/containers/storage v1.23.9/go.mod h1:3b2ktpB6pw53SEeIoFfO0sQfP9+IoJJKPq5iJk74gxE= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -214,25 +223,37 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 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/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.1 h1:bPb7nMRdOZYDrpPMTA3EInUQrdgoBinqUuSwlGdKDdE= github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= +github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/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= 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/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= +github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= @@ -479,6 +500,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 76dfeaf54..a97a403b3 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -216,20 +216,19 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt } data = resp.Body } else { - // If the Dockerfile isn't found try prepending the - // context directory to it. dinfo, err := os.Stat(dfile) - if os.IsNotExist(err) { - // If they are "/workDir/Dockerfile" and "/workDir" - // so don't joint it + if err != nil { + // If the Dockerfile isn't available, try again with + // context directory prepended (if not prepended yet). if !strings.HasPrefix(dfile, options.ContextDirectory) { dfile = filepath.Join(options.ContextDirectory, dfile) + dinfo, err = os.Stat(dfile) } - dinfo, err = os.Stat(dfile) - if err != nil { - return "", nil, err - } } + if err != nil { + return "", nil, err + } + // If given a directory, add '/Dockerfile' to it. if dinfo.Mode().IsDir() { dfile = filepath.Join(dfile, "Dockerfile") diff --git a/vendor/github.com/containers/buildah/imagebuildah/executor.go b/vendor/github.com/containers/buildah/imagebuildah/executor.go index 77c224ad8..8c96b4e67 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/executor.go @@ -17,6 +17,7 @@ import ( "github.com/containers/buildah/util" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -111,6 +112,15 @@ type Executor struct { stagesSemaphore *semaphore.Weighted jobs int logRusage bool + imageInfoLock sync.Mutex + imageInfoCache map[string]imageTypeAndHistoryAndDiffIDs +} + +type imageTypeAndHistoryAndDiffIDs struct { + manifestType string + history []v1.History + diffIDs []digest.Digest + err error } // NewExecutor creates a new instance of the imagebuilder.Executor interface. @@ -215,6 +225,7 @@ func NewExecutor(store storage.Store, options BuildOptions, mainNode *parser.Nod terminatedStage: make(map[string]struct{}), jobs: jobs, logRusage: options.LogRusage, + imageInfoCache: make(map[string]imageTypeAndHistoryAndDiffIDs), } if exec.err == nil { exec.err = os.Stderr @@ -335,22 +346,43 @@ func (b *Executor) waitForStage(ctx context.Context, name string, stages imagebu } } -// getImageHistoryAndDiffIDs returns the history and diff IDs list of imageID. -func (b *Executor) getImageHistoryAndDiffIDs(ctx context.Context, imageID string) ([]v1.History, []digest.Digest, error) { +// getImageTypeAndHistoryAndDiffIDs returns the manifest type, history, and diff IDs list of imageID. +func (b *Executor) getImageTypeAndHistoryAndDiffIDs(ctx context.Context, imageID string) (string, []v1.History, []digest.Digest, error) { + b.imageInfoLock.Lock() + imageInfo, ok := b.imageInfoCache[imageID] + b.imageInfoLock.Unlock() + if ok { + return imageInfo.manifestType, imageInfo.history, imageInfo.diffIDs, imageInfo.err + } imageRef, err := is.Transport.ParseStoreReference(b.store, "@"+imageID) if err != nil { - return nil, nil, errors.Wrapf(err, "error getting image reference %q", imageID) + return "", nil, nil, errors.Wrapf(err, "error getting image reference %q", imageID) } ref, err := imageRef.NewImage(ctx, nil) if err != nil { - return nil, nil, errors.Wrapf(err, "error creating new image from reference to image %q", imageID) + return "", nil, nil, errors.Wrapf(err, "error creating new image from reference to image %q", imageID) } defer ref.Close() oci, err := ref.OCIConfig(ctx) if err != nil { - return nil, nil, errors.Wrapf(err, "error getting possibly-converted OCI config of image %q", imageID) + return "", nil, nil, errors.Wrapf(err, "error getting possibly-converted OCI config of image %q", imageID) + } + manifestBytes, manifestFormat, err := ref.Manifest(ctx) + if err != nil { + return "", nil, nil, errors.Wrapf(err, "error getting manifest of image %q", imageID) + } + if manifestFormat == "" && len(manifestBytes) > 0 { + manifestFormat = manifest.GuessMIMEType(manifestBytes) + } + b.imageInfoLock.Lock() + b.imageInfoCache[imageID] = imageTypeAndHistoryAndDiffIDs{ + manifestType: manifestFormat, + history: oci.History, + diffIDs: oci.RootFS.DiffIDs, + err: nil, } - return oci.History, oci.RootFS.DiffIDs, nil + b.imageInfoLock.Unlock() + return manifestFormat, oci.History, oci.RootFS.DiffIDs, nil } func (b *Executor) buildStage(ctx context.Context, cleanupStages map[int]*StageExecutor, stages imagebuilder.Stages, stageIndex int) (imageID string, ref reference.Canonical, err error) { diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go index e157bb1c8..6c058e226 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go +++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go @@ -1110,7 +1110,7 @@ func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *p var baseHistory []v1.History var baseDiffIDs []digest.Digest if s.builder.FromImageID != "" { - baseHistory, baseDiffIDs, err = s.executor.getImageHistoryAndDiffIDs(ctx, s.builder.FromImageID) + _, baseHistory, baseDiffIDs, err = s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, s.builder.FromImageID) if err != nil { return "", errors.Wrapf(err, "error getting history of base image %q", s.builder.FromImageID) } @@ -1142,10 +1142,15 @@ func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *p } // Next we double check that the history of this image is equivalent to the previous // lines in the Dockerfile up till the point we are at in the build. - history, diffIDs, err := s.executor.getImageHistoryAndDiffIDs(ctx, image.ID) + manifestType, history, diffIDs, err := s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, image.ID) if err != nil { return "", errors.Wrapf(err, "error getting history of %q", image.ID) } + // If this candidate isn't of the type that we're building, then it may have lost + // some format-specific information that a building-without-cache run wouldn't lose. + if manifestType != s.executor.outputFormat { + continue + } // children + currNode is the point of the Dockerfile we are currently at. if s.historyAndDiffIDsMatch(baseHistory, baseDiffIDs, currNode, history, diffIDs, addedContentDigest, buildAddsLayer) { return image.ID, nil @@ -1276,5 +1281,5 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer } func (s *StageExecutor) EnsureContainerPath(path string) error { - return copier.Mkdir(s.mountPoint, path, copier.MkdirOptions{}) + return copier.Mkdir(s.mountPoint, filepath.Join(s.mountPoint, path), copier.MkdirOptions{}) } diff --git a/vendor/github.com/containers/buildah/libdm_tag.sh b/vendor/github.com/containers/buildah/libdm_tag.sh index d3668aab1..815b5d914 100644 --- a/vendor/github.com/containers/buildah/libdm_tag.sh +++ b/vendor/github.com/containers/buildah/libdm_tag.sh @@ -2,7 +2,7 @@ tmpdir="$PWD/tmp.$RANDOM" mkdir -p "$tmpdir" trap 'rm -fr "$tmpdir"' EXIT -cc -o "$tmpdir"/libdm_tag -ldevmapper -x c - > /dev/null 2> /dev/null << EOF +${CC:-cc} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS} -o "$tmpdir"/libdm_tag -x c - -ldevmapper > /dev/null 2> /dev/null << EOF #include int main() { struct dm_task *task; diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index 4f4b1564b..c1abb1cdb 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "math/rand" + "os" "strings" - "time" "github.com/containers/buildah/util" + "github.com/containers/image/v5/docker" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/pkg/shortnames" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -103,145 +104,168 @@ func newContainerIDMappingOptions(idmapOptions *IDMappingOptions) storage.IDMapp return options } -func resolveImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { - type failure struct { - resolvedImageName string - err error - } - candidates, transport, searchRegistriesWereUsedButEmpty, err := util.ResolveName(options.FromImage, options.Registry, systemContext, store) +func resolveLocalImage(systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { + candidates, _, _, err := util.ResolveName(options.FromImage, options.Registry, systemContext, store) if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", options.FromImage) + return nil, "", nil, errors.Wrapf(err, "error resolving local image %q", options.FromImage) } - - failures := []failure{} for _, image := range candidates { - if transport == "" { - img, err := store.Image(image) - if err != nil { - logrus.Debugf("error looking up known-local image %q: %v", image, err) - failures = append(failures, failure{resolvedImageName: image, err: err}) + img, err := store.Image(image) + if err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { continue } - ref, err := is.Transport.ParseStoreReference(store, img.ID) - if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) - } - return ref, transport, img, nil + return nil, "", nil, err } - - trans := transport - if transport != util.DefaultTransport { - trans = trans + ":" - } - srcRef, err := alltransports.ParseImageName(trans + image) + ref, err := is.Transport.ParseStoreReference(store, img.ID) if err != nil { - logrus.Debugf("error parsing image name %q: %v", trans+image, err) - failures = append(failures, failure{ - resolvedImageName: image, - err: errors.Wrapf(err, "error parsing attempted image name %q", trans+image), - }) - continue + return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) } + return ref, ref.Transport().Name(), img, nil + } + + return nil, "", nil, nil +} + +// getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment +// variable. If it's "on", return `nil` to use the defaults from +// containers/image and the registries.conf files on the system. If it's +// "off", empty or unset, return types.ShortNameModeDisabled to turn off +// short-name aliasing by default. +// +// TODO: remove this function once we want to default to short-name aliasing. +func getShortNameMode() *types.ShortNameMode { + env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING") + if strings.ToLower(env) == "on" { + return nil // default to whatever registries.conf and c/image decide + } + mode := types.ShortNameModeDisabled + return &mode +} - if options.PullPolicy == PullAlways { +func resolveImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options BuilderOptions) (types.ImageReference, string, *storage.Image, error) { + if systemContext == nil { + systemContext = &types.SystemContext{} + } + systemContext.ShortNameMode = getShortNameMode() + + fromImage := options.FromImage + // If the image name includes a transport we can use it as it. Special + // treatment for docker references which are subject to pull policies + // that we're handling below. + srcRef, err := alltransports.ParseImageName(options.FromImage) + if err == nil { + if srcRef.Transport().Name() == docker.Transport.Name() { + fromImage = srcRef.DockerReference().String() + } else { pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) - if err != nil { - logrus.Debugf("unable to pull and read image %q: %v", image, err) - failures = append(failures, failure{resolvedImageName: image, err: err}) - continue - } - return pulledReference, transport, pulledImg, nil + return pulledReference, srcRef.Transport().Name(), pulledImg, err } + } - destImage, err := localImageNameForReference(ctx, store, srcRef) - if err != nil { - return nil, "", nil, errors.Wrapf(err, "error computing local image name for %q", transports.ImageName(srcRef)) + localImageRef, _, localImage, err := resolveLocalImage(systemContext, store, options) + if err != nil { + return nil, "", nil, err + } + + // If we could resolve the image locally, check if it was referenced by + // ID. In that case, we don't need to bother any further and can + // prevent prompting the user. + if localImage != nil && strings.HasPrefix(localImage.ID, options.FromImage) { + return localImageRef, localImageRef.Transport().Name(), localImage, nil + } + + if options.PullPolicy == PullNever || options.PullPolicy == PullIfMissing { + if localImage != nil { + return localImageRef, localImageRef.Transport().Name(), localImage, nil } - if destImage == "" { - return nil, "", nil, errors.Errorf("error computing local image name for %q", transports.ImageName(srcRef)) + if options.PullPolicy == PullNever { + return nil, "", nil, errors.Errorf("pull policy is %q but %q could not be found locally", "never", options.FromImage) } - ref, err := is.Transport.ParseStoreReference(store, destImage) + } + + resolved, err := shortnames.Resolve(systemContext, fromImage) + if err != nil { + return nil, "", nil, err + } + + // Print the image-resolution description unless we're looking for a + // new image and already found a local image. In many cases, the + // description will be more confusing than helpful (e.g., `buildah from + // localImage`). + if desc := resolved.Description(); len(desc) > 0 { + logrus.Debug(desc) + if !(options.PullPolicy == PullIfNewer && localImage != nil) { + if options.ReportWriter != nil { + if _, err := options.ReportWriter.Write([]byte(desc + "\n")); err != nil { + return nil, "", nil, err + } + } + } + } + + var pullErrors []error + for _, pullCandidate := range resolved.PullCandidates { + ref, err := docker.NewReference(pullCandidate.Value) if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", destImage) + return nil, "", nil, err } - if options.PullPolicy == PullIfNewer { - img, err := is.Transport.GetStoreImage(store, ref) - if err == nil { - // Let's see if this image is on the repository and if it's there - // then note it's Created date. - var repoImageCreated time.Time - repoImageFound := false - repoImage, err := srcRef.NewImage(ctx, systemContext) - if err == nil { - inspect, err := repoImage.Inspect(ctx) - if err == nil { - repoImageFound = true - repoImageCreated = *inspect.Created - } - repoImage.Close() - } - if !repoImageFound || repoImageCreated == img.Created { - // The image is only local or the same date is on the - // local and repo versions of the image, no need to pull. - return ref, transport, img, nil - } + // We're tasked to pull a "newer" image. If there's no local + // image, we have no base for comparison, so we'll pull the + // first available image. + // + // If there's a local image, the `pullCandidate` is considered + // to be newer if its time stamp differs from the local one. + // Otherwise, we don't pull and skip it. + if options.PullPolicy == PullIfNewer && localImage != nil { + remoteImage, err := ref.NewImage(ctx, systemContext) + if err != nil { + logrus.Debugf("unable to remote-inspect image %q: %v", pullCandidate.Value.String(), err) + pullErrors = append(pullErrors, err) + continue } - } else { - // Get the image from the store if present for PullNever and PullIfMissing - img, err := is.Transport.GetStoreImage(store, ref) - if err == nil { - return ref, transport, img, nil + defer remoteImage.Close() + + remoteData, err := remoteImage.Inspect(ctx) + if err != nil { + logrus.Debugf("unable to remote-inspect image %q: %v", pullCandidate.Value.String(), err) + pullErrors = append(pullErrors, err) + continue } - if errors.Cause(err) == storage.ErrImageUnknown && options.PullPolicy == PullNever { - logrus.Debugf("no such image %q: %v", transports.ImageName(ref), err) - failures = append(failures, failure{ - resolvedImageName: image, - err: errors.Errorf("no such image %q", transports.ImageName(ref)), - }) + + // FIXME: we should compare image digests not time stamps. + // Comparing time stamps is flawed. Be aware that fixing + // it may entail non-trivial changes to the tests. Please + // refer to https://github.com/containers/buildah/issues/2779 + // for more. + if localImage.Created.Equal(*remoteData.Created) { continue } } - pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) + pulledImg, pulledReference, err := pullAndFindImage(ctx, store, ref, options, systemContext) if err != nil { - logrus.Debugf("unable to pull and read image %q: %v", image, err) - failures = append(failures, failure{resolvedImageName: image, err: err}) + logrus.Debugf("unable to pull and read image %q: %v", pullCandidate.Value.String(), err) + pullErrors = append(pullErrors, err) continue } - return pulledReference, transport, pulledImg, nil - } - - if len(failures) != len(candidates) { - return nil, "", nil, errors.Errorf("internal error: %d candidates (%#v) vs. %d failures (%#v)", len(candidates), candidates, len(failures), failures) - } - registriesConfPath := sysregistriesv2.ConfigPath(systemContext) - switch len(failures) { - case 0: - if searchRegistriesWereUsedButEmpty { - return nil, "", nil, errors.Errorf("image name %q is a short name and no search registries are defined in %s.", options.FromImage, registriesConfPath) + // Make sure to record the short-name alias if necessary. + if err = pullCandidate.Record(); err != nil { + return nil, "", nil, err } - return nil, "", nil, errors.Errorf("internal error: no pull candidates were available for %q for an unknown reason", options.FromImage) - case 1: - err := failures[0].err - if failures[0].resolvedImageName != options.FromImage { - err = errors.Wrapf(err, "while pulling %q as %q", options.FromImage, failures[0].resolvedImageName) - } - if searchRegistriesWereUsedButEmpty { - err = errors.Wrapf(err, "(image name %q is a short name and no search registries are defined in %s)", options.FromImage, registriesConfPath) - } - return nil, "", nil, err + return pulledReference, "", pulledImg, nil + } - default: - // NOTE: a multi-line error string: - e := fmt.Sprintf("The following failures happened while trying to pull image specified by %q based on search registries in %s:", options.FromImage, registriesConfPath) - for _, f := range failures { - e = e + fmt.Sprintf("\n* %q: %s", f.resolvedImageName, f.err.Error()) - } - return nil, "", nil, errors.New(e) + // If we were looking for a newer image but could not find one, return + // the local image if present. + if options.PullPolicy == PullIfNewer && localImage != nil { + return localImageRef, localImageRef.Transport().Name(), localImage, nil } + + return nil, "", nil, resolved.FormatPullErrors(pullErrors) } func containerNameExist(name string, containers []storage.Container) bool { diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index af7453c91..62a328de0 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -59,7 +59,6 @@ type BudResults struct { Creds string DisableCompression bool DisableContentTrust bool - DecryptionKeys []string File []string Format string Iidfile string @@ -90,38 +89,39 @@ type BudResults struct { // FromAndBugResults represents the results for common flags // in bud and from type FromAndBudResults struct { - AddHost []string - BlobCache string - CapAdd []string - CapDrop []string - CgroupParent string - CPUPeriod uint64 - CPUQuota int64 - CPUSetCPUs string - CPUSetMems string - CPUShares uint64 - Devices []string - DNSSearch []string - DNSServers []string - DNSOptions []string - HTTPProxy bool - Isolation string - Memory string - MemorySwap string - OverrideArch string - OverrideOS string - SecurityOpt []string - ShmSize string - Ulimit []string - Volumes []string + AddHost []string + BlobCache string + CapAdd []string + CapDrop []string + CgroupParent string + CPUPeriod uint64 + CPUQuota int64 + CPUSetCPUs string + CPUSetMems string + CPUShares uint64 + DecryptionKeys []string + Devices []string + DNSSearch []string + DNSServers []string + DNSOptions []string + HTTPProxy bool + Isolation string + Memory string + MemorySwap string + OverrideArch string + OverrideOS string + SecurityOpt []string + ShmSize string + Ulimit []string + Volumes []string } // GetUserNSFlags returns the common flags for usernamespace func GetUserNSFlags(flags *UserNSResults) pflag.FlagSet { usernsFlags := pflag.FlagSet{} usernsFlags.StringVar(&flags.UserNS, "userns", "", "'container', `path` of user namespace to join, or 'host'") - usernsFlags.StringSliceVar(&flags.UserNSUIDMap, "userns-uid-map", []string{}, "`containerID:hostID:length` UID mapping to use in user namespace") - usernsFlags.StringSliceVar(&flags.UserNSGIDMap, "userns-gid-map", []string{}, "`containerID:hostID:length` GID mapping to use in user namespace") + usernsFlags.StringSliceVar(&flags.UserNSUIDMap, "userns-uid-map", []string{}, "`containerUID:hostUID:length` UID mapping to use in user namespace") + usernsFlags.StringSliceVar(&flags.UserNSGIDMap, "userns-gid-map", []string{}, "`containerGID:hostGID:length` GID mapping to use in user namespace") usernsFlags.StringVar(&flags.UserNSUIDMapUser, "userns-uid-map-user", "", "`name` of entries from /etc/subuid to use to set user namespace UID mapping") usernsFlags.StringVar(&flags.UserNSGIDMapGroup, "userns-gid-map-group", "", "`name` of entries from /etc/subgid to use to set user namespace GID mapping") return usernsFlags @@ -208,6 +208,9 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.StringSliceVar(&flags.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") fs.StringVar(&flags.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") fs.StringVar(&flags.SignaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") + if err := fs.MarkHidden("signature-policy"); err != nil { + panic(fmt.Sprintf("error marking the signature-policy flag as hidden: %v", err)) + } fs.BoolVar(&flags.Squash, "squash", false, "squash newly built layers into a single new layer") fs.StringArrayVarP(&flags.Tag, "tag", "t", []string{}, "tagged `name` to apply to the built image") fs.StringVar(&flags.Target, "target", "", "set the target build stage to build") @@ -265,6 +268,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, fs.Uint64VarP(&flags.CPUShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") fs.StringVar(&flags.CPUSetCPUs, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") fs.StringVar(&flags.CPUSetMems, "cpuset-mems", "", "memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.") + fs.StringSliceVar(&flags.DecryptionKeys, "decryption-key", nil, "key needed to decrypt the image") fs.StringArrayVar(&flags.Devices, "device", defaultContainerConfig.Containers.Devices, "Additional devices to be used within containers (default [])") fs.StringSliceVar(&flags.DNSSearch, "dns-search", defaultContainerConfig.Containers.DNSSearches, "Set custom DNS search domains") fs.StringSliceVar(&flags.DNSServers, "dns", defaultContainerConfig.Containers.DNSServers, "Set custom DNS servers or disable it completely by setting it to 'none', which prevents the automatic creation of `/etc/resolv.conf`.") @@ -308,6 +312,7 @@ func GetFromAndBudFlagsCompletions() commonComp.FlagCompletions { flagCompletion["cpu-shares"] = commonComp.AutocompleteNone flagCompletion["cpuset-cpus"] = commonComp.AutocompleteNone flagCompletion["cpuset-mems"] = commonComp.AutocompleteNone + flagCompletion["decryption-key"] = commonComp.AutocompleteNone flagCompletion["device"] = commonComp.AutocompleteDefault flagCompletion["dns-search"] = commonComp.AutocompleteNone flagCompletion["dns"] = commonComp.AutocompleteNone diff --git a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go index ee2e9a7c8..32f888fa8 100644 --- a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go +++ b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go @@ -38,7 +38,7 @@ type secretData struct { // saveTo saves secret data to given directory func (s secretData) saveTo(dir string) error { path := filepath.Join(dir, s.name) - if err := os.MkdirAll(filepath.Dir(path), s.dirMode); err != nil && !os.IsExist(err) { + if err := os.MkdirAll(filepath.Dir(path), s.dirMode); err != nil { return err } return ioutil.WriteFile(path, s.data, s.mode) diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index bb52ec1ed..d7e7b8890 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -7,7 +7,6 @@ import ( "time" "github.com/containers/buildah/pkg/blobcache" - "github.com/containers/buildah/util" "github.com/containers/image/v5/directory" "github.com/containers/image/v5/docker" dockerarchive "github.com/containers/image/v5/docker/archive" @@ -18,6 +17,7 @@ import ( "github.com/containers/image/v5/signature" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" encconfig "github.com/containers/ocicrypt/config" "github.com/containers/storage" @@ -171,63 +171,63 @@ func Pull(ctx context.Context, imageName string, options PullOptions) (imageID s OciDecryptConfig: options.OciDecryptConfig, } - storageRef, transport, img, err := resolveImage(ctx, systemContext, options.Store, boptions) + if !options.AllTags { + _, _, img, err := resolveImage(ctx, systemContext, options.Store, boptions) + if err != nil { + return "", err + } + return img.ID, nil + } + + srcRef, err := alltransports.ParseImageName(imageName) + if err == nil && srcRef.Transport().Name() != docker.Transport.Name() { + return "", errors.New("Non-docker transport is not supported, for --all-tags pulling") + } + + storageRef, _, _, err := resolveImage(ctx, systemContext, options.Store, boptions) if err != nil { return "", err } var errs *multierror.Error - if options.AllTags { - if transport != util.DefaultTransport { - return "", errors.New("Non-docker transport is not supported, for --all-tags pulling") - } - - repo := reference.TrimNamed(storageRef.DockerReference()) - dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) + repo := reference.TrimNamed(storageRef.DockerReference()) + dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) + if err != nil { + return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) + } + tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) + if err != nil { + return "", errors.Wrapf(err, "error getting repository tags") + } + for _, tag := range tags { + tagged, err := reference.WithTag(repo, tag) if err != nil { - return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) + errs = multierror.Append(errs, err) + continue } - tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) + taggedRef, err := docker.NewReference(tagged) if err != nil { - return "", errors.Wrapf(err, "error getting repository tags") + return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) } - for _, tag := range tags { - tagged, err := reference.WithTag(repo, tag) - if err != nil { - errs = multierror.Append(errs, err) - continue + if options.ReportWriter != nil { + if _, err := options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")); err != nil { + return "", errors.Wrapf(err, "error writing pull report") } - taggedRef, err := docker.NewReference(tagged) - if err != nil { - return "", errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) - } - if options.ReportWriter != nil { - if _, err := options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")); err != nil { - return "", errors.Wrapf(err, "error writing pull report") - } - } - ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - imageID = taggedImg.ID } - } else { - imageID = img.ID - } - if errs == nil { - err = nil - } else { - err = errs.ErrorOrNil() + ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + imageID = taggedImg.ID } - return imageID, err + return imageID, errs.ErrorOrNil() } func pullImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 3a07407b0..d907941ed 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -23,6 +23,7 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containers/buildah/bind" "github.com/containers/buildah/chroot" + "github.com/containers/buildah/copier" "github.com/containers/buildah/pkg/overlay" "github.com/containers/buildah/pkg/secrets" "github.com/containers/buildah/util" @@ -165,11 +166,6 @@ func (b *Builder) Run(command []string, options RunOptions) error { spec := g.Config g = nil - logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd)) - if err = os.MkdirAll(filepath.Join(mountPoint, spec.Process.Cwd), 0755); err != nil && !os.IsExist(err) { - return err - } - // Set the seccomp configuration using the specified profile name. Some syscalls are // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot), // so we sorted out the capabilities lists first. @@ -184,6 +180,15 @@ func (b *Builder) Run(command []string, options RunOptions) error { } rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)} + mode := os.FileMode(0755) + coptions := copier.MkdirOptions{ + ChownNew: rootIDPair, + ChmodNew: &mode, + } + if err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, spec.Process.Cwd), coptions); err != nil { + return err + } + bindFiles := make(map[string]string) namespaceOptions := append(b.NamespaceOptions, options.NamespaceOptions...) volumes := b.Volumes() @@ -1981,7 +1986,6 @@ func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions } func setupRootlessSpecChanges(spec *specs.Spec, bundleDir string, shmSize string) error { - spec.Hostname = "" spec.Process.User.AdditionalGids = nil spec.Linux.Resources = nil @@ -2137,10 +2141,6 @@ func checkAndOverrideIsolationOptions(isolation Isolation, options *RunOptions) logrus.Debugf("Forcing use of a user namespace.") } options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UserNamespace)}) - if ns := options.NamespaceOptions.Find(string(specs.UTSNamespace)); ns != nil && !ns.Host { - logrus.Debugf("Disabling UTS namespace.") - } - options.NamespaceOptions.AddOrReplace(NamespaceOption{Name: string(specs.UTSNamespace), Host: true}) case IsolationOCI: pidns := options.NamespaceOptions.Find(string(specs.PIDNamespace)) userns := options.NamespaceOptions.Find(string(specs.UserNamespace)) diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 00efc8d21..99f68d9e1 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -12,10 +12,10 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/signature" is "github.com/containers/image/v5/storage" - "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/storage" @@ -69,42 +69,10 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto } } - // If the image includes a transport's name as a prefix, use it as-is. - if strings.HasPrefix(name, DefaultTransport) { - return []string{strings.TrimPrefix(name, DefaultTransport)}, DefaultTransport, false, nil - } - split := strings.SplitN(name, ":", 2) - if StartsWithValidTransport(name) && len(split) == 2 { - if trans := transports.Get(split[0]); trans != nil { - return []string{split[1]}, trans.Name(), false, nil - } - } - // If the image name already included a domain component, we're done. - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, "", false, errors.Wrapf(err, "error parsing image name %q", name) - } - if named.String() == name { - // Parsing produced the same result, so there was a domain name in there to begin with. - return []string{name}, DefaultTransport, false, nil - } - if reference.Domain(named) != "" && RegistryDefaultPathPrefix[reference.Domain(named)] != "" { - // If this domain can cause us to insert something in the middle, check if that happened. - repoPath := reference.Path(named) - domain := reference.Domain(named) - tag := "" - if tagged, ok := named.(reference.Tagged); ok { - tag = ":" + tagged.Tag() - } - digest := "" - if digested, ok := named.(reference.Digested); ok { - digest = "@" + digested.Digest().String() - } - defaultPrefix := RegistryDefaultPathPrefix[reference.Domain(named)] + "/" - if strings.HasPrefix(repoPath, defaultPrefix) && path.Join(domain, repoPath[len(defaultPrefix):])+tag+digest == name { - // Yup, parsing just inserted a bit in the middle, so there was a domain name there to begin with. - return []string{name}, DefaultTransport, false, nil - } + // Transports are not supported for local image look ups. + srcRef, err := alltransports.ParseImageName(name) + if err == nil { + return []string{srcRef.StringWithinTransport()}, srcRef.Transport().Name(), false, nil } // Figure out the list of registries. @@ -126,25 +94,26 @@ func ResolveName(name string, firstRegistry string, sc *types.SystemContext, sto } searchRegistriesAreEmpty := len(registries) == 0 - // Create all of the combinations. Some registries need an additional component added, so - // use our lookaside map to keep track of them. If there are no configured registries, we'll - // return a name using "localhost" as the registry name. - candidates := []string{} - initRegistries := []string{"localhost"} + var candidates []string + // Set the first registry if requested. if firstRegistry != "" && firstRegistry != "localhost" { - initRegistries = append([]string{firstRegistry}, initRegistries...) - } - for _, registry := range append(initRegistries, registries...) { - if registry == "" { - continue - } middle := "" - if prefix, ok := RegistryDefaultPathPrefix[registry]; ok && !strings.ContainsRune(name, '/') { + if prefix, ok := RegistryDefaultPathPrefix[firstRegistry]; ok && !strings.ContainsRune(name, '/') { middle = prefix } - candidate := path.Join(registry, middle, name) + candidate := path.Join(firstRegistry, middle, name) candidates = append(candidates, candidate) } + + // Local short-name resolution. + namedCandidates, err := shortnames.ResolveLocally(sc, name) + if err != nil { + return nil, "", false, err + } + for _, named := range namedCandidates { + candidates = append(candidates, named.String()) + } + return candidates, DefaultTransport, searchRegistriesAreEmpty, nil } diff --git a/vendor/github.com/containers/buildah/util/util_linux.go b/vendor/github.com/containers/buildah/util/util_linux.go index 1a13699df..cca1f9e7e 100644 --- a/vendor/github.com/containers/buildah/util/util_linux.go +++ b/vendor/github.com/containers/buildah/util/util_linux.go @@ -1,7 +1,6 @@ package util import ( - "os" "syscall" "golang.org/x/sys/unix" @@ -19,11 +18,3 @@ func IsCgroup2UnifiedMode() (bool, error) { }) return isUnified, isUnifiedErr } - -func UID(st os.FileInfo) int { - return int(st.Sys().(*syscall.Stat_t).Uid) -} - -func GID(st os.FileInfo) int { - return int(st.Sys().(*syscall.Stat_t).Gid) -} diff --git a/vendor/github.com/containers/buildah/util/util_unix.go b/vendor/github.com/containers/buildah/util/util_unix.go index 04d9a01cc..29983e40f 100644 --- a/vendor/github.com/containers/buildah/util/util_unix.go +++ b/vendor/github.com/containers/buildah/util/util_unix.go @@ -29,3 +29,11 @@ func (h *HardlinkChecker) Add(fi os.FileInfo, name string) { h.hardlinks.Store(makeHardlinkDeviceAndInode(st), name) } } + +func UID(st os.FileInfo) int { + return int(st.Sys().(*syscall.Stat_t).Uid) +} + +func GID(st os.FileInfo) int { + return int(st.Sys().(*syscall.Stat_t).Gid) +} diff --git a/vendor/github.com/containers/buildah/util/util_unsupported.go b/vendor/github.com/containers/buildah/util/util_unsupported.go index 8810536a6..05a68f60b 100644 --- a/vendor/github.com/containers/buildah/util/util_unsupported.go +++ b/vendor/github.com/containers/buildah/util/util_unsupported.go @@ -2,19 +2,7 @@ package util -import ( - "os" -) - // IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. func IsCgroup2UnifiedMode() (bool, error) { return false, nil } - -func UID(st os.FileInfo) int { - return 0 -} - -func GID(st os.FileInfo) int { - return 0 -} diff --git a/vendor/github.com/containers/buildah/util/util_windows.go b/vendor/github.com/containers/buildah/util/util_windows.go index 0e7f92325..18965ab17 100644 --- a/vendor/github.com/containers/buildah/util/util_windows.go +++ b/vendor/github.com/containers/buildah/util/util_windows.go @@ -14,3 +14,11 @@ func (h *HardlinkChecker) Check(fi os.FileInfo) string { } func (h *HardlinkChecker) Add(fi os.FileInfo, name string) { } + +func UID(st os.FileInfo) int { + return 0 +} + +func GID(st os.FileInfo) int { + return 0 +} diff --git a/vendor/github.com/containers/image/v5/copy/copy.go b/vendor/github.com/containers/image/v5/copy/copy.go index d8e3fa106..4d5b07689 100644 --- a/vendor/github.com/containers/image/v5/copy/copy.go +++ b/vendor/github.com/containers/image/v5/copy/copy.go @@ -1166,6 +1166,21 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc) return digest.Canonical.FromReader(stream) } +// errorAnnotationReader wraps the io.Reader passed to PutBlob for annotating the error happened during read. +// These errors are reported as PutBlob errors, so we would otherwise misleadingly attribute them to the copy destination. +type errorAnnotationReader struct { + reader io.Reader +} + +// Read annotates the error happened during read +func (r errorAnnotationReader) Read(b []byte) (n int, err error) { + n, err = r.reader.Read(b) + if err != io.EOF { + return n, errors.Wrapf(err, "error happened during read") + } + return n, err +} + // copyBlobFromStream copies a blob with srcInfo (with known Digest and Annotations and possibly known Size) from srcStream to dest, // perhaps sending a copy to an io.Writer if getOriginalLayerCopyWriter != nil, // perhaps compressing it if canCompress, @@ -1335,7 +1350,7 @@ func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, sr } // === Finally, send the layer stream to dest. - uploadedInfo, err := c.dest.PutBlob(ctx, destStream, inputInfo, c.blobInfoCache, isConfig) + uploadedInfo, err := c.dest.PutBlob(ctx, &errorAnnotationReader{destStream}, inputInfo, c.blobInfoCache, isConfig) if err != nil { return types.BlobInfo{}, errors.Wrap(err, "Error writing blob") } diff --git a/vendor/github.com/containers/image/v5/docker/docker_image.go b/vendor/github.com/containers/image/v5/docker/docker_image.go index 479effa59..0c1cee0d3 100644 --- a/vendor/github.com/containers/image/v5/docker/docker_image.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image.go @@ -4,12 +4,15 @@ import ( "context" "encoding/json" "fmt" + "net/http" "net/url" "strings" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -103,3 +106,47 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types. } return tags, nil } + +// GetDigest returns the image's digest +// Use this to optimize and avoid use of an ImageSource based on the returned digest; +// if you are going to use an ImageSource anyway, it’s more efficient to create it first +// and compute the digest from the value returned by GetManifest. +// NOTE: Implemented to avoid Docker Hub API limits, and mirror configuration may be +// ignored (but may be implemented in the future) +func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) (digest.Digest, error) { + dr, ok := ref.(dockerReference) + if !ok { + return "", errors.Errorf("ref must be a dockerReference") + } + + tagOrDigest, err := dr.tagOrDigest() + if err != nil { + return "", err + } + + client, err := newDockerClientFromRef(sys, dr, false, "pull") + if err != nil { + return "", errors.Wrap(err, "failed to create client") + } + + path := fmt.Sprintf(manifestPath, reference.Path(dr.ref), tagOrDigest) + headers := map[string][]string{ + "Accept": manifest.DefaultRequestedManifestMIMETypes, + } + + res, err := client.makeRequest(ctx, "HEAD", path, headers, nil, v2Auth, nil) + if err != nil { + return "", err + } + + if res.StatusCode != http.StatusOK { + return "", errors.Wrapf(registryHTTPResponseToError(res), "Error reading digest %s in %s", tagOrDigest, dr.ref.Name()) + } + + dig, err := digest.Parse(res.Header.Get("Docker-Content-Digest")) + if err != nil { + return "", err + } + + return dig, nil +} diff --git a/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go new file mode 100644 index 000000000..e02703d77 --- /dev/null +++ b/vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go @@ -0,0 +1,458 @@ +package shortnames + +import ( + "fmt" + "os" + "strings" + + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/types" + "github.com/manifoldco/promptui" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "golang.org/x/crypto/ssh/terminal" +) + +// IsShortName returns true if the specified input is a "short name". A "short +// name" refers to a container image without a fully-qualified reference, and +// is hence missing a registry (or domain). Names including a digest are not +// short names. +// +// Examples: +// * short names: "image:tag", "library/fedora" +// * not short names: "quay.io/image", "localhost/image:tag", +// "server.org:5000/lib/image", "image@sha256:..." +func IsShortName(input string) bool { + isShort, _, _ := parseUnnormalizedShortName(input) + return isShort +} + +// parseUnnormalizedShortName parses the input and returns if it's short name, +// the unnormalized reference.Named, and a parsing error. +func parseUnnormalizedShortName(input string) (bool, reference.Named, error) { + ref, err := reference.Parse(input) + if err != nil { + return false, nil, errors.Wrapf(err, "cannot parse input: %q", input) + } + + named, ok := ref.(reference.Named) + if !ok { + return true, nil, errors.Errorf("%q is not a named reference", input) + } + + registry := reference.Domain(named) + if strings.ContainsAny(registry, ".:") || registry == "localhost" { + // A final parse to make sure that docker.io references are correctly + // normalized (e.g., docker.io/alpine to docker.io/library/alpine. + named, err = reference.ParseNormalizedNamed(input) + if err != nil { + return false, nil, errors.Wrapf(err, "cannot normalize input: %q", input) + } + return false, named, nil + } + + return true, named, nil +} + +// splitUserInput parses the user-specified reference. Namely, it strips off +// the tag or digest and stores it in the return values so that both can be +// re-added to a possible resolved alias' or USRs at a later point. +func splitUserInput(named reference.Named) (isTagged bool, isDigested bool, normalized reference.Named, tag string, digest digest.Digest) { + normalized = named + + tagged, isT := named.(reference.NamedTagged) + if isT { + isTagged = true + tag = tagged.Tag() + } + + digested, isD := named.(reference.Digested) + if isD { + isDigested = true + digest = digested.Digest() + } + + // Strip off tag/digest if present. + normalized = reference.TrimNamed(named) + + return +} + +// Add records the specified name-value pair as a new short-name alias to the +// user-specific aliases.conf. It may override an existing alias for `name`. +func Add(ctx *types.SystemContext, name string, value reference.Named) error { + isShort, _, err := parseUnnormalizedShortName(name) + if err != nil { + return err + } + if !isShort { + return errors.Errorf("%q is not a short name", name) + } + return sysregistriesv2.AddShortNameAlias(ctx, name, value.String()) +} + +// Remove clears the short-name alias for the specified name. It throws an +// error in case name does not exist in the machine-generated +// short-name-alias.conf. In such case, the alias must be specified in one of +// the registries.conf files, which is the users' responsibility. +func Remove(ctx *types.SystemContext, name string) error { + isShort, _, err := parseUnnormalizedShortName(name) + if err != nil { + return err + } + if !isShort { + return errors.Errorf("%q is not a short name", name) + } + return sysregistriesv2.RemoveShortNameAlias(ctx, name) +} + +// Resolved encapsulates all data for a resolved image name. +type Resolved struct { + PullCandidates []PullCandidate + + userInput reference.Named + systemContext *types.SystemContext + rationale rationale + originDescription string +} + +func (r *Resolved) addCandidate(named reference.Named) { + r.PullCandidates = append(r.PullCandidates, PullCandidate{named, false, r}) +} + +func (r *Resolved) addCandidateToRecord(named reference.Named) { + r.PullCandidates = append(r.PullCandidates, PullCandidate{named, true, r}) +} + +// Allows to reason over pull errors and add some context information. +// Used in (*Resolved).WrapPullError. +type rationale int + +const ( + // No additional context. + rationaleNone rationale = iota + // Resolved value is a short-name alias. + rationaleAlias + // Resolved value has been completed with an Unqualified Search Registry. + rationaleUSR + // Resolved value has been selected by the user (via the prompt). + rationaleUserSelection +) + +// Description returns a human-readable description about the resolution +// process (e.g., short-name alias, unqualified-search registries, etc.). +// It is meant to be printed before attempting to pull the pull candidates +// to make the short-name resolution more transparent to user. +// +// If the returned string is empty, it is not meant to be printed. +func (r *Resolved) Description() string { + switch r.rationale { + case rationaleAlias: + return fmt.Sprintf("Resolved short name %q to a recorded short-name alias (origin: %s)", r.userInput, r.originDescription) + case rationaleUSR: + return fmt.Sprintf("Completed short name %q with unqualified-search registries (origin: %s)", r.userInput, r.originDescription) + case rationaleUserSelection, rationaleNone: + fallthrough + default: + return "" + } +} + +// FormatPullErrors is a convenience function to format errors that occurred +// while trying to pull all of the resolved pull candidates. +// +// Note that nil is returned if len(pullErrors) == 0. Otherwise, the amount of +// pull errors must equal the amount of pull candidates. +func (r *Resolved) FormatPullErrors(pullErrors []error) error { + if len(pullErrors) >= 0 && len(pullErrors) != len(r.PullCandidates) { + pullErrors = append(pullErrors, + errors.Errorf("internal error: expected %d instead of %d errors for %d pull candidates", + len(r.PullCandidates), len(pullErrors), len(r.PullCandidates))) + } + + switch len(pullErrors) { + case 0: + return nil + case 1: + return pullErrors[0] + default: + var sb strings.Builder + sb.WriteString(fmt.Sprintf("%d errors occurred while pulling:", len(pullErrors))) + for _, e := range pullErrors { + sb.WriteString("\n * ") + sb.WriteString(e.Error()) + } + return errors.New(sb.String()) + } +} + +// PullCandidate is a resolved name. Once the Value has been used +// successfully, users MUST call `(*PullCandidate).Record(..)` to possibly +// record it as a new short-name alias. +type PullCandidate struct { + // Fully-qualified reference with tag or digest. + Value reference.Named + // Control whether to record it permanently as an alias. + record bool + + // Backwards pointer to the Resolved "parent". + resolved *Resolved +} + +// Record may store a short-name alias for the PullCandidate. +func (c *PullCandidate) Record() error { + if !c.record { + return nil + } + + // Strip off tags/digests from name/value. + name := reference.TrimNamed(c.resolved.userInput) + value := reference.TrimNamed(c.Value) + + if err := Add(c.resolved.systemContext, name.String(), value); err != nil { + return errors.Wrapf(err, "error recording short-name alias (%q=%q)", c.resolved.userInput, c.Value) + } + return nil +} + +// Resolve resolves the specified name to either one or more fully-qualified +// image references that the short name may be *pulled* from. If the specified +// name is already a fully-qualified reference (i.e., not a short name), it is +// returned as is. In case, it's a short name, it's resolved according to the +// ShortNameMode in the SystemContext (if specified) or in the registries.conf. +// +// Note that tags and digests are stripped from the specified name before +// looking up an alias. Stripped off tags and digests are later on appended to +// all candidates. If neither tag nor digest is specified, candidates are +// normalized with the "latest" tag. PullCandidates in the returned value may +// be empty if there is no matching alias and no unqualified-search registries +// are configured. +// +// Note that callers *must* call `(PullCandidate).Record` after a returned +// item has been pulled successfully; this callback will record a new +// short-name alias (depending on the specified short-name mode). +// +// Furthermore, before attempting to pull callers *should* call +// `(Resolved).Description` and afterwards use +// `(Resolved).FormatPullErrors` in case of pull errors. +func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) { + resolved := &Resolved{} + + // Create a copy of the system context to make it usable beyond this + // function call. + var sys *types.SystemContext + if ctx != nil { + sys = &(*ctx) + } + resolved.systemContext = ctx + + // Detect which mode we're running in. + mode, err := sysregistriesv2.GetShortNameMode(sys) + if err != nil { + return nil, err + } + + // Sanity check the short-name mode. + switch mode { + case types.ShortNameModeDisabled, types.ShortNameModePermissive, types.ShortNameModeEnforcing: + // We're good. + default: + return nil, errors.Errorf("unsupported short-name mode (%v)", mode) + } + + isShort, shortRef, err := parseUnnormalizedShortName(name) + if err != nil { + return nil, err + } + if !isShort { // no short name + named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed + resolved.addCandidate(named) + return resolved, nil + } + + // Strip off the tag to normalize the short name for looking it up in + // the config files. + isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef) + resolved.userInput = shortNameRepo + + // If there's already an alias, use it. + namedAlias, aliasOriginDescription, err := sysregistriesv2.ResolveShortNameAlias(sys, shortNameRepo.String()) + if err != nil { + return nil, err + } + + // Always use an alias if present. + if namedAlias != nil { + if isTagged { + namedAlias, err = reference.WithTag(namedAlias, tag) + if err != nil { + return nil, err + } + } + if isDigested { + namedAlias, err = reference.WithDigest(namedAlias, digest) + if err != nil { + return nil, err + } + } + // Make sure to add ":latest" if needed + namedAlias = reference.TagNameOnly(namedAlias) + + resolved.addCandidate(namedAlias) + resolved.rationale = rationaleAlias + resolved.originDescription = aliasOriginDescription + return resolved, nil + } + + resolved.rationale = rationaleUSR + + // Query the registry for unqualified-search registries. + unqualifiedSearchRegistries, usrConfig, err := sysregistriesv2.UnqualifiedSearchRegistriesWithOrigin(sys) + if err != nil { + return nil, err + } + resolved.originDescription = usrConfig + + for _, reg := range unqualifiedSearchRegistries { + named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name)) + if err != nil { + return nil, errors.Wrapf(err, "error creating reference with unqualified-search registry %q", reg) + } + // Make sure to add ":latest" if needed + named = reference.TagNameOnly(named) + + resolved.addCandidate(named) + } + + // If we're running in disabled, return the candidates without + // prompting (and without recording). + if mode == types.ShortNameModeDisabled { + return resolved, nil + } + + // If we have only one candidate, there's no ambiguity. In case of an + // empty candidate slices, callers can implement custom logic or raise + // an error. + if len(resolved.PullCandidates) <= 1 { + return resolved, nil + } + + // If we don't have a TTY, act according to the mode. + if !terminal.IsTerminal(int(os.Stdout.Fd())) || !terminal.IsTerminal(int(os.Stdin.Fd())) { + switch mode { + case types.ShortNameModePermissive: + // Permissive falls back to using all candidates. + return resolved, nil + case types.ShortNameModeEnforcing: + // Enforcing errors out without a prompt. + return nil, errors.New("short-name resolution enforced but cannot prompt without a TTY") + default: + // We should not end up here. + return nil, errors.Errorf("unexpected short-name mode (%v) during resolution", mode) + } + } + + // We have a TTY, and can prompt the user with a selection of all + // possible candidates. + strCandidates := []string{} + for _, candidate := range resolved.PullCandidates { + strCandidates = append(strCandidates, candidate.Value.String()) + } + prompt := promptui.Select{ + Label: "Please select an image", + Items: strCandidates, + HideHelp: true, // do not show navigation help + } + + _, selection, err := prompt.Run() + if err != nil { + return nil, err + } + + named, err := reference.ParseNormalizedNamed(selection) + if err != nil { + return nil, errors.Wrapf(err, "selection %q is not a valid reference", selection) + } + + resolved.PullCandidates = nil + resolved.addCandidateToRecord(named) + resolved.rationale = rationaleUserSelection + + return resolved, nil +} + +// ResolveLocally resolves the specified name to either one or more local +// images. If the specified name is already a fully-qualified reference (i.e., +// not a short name), it is returned as is. In case, it's a short name, the +// returned slice of named references looks as follows: +// +// 1) If present, the short-name alias +// 2) "localhost/" as used by many container engines such as Podman and Buildah +// 3) Unqualified-search registries from the registries.conf files +// +// Note that tags and digests are stripped from the specified name before +// looking up an alias. Stripped off tags and digests are later on appended to +// all candidates. If neither tag nor digest is specified, candidates are +// normalized with the "latest" tag. The returned slice contains at least one +// item. +func ResolveLocally(ctx *types.SystemContext, name string) ([]reference.Named, error) { + isShort, shortRef, err := parseUnnormalizedShortName(name) + if err != nil { + return nil, err + } + if !isShort { // no short name + named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed + return []reference.Named{named}, nil + } + + var candidates []reference.Named + + // Strip off the tag to normalize the short name for looking it up in + // the config files. + isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef) + + // If there's already an alias, use it. + namedAlias, _, err := sysregistriesv2.ResolveShortNameAlias(ctx, shortNameRepo.String()) + if err != nil { + return nil, err + } + if namedAlias != nil { + if isTagged { + namedAlias, err = reference.WithTag(namedAlias, tag) + if err != nil { + return nil, err + } + } + if isDigested { + namedAlias, err = reference.WithDigest(namedAlias, digest) + if err != nil { + return nil, err + } + } + // Make sure to add ":latest" if needed + namedAlias = reference.TagNameOnly(namedAlias) + + candidates = append(candidates, namedAlias) + } + + // Query the registry for unqualified-search registries. + unqualifiedSearchRegistries, err := sysregistriesv2.UnqualifiedSearchRegistries(ctx) + if err != nil { + return nil, err + } + + // Note that "localhost" has precedence over the unqualified-search registries. + for _, reg := range append([]string{"localhost"}, unqualifiedSearchRegistries...) { + named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name)) + if err != nil { + return nil, errors.Wrapf(err, "error creating reference with unqualified-search registry %q", reg) + } + // Make sure to add ":latest" if needed + named = reference.TagNameOnly(named) + + candidates = append(candidates, named) + } + + return candidates, nil +} diff --git a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go new file mode 100644 index 000000000..fadfe1a35 --- /dev/null +++ b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go @@ -0,0 +1,328 @@ +package sysregistriesv2 + +import ( + "os" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" + "github.com/containers/storage/pkg/lockfile" + "github.com/docker/docker/pkg/homedir" + "github.com/pkg/errors" +) + +// defaultShortNameMode is the default mode of registries.conf files if the +// corresponding field is left empty. +const defaultShortNameMode = types.ShortNameModePermissive + +// userShortNamesFile is the user-specific config file to store aliases. +var userShortNamesFile = filepath.FromSlash("containers/short-name-aliases.conf") + +// shortNameAliasesConfPath returns the path to the machine-generated +// short-name-aliases.conf file. +func shortNameAliasesConfPath(ctx *types.SystemContext) (string, error) { + if ctx != nil && len(ctx.UserShortNameAliasConfPath) > 0 { + return ctx.UserShortNameAliasConfPath, nil + } + + configHome, err := homedir.GetConfigHome() + if err != nil { + return "", err + } + + return filepath.Join(configHome, userShortNamesFile), nil +} + +// shortNameAliasConf is a subset of the `V2RegistriesConf` format. It's used in the +// software-maintained `userShortNamesFile`. +type shortNameAliasConf struct { + // A map for aliasing short names to their fully-qualified image + // reference counter parts. + // Note that Aliases is niled after being loaded from a file. + Aliases map[string]string `toml:"aliases"` +} + +// alias combines the parsed value of an alias with the config file it has been +// specified in. The config file is crucial for an improved user experience +// such that users are able to resolve potential pull errors. +type alias struct { + // The parsed value of an alias. May be nil if set to "" in a config. + value reference.Named + // The config file the alias originates from. + configOrigin string +} + +// shortNameAliasCache is the result of parsing shortNameAliasConf, +// pre-processed for faster usage. +type shortNameAliasCache struct { + // Note that an alias value may be nil iff it's set as an empty string + // in the config. + namedAliases map[string]alias +} + +// ResolveShortNameAlias performs an alias resolution of the specified name. +// The user-specific short-name-aliases.conf has precedence over aliases in the +// assembled registries.conf. It returns the possibly resolved alias or nil, a +// human-readable description of the config where the alias is specified, and +// an error. The origin of the config file is crucial for an improved user +// experience such that users are able to resolve potential pull errors. +// Almost all callers should use pkg/shortnames instead. +// +// Note that it’s the caller’s responsibility to pass only a repository +// (reference.IsNameOnly) as the short name. +func ResolveShortNameAlias(ctx *types.SystemContext, name string) (reference.Named, string, error) { + if err := validateShortName(name); err != nil { + return nil, "", err + } + confPath, lock, err := shortNameAliasesConfPathAndLock(ctx) + if err != nil { + return nil, "", err + } + + // Acquire the lock as a reader to allow for multiple routines in the + // same process space to read simultaneously. + lock.RLock() + defer lock.Unlock() + + _, aliasCache, err := loadShortNameAliasConf(confPath) + if err != nil { + return nil, "", err + } + + // First look up the short-name-aliases.conf. Note that a value may be + // nil iff it's set as an empty string in the config. + alias, resolved := aliasCache.namedAliases[name] + if resolved { + return alias.value, alias.configOrigin, nil + } + + config, err := getConfig(ctx) + if err != nil { + return nil, "", err + } + alias, resolved = config.aliasCache.namedAliases[name] + if resolved { + return alias.value, alias.configOrigin, nil + } + return nil, "", nil +} + +// editShortNameAlias loads the aliases.conf file and changes it. If value is +// set, it adds the name-value pair as a new alias. Otherwise, it will remove +// name from the config. +func editShortNameAlias(ctx *types.SystemContext, name string, value *string) error { + if err := validateShortName(name); err != nil { + return err + } + if value != nil { + if _, err := parseShortNameValue(*value); err != nil { + return err + } + } + + confPath, lock, err := shortNameAliasesConfPathAndLock(ctx) + if err != nil { + return err + } + + // Acquire the lock as a writer to prevent data corruption. + lock.Lock() + defer lock.Unlock() + + // Load the short-name-alias.conf, add the specified name-value pair, + // and write it back to the file. + conf, _, err := loadShortNameAliasConf(confPath) + if err != nil { + return err + } + + if conf.Aliases == nil { // Ensure we have a map to update. + conf.Aliases = make(map[string]string) + } + if value != nil { + conf.Aliases[name] = *value + } else { + // If the name does not exist, throw an error. + if _, exists := conf.Aliases[name]; !exists { + return errors.Errorf("short-name alias %q not found in %q: please check registries.conf files", name, confPath) + } + + delete(conf.Aliases, name) + } + + f, err := os.OpenFile(confPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer f.Close() + + encoder := toml.NewEncoder(f) + return encoder.Encode(conf) +} + +// AddShortNameAlias adds the specified name-value pair as a new alias to the +// user-specific aliases.conf. It may override an existing alias for `name`. +// +// Note that it’s the caller’s responsibility to pass only a repository +// (reference.IsNameOnly) as the short name. +func AddShortNameAlias(ctx *types.SystemContext, name string, value string) error { + return editShortNameAlias(ctx, name, &value) +} + +// RemoveShortNameAlias clears the alias for the specified name. It throws an +// error in case name does not exist in the machine-generated +// short-name-alias.conf. In such case, the alias must be specified in one of +// the registries.conf files, which is the users' responsibility. +// +// Note that it’s the caller’s responsibility to pass only a repository +// (reference.IsNameOnly) as the short name. +func RemoveShortNameAlias(ctx *types.SystemContext, name string) error { + return editShortNameAlias(ctx, name, nil) +} + +// parseShortNameValue parses the specified alias into a reference.Named. The alias is +// expected to not be tagged or carry a digest and *must* include a +// domain/registry. +// +// Note that the returned reference is always normalized. +func parseShortNameValue(alias string) (reference.Named, error) { + ref, err := reference.Parse(alias) + if err != nil { + return nil, errors.Wrapf(err, "error parsing alias %q", alias) + } + + if _, ok := ref.(reference.Digested); ok { + return nil, errors.Errorf("invalid alias %q: must not contain digest", alias) + } + + if _, ok := ref.(reference.Tagged); ok { + return nil, errors.Errorf("invalid alias %q: must not contain tag", alias) + } + + named, ok := ref.(reference.Named) + if !ok { + return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias) + } + + registry := reference.Domain(named) + if !(strings.ContainsAny(registry, ".:") || registry == "localhost") { + return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias) + } + + // A final parse to make sure that docker.io references are correctly + // normalized (e.g., docker.io/alpine to docker.io/library/alpine. + named, err = reference.ParseNormalizedNamed(alias) + return named, err +} + +// validateShortName parses the specified `name` of an alias (i.e., the left-hand +// side) and checks if it's a short name and does not include a tag or digest. +func validateShortName(name string) error { + repo, err := reference.Parse(name) + if err != nil { + return errors.Wrapf(err, "cannot parse short name: %q", name) + } + + if _, ok := repo.(reference.Digested); ok { + return errors.Errorf("invalid short name %q: must not contain digest", name) + } + + if _, ok := repo.(reference.Tagged); ok { + return errors.Errorf("invalid short name %q: must not contain tag", name) + } + + named, ok := repo.(reference.Named) + if !ok { + return errors.Errorf("invalid short name %q: no name", name) + } + + registry := reference.Domain(named) + if strings.ContainsAny(registry, ".:") || registry == "localhost" { + return errors.Errorf("invalid short name %q: must not contain registry", name) + } + return nil +} + +// newShortNameAliasCache parses shortNameAliasConf and returns the corresponding internal +// representation. +func newShortNameAliasCache(path string, conf *shortNameAliasConf) (*shortNameAliasCache, error) { + res := shortNameAliasCache{ + namedAliases: make(map[string]alias), + } + errs := []error{} + for name, value := range conf.Aliases { + if err := validateShortName(name); err != nil { + errs = append(errs, err) + } + + // Empty right-hand side values in config files allow to reset + // an alias in a previously loaded config. This way, drop-in + // config files from registries.conf.d can reset potentially + // malconfigured aliases. + if value == "" { + res.namedAliases[name] = alias{nil, path} + continue + } + + named, err := parseShortNameValue(value) + if err != nil { + // We want to report *all* malformed entries to avoid a + // whack-a-mole for the user. + errs = append(errs, err) + } else { + res.namedAliases[name] = alias{named, path} + } + } + if len(errs) > 0 { + err := errs[0] + for i := 1; i < len(errs); i++ { + err = errors.Wrapf(err, "%v\n", errs[i]) + } + return nil, err + } + return &res, nil +} + +// updateWithConfigurationFrom updates c with configuration from updates. +// In case of conflict, updates is preferred. +func (c *shortNameAliasCache) updateWithConfigurationFrom(updates *shortNameAliasCache) { + for name, value := range updates.namedAliases { + c.namedAliases[name] = value + } +} + +func loadShortNameAliasConf(confPath string) (*shortNameAliasConf, *shortNameAliasCache, error) { + conf := shortNameAliasConf{} + + _, err := toml.DecodeFile(confPath, &conf) + if err != nil && !os.IsNotExist(err) { + // It's okay if the config doesn't exist. Other errors are not. + return nil, nil, errors.Wrapf(err, "error loading short-name aliases config file %q", confPath) + } + + // Even if we don’t always need the cache, doing so validates the machine-generated config. The + // file could still be corrupted by another process or user. + cache, err := newShortNameAliasCache(confPath, &conf) + if err != nil { + return nil, nil, errors.Wrapf(err, "error loading short-name aliases config file %q", confPath) + } + + return &conf, cache, nil +} + +func shortNameAliasesConfPathAndLock(ctx *types.SystemContext) (string, lockfile.Locker, error) { + shortNameAliasesConfPath, err := shortNameAliasesConfPath(ctx) + if err != nil { + return "", nil, err + } + // Make sure the path to file exists. + if err := os.MkdirAll(filepath.Dir(shortNameAliasesConfPath), 0700); err != nil { + return "", nil, err + } + + lockPath := shortNameAliasesConfPath + ".lock" + locker, err := lockfile.GetLockfile(lockPath) + return shortNameAliasesConfPath, locker, err +} diff --git a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go index ea2b21575..89ad7c533 100644 --- a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go +++ b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go @@ -154,6 +154,19 @@ type V2RegistriesConf struct { Registries []Registry `toml:"registry"` // An array of host[:port] (not prefix!) entries to use for resolving unqualified image references UnqualifiedSearchRegistries []string `toml:"unqualified-search-registries"` + + // ShortNameMode defines how short-name resolution should be handled by + // _consumers_ of this package. Depending on the mode, the user should + // be prompted with a choice of using one of the unqualified-search + // registries when referring to a short name. + // + // Valid modes are: * "prompt": prompt if stdout is a TTY, otherwise + // use all unqualified-search registries * "enforcing": always prompt + // and error if stdout is not a TTY * "disabled": do not prompt and + // potentially use all unqualified-search registries + ShortNameMode string `toml:"short-name-mode"` + + shortNameAliasConf } // Nonempty returns true if config contains at least one configuration entry. @@ -162,10 +175,23 @@ func (config *V2RegistriesConf) Nonempty() bool { len(config.UnqualifiedSearchRegistries) != 0) } -// tomlConfig is the data type used to unmarshal the toml config. -type tomlConfig struct { - V2RegistriesConf - V1RegistriesConf // for backwards compatibility with sysregistries v1 +// parsedConfig is the result of parsing, and possibly merging, configuration files; +// it is the boundary between the process of reading+ingesting the files, and +// later interpreting the configuraiton based on caller’s requests. +type parsedConfig struct { + // NOTE: Update also parsedConfig.updateWithConfigurationFrom! + + // partialV2 must continue to exist to maintain the return value of TryUpdatingCache + // for compatibility with existing callers. + // We store the authoritative Registries and UnqualifiedSearchRegistries values there as well. + partialV2 V2RegistriesConf + // Absolute path to the configuration file that set the UnqualifiedSearchRegistries. + unqualifiedSearchRegistriesOrigin string + // Result of parsing of partialV2.ShortNameMode. + // NOTE: May be ShortNameModeInvalid to represent ShortNameMode == "" in intermediate values; + // the full configuration in configCache / getConfig() always contains a valid value. + shortNameMode types.ShortNameMode + aliasCache *shortNameAliasCache } // InvalidRegistries represents an invalid registry configurations. An example @@ -254,7 +280,7 @@ var anchoredDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.Strin // postProcess checks the consistency of all the configuration, looks for conflicts, // and normalizes the configuration (e.g., sets the Prefix to Location if not set). -func (config *V2RegistriesConf) postProcess() error { +func (config *V2RegistriesConf) postProcessRegistries() error { regMap := make(map[string][]*Registry) for i := range config.Registries { @@ -301,6 +327,7 @@ func (config *V2RegistriesConf) postProcess() error { msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'insecure' setting", reg.Location) return &InvalidRegistries{s: msg} } + if reg.Blocked != other.Blocked { msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'blocked' setting", reg.Location) return &InvalidRegistries{s: msg} @@ -323,16 +350,25 @@ func (config *V2RegistriesConf) postProcess() error { // rendering later items with the same prefix non-existent. We cannot error // out anymore as this might break existing users, so let's just ignore them // to guarantee that the same prefix exists only once. - knownPrefixes := make(map[string]bool) - uniqueRegistries := []Registry{} + // + // As a side effect of parsedConfig.updateWithConfigurationFrom, the Registries slice + // is always sorted. To be consistent in situations where it is not called (no drop-ins), + // sort it here as well. + prefixes := []string{} + uniqueRegistries := make(map[string]Registry) for i := range config.Registries { // TODO: should we warn if we see the same prefix being used multiple times? - if _, exists := knownPrefixes[config.Registries[i].Prefix]; !exists { - knownPrefixes[config.Registries[i].Prefix] = true - uniqueRegistries = append(uniqueRegistries, config.Registries[i]) + prefix := config.Registries[i].Prefix + if _, exists := uniqueRegistries[prefix]; !exists { + uniqueRegistries[prefix] = config.Registries[i] + prefixes = append(prefixes, prefix) } } - config.Registries = uniqueRegistries + sort.Strings(prefixes) + config.Registries = []Registry{} + for _, prefix := range prefixes { + config.Registries = append(config.Registries, uniqueRegistries[prefix]) + } return nil } @@ -385,6 +421,7 @@ func newConfigWrapper(ctx *types.SystemContext) configWrapper { } else { wrapper.userConfigDirPath = userRegistriesDirPath } + return wrapper } else if ctx != nil && ctx.RootForImplicitAbsolutePaths != "" { wrapper.configPath = filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesConfPath) @@ -426,7 +463,7 @@ var configMutex = sync.Mutex{} // configCache caches already loaded configs with config paths as keys and is // used to avoid redundantly parsing configs. Concurrent accesses to the cache // are synchronized via configMutex. -var configCache = make(map[configWrapper]*V2RegistriesConf) +var configCache = make(map[configWrapper]*parsedConfig) // InvalidateCache invalidates the registry cache. This function is meant to be // used for long-running processes that need to reload potential changes made to @@ -434,11 +471,11 @@ var configCache = make(map[configWrapper]*V2RegistriesConf) func InvalidateCache() { configMutex.Lock() defer configMutex.Unlock() - configCache = make(map[configWrapper]*V2RegistriesConf) + configCache = make(map[configWrapper]*parsedConfig) } // getConfig returns the config object corresponding to ctx, loading it if it is not yet cached. -func getConfig(ctx *types.SystemContext) (*V2RegistriesConf, error) { +func getConfig(ctx *types.SystemContext) (*parsedConfig, error) { wrapper := newConfigWrapper(ctx) configMutex.Lock() if config, inCache := configCache[wrapper]; inCache { @@ -504,27 +541,37 @@ func dropInConfigs(wrapper configWrapper) ([]string, error) { // TryUpdatingCache loads the configuration from the provided `SystemContext` // without using the internal cache. On success, the loaded configuration will // be added into the internal registry cache. +// It returns the resulting configuration; this is DEPRECATED and may not correctly +// reflect any future data handled by this package. func TryUpdatingCache(ctx *types.SystemContext) (*V2RegistriesConf, error) { - return tryUpdatingCache(ctx, newConfigWrapper(ctx)) + config, err := tryUpdatingCache(ctx, newConfigWrapper(ctx)) + if err != nil { + return nil, err + } + return &config.partialV2, err } // tryUpdatingCache implements TryUpdatingCache with an additional configWrapper // argument to avoid redundantly calculating the config paths. -func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*V2RegistriesConf, error) { +func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*parsedConfig, error) { configMutex.Lock() defer configMutex.Unlock() // load the config - config := &tomlConfig{} - if err := config.loadConfig(wrapper.configPath, false); err != nil { + config, err := loadConfigFile(wrapper.configPath, false) + if err != nil { // Continue with an empty []Registry if we use the default config, which // implies that the config path of the SystemContext isn't set. // // Note: if ctx.SystemRegistriesConfPath points to the default config, // we will still return an error. if os.IsNotExist(err) && (ctx == nil || ctx.SystemRegistriesConfPath == "") { - config = &tomlConfig{} - config.V2RegistriesConf = V2RegistriesConf{Registries: []Registry{}} + config = &parsedConfig{} + config.partialV2 = V2RegistriesConf{Registries: []Registry{}} + config.aliasCache, err = newShortNameAliasCache("", &shortNameAliasConf{}) + if err != nil { + return nil, err // Should never happen + } } else { return nil, errors.Wrapf(err, "error loading registries configuration %q", wrapper.configPath) } @@ -537,16 +584,20 @@ func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*V2Regis } for _, path := range dinConfigs { // Enforce v2 format for drop-in-configs. - if err := config.loadConfig(path, true); err != nil { + dropIn, err := loadConfigFile(path, true) + if err != nil { return nil, errors.Wrapf(err, "error loading drop-in registries configuration %q", path) } + config.updateWithConfigurationFrom(dropIn) } - v2Config := &config.V2RegistriesConf + if config.shortNameMode == types.ShortNameModeInvalid { + config.shortNameMode = defaultShortNameMode + } // populate the cache - configCache[wrapper] = v2Config - return v2Config, nil + configCache[wrapper] = config + return config, nil } // GetRegistries loads and returns the registries specified in the config. @@ -557,17 +608,53 @@ func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { if err != nil { return nil, err } - return config.Registries, nil + return config.partialV2.Registries, nil } // UnqualifiedSearchRegistries returns a list of host[:port] entries to try // for unqualified image search, in the returned order) func UnqualifiedSearchRegistries(ctx *types.SystemContext) ([]string, error) { + registries, _, err := UnqualifiedSearchRegistriesWithOrigin(ctx) + return registries, err +} + +// UnqualifiedSearchRegistriesWithOrigin returns a list of host[:port] entries +// to try for unqualified image search, in the returned order. It also returns +// a human-readable description of where these entries are specified (e.g., a +// registries.conf file). +func UnqualifiedSearchRegistriesWithOrigin(ctx *types.SystemContext) ([]string, string, error) { config, err := getConfig(ctx) if err != nil { - return nil, err + return nil, "", err } - return config.UnqualifiedSearchRegistries, nil + return config.partialV2.UnqualifiedSearchRegistries, config.unqualifiedSearchRegistriesOrigin, nil +} + +// parseShortNameMode translates the string into well-typed +// types.ShortNameMode. +func parseShortNameMode(mode string) (types.ShortNameMode, error) { + switch mode { + case "disabled": + return types.ShortNameModeDisabled, nil + case "enforcing": + return types.ShortNameModeEnforcing, nil + case "permissive": + return types.ShortNameModePermissive, nil + default: + return types.ShortNameModeInvalid, errors.Errorf("invalid short-name mode: %q", mode) + } +} + +// GetShortNameMode returns the configured types.ShortNameMode. +func GetShortNameMode(ctx *types.SystemContext) (types.ShortNameMode, error) { + if ctx != nil && ctx.ShortNameMode != nil { + return *ctx.ShortNameMode, nil + } + config, err := getConfig(ctx) + if err != nil { + return -1, err + } + return config.shortNameMode, err } // refMatchesPrefix returns true iff ref, @@ -609,7 +696,7 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) { reg := Registry{} prefixLen := 0 - for _, r := range config.Registries { + for _, r := range config.partialV2.Registries { if refMatchesPrefix(ref, r.Prefix) { length := len(r.Prefix) if length > prefixLen { @@ -624,55 +711,87 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) { return nil, nil } -// loadConfig loads and unmarshals the configuration at the specified path. Note -// that v1 configs are translated into v2 and are cleared. Use forceV2 if the -// config must in the v2 format. -// -// Note that specified fields in path will replace already set fields in the -// tomlConfig. Only the [[registry]] tables are merged by prefix. -func (c *tomlConfig) loadConfig(path string, forceV2 bool) error { +// loadConfigFile loads and unmarshals a single config file. +// Use forceV2 if the config must in the v2 format. +func loadConfigFile(path string, forceV2 bool) (*parsedConfig, error) { logrus.Debugf("Loading registries configuration %q", path) - // Save the registries before decoding the file where they could be lost. - // We merge them later again. - registryMap := make(map[string]Registry) - for i := range c.Registries { - registryMap[c.Registries[i].Prefix] = c.Registries[i] + // tomlConfig allows us to unmarshal either V1 or V2 simultaneously. + type tomlConfig struct { + V2RegistriesConf + V1RegistriesConf // for backwards compatibility with sysregistries v1 } // Load the tomlConfig. Note that `DecodeFile` will overwrite set fields. - c.Registries = nil // important to clear the memory to prevent us from overlapping fields - _, err := toml.DecodeFile(path, c) + var combinedTOML tomlConfig + _, err := toml.DecodeFile(path, &combinedTOML) if err != nil { - return err + return nil, err } - if c.V1RegistriesConf.Nonempty() { + if combinedTOML.V1RegistriesConf.Nonempty() { // Enforce the v2 format if requested. if forceV2 { - return &InvalidRegistries{s: "registry must be in v2 format but is in v1"} + return nil, &InvalidRegistries{s: "registry must be in v2 format but is in v1"} } // Convert a v1 config into a v2 config. - if c.V2RegistriesConf.Nonempty() { - return &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"} + if combinedTOML.V2RegistriesConf.Nonempty() { + return nil, &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"} } - v2, err := c.V1RegistriesConf.ConvertToV2() + converted, err := combinedTOML.V1RegistriesConf.ConvertToV2() if err != nil { - return err + return nil, err } - c.V1RegistriesConf = V1RegistriesConf{} - c.V2RegistriesConf = *v2 + combinedTOML.V1RegistriesConf = V1RegistriesConf{} + combinedTOML.V2RegistriesConf = *converted } + res := parsedConfig{partialV2: combinedTOML.V2RegistriesConf} + // Post process registries, set the correct prefixes, sanity checks, etc. - if err := c.postProcess(); err != nil { - return err + if err := res.partialV2.postProcessRegistries(); err != nil { + return nil, err } + res.unqualifiedSearchRegistriesOrigin = path + + if len(res.partialV2.ShortNameMode) > 0 { + mode, err := parseShortNameMode(res.partialV2.ShortNameMode) + if err != nil { + return nil, err + } + res.shortNameMode = mode + } else { + res.shortNameMode = types.ShortNameModeInvalid + } + + // Parse and validate short-name aliases. + cache, err := newShortNameAliasCache(path, &res.partialV2.shortNameAliasConf) + if err != nil { + return nil, errors.Wrap(err, "error validating short-name aliases") + } + res.aliasCache = cache + // Clear conf.partialV2.shortNameAliasConf to make it available for garbage collection and + // reduce memory consumption. We're consulting aliasCache for lookups. + res.partialV2.shortNameAliasConf = shortNameAliasConf{} + + return &res, nil +} + +// updateWithConfigurationFrom updates c with configuration from updates. +// +// Fields present in updates will typically replace already set fields in c. +// The [[registry]] and alias tables are merged. +func (c *parsedConfig) updateWithConfigurationFrom(updates *parsedConfig) { + // == Merge Registries: + registryMap := make(map[string]Registry) + for i := range c.partialV2.Registries { + registryMap[c.partialV2.Registries[i].Prefix] = c.partialV2.Registries[i] + } // Merge the freshly loaded registries. - for i := range c.Registries { - registryMap[c.Registries[i].Prefix] = c.Registries[i] + for i := range updates.partialV2.Registries { + registryMap[updates.partialV2.Registries[i].Prefix] = updates.partialV2.Registries[i] } // Go maps have a non-deterministic order when iterating the keys, so @@ -686,10 +805,27 @@ func (c *tomlConfig) loadConfig(path string, forceV2 bool) error { } sort.Strings(prefixes) - c.Registries = []Registry{} + c.partialV2.Registries = []Registry{} for _, prefix := range prefixes { - c.Registries = append(c.Registries, registryMap[prefix]) + c.partialV2.Registries = append(c.partialV2.Registries, registryMap[prefix]) } - return nil + // == Merge UnqualifiedSearchRegistries: + // This depends on an subtlety of the behavior of the TOML decoder, where a missing array field + // is not modified while unmarshaling (in our case remains to nil), while an [] is unmarshaled + // as a non-nil []string{}. + if updates.partialV2.UnqualifiedSearchRegistries != nil { + c.partialV2.UnqualifiedSearchRegistries = updates.partialV2.UnqualifiedSearchRegistries + c.unqualifiedSearchRegistriesOrigin = updates.unqualifiedSearchRegistriesOrigin + } + + // == Merge shortNameMode: + // We don’t maintain c.partialV2.ShortNameMode. + if updates.shortNameMode != types.ShortNameModeInvalid { + c.shortNameMode = updates.shortNameMode + } + + // == Merge aliasCache: + // We don’t maintain (in fact we actively clear) c.partialV2.shortNameAliasConf. + c.aliasCache.updateWithConfigurationFrom(updates.aliasCache) } diff --git a/vendor/github.com/containers/image/v5/types/types.go b/vendor/github.com/containers/image/v5/types/types.go index 5a91f0096..3c5126b4e 100644 --- a/vendor/github.com/containers/image/v5/types/types.go +++ b/vendor/github.com/containers/image/v5/types/types.go @@ -486,6 +486,36 @@ func NewOptionalBool(b bool) OptionalBool { return o } +// ShortNameMode defines the mode of short-name resolution. +// +// The use of unqualified-search registries entails an ambiguity as it's +// unclear from which registry a given image, referenced by a short name, may +// be pulled from. +// +// The ShortNameMode type defines how short names should resolve. +type ShortNameMode int + +const ( + ShortNameModeInvalid ShortNameMode = iota + // Use all configured unqualified-search registries without prompting + // the user. + ShortNameModeDisabled + // If stdout and stdin are a TTY, prompt the user to select a configured + // unqualified-search registry. Otherwise, use all configured + // unqualified-search registries. + // + // Note that if only one unqualified-search registry is set, it will be + // used without prompting. + ShortNameModePermissive + // Always prompt the user to select a configured unqualified-search + // registry. Throw an error if stdout or stdin is not a TTY as + // prompting isn't possible. + // + // Note that if only one unqualified-search registry is set, it will be + // used without prompting. + ShortNameModeEnforcing +) + // SystemContext allows parameterizing access to implicitly-accessed resources, // like configuration files in /etc and users' login state in their home directory. // Various components can share the same field only if their semantics is exactly @@ -509,6 +539,10 @@ type SystemContext struct { SystemRegistriesConfPath string // Path to the system-wide registries configuration directory SystemRegistriesConfDirPath string + // Path to the user-specific short-names configuration file + UserShortNameAliasConfPath string + // If set, short-name resolution in pkg/shortnames must follow the specified mode + ShortNameMode *ShortNameMode // If not "", overrides the default path for the authentication file, but only new format files AuthFilePath string // if not "", overrides the default path for the authentication file, but with the legacy format; diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index b6b79f26c..3ef1c2410 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -6,7 +6,7 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 5 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 7 + VersionMinor = 8 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 diff --git a/vendor/github.com/juju/ansiterm/LICENSE b/vendor/github.com/juju/ansiterm/LICENSE new file mode 100644 index 000000000..ade9307b3 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/LICENSE @@ -0,0 +1,191 @@ +All files in this repository are licensed as follows. If you contribute +to this repository, it is assumed that you license your contribution +under the same license unless you state otherwise. + +All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. + +This software is licensed under the LGPLv3, included below. + +As a special exception to the GNU Lesser General Public License version 3 +("LGPL3"), the copyright holders of this Library give you permission to +convey to a third party a Combined Work that links statically or dynamically +to this Library without providing any Minimal Corresponding Source or +Minimal Application Code as set out in 4d or providing the installation +information set out in section 4e, provided that you comply with the other +provisions of LGPL3 and provided that you meet, for the Application the +terms and conditions of the license(s) which apply to the Application. + +Except as stated in this special exception, the provisions of LGPL3 will +continue to comply in full to this Library. If you modify this Library, you +may apply this exception to your version of this Library, but you are not +obliged to do so. If you do not wish to do so, delete this exception +statement from your version. This exception does not (and cannot) modify any +license terms which apply to the Application, with which you must still +comply. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/github.com/juju/ansiterm/Makefile b/vendor/github.com/juju/ansiterm/Makefile new file mode 100644 index 000000000..212fdcbe5 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/Makefile @@ -0,0 +1,14 @@ +# Copyright 2016 Canonical Ltd. +# Licensed under the LGPLv3, see LICENCE file for details. + +default: check + +check: + go test + +docs: + godoc2md github.com/juju/ansiterm > README.md + sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/ansiterm?status.svg)](https://godoc.org/github.com/juju/ansiterm)|' README.md + + +.PHONY: default check docs diff --git a/vendor/github.com/juju/ansiterm/README.md b/vendor/github.com/juju/ansiterm/README.md new file mode 100644 index 000000000..567438721 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/README.md @@ -0,0 +1,323 @@ + +# ansiterm + import "github.com/juju/ansiterm" + +Package ansiterm provides a Writer that writes out the ANSI escape +codes for color and styles. + + + + + + + +## type Color +``` go +type Color int +``` +Color represents one of the standard 16 ANSI colors. + + + +``` go +const ( + Default Color + Black + Red + Green + Yellow + Blue + Magenta + Cyan + Gray + DarkGray + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + White +) +``` + + + + + + + + +### func (Color) String +``` go +func (c Color) String() string +``` +String returns the name of the color. + + + +## type Context +``` go +type Context struct { + Foreground Color + Background Color + Styles []Style +} +``` +Context provides a way to specify both foreground and background colors +along with other styles and write text to a Writer with those colors and +styles. + + + + + + + + + +### func Background +``` go +func Background(color Color) *Context +``` +Background is a convenience function that creates a Context with the +specified color as the background color. + + +### func Foreground +``` go +func Foreground(color Color) *Context +``` +Foreground is a convenience function that creates a Context with the +specified color as the foreground color. + + +### func Styles +``` go +func Styles(styles ...Style) *Context +``` +Styles is a convenience function that creates a Context with the +specified styles set. + + + + +### func (\*Context) Fprint +``` go +func (c *Context) Fprint(w sgrWriter, args ...interface{}) +``` +Fprint will set the sgr values of the writer to the specified foreground, +background and styles, then formats using the default formats for its +operands and writes to w. Spaces are added between operands when neither is +a string. It returns the number of bytes written and any write error +encountered. + + + +### func (\*Context) Fprintf +``` go +func (c *Context) Fprintf(w sgrWriter, format string, args ...interface{}) +``` +Fprintf will set the sgr values of the writer to the specified +foreground, background and styles, then write the formatted string, +then reset the writer. + + + +### func (\*Context) SetBackground +``` go +func (c *Context) SetBackground(color Color) *Context +``` +SetBackground sets the background to the specified color. + + + +### func (\*Context) SetForeground +``` go +func (c *Context) SetForeground(color Color) *Context +``` +SetForeground sets the foreground to the specified color. + + + +### func (\*Context) SetStyle +``` go +func (c *Context) SetStyle(styles ...Style) *Context +``` +SetStyle replaces the styles with the new values. + + + +## type Style +``` go +type Style int +``` + + +``` go +const ( + Bold Style + Faint + Italic + Underline + Blink + Reverse + Strikethrough + Conceal +) +``` + + + + + + + + +### func (Style) String +``` go +func (s Style) String() string +``` + + +## type TabWriter +``` go +type TabWriter struct { + Writer + // contains filtered or unexported fields +} +``` +TabWriter is a filter that inserts padding around tab-delimited +columns in its input to align them in the output. + +It also setting of colors and styles over and above the standard +tabwriter package. + + + + + + + + + +### func NewTabWriter +``` go +func NewTabWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter +``` +NewTabWriter returns a writer that is able to set colors and styels. +The ansi escape codes are stripped for width calculations. + + + + +### func (\*TabWriter) Flush +``` go +func (t *TabWriter) Flush() error +``` +Flush should be called after the last call to Write to ensure +that any data buffered in the Writer is written to output. Any +incomplete escape sequence at the end is considered +complete for formatting purposes. + + + +### func (\*TabWriter) Init +``` go +func (t *TabWriter) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter +``` +A Writer must be initialized with a call to Init. The first parameter (output) +specifies the filter output. The remaining parameters control the formatting: + + + minwidth minimal cell width including any padding + tabwidth width of tab characters (equivalent number of spaces) + padding padding added to a cell before computing its width + padchar ASCII char used for padding + if padchar == '\t', the Writer will assume that the + width of a '\t' in the formatted output is tabwidth, + and cells are left-aligned independent of align_left + (for correct-looking results, tabwidth must correspond + to the tab width in the viewer displaying the result) + flags formatting control + + + +## type Writer +``` go +type Writer struct { + io.Writer + // contains filtered or unexported fields +} +``` +Writer allows colors and styles to be specified. If the io.Writer +is not a terminal capable of color, all attempts to set colors or +styles are no-ops. + + + + + + + + + +### func NewWriter +``` go +func NewWriter(w io.Writer) *Writer +``` +NewWriter returns a Writer that allows the caller to specify colors and +styles. If the io.Writer is not a terminal capable of color, all attempts +to set colors or styles are no-ops. + + + + +### func (\*Writer) ClearStyle +``` go +func (w *Writer) ClearStyle(s Style) +``` +ClearStyle clears the text style. + + + +### func (\*Writer) Reset +``` go +func (w *Writer) Reset() +``` +Reset returns the default foreground and background colors with no styles. + + + +### func (\*Writer) SetBackground +``` go +func (w *Writer) SetBackground(c Color) +``` +SetBackground sets the background color. + + + +### func (\*Writer) SetForeground +``` go +func (w *Writer) SetForeground(c Color) +``` +SetForeground sets the foreground color. + + + +### func (\*Writer) SetStyle +``` go +func (w *Writer) SetStyle(s Style) +``` +SetStyle sets the text style. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) \ No newline at end of file diff --git a/vendor/github.com/juju/ansiterm/attribute.go b/vendor/github.com/juju/ansiterm/attribute.go new file mode 100644 index 000000000..f2daa4813 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/attribute.go @@ -0,0 +1,50 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "sort" + "strings" +) + +type attribute int + +const ( + unknownAttribute attribute = -1 + reset attribute = 0 +) + +// sgr returns the escape sequence for the Select Graphic Rendition +// for the attribute. +func (a attribute) sgr() string { + if a < 0 { + return "" + } + return fmt.Sprintf("\x1b[%dm", a) +} + +type attributes []attribute + +func (a attributes) Len() int { return len(a) } +func (a attributes) Less(i, j int) bool { return a[i] < a[j] } +func (a attributes) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// sgr returns the combined escape sequence for the Select Graphic Rendition +// for the sequence of attributes. +func (a attributes) sgr() string { + switch len(a) { + case 0: + return "" + case 1: + return a[0].sgr() + default: + sort.Sort(a) + var values []string + for _, attr := range a { + values = append(values, fmt.Sprint(attr)) + } + return fmt.Sprintf("\x1b[%sm", strings.Join(values, ";")) + } +} diff --git a/vendor/github.com/juju/ansiterm/color.go b/vendor/github.com/juju/ansiterm/color.go new file mode 100644 index 000000000..0a97de31e --- /dev/null +++ b/vendor/github.com/juju/ansiterm/color.go @@ -0,0 +1,119 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +const ( + _ Color = iota + Default + Black + Red + Green + Yellow + Blue + Magenta + Cyan + Gray + DarkGray + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + White +) + +// Color represents one of the standard 16 ANSI colors. +type Color int + +// String returns the name of the color. +func (c Color) String() string { + switch c { + case Default: + return "default" + case Black: + return "black" + case Red: + return "red" + case Green: + return "green" + case Yellow: + return "yellow" + case Blue: + return "blue" + case Magenta: + return "magenta" + case Cyan: + return "cyan" + case Gray: + return "gray" + case DarkGray: + return "darkgray" + case BrightRed: + return "brightred" + case BrightGreen: + return "brightgreen" + case BrightYellow: + return "brightyellow" + case BrightBlue: + return "brightblue" + case BrightMagenta: + return "brightmagenta" + case BrightCyan: + return "brightcyan" + case White: + return "white" + default: + return "" + } +} + +func (c Color) foreground() attribute { + switch c { + case Default: + return 39 + case Black: + return 30 + case Red: + return 31 + case Green: + return 32 + case Yellow: + return 33 + case Blue: + return 34 + case Magenta: + return 35 + case Cyan: + return 36 + case Gray: + return 37 + case DarkGray: + return 90 + case BrightRed: + return 91 + case BrightGreen: + return 92 + case BrightYellow: + return 93 + case BrightBlue: + return 94 + case BrightMagenta: + return 95 + case BrightCyan: + return 96 + case White: + return 97 + default: + return unknownAttribute + } +} + +func (c Color) background() attribute { + value := c.foreground() + if value != unknownAttribute { + return value + 10 + } + return value +} diff --git a/vendor/github.com/juju/ansiterm/context.go b/vendor/github.com/juju/ansiterm/context.go new file mode 100644 index 000000000..e61a867ff --- /dev/null +++ b/vendor/github.com/juju/ansiterm/context.go @@ -0,0 +1,95 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "io" +) + +// Context provides a way to specify both foreground and background colors +// along with other styles and write text to a Writer with those colors and +// styles. +type Context struct { + Foreground Color + Background Color + Styles []Style +} + +// Foreground is a convenience function that creates a Context with the +// specified color as the foreground color. +func Foreground(color Color) *Context { + return &Context{Foreground: color} +} + +// Background is a convenience function that creates a Context with the +// specified color as the background color. +func Background(color Color) *Context { + return &Context{Background: color} +} + +// Styles is a convenience function that creates a Context with the +// specified styles set. +func Styles(styles ...Style) *Context { + return &Context{Styles: styles} +} + +// SetForeground sets the foreground to the specified color. +func (c *Context) SetForeground(color Color) *Context { + c.Foreground = color + return c +} + +// SetBackground sets the background to the specified color. +func (c *Context) SetBackground(color Color) *Context { + c.Background = color + return c +} + +// SetStyle replaces the styles with the new values. +func (c *Context) SetStyle(styles ...Style) *Context { + c.Styles = styles + return c +} + +type sgrWriter interface { + io.Writer + writeSGR(value sgr) +} + +// Fprintf will set the sgr values of the writer to the specified +// foreground, background and styles, then write the formatted string, +// then reset the writer. +func (c *Context) Fprintf(w sgrWriter, format string, args ...interface{}) { + w.writeSGR(c) + fmt.Fprintf(w, format, args...) + w.writeSGR(reset) +} + +// Fprint will set the sgr values of the writer to the specified foreground, +// background and styles, then formats using the default formats for its +// operands and writes to w. Spaces are added between operands when neither is +// a string. It returns the number of bytes written and any write error +// encountered. +func (c *Context) Fprint(w sgrWriter, args ...interface{}) { + w.writeSGR(c) + fmt.Fprint(w, args...) + w.writeSGR(reset) +} + +func (c *Context) sgr() string { + var values attributes + if foreground := c.Foreground.foreground(); foreground != unknownAttribute { + values = append(values, foreground) + } + if background := c.Background.background(); background != unknownAttribute { + values = append(values, background) + } + for _, style := range c.Styles { + if value := style.enable(); value != unknownAttribute { + values = append(values, value) + } + } + return values.sgr() +} diff --git a/vendor/github.com/juju/ansiterm/doc.go b/vendor/github.com/juju/ansiterm/doc.go new file mode 100644 index 000000000..782700779 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/doc.go @@ -0,0 +1,6 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +// Package ansiterm provides a Writer that writes out the ANSI escape +// codes for color and styles. +package ansiterm diff --git a/vendor/github.com/juju/ansiterm/style.go b/vendor/github.com/juju/ansiterm/style.go new file mode 100644 index 000000000..0be42da56 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/style.go @@ -0,0 +1,72 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +const ( + _ Style = iota + Bold + Faint + Italic + Underline + Blink + Reverse + Strikethrough + Conceal +) + +type Style int + +func (s Style) String() string { + switch s { + case Bold: + return "bold" + case Faint: + return "faint" + case Italic: + return "italic" + case Underline: + return "underline" + case Blink: + return "blink" + case Reverse: + return "reverse" + case Strikethrough: + return "strikethrough" + case Conceal: + return "conceal" + default: + return "" + } +} + +func (s Style) enable() attribute { + switch s { + case Bold: + return 1 + case Faint: + return 2 + case Italic: + return 3 + case Underline: + return 4 + case Blink: + return 5 + case Reverse: + return 7 + case Conceal: + return 8 + case Strikethrough: + return 9 + default: + return unknownAttribute + } +} + +func (s Style) disable() attribute { + value := s.enable() + if value != unknownAttribute { + return value + 20 + } + return value +} diff --git a/vendor/github.com/juju/ansiterm/tabwriter.go b/vendor/github.com/juju/ansiterm/tabwriter.go new file mode 100644 index 000000000..1ff6faaaf --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter.go @@ -0,0 +1,64 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "io" + + "github.com/juju/ansiterm/tabwriter" +) + +// NewTabWriter returns a writer that is able to set colors and styels. +// The ansi escape codes are stripped for width calculations. +func NewTabWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { + return new(TabWriter).Init(output, minwidth, tabwidth, padding, padchar, flags) +} + +// TabWriter is a filter that inserts padding around tab-delimited +// columns in its input to align them in the output. +// +// It also setting of colors and styles over and above the standard +// tabwriter package. +type TabWriter struct { + Writer + tw tabwriter.Writer +} + +// Flush should be called after the last call to Write to ensure +// that any data buffered in the Writer is written to output. Any +// incomplete escape sequence at the end is considered +// complete for formatting purposes. +// +func (t *TabWriter) Flush() error { + return t.tw.Flush() +} + +// SetColumnAlignRight will mark a particular column as align right. +// This is reset on the next flush. +func (t *TabWriter) SetColumnAlignRight(column int) { + t.tw.SetColumnAlignRight(column) +} + +// A Writer must be initialized with a call to Init. The first parameter (output) +// specifies the filter output. The remaining parameters control the formatting: +// +// minwidth minimal cell width including any padding +// tabwidth width of tab characters (equivalent number of spaces) +// padding padding added to a cell before computing its width +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwidth, +// and cells are left-aligned independent of align_left +// (for correct-looking results, tabwidth must correspond +// to the tab width in the viewer displaying the result) +// flags formatting control +// +func (t *TabWriter) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { + writer, colorCapable := colorEnabledWriter(output) + t.Writer = Writer{ + Writer: t.tw.Init(writer, minwidth, tabwidth, padding, padchar, flags), + noColor: !colorCapable, + } + return t +} diff --git a/vendor/github.com/juju/ansiterm/tabwriter/LICENSE b/vendor/github.com/juju/ansiterm/tabwriter/LICENSE new file mode 100644 index 000000000..744875676 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go b/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go new file mode 100644 index 000000000..98949d036 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go @@ -0,0 +1,587 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is mostly a copy of the go standard library text/tabwriter. With +// the additional stripping of ansi control characters for width calculations. + +// Package tabwriter implements a write filter (tabwriter.Writer) that +// translates tabbed columns in input into properly aligned text. +// +// The package is using the Elastic Tabstops algorithm described at +// http://nickgravgaard.com/elastictabstops/index.html. +// +package tabwriter + +import ( + "bytes" + "io" + "unicode/utf8" + + "github.com/lunixbochs/vtclean" +) + +// ---------------------------------------------------------------------------- +// Filter implementation + +// A cell represents a segment of text terminated by tabs or line breaks. +// The text itself is stored in a separate buffer; cell only describes the +// segment's size in bytes, its width in runes, and whether it's an htab +// ('\t') terminated cell. +// +type cell struct { + size int // cell size in bytes + width int // cell width in runes + htab bool // true if the cell is terminated by an htab ('\t') +} + +// A Writer is a filter that inserts padding around tab-delimited +// columns in its input to align them in the output. +// +// The Writer treats incoming bytes as UTF-8 encoded text consisting +// of cells terminated by (horizontal or vertical) tabs or line +// breaks (newline or formfeed characters). Cells in adjacent lines +// constitute a column. The Writer inserts padding as needed to +// make all cells in a column have the same width, effectively +// aligning the columns. It assumes that all characters have the +// same width except for tabs for which a tabwidth must be specified. +// Note that cells are tab-terminated, not tab-separated: trailing +// non-tab text at the end of a line does not form a column cell. +// +// The Writer assumes that all Unicode code points have the same width; +// this may not be true in some fonts. +// +// If DiscardEmptyColumns is set, empty columns that are terminated +// entirely by vertical (or "soft") tabs are discarded. Columns +// terminated by horizontal (or "hard") tabs are not affected by +// this flag. +// +// If a Writer is configured to filter HTML, HTML tags and entities +// are passed through. The widths of tags and entities are +// assumed to be zero (tags) and one (entities) for formatting purposes. +// +// A segment of text may be escaped by bracketing it with Escape +// characters. The tabwriter passes escaped text segments through +// unchanged. In particular, it does not interpret any tabs or line +// breaks within the segment. If the StripEscape flag is set, the +// Escape characters are stripped from the output; otherwise they +// are passed through as well. For the purpose of formatting, the +// width of the escaped text is always computed excluding the Escape +// characters. +// +// The formfeed character ('\f') acts like a newline but it also +// terminates all columns in the current line (effectively calling +// Flush). Cells in the next line start new columns. Unless found +// inside an HTML tag or inside an escaped text segment, formfeed +// characters appear as newlines in the output. +// +// The Writer must buffer input internally, because proper spacing +// of one line may depend on the cells in future lines. Clients must +// call Flush when done calling Write. +// +type Writer struct { + // configuration + output io.Writer + minwidth int + tabwidth int + padding int + padbytes [8]byte + flags uint + + // current state + buf bytes.Buffer // collected text excluding tabs or line breaks + pos int // buffer position up to which cell.width of incomplete cell has been computed + cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections + endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0) + lines [][]cell // list of lines; each line is a list of cells + widths []int // list of column widths in runes - re-used during formatting + alignment map[int]uint // column alignment +} + +func (b *Writer) addLine() { b.lines = append(b.lines, []cell{}) } + +// Reset the current state. +func (b *Writer) reset() { + b.buf.Reset() + b.pos = 0 + b.cell = cell{} + b.endChar = 0 + b.lines = b.lines[0:0] + b.widths = b.widths[0:0] + b.alignment = make(map[int]uint) + b.addLine() +} + +// Internal representation (current state): +// +// - all text written is appended to buf; tabs and line breaks are stripped away +// - at any given time there is a (possibly empty) incomplete cell at the end +// (the cell starts after a tab or line break) +// - cell.size is the number of bytes belonging to the cell so far +// - cell.width is text width in runes of that cell from the start of the cell to +// position pos; html tags and entities are excluded from this width if html +// filtering is enabled +// - the sizes and widths of processed text are kept in the lines list +// which contains a list of cells for each line +// - the widths list is a temporary list with current widths used during +// formatting; it is kept in Writer because it's re-used +// +// |<---------- size ---------->| +// | | +// |<- width ->|<- ignored ->| | +// | | | | +// [---processed---tab------------......] +// ^ ^ ^ +// | | | +// buf start of incomplete cell pos + +// Formatting can be controlled with these flags. +const ( + // Ignore html tags and treat entities (starting with '&' + // and ending in ';') as single characters (width = 1). + FilterHTML uint = 1 << iota + + // Strip Escape characters bracketing escaped text segments + // instead of passing them through unchanged with the text. + StripEscape + + // Force right-alignment of cell content. + // Default is left-alignment. + AlignRight + + // Handle empty columns as if they were not present in + // the input in the first place. + DiscardEmptyColumns + + // Always use tabs for indentation columns (i.e., padding of + // leading empty cells on the left) independent of padchar. + TabIndent + + // Print a vertical bar ('|') between columns (after formatting). + // Discarded columns appear as zero-width columns ("||"). + Debug +) + +// A Writer must be initialized with a call to Init. The first parameter (output) +// specifies the filter output. The remaining parameters control the formatting: +// +// minwidth minimal cell width including any padding +// tabwidth width of tab characters (equivalent number of spaces) +// padding padding added to a cell before computing its width +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwidth, +// and cells are left-aligned independent of align_left +// (for correct-looking results, tabwidth must correspond +// to the tab width in the viewer displaying the result) +// flags formatting control +// +func (b *Writer) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer { + if minwidth < 0 || tabwidth < 0 || padding < 0 { + panic("negative minwidth, tabwidth, or padding") + } + b.output = output + b.minwidth = minwidth + b.tabwidth = tabwidth + b.padding = padding + for i := range b.padbytes { + b.padbytes[i] = padchar + } + if padchar == '\t' { + // tab padding enforces left-alignment + flags &^= AlignRight + } + b.flags = flags + + b.reset() + + return b +} + +// debugging support (keep code around) +func (b *Writer) dump() { + pos := 0 + for i, line := range b.lines { + print("(", i, ") ") + for _, c := range line { + print("[", string(b.buf.Bytes()[pos:pos+c.size]), "]") + pos += c.size + } + print("\n") + } + print("\n") +} + +// local error wrapper so we can distinguish errors we want to return +// as errors from genuine panics (which we don't want to return as errors) +type osError struct { + err error +} + +func (b *Writer) write0(buf []byte) { + n, err := b.output.Write(buf) + if n != len(buf) && err == nil { + err = io.ErrShortWrite + } + if err != nil { + panic(osError{err}) + } +} + +func (b *Writer) writeN(src []byte, n int) { + for n > len(src) { + b.write0(src) + n -= len(src) + } + b.write0(src[0:n]) +} + +var ( + newline = []byte{'\n'} + tabs = []byte("\t\t\t\t\t\t\t\t") +) + +func (b *Writer) writePadding(textw, cellw int, useTabs bool) { + if b.padbytes[0] == '\t' || useTabs { + // padding is done with tabs + if b.tabwidth == 0 { + return // tabs have no width - can't do any padding + } + // make cellw the smallest multiple of b.tabwidth + cellw = (cellw + b.tabwidth - 1) / b.tabwidth * b.tabwidth + n := cellw - textw // amount of padding + if n < 0 { + panic("internal error") + } + b.writeN(tabs, (n+b.tabwidth-1)/b.tabwidth) + return + } + + // padding is done with non-tab characters + b.writeN(b.padbytes[0:], cellw-textw) +} + +var vbar = []byte{'|'} + +func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) { + pos = pos0 + for i := line0; i < line1; i++ { + line := b.lines[i] + + // if TabIndent is set, use tabs to pad leading empty cells + useTabs := b.flags&TabIndent != 0 + + for j, c := range line { + if j > 0 && b.flags&Debug != 0 { + // indicate column break + b.write0(vbar) + } + + if c.size == 0 { + // empty cell + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], useTabs) + } + } else { + // non-empty cell + useTabs = false + alignColumnRight := b.alignment[j] == AlignRight + if (b.flags&AlignRight == 0) && !alignColumnRight { // align left + b.write0(b.buf.Bytes()[pos : pos+c.size]) + pos += c.size + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], false) + } + } else if alignColumnRight && j < len(b.widths) { + // just this column + internalSize := b.widths[j] - b.padding + if j < len(b.widths) { + b.writePadding(c.width, internalSize, false) + } + b.write0(b.buf.Bytes()[pos : pos+c.size]) + if b.padding > 0 { + b.writePadding(0, b.padding, false) + } + pos += c.size + } else { // align right + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], false) + } + b.write0(b.buf.Bytes()[pos : pos+c.size]) + pos += c.size + } + } + } + + if i+1 == len(b.lines) { + // last buffered line - we don't have a newline, so just write + // any outstanding buffered data + b.write0(b.buf.Bytes()[pos : pos+b.cell.size]) + pos += b.cell.size + } else { + // not the last line - write newline + b.write0(newline) + } + } + return +} + +// Format the text between line0 and line1 (excluding line1); pos +// is the buffer position corresponding to the beginning of line0. +// Returns the buffer position corresponding to the beginning of +// line1 and an error, if any. +// +func (b *Writer) format(pos0 int, line0, line1 int) (pos int) { + pos = pos0 + column := len(b.widths) + for this := line0; this < line1; this++ { + line := b.lines[this] + + if column < len(line)-1 { + // cell exists in this column => this line + // has more cells than the previous line + // (the last cell per line is ignored because cells are + // tab-terminated; the last cell per line describes the + // text before the newline/formfeed and does not belong + // to a column) + + // print unprinted lines until beginning of block + pos = b.writeLines(pos, line0, this) + line0 = this + + // column block begin + width := b.minwidth // minimal column width + discardable := true // true if all cells in this column are empty and "soft" + for ; this < line1; this++ { + line = b.lines[this] + if column < len(line)-1 { + // cell exists in this column + c := line[column] + // update width + if w := c.width + b.padding; w > width { + width = w + } + // update discardable + if c.width > 0 || c.htab { + discardable = false + } + } else { + break + } + } + // column block end + + // discard empty columns if necessary + if discardable && b.flags&DiscardEmptyColumns != 0 { + width = 0 + } + + // format and print all columns to the right of this column + // (we know the widths of this column and all columns to the left) + b.widths = append(b.widths, width) // push width + pos = b.format(pos, line0, this) + b.widths = b.widths[0 : len(b.widths)-1] // pop width + line0 = this + } + } + + // print unprinted lines until end + return b.writeLines(pos, line0, line1) +} + +// Append text to current cell. +func (b *Writer) append(text []byte) { + b.buf.Write(text) + b.cell.size += len(text) +} + +// Update the cell width. +func (b *Writer) updateWidth() { + // ---- Changes here ----- + newChars := b.buf.Bytes()[b.pos:b.buf.Len()] + cleaned := vtclean.Clean(string(newChars), false) // false to strip colors + b.cell.width += utf8.RuneCount([]byte(cleaned)) + // --- end of changes ---- + b.pos = b.buf.Len() +} + +// To escape a text segment, bracket it with Escape characters. +// For instance, the tab in this string "Ignore this tab: \xff\t\xff" +// does not terminate a cell and constitutes a single character of +// width one for formatting purposes. +// +// The value 0xff was chosen because it cannot appear in a valid UTF-8 sequence. +// +const Escape = '\xff' + +// Start escaped mode. +func (b *Writer) startEscape(ch byte) { + switch ch { + case Escape: + b.endChar = Escape + case '<': + b.endChar = '>' + case '&': + b.endChar = ';' + } +} + +// Terminate escaped mode. If the escaped text was an HTML tag, its width +// is assumed to be zero for formatting purposes; if it was an HTML entity, +// its width is assumed to be one. In all other cases, the width is the +// unicode width of the text. +// +func (b *Writer) endEscape() { + switch b.endChar { + case Escape: + b.updateWidth() + if b.flags&StripEscape == 0 { + b.cell.width -= 2 // don't count the Escape chars + } + case '>': // tag of zero width + case ';': + b.cell.width++ // entity, count as one rune + } + b.pos = b.buf.Len() + b.endChar = 0 +} + +// Terminate the current cell by adding it to the list of cells of the +// current line. Returns the number of cells in that line. +// +func (b *Writer) terminateCell(htab bool) int { + b.cell.htab = htab + line := &b.lines[len(b.lines)-1] + *line = append(*line, b.cell) + b.cell = cell{} + return len(*line) +} + +func handlePanic(err *error, op string) { + if e := recover(); e != nil { + if nerr, ok := e.(osError); ok { + *err = nerr.err + return + } + panic("tabwriter: panic during " + op) + } +} + +// Flush should be called after the last call to Write to ensure +// that any data buffered in the Writer is written to output. Any +// incomplete escape sequence at the end is considered +// complete for formatting purposes. +// +func (b *Writer) Flush() (err error) { + defer b.reset() // even in the presence of errors + defer handlePanic(&err, "Flush") + + // add current cell if not empty + if b.cell.size > 0 { + if b.endChar != 0 { + // inside escape - terminate it even if incomplete + b.endEscape() + } + b.terminateCell(false) + } + + // format contents of buffer + b.format(0, 0, len(b.lines)) + + return +} + +var hbar = []byte("---\n") + +// SetColumnAlignRight will mark a particular column as align right. +// This is reset on the next flush. +func (b *Writer) SetColumnAlignRight(column int) { + b.alignment[column] = AlignRight +} + +// Write writes buf to the writer b. +// The only errors returned are ones encountered +// while writing to the underlying output stream. +// +func (b *Writer) Write(buf []byte) (n int, err error) { + defer handlePanic(&err, "Write") + + // split text into cells + n = 0 + for i, ch := range buf { + if b.endChar == 0 { + // outside escape + switch ch { + case '\t', '\v', '\n', '\f': + // end of cell + b.append(buf[n:i]) + b.updateWidth() + n = i + 1 // ch consumed + ncells := b.terminateCell(ch == '\t') + if ch == '\n' || ch == '\f' { + // terminate line + b.addLine() + if ch == '\f' || ncells == 1 { + // A '\f' always forces a flush. Otherwise, if the previous + // line has only one cell which does not have an impact on + // the formatting of the following lines (the last cell per + // line is ignored by format()), thus we can flush the + // Writer contents. + if err = b.Flush(); err != nil { + return + } + if ch == '\f' && b.flags&Debug != 0 { + // indicate section break + b.write0(hbar) + } + } + } + + case Escape: + // start of escaped sequence + b.append(buf[n:i]) + b.updateWidth() + n = i + if b.flags&StripEscape != 0 { + n++ // strip Escape + } + b.startEscape(Escape) + + case '<', '&': + // possibly an html tag/entity + if b.flags&FilterHTML != 0 { + // begin of tag/entity + b.append(buf[n:i]) + b.updateWidth() + n = i + b.startEscape(ch) + } + } + + } else { + // inside escape + if ch == b.endChar { + // end of tag/entity + j := i + 1 + if ch == Escape && b.flags&StripEscape != 0 { + j = i // strip Escape + } + b.append(buf[n:j]) + n = i + 1 // ch consumed + b.endEscape() + } + } + } + + // append leftover text + b.append(buf[n:]) + n = len(buf) + return +} + +// NewWriter allocates and initializes a new tabwriter.Writer. +// The parameters are the same as for the Init function. +// +func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer { + return new(Writer).Init(output, minwidth, tabwidth, padding, padchar, flags) +} diff --git a/vendor/github.com/juju/ansiterm/terminal.go b/vendor/github.com/juju/ansiterm/terminal.go new file mode 100644 index 000000000..96fd11c51 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/terminal.go @@ -0,0 +1,32 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "io" + "os" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// colorEnabledWriter returns a writer that can handle the ansi color codes +// and true if the writer passed in is a terminal capable of color. If the +// TERM environment variable is set to "dumb", the terminal is not considered +// color capable. +func colorEnabledWriter(w io.Writer) (io.Writer, bool) { + f, ok := w.(*os.File) + if !ok { + return w, false + } + // Check the TERM environment variable specifically + // to check for "dumb" terminals. + if os.Getenv("TERM") == "dumb" { + return w, false + } + if !isatty.IsTerminal(f.Fd()) { + return w, false + } + return colorable.NewColorable(f), true +} diff --git a/vendor/github.com/juju/ansiterm/writer.go b/vendor/github.com/juju/ansiterm/writer.go new file mode 100644 index 000000000..32437bb27 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/writer.go @@ -0,0 +1,74 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "io" +) + +// Writer allows colors and styles to be specified. If the io.Writer +// is not a terminal capable of color, all attempts to set colors or +// styles are no-ops. +type Writer struct { + io.Writer + + noColor bool +} + +// NewWriter returns a Writer that allows the caller to specify colors and +// styles. If the io.Writer is not a terminal capable of color, all attempts +// to set colors or styles are no-ops. +func NewWriter(w io.Writer) *Writer { + writer, colorCapable := colorEnabledWriter(w) + return &Writer{ + Writer: writer, + noColor: !colorCapable, + } +} + +// SetColorCapable forces the writer to either write the ANSI escape color +// if capable is true, or to not write them if capable is false. +func (w *Writer) SetColorCapable(capable bool) { + w.noColor = !capable +} + +// SetForeground sets the foreground color. +func (w *Writer) SetForeground(c Color) { + w.writeSGR(c.foreground()) +} + +// SetBackground sets the background color. +func (w *Writer) SetBackground(c Color) { + w.writeSGR(c.background()) +} + +// SetStyle sets the text style. +func (w *Writer) SetStyle(s Style) { + w.writeSGR(s.enable()) +} + +// ClearStyle clears the text style. +func (w *Writer) ClearStyle(s Style) { + w.writeSGR(s.disable()) +} + +// Reset returns the default foreground and background colors with no styles. +func (w *Writer) Reset() { + w.writeSGR(reset) +} + +type sgr interface { + // sgr returns the combined escape sequence for the Select Graphic Rendition. + sgr() string +} + +// writeSGR takes the appropriate integer SGR parameters +// and writes out the ANIS escape code. +func (w *Writer) writeSGR(value sgr) { + if w.noColor { + return + } + fmt.Fprint(w, value.sgr()) +} diff --git a/vendor/github.com/lunixbochs/vtclean/.travis.yml b/vendor/github.com/lunixbochs/vtclean/.travis.yml new file mode 100644 index 000000000..fc0a54325 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/.travis.yml @@ -0,0 +1,9 @@ +language: go +sudo: false + +script: go test -v + +go: + - 1.5 + - 1.6 + - 1.7 diff --git a/vendor/github.com/lunixbochs/vtclean/LICENSE b/vendor/github.com/lunixbochs/vtclean/LICENSE new file mode 100644 index 000000000..42e82633f --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Ryan Hileman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/lunixbochs/vtclean/README.md b/vendor/github.com/lunixbochs/vtclean/README.md new file mode 100644 index 000000000..99910a460 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/README.md @@ -0,0 +1,46 @@ +[![Build Status](https://travis-ci.org/lunixbochs/vtclean.svg?branch=master)](https://travis-ci.org/lunixbochs/vtclean) + +vtclean +---- + +Clean up raw terminal output by stripping escape sequences, optionally preserving color. + +Get it: `go get github.com/lunixbochs/vtclean/vtclean` + +API: + + import "github.com/lunixbochs/vtclean" + vtclean.Clean(line string, color bool) string + +Command line example: + + $ echo -e '\x1b[1;32mcolor example + color forced to stop at end of line + backspace is ba\b\bgood + no beeps!\x07\x07' | ./vtclean -color + + color example + color forced to stop at end of line + backspace is good + no beeps! + +Go example: + + package main + + import ( + "fmt" + "github.com/lunixbochs/vtclean" + ) + + func main() { + line := vtclean.Clean( + "\033[1;32mcolor, " + + "curs\033[Aor, " + + "backspace\b\b\b\b\b\b\b\b\b\b\b\033[K", false) + fmt.Println(line) + } + +Output: + + color, cursor diff --git a/vendor/github.com/lunixbochs/vtclean/io.go b/vendor/github.com/lunixbochs/vtclean/io.go new file mode 100644 index 000000000..31be0076a --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/io.go @@ -0,0 +1,93 @@ +package vtclean + +import ( + "bufio" + "bytes" + "io" +) + +type reader struct { + io.Reader + scanner *bufio.Scanner + buf []byte + + color bool +} + +func NewReader(r io.Reader, color bool) io.Reader { + return &reader{Reader: r, color: color} +} + +func (r *reader) scan() bool { + if r.scanner == nil { + r.scanner = bufio.NewScanner(r.Reader) + } + if len(r.buf) > 0 { + return true + } + if r.scanner.Scan() { + r.buf = []byte(Clean(r.scanner.Text(), r.color) + "\n") + return true + } + return false +} + +func (r *reader) fill(p []byte) int { + n := len(r.buf) + copy(p, r.buf) + if len(p) < len(r.buf) { + r.buf = r.buf[len(p):] + n = len(p) + } else { + r.buf = nil + } + return n +} + +func (r *reader) Read(p []byte) (int, error) { + n := r.fill(p) + if n < len(p) { + if !r.scan() { + if n == 0 { + return 0, io.EOF + } + return n, nil + } + n += r.fill(p[n:]) + } + return n, nil +} + +type writer struct { + io.Writer + buf []byte + color bool +} + +func NewWriter(w io.Writer, color bool) io.WriteCloser { + return &writer{Writer: w, color: color} +} + +func (w *writer) Write(p []byte) (int, error) { + buf := append(w.buf, p...) + lines := bytes.Split(buf, []byte("\n")) + if len(lines) > 0 { + last := len(lines) - 1 + w.buf = lines[last] + count := 0 + for _, line := range lines[:last] { + n, err := w.Writer.Write([]byte(Clean(string(line), w.color) + "\n")) + count += n + if err != nil { + return count, err + } + } + } + return len(p), nil +} + +func (w *writer) Close() error { + cl := Clean(string(w.buf), w.color) + _, err := w.Writer.Write([]byte(cl)) + return err +} diff --git a/vendor/github.com/lunixbochs/vtclean/line.go b/vendor/github.com/lunixbochs/vtclean/line.go new file mode 100644 index 000000000..66ee990be --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/line.go @@ -0,0 +1,113 @@ +package vtclean + +type char struct { + char byte + vt100 []byte +} + +func chars(p []byte) []char { + tmp := make([]char, len(p)) + for i, v := range p { + tmp[i].char = v + } + return tmp +} + +type lineEdit struct { + buf []char + pos, size int + vt100 []byte +} + +func newLineEdit(length int) *lineEdit { + return &lineEdit{buf: make([]char, length)} +} + +func (l *lineEdit) Vt100(p []byte) { + l.vt100 = p +} + +func (l *lineEdit) Move(x int) { + if x < 0 && l.pos <= -x { + l.pos = 0 + } else if x > 0 && l.pos+x > l.size { + l.pos = l.size + } else { + l.pos += x + } +} + +func (l *lineEdit) MoveAbs(x int) { + if x < l.size { + l.pos = x + } +} + +func (l *lineEdit) Write(p []byte) { + c := chars(p) + if len(c) > 0 { + c[0].vt100 = l.vt100 + l.vt100 = nil + } + if len(l.buf)-l.pos < len(c) { + l.buf = append(l.buf[:l.pos], c...) + } else { + copy(l.buf[l.pos:], c) + } + l.pos += len(c) + if l.pos > l.size { + l.size = l.pos + } +} + +func (l *lineEdit) Insert(p []byte) { + c := chars(p) + if len(c) > 0 { + c[0].vt100 = l.vt100 + l.vt100 = nil + } + l.size += len(c) + c = append(c, l.buf[l.pos:]...) + l.buf = append(l.buf[:l.pos], c...) +} + +func (l *lineEdit) Delete(n int) { + most := l.size - l.pos + if n > most { + n = most + } + copy(l.buf[l.pos:], l.buf[l.pos+n:]) + l.size -= n +} + +func (l *lineEdit) Clear() { + for i := 0; i < len(l.buf); i++ { + l.buf[i].char = ' ' + } +} +func (l *lineEdit) ClearLeft() { + for i := 0; i < l.pos+1; i++ { + l.buf[i].char = ' ' + } +} +func (l *lineEdit) ClearRight() { + l.size = l.pos +} + +func (l *lineEdit) Bytes() []byte { + length := 0 + buf := l.buf[:l.size] + for _, v := range buf { + length += 1 + len(v.vt100) + } + tmp := make([]byte, 0, length) + for _, v := range buf { + tmp = append(tmp, v.vt100...) + tmp = append(tmp, v.char) + } + return tmp +} + +func (l *lineEdit) String() string { + return string(l.Bytes()) +} diff --git a/vendor/github.com/lunixbochs/vtclean/vtclean.go b/vendor/github.com/lunixbochs/vtclean/vtclean.go new file mode 100644 index 000000000..64fe01fdb --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/vtclean.go @@ -0,0 +1,95 @@ +package vtclean + +import ( + "bytes" + "regexp" + "strconv" +) + +// regex based on ECMA-48: +// 1. optional: +// one of [ or ] +// any amount of 0x30-0x3f +// any amount of 0x20-0x2f +// 3. exactly one 0x40-0x7e +var vt100re = regexp.MustCompile(`^\033([\[\]]([0-9:;<=>\?]*)([!"#$%&'()*+,\-./]*))?([@A-Z\[\]^_\x60a-z{|}~])`) +var vt100exc = regexp.MustCompile(`^\033(\[[^a-zA-Z0-9@\?]+|[\(\)]).`) + +// this is to handle the RGB escape generated by `tput initc 1 500 500 500` +var vt100long = regexp.MustCompile(`^\033](\d+);([^\033]+)\033\\`) + +func Clean(line string, color bool) string { + var edit = newLineEdit(len(line)) + lineb := []byte(line) + + hadColor := false + for i := 0; i < len(lineb); { + c := lineb[i] + switch c { + case '\r': + edit.MoveAbs(0) + case '\b': + edit.Move(-1) + case '\033': + // set terminal title + if bytes.HasPrefix(lineb[i:], []byte("\x1b]0;")) { + pos := bytes.Index(lineb[i:], []byte("\a")) + if pos != -1 { + i += pos + 1 + continue + } + } + if m := vt100long.Find(lineb[i:]); m != nil { + i += len(m) + } else if m := vt100exc.Find(lineb[i:]); m != nil { + i += len(m) + } else if m := vt100re.FindSubmatch(lineb[i:]); m != nil { + i += len(m[0]) + num := string(m[2]) + n, err := strconv.Atoi(num) + if err != nil || n > 10000 { + n = 1 + } + switch m[4][0] { + case 'm': + if color { + hadColor = true + edit.Vt100(m[0]) + } + case '@': + edit.Insert(bytes.Repeat([]byte{' '}, n)) + case 'G': + edit.MoveAbs(n) + case 'C': + edit.Move(n) + case 'D': + edit.Move(-n) + case 'P': + edit.Delete(n) + case 'K': + switch num { + case "", "0": + edit.ClearRight() + case "1": + edit.ClearLeft() + case "2": + edit.Clear() + } + } + } else { + i += 1 + } + continue + default: + if c == '\n' || c == '\t' || c >= ' ' { + edit.Write([]byte{c}) + } + } + i += 1 + } + out := edit.Bytes() + if hadColor { + out = append(out, []byte("\033[0m")...) + } + return string(out) +} diff --git a/vendor/github.com/manifoldco/promptui/.gitignore b/vendor/github.com/manifoldco/promptui/.gitignore new file mode 100644 index 000000000..8ee1778b5 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.gitignore @@ -0,0 +1,3 @@ +vendor +all-cover.txt +bin/ diff --git a/vendor/github.com/manifoldco/promptui/.golangci.yml b/vendor/github.com/manifoldco/promptui/.golangci.yml new file mode 100644 index 000000000..e232bfbe5 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.golangci.yml @@ -0,0 +1,26 @@ +run: + deadline: 5m + +issues: + # Disable maximums so we see all issues + max-per-linter: 0 + max-same-issues: 0 + + # golangci-lint ignores missing docstrings by default. That's no good! + exclude-use-default: false + +linters: + disable-all: true + enable: + - misspell + - golint + - goimports + - ineffassign + - deadcode + - gofmt + - govet + - structcheck + - unconvert + - megacheck + - typecheck + - varcheck diff --git a/vendor/github.com/manifoldco/promptui/.travis.yml b/vendor/github.com/manifoldco/promptui/.travis.yml new file mode 100644 index 000000000..01c16c440 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.travis.yml @@ -0,0 +1,14 @@ +dist: bionic +language: go + +go: + - "1.12.x" + - "1.13.x" + +branches: + only: + - master + +after_success: + # only report coverage for go-version 1.11 + - if [[ $TRAVIS_GO_VERSION =~ ^1\.11 ]] ; then bash <(curl -s https://codecov.io/bash) -f all-cover.txt; fi diff --git a/vendor/github.com/manifoldco/promptui/CHANGELOG.md b/vendor/github.com/manifoldco/promptui/CHANGELOG.md new file mode 100644 index 000000000..563e9d00a --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/CHANGELOG.md @@ -0,0 +1,123 @@ +# CHANGELOG + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## Unreleased + +## [0.8.0] - 2020-09-28 + +### Added + +- Support ctrl-h for backspace +- Allow hiding entered data after submit +- Allow masking input with an empty rune to hide input length + +### Fixed + +- Fix echo of cursor after input is finished +- Better support for keycodes on Windows + + +## [0.7.0] - 2020-01-11 + +### Added + +- Add support for configurable Stdin/Stdout on Prompt +- Add support for setting initial cursor position +- Switch to golangci-lint for linting + +### Removed + +- Removed support for Go 1.11 + +### Fixed + +- Reduce tool-based deps, hopefully fixing any install issues + +## [0.6.0] - 2019-11-29 + +### Added + +- Support configurable stdin + +### Fixed + +- Correct the dep on go-i18n + +## [0.5.0] - 2019-11-29 + +### Added + +- Now building and testing on go 1.11, go 1.12, and go 1.13 + +### Removed + +- Removed support for Go versions that don't include modules. + +## [0.4.0] - 2019-02-19 + +### Added + +- The text displayed when an item was successfully selected can be hidden + +## [0.3.2] - 2018-11-26 + +### Added + +- Support Go modules + +### Fixed + +- Fix typos in PromptTemplates documentation + +## [0.3.1] - 2018-07-26 + +### Added + +- Improved documentation for GoDoc +- Navigation keys information for Windows + +### Fixed + +- `success` template was not properly displayed after a successful prompt. + +## [0.3.0] - 2018-05-22 + +### Added + +- Background colors codes and template helpers +- `AllowEdit` for prompt to prevent deletion of the default value by any key +- Added `StartInSearchMode` to allow starting the prompt in search mode + +### Fixed + +- `` key press on Windows +- `juju/ansiterm` dependency +- `chzyer/readline#136` new api with ReadCloser +- Deleting UTF-8 characters sequence + +## [0.2.1] - 2017-11-30 + +### Fixed + +- `SelectWithAdd` panicking on `.Run` due to lack of keys setup +- Backspace key on Windows + +## [0.2.0] - 2017-11-16 + +### Added + +- `Select` items can now be searched + +## [0.1.0] - 2017-11-02 + +### Added + +- extract `promptui` from [torus](https://github.com/manifoldco/torus-cli) as a + standalone lib. +- `promptui.Prompt` provides a single input line to capture user information. +- `promptui.Select` provides a list of options to choose from. Users can + navigate through the list either one item at time or by pagination diff --git a/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md b/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..cc58cce02 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual +identity and orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behaviour and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviours that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. Representation of a +project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at +[hello@manifold.co](mailto:hello@manifold.co). All complaints will be reviewed +and investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +[http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4). diff --git a/vendor/github.com/manifoldco/promptui/LICENSE.md b/vendor/github.com/manifoldco/promptui/LICENSE.md new file mode 100644 index 000000000..3ae687b2c --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Arigato Machine Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/manifoldco/promptui/Makefile b/vendor/github.com/manifoldco/promptui/Makefile new file mode 100644 index 000000000..078a5613a --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/Makefile @@ -0,0 +1,49 @@ +export GO111MODULE := on +export PATH := ./bin:$(PATH) + +ci: bootstrap lint cover +.PHONY: ci + +################################################# +# Bootstrapping for base golang package and tool deps +################################################# + +bootstrap: + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.21.0 +.PHONY: bootstrap + +mod-update: + go get -u -m + go mod tidy + +mod-tidy: + go mod tidy + +.PHONY: $(CMD_PKGS) +.PHONY: mod-update mod-tidy + +################################################# +# Test and linting +################################################# +# Run all the linters +lint: + bin/golangci-lint run ./... +.PHONY: lint + +test: + CGO_ENABLED=0 go test $$(go list ./... | grep -v generated) +.PHONY: test + +COVER_TEST_PKGS:=$(shell find . -type f -name '*_test.go' | rev | cut -d "/" -f 2- | rev | grep -v generated | sort -u) +$(COVER_TEST_PKGS:=-cover): %-cover: all-cover.txt + @CGO_ENABLED=0 go test -v -coverprofile=$@.out -covermode=atomic ./$* + @if [ -f $@.out ]; then \ + grep -v "mode: atomic" < $@.out >> all-cover.txt; \ + rm $@.out; \ + fi + +all-cover.txt: + echo "mode: atomic" > all-cover.txt + +cover: all-cover.txt $(COVER_TEST_PKGS:=-cover) +.PHONY: cover all-cover.txt diff --git a/vendor/github.com/manifoldco/promptui/README.md b/vendor/github.com/manifoldco/promptui/README.md new file mode 100644 index 000000000..4fd4dc6d4 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/README.md @@ -0,0 +1,107 @@ +# promptui + +Interactive prompt for command-line applications. + +We built Promptui because we wanted to make it easy and fun to explore cloud +services with [manifold cli](https://github.com/manifoldco/manifold-cli). + +[Code of Conduct](./CODE_OF_CONDUCT.md) | +[Contribution Guidelines](./.github/CONTRIBUTING.md) + +[![GitHub release](https://img.shields.io/github/tag/manifoldco/promptui.svg?label=latest)](https://github.com/manifoldco/promptui/releases) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/manifoldco/promptui) +[![Travis](https://img.shields.io/travis/manifoldco/promptui/master.svg)](https://travis-ci.org/manifoldco/promptui) +[![Go Report Card](https://goreportcard.com/badge/github.com/manifoldco/promptui)](https://goreportcard.com/report/github.com/manifoldco/promptui) +[![License](https://img.shields.io/badge/license-BSD-blue.svg)](./LICENSE.md) + +## Overview + +![promptui](https://media.giphy.com/media/xUNda0Ngb5qsogLsBi/giphy.gif) + +Promptui is a library providing a simple interface to create command-line +prompts for go. It can be easily integrated into +[spf13/cobra](https://github.com/spf13/cobra), +[urfave/cli](https://github.com/urfave/cli) or any cli go application. + +Promptui has two main input modes: + +- `Prompt` provides a single line for user input. Prompt supports + optional live validation, confirmation and masking the input. + +- `Select` provides a list of options to choose from. Select supports + pagination, search, detailed view and custom templates. + +For a full list of options check [GoDoc](https://godoc.org/github.com/manifoldco/promptui). + +## Basic Usage + +### Prompt + +```go +package main + +import ( + "errors" + "fmt" + "strconv" + + "github.com/manifoldco/promptui" +) + +func main() { + validate := func(input string) error { + _, err := strconv.ParseFloat(input, 64) + if err != nil { + return errors.New("Invalid number") + } + return nil + } + + prompt := promptui.Prompt{ + Label: "Number", + Validate: validate, + } + + result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", result) +} +``` + +### Select + +```go +package main + +import ( + "fmt" + + "github.com/manifoldco/promptui" +) + +func main() { + prompt := promptui.Select{ + Label: "Select Day", + Items: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", + "Saturday", "Sunday"}, + } + + _, result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", result) +} +``` + +### More Examples + +See full list of [examples](https://github.com/manifoldco/promptui/tree/master/_examples) diff --git a/vendor/github.com/manifoldco/promptui/codes.go b/vendor/github.com/manifoldco/promptui/codes.go new file mode 100644 index 000000000..8138c40d6 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/codes.go @@ -0,0 +1,120 @@ +package promptui + +import ( + "fmt" + "strconv" + "strings" + "text/template" +) + +const esc = "\033[" + +type attribute int + +// The possible state of text inside the application, either Bold, faint, italic or underline. +// +// These constants are called through the use of the Styler function. +const ( + reset attribute = iota + + FGBold + FGFaint + FGItalic + FGUnderline +) + +// The possible colors of text inside the application. +// +// These constants are called through the use of the Styler function. +const ( + FGBlack attribute = iota + 30 + FGRed + FGGreen + FGYellow + FGBlue + FGMagenta + FGCyan + FGWhite +) + +// The possible background colors of text inside the application. +// +// These constants are called through the use of the Styler function. +const ( + BGBlack attribute = iota + 40 + BGRed + BGGreen + BGYellow + BGBlue + BGMagenta + BGCyan + BGWhite +) + +// ResetCode is the character code used to reset the terminal formatting +var ResetCode = fmt.Sprintf("%s%dm", esc, reset) + +const ( + hideCursor = esc + "?25l" + showCursor = esc + "?25h" + clearLine = esc + "2K" +) + +// FuncMap defines template helpers for the output. It can be extended as a regular map. +// +// The functions inside the map link the state, color and background colors strings detected in templates to a Styler +// function that applies the given style using the corresponding constant. +var FuncMap = template.FuncMap{ + "black": Styler(FGBlack), + "red": Styler(FGRed), + "green": Styler(FGGreen), + "yellow": Styler(FGYellow), + "blue": Styler(FGBlue), + "magenta": Styler(FGMagenta), + "cyan": Styler(FGCyan), + "white": Styler(FGWhite), + "bgBlack": Styler(BGBlack), + "bgRed": Styler(BGRed), + "bgGreen": Styler(BGGreen), + "bgYellow": Styler(BGYellow), + "bgBlue": Styler(BGBlue), + "bgMagenta": Styler(BGMagenta), + "bgCyan": Styler(BGCyan), + "bgWhite": Styler(BGWhite), + "bold": Styler(FGBold), + "faint": Styler(FGFaint), + "italic": Styler(FGItalic), + "underline": Styler(FGUnderline), +} + +func upLine(n uint) string { + return movementCode(n, 'A') +} + +func movementCode(n uint, code rune) string { + return esc + strconv.FormatUint(uint64(n), 10) + string(code) +} + +// Styler is a function that accepts multiple possible styling transforms from the state, +// color and background colors constants and transforms them into a templated string +// to apply those styles in the CLI. +// +// The returned styling function accepts a string that will be extended with +// the wrapping function's styling attributes. +func Styler(attrs ...attribute) func(interface{}) string { + attrstrs := make([]string, len(attrs)) + for i, v := range attrs { + attrstrs[i] = strconv.Itoa(int(v)) + } + + seq := strings.Join(attrstrs, ";") + + return func(v interface{}) string { + end := "" + s, ok := v.(string) + if !ok || !strings.HasSuffix(s, ResetCode) { + end = ResetCode + } + return fmt.Sprintf("%s%sm%v%s", esc, seq, v, end) + } +} diff --git a/vendor/github.com/manifoldco/promptui/cursor.go b/vendor/github.com/manifoldco/promptui/cursor.go new file mode 100644 index 000000000..7f0961bdb --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/cursor.go @@ -0,0 +1,232 @@ +package promptui + +import ( + "fmt" + "strings" +) + +// Pointer is A specific type that translates a given set of runes into a given +// set of runes pointed at by the cursor. +type Pointer func(to []rune) []rune + +func defaultCursor(ignored []rune) []rune { + return []rune("\u2588") +} + +func blockCursor(input []rune) []rune { + return []rune(fmt.Sprintf("\\e[7m%s\\e[0m", string(input))) +} + +func pipeCursor(input []rune) []rune { + marker := []rune("|") + out := []rune{} + out = append(out, marker...) + out = append(out, input...) + return out +} + +var ( + // DefaultCursor is a big square block character. Obscures whatever was + // input. + DefaultCursor Pointer = defaultCursor + // BlockCursor is a cursor which highlights a character by inverting colors + // on it. + BlockCursor Pointer = blockCursor + // PipeCursor is a pipe character "|" which appears before the input + // character. + PipeCursor Pointer = pipeCursor +) + +// Cursor tracks the state associated with the movable cursor +// The strategy is to keep the prompt, input pristine except for requested +// modifications. The insertion of the cursor happens during a `format` call +// and we read in new input via an `Update` call +type Cursor struct { + // shows where the user inserts/updates text + Cursor Pointer + // what the user entered, and what we will echo back to them, after + // insertion of the cursor and prefixing with the prompt + input []rune + // Put the cursor before this slice + Position int + erase bool +} + +// NewCursor create a new cursor, with the DefaultCursor, the specified input, +// and position at the end of the specified starting input. +func NewCursor(startinginput string, pointer Pointer, eraseDefault bool) Cursor { + if pointer == nil { + pointer = defaultCursor + } + cur := Cursor{Cursor: pointer, Position: len(startinginput), input: []rune(startinginput), erase: eraseDefault} + if eraseDefault { + cur.Start() + } else { + cur.End() + } + return cur +} + +func (c *Cursor) String() string { + return fmt.Sprintf( + "Cursor: %s, input %s, Position %d", + string(c.Cursor([]rune(""))), string(c.input), c.Position) +} + +// End is a convenience for c.Place(len(c.input)) so you don't have to know how I +// indexed. +func (c *Cursor) End() { + c.Place(len(c.input)) +} + +// Start is convenience for c.Place(0) so you don't have to know how I +// indexed. +func (c *Cursor) Start() { + c.Place(0) +} + +// ensures we are in bounds. +func (c *Cursor) correctPosition() { + if c.Position > len(c.input) { + c.Position = len(c.input) + } + + if c.Position < 0 { + c.Position = 0 + } +} + +// insert the cursor rune array into r before the provided index +func format(a []rune, c *Cursor) string { + i := c.Position + var b []rune + + out := make([]rune, 0) + if i < len(a) { + b = c.Cursor(a[i : i+1]) + out = append(out, a[:i]...) // does not include i + out = append(out, b...) // add the cursor + out = append(out, a[i+1:]...) // add the rest after i + } else { + b = c.Cursor([]rune{}) + out = append(out, a...) + out = append(out, b...) + } + return string(out) +} + +// Format renders the input with the Cursor appropriately positioned. +func (c *Cursor) Format() string { + r := c.input + // insert the cursor + return format(r, c) +} + +// FormatMask replaces all input runes with the mask rune. +func (c *Cursor) FormatMask(mask rune) string { + if mask == ' ' { + return format([]rune{}, c) + } + + r := make([]rune, len(c.input)) + for i := range r { + r[i] = mask + } + return format(r, c) +} + +// Update inserts newinput into the input []rune in the appropriate place. +// The cursor is moved to the end of the inputed sequence. +func (c *Cursor) Update(newinput string) { + a := c.input + b := []rune(newinput) + i := c.Position + a = append(a[:i], append(b, a[i:]...)...) + c.input = a + c.Move(len(b)) +} + +// Get returns a copy of the input +func (c *Cursor) Get() string { + return string(c.input) +} + +// GetMask returns a mask string with length equal to the input +func (c *Cursor) GetMask(mask rune) string { + return strings.Repeat(string(mask), len(c.input)) +} + +// Replace replaces the previous input with whatever is specified, and moves the +// cursor to the end position +func (c *Cursor) Replace(input string) { + c.input = []rune(input) + c.End() +} + +// Place moves the cursor to the absolute array index specified by position +func (c *Cursor) Place(position int) { + c.Position = position + c.correctPosition() +} + +// Move moves the cursor over in relative terms, by shift indices. +func (c *Cursor) Move(shift int) { + // delete the current cursor + c.Position = c.Position + shift + c.correctPosition() +} + +// Backspace removes the rune that precedes the cursor +// +// It handles being at the beginning or end of the row, and moves the cursor to +// the appropriate position. +func (c *Cursor) Backspace() { + a := c.input + i := c.Position + if i == 0 { + // Shrug + return + } + if i == len(a) { + c.input = a[:i-1] + } else { + c.input = append(a[:i-1], a[i:]...) + } + // now it's pointing to the i+1th element + c.Move(-1) +} + +// Listen is a readline Listener that updates internal cursor state appropriately. +func (c *Cursor) Listen(line []rune, pos int, key rune) ([]rune, int, bool) { + if line != nil { + // no matter what, update our internal representation. + c.Update(string(line)) + } + + switch key { + case 0: // empty + case KeyEnter: + return []rune(c.Get()), c.Position, false + case KeyBackspace, KeyCtrlH: + if c.erase { + c.erase = false + c.Replace("") + } + c.Backspace() + case KeyForward: + // the user wants to edit the default, despite how we set it up. Let + // them. + c.erase = false + c.Move(1) + case KeyBackward: + c.Move(-1) + default: + if c.erase { + c.erase = false + c.Replace("") + c.Update(string(key)) + } + } + + return []rune(c.Get()), c.Position, true +} diff --git a/vendor/github.com/manifoldco/promptui/go.mod b/vendor/github.com/manifoldco/promptui/go.mod new file mode 100644 index 000000000..760a44deb --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/go.mod @@ -0,0 +1,16 @@ +module github.com/manifoldco/promptui + +go 1.12 + +require ( + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a + github.com/kr/pretty v0.1.0 // indirect + github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/vendor/github.com/manifoldco/promptui/go.sum b/vendor/github.com/manifoldco/promptui/go.sum new file mode 100644 index 000000000..be5f99025 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/go.sum @@ -0,0 +1,23 @@ +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +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= +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/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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= diff --git a/vendor/github.com/manifoldco/promptui/keycodes.go b/vendor/github.com/manifoldco/promptui/keycodes.go new file mode 100644 index 000000000..ef5cd6f0a --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes.go @@ -0,0 +1,29 @@ +package promptui + +import "github.com/chzyer/readline" + +// These runes are used to identify the commands entered by the user in the command prompt. They map +// to specific actions of promptui in prompt mode and can be remapped if necessary. +var ( + // KeyEnter is the default key for submission/selection. + KeyEnter rune = readline.CharEnter + + // KeyCtrlH is the key for deleting input text. + KeyCtrlH rune = readline.CharCtrlH + + // KeyPrev is the default key to go up during selection. + KeyPrev rune = readline.CharPrev + KeyPrevDisplay = "↑" + + // KeyNext is the default key to go down during selection. + KeyNext rune = readline.CharNext + KeyNextDisplay = "↓" + + // KeyBackward is the default key to page up during selection. + KeyBackward rune = readline.CharBackward + KeyBackwardDisplay = "←" + + // KeyForward is the default key to page down during selection. + KeyForward rune = readline.CharForward + KeyForwardDisplay = "→" +) diff --git a/vendor/github.com/manifoldco/promptui/keycodes_other.go b/vendor/github.com/manifoldco/promptui/keycodes_other.go new file mode 100644 index 000000000..789feae4c --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes_other.go @@ -0,0 +1,10 @@ +// +build !windows + +package promptui + +import "github.com/chzyer/readline" + +var ( + // KeyBackspace is the default key for deleting input text. + KeyBackspace rune = readline.CharBackspace +) diff --git a/vendor/github.com/manifoldco/promptui/keycodes_windows.go b/vendor/github.com/manifoldco/promptui/keycodes_windows.go new file mode 100644 index 000000000..737d1566e --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes_windows.go @@ -0,0 +1,10 @@ +// +build windows + +package promptui + +// source: https://msdn.microsoft.com/en-us/library/aa243025(v=vs.60).aspx + +var ( + // KeyBackspace is the default key for deleting input text inside a command line prompt. + KeyBackspace rune = 8 +) diff --git a/vendor/github.com/manifoldco/promptui/list/list.go b/vendor/github.com/manifoldco/promptui/list/list.go new file mode 100644 index 000000000..c98a39cfa --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/list/list.go @@ -0,0 +1,237 @@ +package list + +import ( + "fmt" + "reflect" + "strings" +) + +// Searcher is a base function signature that is used inside select when activating the search mode. +// If defined, it is called on each items of the select and should return a boolean for whether or not +// the item fits the searched term. +type Searcher func(input string, index int) bool + +// NotFound is an index returned when no item was selected. This could +// happen due to a search without results. +const NotFound = -1 + +// List holds a collection of items that can be displayed with an N number of +// visible items. The list can be moved up, down by one item of time or an +// entire page (ie: visible size). It keeps track of the current selected item. +type List struct { + items []*interface{} + scope []*interface{} + cursor int // cursor holds the index of the current selected item + size int // size is the number of visible options + start int + Searcher Searcher +} + +// New creates and initializes a list of searchable items. The items attribute must be a slice type with a +// size greater than 0. Error will be returned if those two conditions are not met. +func New(items interface{}, size int) (*List, error) { + if size < 1 { + return nil, fmt.Errorf("list size %d must be greater than 0", size) + } + + if items == nil || reflect.TypeOf(items).Kind() != reflect.Slice { + return nil, fmt.Errorf("items %v is not a slice", items) + } + + slice := reflect.ValueOf(items) + values := make([]*interface{}, slice.Len()) + + for i := range values { + item := slice.Index(i).Interface() + values[i] = &item + } + + return &List{size: size, items: values, scope: values}, nil +} + +// Prev moves the visible list back one item. If the selected item is out of +// view, the new select item becomes the last visible item. If the list is +// already at the top, nothing happens. +func (l *List) Prev() { + if l.cursor > 0 { + l.cursor-- + } + + if l.start > l.cursor { + l.start = l.cursor + } +} + +// Search allows the list to be filtered by a given term. The list must +// implement the searcher function signature for this functionality to work. +func (l *List) Search(term string) { + term = strings.Trim(term, " ") + l.cursor = 0 + l.start = 0 + l.search(term) +} + +// CancelSearch stops the current search and returns the list to its +// original order. +func (l *List) CancelSearch() { + l.cursor = 0 + l.start = 0 + l.scope = l.items +} + +func (l *List) search(term string) { + var scope []*interface{} + + for i, item := range l.items { + if l.Searcher(term, i) { + scope = append(scope, item) + } + } + + l.scope = scope +} + +// Start returns the current render start position of the list. +func (l *List) Start() int { + return l.start +} + +// SetStart sets the current scroll position. Values out of bounds will be +// clamped. +func (l *List) SetStart(i int) { + if i < 0 { + i = 0 + } + if i > l.cursor { + l.start = l.cursor + } else { + l.start = i + } +} + +// SetCursor sets the position of the cursor in the list. Values out of bounds +// will be clamped. +func (l *List) SetCursor(i int) { + max := len(l.scope) - 1 + if i >= max { + i = max + } + if i < 0 { + i = 0 + } + l.cursor = i + + if l.start > l.cursor { + l.start = l.cursor + } else if l.start+l.size <= l.cursor { + l.start = l.cursor - l.size + 1 + } +} + +// Next moves the visible list forward one item. If the selected item is out of +// view, the new select item becomes the first visible item. If the list is +// already at the bottom, nothing happens. +func (l *List) Next() { + max := len(l.scope) - 1 + + if l.cursor < max { + l.cursor++ + } + + if l.start+l.size <= l.cursor { + l.start = l.cursor - l.size + 1 + } +} + +// PageUp moves the visible list backward by x items. Where x is the size of the +// visible items on the list. The selected item becomes the first visible item. +// If the list is already at the bottom, the selected item becomes the last +// visible item. +func (l *List) PageUp() { + start := l.start - l.size + if start < 0 { + l.start = 0 + } else { + l.start = start + } + + cursor := l.start + + if cursor < l.cursor { + l.cursor = cursor + } +} + +// PageDown moves the visible list forward by x items. Where x is the size of +// the visible items on the list. The selected item becomes the first visible +// item. +func (l *List) PageDown() { + start := l.start + l.size + max := len(l.scope) - l.size + + switch { + case len(l.scope) < l.size: + l.start = 0 + case start > max: + l.start = max + default: + l.start = start + } + + cursor := l.start + + if cursor == l.cursor { + l.cursor = len(l.scope) - 1 + } else if cursor > l.cursor { + l.cursor = cursor + } +} + +// CanPageDown returns whether a list can still PageDown(). +func (l *List) CanPageDown() bool { + max := len(l.scope) + return l.start+l.size < max +} + +// CanPageUp returns whether a list can still PageUp(). +func (l *List) CanPageUp() bool { + return l.start > 0 +} + +// Index returns the index of the item currently selected inside the searched list. If no item is selected, +// the NotFound (-1) index is returned. +func (l *List) Index() int { + selected := l.scope[l.cursor] + + for i, item := range l.items { + if item == selected { + return i + } + } + + return NotFound +} + +// Items returns a slice equal to the size of the list with the current visible +// items and the index of the active item in this list. +func (l *List) Items() ([]interface{}, int) { + var result []interface{} + max := len(l.scope) + end := l.start + l.size + + if end > max { + end = max + } + + active := NotFound + + for i, j := l.start, 0; i < end; i, j = i+1, j+1 { + if l.cursor == i { + active = j + } + + result = append(result, *l.scope[i]) + } + + return result, active +} diff --git a/vendor/github.com/manifoldco/promptui/prompt.go b/vendor/github.com/manifoldco/promptui/prompt.go new file mode 100644 index 000000000..8e35123b0 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/prompt.go @@ -0,0 +1,341 @@ +package promptui + +import ( + "fmt" + "io" + "strings" + "text/template" + + "github.com/chzyer/readline" + "github.com/manifoldco/promptui/screenbuf" +) + +// Prompt represents a single line text field input with options for validation and input masks. +type Prompt struct { + // Label is the value displayed on the command line prompt. + // + // The value for Label can be a simple string or a struct that will need to be accessed by dot notation + // inside the templates. For example, `{{ .Name }}` will display the name property of a struct. + Label interface{} + + // Default is the initial value for the prompt. This value will be displayed next to the prompt's label + // and the user will be able to view or change it depending on the options. + Default string + + // AllowEdit lets the user edit the default value. If false, any key press + // other than automatically clears the default value. + AllowEdit bool + + // Validate is an optional function that fill be used against the entered value in the prompt to validate it. + Validate ValidateFunc + + // Mask is an optional rune that sets which character to display instead of the entered characters. This + // allows hiding private information like passwords. + Mask rune + + // HideEntered sets whether to hide the text after the user has pressed enter. + HideEntered bool + + // Templates can be used to customize the prompt output. If nil is passed, the + // default templates are used. See the PromptTemplates docs for more info. + Templates *PromptTemplates + + // IsConfirm makes the prompt ask for a yes or no ([Y/N]) question rather than request an input. When set, + // most properties related to input will be ignored. + IsConfirm bool + + // IsVimMode enables vi-like movements (hjkl) and editing. + IsVimMode bool + + // the Pointer defines how to render the cursor. + Pointer Pointer + + Stdin io.ReadCloser + Stdout io.WriteCloser +} + +// PromptTemplates allow a prompt to be customized following stdlib +// text/template syntax. Custom state, colors and background color are available for use inside +// the templates and are documented inside the Variable section of the docs. +// +// Examples +// +// text/templates use a special notation to display programmable content. Using the double bracket notation, +// the value can be printed with specific helper functions. For example +// +// This displays the value given to the template as pure, unstylized text. +// '{{ . }}' +// +// This displays the value colored in cyan +// '{{ . | cyan }}' +// +// This displays the value colored in red with a cyan background-color +// '{{ . | red | cyan }}' +// +// See the doc of text/template for more info: https://golang.org/pkg/text/template/ +type PromptTemplates struct { + // Prompt is a text/template for the prompt label displayed on the left side of the prompt. + Prompt string + + // Prompt is a text/template for the prompt label when IsConfirm is set as true. + Confirm string + + // Valid is a text/template for the prompt label when the value entered is valid. + Valid string + + // Invalid is a text/template for the prompt label when the value entered is invalid. + Invalid string + + // Success is a text/template for the prompt label when the user has pressed entered and the value has been + // deemed valid by the validation function. The label will keep using this template even when the prompt ends + // inside the console. + Success string + + // Prompt is a text/template for the prompt label when the value is invalid due to an error triggered by + // the prompt's validation function. + ValidationError string + + // FuncMap is a map of helper functions that can be used inside of templates according to the text/template + // documentation. + // + // By default, FuncMap contains the color functions used to color the text in templates. If FuncMap + // is overridden, the colors functions must be added in the override from promptui.FuncMap to work. + FuncMap template.FuncMap + + prompt *template.Template + valid *template.Template + invalid *template.Template + validation *template.Template + success *template.Template +} + +// Run executes the prompt. Its displays the label and default value if any, asking the user to enter a value. +// Run will keep the prompt alive until it has been canceled from the command prompt or it has received a valid +// value. It will return the value and an error if any occurred during the prompt's execution. +func (p *Prompt) Run() (string, error) { + var err error + + err = p.prepareTemplates() + if err != nil { + return "", err + } + + c := &readline.Config{ + Stdin: p.Stdin, + Stdout: p.Stdout, + EnableMask: p.Mask != 0, + MaskRune: p.Mask, + HistoryLimit: -1, + VimMode: p.IsVimMode, + UniqueEditLine: true, + } + + err = c.Init() + if err != nil { + return "", err + } + + rl, err := readline.NewEx(c) + if err != nil { + return "", err + } + // we're taking over the cursor, so stop showing it. + rl.Write([]byte(hideCursor)) + sb := screenbuf.New(rl) + + validFn := func(x string) error { + return nil + } + if p.Validate != nil { + validFn = p.Validate + } + + var inputErr error + input := p.Default + if p.IsConfirm { + input = "" + } + eraseDefault := input != "" && !p.AllowEdit + cur := NewCursor(input, p.Pointer, eraseDefault) + + listen := func(input []rune, pos int, key rune) ([]rune, int, bool) { + _, _, keepOn := cur.Listen(input, pos, key) + err := validFn(cur.Get()) + var prompt []byte + + if err != nil { + prompt = render(p.Templates.invalid, p.Label) + } else { + prompt = render(p.Templates.valid, p.Label) + if p.IsConfirm { + prompt = render(p.Templates.prompt, p.Label) + } + } + + echo := cur.Format() + if p.Mask != 0 { + echo = cur.FormatMask(p.Mask) + } + + prompt = append(prompt, []byte(echo)...) + sb.Reset() + sb.Write(prompt) + if inputErr != nil { + validation := render(p.Templates.validation, inputErr) + sb.Write(validation) + inputErr = nil + } + sb.Flush() + return nil, 0, keepOn + } + + c.SetListener(listen) + + for { + _, err = rl.Readline() + inputErr = validFn(cur.Get()) + if inputErr == nil { + break + } + + if err != nil { + break + } + } + + if err != nil { + switch err { + case readline.ErrInterrupt: + err = ErrInterrupt + case io.EOF: + err = ErrEOF + } + if err.Error() == "Interrupt" { + err = ErrInterrupt + } + sb.Reset() + sb.WriteString("") + sb.Flush() + rl.Write([]byte(showCursor)) + rl.Close() + return "", err + } + + echo := cur.Get() + if p.Mask != 0 { + echo = cur.GetMask(p.Mask) + } + + prompt := render(p.Templates.success, p.Label) + prompt = append(prompt, []byte(echo)...) + + if p.IsConfirm { + lowerDefault := strings.ToLower(p.Default) + if strings.ToLower(cur.Get()) != "y" && (lowerDefault != "y" || (lowerDefault == "y" && cur.Get() != "")) { + prompt = render(p.Templates.invalid, p.Label) + err = ErrAbort + } + } + + if p.HideEntered { + clearScreen(sb) + } else { + sb.Reset() + sb.Write(prompt) + sb.Flush() + } + + rl.Write([]byte(showCursor)) + rl.Close() + + return cur.Get(), err +} + +func (p *Prompt) prepareTemplates() error { + tpls := p.Templates + if tpls == nil { + tpls = &PromptTemplates{} + } + + if tpls.FuncMap == nil { + tpls.FuncMap = FuncMap + } + + bold := Styler(FGBold) + + if p.IsConfirm { + if tpls.Confirm == "" { + confirm := "y/N" + if strings.ToLower(p.Default) == "y" { + confirm = "Y/n" + } + tpls.Confirm = fmt.Sprintf(`{{ "%s" | bold }} {{ . | bold }}? {{ "[%s]" | faint }} `, IconInitial, confirm) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Confirm) + if err != nil { + return err + } + + tpls.prompt = tpl + } else { + if tpls.Prompt == "" { + tpls.Prompt = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconInitial), bold(":")) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Prompt) + if err != nil { + return err + } + + tpls.prompt = tpl + } + + if tpls.Valid == "" { + tpls.Valid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconGood), bold(":")) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Valid) + if err != nil { + return err + } + + tpls.valid = tpl + + if tpls.Invalid == "" { + tpls.Invalid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconBad), bold(":")) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Invalid) + if err != nil { + return err + } + + tpls.invalid = tpl + + if tpls.ValidationError == "" { + tpls.ValidationError = `{{ ">>" | red }} {{ . | red }}` + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.ValidationError) + if err != nil { + return err + } + + tpls.validation = tpl + + if tpls.Success == "" { + tpls.Success = fmt.Sprintf("{{ . | faint }}%s ", Styler(FGFaint)(":")) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Success) + if err != nil { + return err + } + + tpls.success = tpl + + p.Templates = tpls + + return nil +} diff --git a/vendor/github.com/manifoldco/promptui/promptui.go b/vendor/github.com/manifoldco/promptui/promptui.go new file mode 100644 index 000000000..6248e5859 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/promptui.go @@ -0,0 +1,27 @@ +// Package promptui is a library providing a simple interface to create command-line prompts for go. +// It can be easily integrated into spf13/cobra, urfave/cli or any cli go application. +// +// promptui has two main input modes: +// +// Prompt provides a single line for user input. It supports optional live validation, +// confirmation and masking the input. +// +// Select provides a list of options to choose from. It supports pagination, search, +// detailed view and custom templates. +package promptui + +import "errors" + +// ErrEOF is the error returned from prompts when EOF is encountered. +var ErrEOF = errors.New("^D") + +// ErrInterrupt is the error returned from prompts when an interrupt (ctrl-c) is +// encountered. +var ErrInterrupt = errors.New("^C") + +// ErrAbort is the error returned when confirm prompts are supplied "n" +var ErrAbort = errors.New("") + +// ValidateFunc is a placeholder type for any validation functions that validates a given input. It should return +// a ValidationError if the input is not valid. +type ValidateFunc func(string) error diff --git a/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go b/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go new file mode 100644 index 000000000..861390045 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go @@ -0,0 +1,151 @@ +package screenbuf + +import ( + "bytes" + "fmt" + "io" +) + +const esc = "\033[" + +var ( + clearLine = []byte(esc + "2K\r") + moveUp = []byte(esc + "1A") + moveDown = []byte(esc + "1B") +) + +// ScreenBuf is a convenient way to write to terminal screens. It creates, +// clears and, moves up or down lines as needed to write the output to the +// terminal using ANSI escape codes. +type ScreenBuf struct { + w io.Writer + buf *bytes.Buffer + reset bool + cursor int + height int +} + +// New creates and initializes a new ScreenBuf. +func New(w io.Writer) *ScreenBuf { + return &ScreenBuf{buf: &bytes.Buffer{}, w: w} +} + +// Reset truncates the underlining buffer and marks all its previous lines to be +// cleared during the next Write. +func (s *ScreenBuf) Reset() { + s.buf.Reset() + s.reset = true +} + +// Clear clears all previous lines and the output starts from the top. +func (s *ScreenBuf) Clear() error { + for i := 0; i < s.height; i++ { + _, err := s.buf.Write(moveUp) + if err != nil { + return err + } + _, err = s.buf.Write(clearLine) + if err != nil { + return err + } + } + s.cursor = 0 + s.height = 0 + s.reset = false + return nil +} + +// Write writes a single line to the underlining buffer. If the ScreenBuf was +// previously reset, all previous lines are cleared and the output starts from +// the top. Lines with \r or \n will cause an error since they can interfere with the +// terminal ability to move between lines. +func (s *ScreenBuf) Write(b []byte) (int, error) { + if bytes.ContainsAny(b, "\r\n") { + return 0, fmt.Errorf("%q should not contain either \\r or \\n", b) + } + + if s.reset { + if err := s.Clear(); err != nil { + return 0, err + } + } + + switch { + case s.cursor == s.height: + n, err := s.buf.Write(clearLine) + if err != nil { + return n, err + } + + n, err = s.buf.Write(b) + if err != nil { + return n, err + } + + _, err = s.buf.Write([]byte("\n")) + if err != nil { + return n, err + } + + s.height++ + s.cursor++ + return n, nil + case s.cursor < s.height: + n, err := s.buf.Write(clearLine) + if err != nil { + return n, err + } + n, err = s.buf.Write(b) + if err != nil { + return n, err + } + n, err = s.buf.Write(moveDown) + if err != nil { + return n, err + } + s.cursor++ + return n, nil + default: + return 0, fmt.Errorf("Invalid write cursor position (%d) exceeded line height: %d", s.cursor, s.height) + } +} + +// Flush writes any buffered data to the underlying io.Writer, ensuring that any pending data is displayed. +func (s *ScreenBuf) Flush() error { + for i := s.cursor; i < s.height; i++ { + if i < s.height { + _, err := s.buf.Write(clearLine) + if err != nil { + return err + } + } + _, err := s.buf.Write(moveDown) + if err != nil { + return err + } + } + + _, err := s.buf.WriteTo(s.w) + if err != nil { + return err + } + + s.buf.Reset() + + for i := 0; i < s.height; i++ { + _, err := s.buf.Write(moveUp) + if err != nil { + return err + } + } + + s.cursor = 0 + + return nil +} + +// WriteString is a convenient function to write a new line passing a string. +// Check ScreenBuf.Write() for a detailed explanation of the function behaviour. +func (s *ScreenBuf) WriteString(str string) (int, error) { + return s.Write([]byte(str)) +} diff --git a/vendor/github.com/manifoldco/promptui/select.go b/vendor/github.com/manifoldco/promptui/select.go new file mode 100644 index 000000000..19b9e0c2e --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/select.go @@ -0,0 +1,637 @@ +package promptui + +import ( + "bytes" + "fmt" + "io" + "os" + "text/template" + + "github.com/chzyer/readline" + "github.com/juju/ansiterm" + "github.com/manifoldco/promptui/list" + "github.com/manifoldco/promptui/screenbuf" +) + +// SelectedAdd is used internally inside SelectWithAdd when the add option is selected in select mode. +// Since -1 is not a possible selected index, this ensure that add mode is always unique inside +// SelectWithAdd's logic. +const SelectedAdd = -1 + +// Select represents a list of items used to enable selections, they can be used as search engines, menus +// or as a list of items in a cli based prompt. +type Select struct { + // Label is the text displayed on top of the list to direct input. The IconInitial value "?" will be + // appended automatically to the label so it does not need to be added. + // + // The value for Label can be a simple string or a struct that will need to be accessed by dot notation + // inside the templates. For example, `{{ .Name }}` will display the name property of a struct. + Label interface{} + + // Items are the items to display inside the list. It expect a slice of any kind of values, including strings. + // + // If using a slice of strings, promptui will use those strings directly into its base templates or the + // provided templates. If using any other type in the slice, it will attempt to transform it into a string + // before giving it to its templates. Custom templates will override this behavior if using the dot notation + // inside the templates. + // + // For example, `{{ .Name }}` will display the name property of a struct. + Items interface{} + + // Size is the number of items that should appear on the select before scrolling is necessary. Defaults to 5. + Size int + + // CursorPos is the initial position of the cursor. + CursorPos int + + // IsVimMode sets whether to use vim mode when using readline in the command prompt. Look at + // https://godoc.org/github.com/chzyer/readline#Config for more information on readline. + IsVimMode bool + + // HideHelp sets whether to hide help information. + HideHelp bool + + // HideSelected sets whether to hide the text displayed after an item is successfully selected. + HideSelected bool + + // Templates can be used to customize the select output. If nil is passed, the + // default templates are used. See the SelectTemplates docs for more info. + Templates *SelectTemplates + + // Keys is the set of keys used in select mode to control the command line interface. See the SelectKeys docs for + // more info. + Keys *SelectKeys + + // Searcher is a function that can be implemented to refine the base searching algorithm in selects. + // + // Search is a function that will receive the searched term and the item's index and should return a boolean + // for whether or not the terms are alike. It is unimplemented by default and search will not work unless + // it is implemented. + Searcher list.Searcher + + // StartInSearchMode sets whether or not the select mode should start in search mode or selection mode. + // For search mode to work, the Search property must be implemented. + StartInSearchMode bool + + list *list.List + + // A function that determines how to render the cursor + Pointer Pointer + + Stdin io.ReadCloser + Stdout io.WriteCloser +} + +// SelectKeys defines the available keys used by select mode to enable the user to move around the list +// and trigger search mode. See the Key struct docs for more information on keys. +type SelectKeys struct { + // Next is the key used to move to the next element inside the list. Defaults to down arrow key. + Next Key + + // Prev is the key used to move to the previous element inside the list. Defaults to up arrow key. + Prev Key + + // PageUp is the key used to jump back to the first element inside the list. Defaults to left arrow key. + PageUp Key + + // PageUp is the key used to jump forward to the last element inside the list. Defaults to right arrow key. + PageDown Key + + // Search is the key used to trigger the search mode for the list. Default to the "/" key. + Search Key +} + +// Key defines a keyboard code and a display representation for the help menu. +type Key struct { + // Code is a rune that will be used to compare against typed keys with readline. + // Check https://github.com/chzyer/readline for a list of codes + Code rune + + // Display is the string that will be displayed inside the help menu to help inform the user + // of which key to use on his keyboard for various functions. + Display string +} + +// SelectTemplates allow a select list to be customized following stdlib +// text/template syntax. Custom state, colors and background color are available for use inside +// the templates and are documented inside the Variable section of the docs. +// +// Examples +// +// text/templates use a special notation to display programmable content. Using the double bracket notation, +// the value can be printed with specific helper functions. For example +// +// This displays the value given to the template as pure, unstylized text. Structs are transformed to string +// with this notation. +// '{{ . }}' +// +// This displays the name property of the value colored in cyan +// '{{ .Name | cyan }}' +// +// This displays the label property of value colored in red with a cyan background-color +// '{{ .Label | red | cyan }}' +// +// See the doc of text/template for more info: https://golang.org/pkg/text/template/ +// +// Notes +// +// Setting any of these templates will remove the icons from the default templates. They must +// be added back in each of their specific templates. The styles.go constants contains the default icons. +type SelectTemplates struct { + // Label is a text/template for the main command line label. Defaults to printing the label as it with + // the IconInitial. + Label string + + // Active is a text/template for when an item is currently active within the list. + Active string + + // Inactive is a text/template for when an item is not currently active inside the list. This + // template is used for all items unless they are active or selected. + Inactive string + + // Selected is a text/template for when an item was successfully selected. + Selected string + + // Details is a text/template for when an item current active to show + // additional information. It can have multiple lines. + // + // Detail will always be displayed for the active element and thus can be used to display additional + // information on the element beyond its label. + // + // promptui will not trim spaces and tabs will be displayed if the template is indented. + Details string + + // Help is a text/template for displaying instructions at the top. By default + // it shows keys for movement and search. + Help string + + // FuncMap is a map of helper functions that can be used inside of templates according to the text/template + // documentation. + // + // By default, FuncMap contains the color functions used to color the text in templates. If FuncMap + // is overridden, the colors functions must be added in the override from promptui.FuncMap to work. + FuncMap template.FuncMap + + label *template.Template + active *template.Template + inactive *template.Template + selected *template.Template + details *template.Template + help *template.Template +} + +// SearchPrompt is the prompt displayed in search mode. +var SearchPrompt = "Search: " + +// Run executes the select list. It displays the label and the list of items, asking the user to chose any +// value within to list. Run will keep the prompt alive until it has been canceled from +// the command prompt or it has received a valid value. It will return the value and an error if any +// occurred during the select's execution. +func (s *Select) Run() (int, string, error) { + return s.RunCursorAt(s.CursorPos, 0) +} + +// RunCursorAt executes the select list, initializing the cursor to the given +// position. Invalid cursor positions will be clamped to valid values. It +// displays the label and the list of items, asking the user to chose any value +// within to list. Run will keep the prompt alive until it has been canceled +// from the command prompt or it has received a valid value. It will return +// the value and an error if any occurred during the select's execution. +func (s *Select) RunCursorAt(cursorPos, scroll int) (int, string, error) { + if s.Size == 0 { + s.Size = 5 + } + + l, err := list.New(s.Items, s.Size) + if err != nil { + return 0, "", err + } + l.Searcher = s.Searcher + + s.list = l + + s.setKeys() + + err = s.prepareTemplates() + if err != nil { + return 0, "", err + } + return s.innerRun(cursorPos, scroll, ' ') +} + +func (s *Select) innerRun(cursorPos, scroll int, top rune) (int, string, error) { + c := &readline.Config{ + Stdin: s.Stdin, + Stdout: s.Stdout, + } + err := c.Init() + if err != nil { + return 0, "", err + } + + c.Stdin = readline.NewCancelableStdin(c.Stdin) + + if s.IsVimMode { + c.VimMode = true + } + + c.HistoryLimit = -1 + c.UniqueEditLine = true + + rl, err := readline.NewEx(c) + if err != nil { + return 0, "", err + } + + rl.Write([]byte(hideCursor)) + sb := screenbuf.New(rl) + + cur := NewCursor("", s.Pointer, false) + + canSearch := s.Searcher != nil + searchMode := s.StartInSearchMode + s.list.SetCursor(cursorPos) + s.list.SetStart(scroll) + + c.SetListener(func(line []rune, pos int, key rune) ([]rune, int, bool) { + switch { + case key == KeyEnter: + return nil, 0, true + case key == s.Keys.Next.Code || (key == 'j' && !searchMode): + s.list.Next() + case key == s.Keys.Prev.Code || (key == 'k' && !searchMode): + s.list.Prev() + case key == s.Keys.Search.Code: + if !canSearch { + break + } + + if searchMode { + searchMode = false + cur.Replace("") + s.list.CancelSearch() + } else { + searchMode = true + } + case key == KeyBackspace || key == KeyCtrlH: + if !canSearch || !searchMode { + break + } + + cur.Backspace() + if len(cur.Get()) > 0 { + s.list.Search(cur.Get()) + } else { + s.list.CancelSearch() + } + case key == s.Keys.PageUp.Code || (key == 'h' && !searchMode): + s.list.PageUp() + case key == s.Keys.PageDown.Code || (key == 'l' && !searchMode): + s.list.PageDown() + default: + if canSearch && searchMode { + cur.Update(string(line)) + s.list.Search(cur.Get()) + } + } + + if searchMode { + header := SearchPrompt + cur.Format() + sb.WriteString(header) + } else if !s.HideHelp { + help := s.renderHelp(canSearch) + sb.Write(help) + } + + label := render(s.Templates.label, s.Label) + sb.Write(label) + + items, idx := s.list.Items() + last := len(items) - 1 + + for i, item := range items { + page := " " + + switch i { + case 0: + if s.list.CanPageUp() { + page = "↑" + } else { + page = string(top) + } + case last: + if s.list.CanPageDown() { + page = "↓" + } + } + + output := []byte(page + " ") + + if i == idx { + output = append(output, render(s.Templates.active, item)...) + } else { + output = append(output, render(s.Templates.inactive, item)...) + } + + sb.Write(output) + } + + if idx == list.NotFound { + sb.WriteString("") + sb.WriteString("No results") + } else { + active := items[idx] + + details := s.renderDetails(active) + for _, d := range details { + sb.Write(d) + } + } + + sb.Flush() + + return nil, 0, true + }) + + for { + _, err = rl.Readline() + + if err != nil { + switch { + case err == readline.ErrInterrupt, err.Error() == "Interrupt": + err = ErrInterrupt + case err == io.EOF: + err = ErrEOF + } + break + } + + _, idx := s.list.Items() + if idx != list.NotFound { + break + } + + } + + if err != nil { + if err.Error() == "Interrupt" { + err = ErrInterrupt + } + sb.Reset() + sb.WriteString("") + sb.Flush() + rl.Write([]byte(showCursor)) + rl.Close() + return 0, "", err + } + + items, idx := s.list.Items() + item := items[idx] + + if s.HideSelected { + clearScreen(sb) + } else { + sb.Reset() + sb.Write(render(s.Templates.selected, item)) + sb.Flush() + } + + rl.Write([]byte(showCursor)) + rl.Close() + + return s.list.Index(), fmt.Sprintf("%v", item), err +} + +// ScrollPosition returns the current scroll position. +func (s *Select) ScrollPosition() int { + return s.list.Start() +} + +func (s *Select) prepareTemplates() error { + tpls := s.Templates + if tpls == nil { + tpls = &SelectTemplates{} + } + + if tpls.FuncMap == nil { + tpls.FuncMap = FuncMap + } + + if tpls.Label == "" { + tpls.Label = fmt.Sprintf("%s {{.}}: ", IconInitial) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Label) + if err != nil { + return err + } + + tpls.label = tpl + + if tpls.Active == "" { + tpls.Active = fmt.Sprintf("%s {{ . | underline }}", IconSelect) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Active) + if err != nil { + return err + } + + tpls.active = tpl + + if tpls.Inactive == "" { + tpls.Inactive = " {{.}}" + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Inactive) + if err != nil { + return err + } + + tpls.inactive = tpl + + if tpls.Selected == "" { + tpls.Selected = fmt.Sprintf(`{{ "%s" | green }} {{ . | faint }}`, IconGood) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Selected) + if err != nil { + return err + } + tpls.selected = tpl + + if tpls.Details != "" { + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Details) + if err != nil { + return err + } + + tpls.details = tpl + } + + if tpls.Help == "" { + tpls.Help = fmt.Sprintf(`{{ "Use the arrow keys to navigate:" | faint }} {{ .NextKey | faint }} ` + + `{{ .PrevKey | faint }} {{ .PageDownKey | faint }} {{ .PageUpKey | faint }} ` + + `{{ if .Search }} {{ "and" | faint }} {{ .SearchKey | faint }} {{ "toggles search" | faint }}{{ end }}`) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Help) + if err != nil { + return err + } + + tpls.help = tpl + + s.Templates = tpls + + return nil +} + +// SelectWithAdd represents a list for selecting a single item inside a list of items with the possibility to +// add new items to the list. +type SelectWithAdd struct { + // Label is the text displayed on top of the list to direct input. The IconInitial value "?" will be + // appended automatically to the label so it does not need to be added. + Label string + + // Items are the items to display inside the list. Each item will be listed individually with the + // AddLabel as the first item of the list. + Items []string + + // AddLabel is the label used for the first item of the list that enables adding a new item. + // Selecting this item in the list displays the add item prompt using promptui/prompt. + AddLabel string + + // Validate is an optional function that fill be used against the entered value in the prompt to validate it. + // If the value is valid, it is returned to the callee to be added in the list. + Validate ValidateFunc + + // IsVimMode sets whether to use vim mode when using readline in the command prompt. Look at + // https://godoc.org/github.com/chzyer/readline#Config for more information on readline. + IsVimMode bool + + // a function that defines how to render the cursor + Pointer Pointer + + // HideHelp sets whether to hide help information. + HideHelp bool +} + +// Run executes the select list. Its displays the label and the list of items, asking the user to chose any +// value within to list or add his own. Run will keep the prompt alive until it has been canceled from +// the command prompt or it has received a valid value. +// +// If the addLabel is selected in the list, this function will return a -1 index with the added label and no error. +// Otherwise, it will return the index and the value of the selected item. In any case, if an error is triggered, it +// will also return the error as its third return value. +func (sa *SelectWithAdd) Run() (int, string, error) { + if len(sa.Items) > 0 { + newItems := append([]string{sa.AddLabel}, sa.Items...) + + list, err := list.New(newItems, 5) + if err != nil { + return 0, "", err + } + + s := Select{ + Label: sa.Label, + Items: newItems, + IsVimMode: sa.IsVimMode, + HideHelp: sa.HideHelp, + Size: 5, + list: list, + Pointer: sa.Pointer, + } + s.setKeys() + + err = s.prepareTemplates() + if err != nil { + return 0, "", err + } + + selected, value, err := s.innerRun(1, 0, '+') + if err != nil || selected != 0 { + return selected - 1, value, err + } + + // XXX run through terminal for windows + os.Stdout.Write([]byte(upLine(1) + "\r" + clearLine)) + } + + p := Prompt{ + Label: sa.AddLabel, + Validate: sa.Validate, + IsVimMode: sa.IsVimMode, + Pointer: sa.Pointer, + } + value, err := p.Run() + return SelectedAdd, value, err +} + +func (s *Select) setKeys() { + if s.Keys != nil { + return + } + s.Keys = &SelectKeys{ + Prev: Key{Code: KeyPrev, Display: KeyPrevDisplay}, + Next: Key{Code: KeyNext, Display: KeyNextDisplay}, + PageUp: Key{Code: KeyBackward, Display: KeyBackwardDisplay}, + PageDown: Key{Code: KeyForward, Display: KeyForwardDisplay}, + Search: Key{Code: '/', Display: "/"}, + } +} + +func (s *Select) renderDetails(item interface{}) [][]byte { + if s.Templates.details == nil { + return nil + } + + var buf bytes.Buffer + w := ansiterm.NewTabWriter(&buf, 0, 0, 8, ' ', 0) + + err := s.Templates.details.Execute(w, item) + if err != nil { + fmt.Fprintf(w, "%v", item) + } + + w.Flush() + + output := buf.Bytes() + + return bytes.Split(output, []byte("\n")) +} + +func (s *Select) renderHelp(b bool) []byte { + keys := struct { + NextKey string + PrevKey string + PageDownKey string + PageUpKey string + Search bool + SearchKey string + }{ + NextKey: s.Keys.Next.Display, + PrevKey: s.Keys.Prev.Display, + PageDownKey: s.Keys.PageDown.Display, + PageUpKey: s.Keys.PageUp.Display, + SearchKey: s.Keys.Search.Display, + Search: b, + } + + return render(s.Templates.help, keys) +} + +func render(tpl *template.Template, data interface{}) []byte { + var buf bytes.Buffer + err := tpl.Execute(&buf, data) + if err != nil { + return []byte(fmt.Sprintf("%v", data)) + } + return buf.Bytes() +} + +func clearScreen(sb *screenbuf.ScreenBuf) { + sb.Reset() + sb.Clear() + sb.Flush() +} diff --git a/vendor/github.com/manifoldco/promptui/styles.go b/vendor/github.com/manifoldco/promptui/styles.go new file mode 100644 index 000000000..d7698c9cf --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/styles.go @@ -0,0 +1,23 @@ +// +build !windows + +package promptui + +// These are the default icons used by promptui for select and prompts. These should not be overridden and instead +// customized through the use of custom templates +var ( + // IconInitial is the icon used when starting in prompt mode and the icon next to the label when + // starting in select mode. + IconInitial = Styler(FGBlue)("?") + + // IconGood is the icon used when a good answer is entered in prompt mode. + IconGood = Styler(FGGreen)("✔") + + // IconWarn is the icon used when a good, but potentially invalid answer is entered in prompt mode. + IconWarn = Styler(FGYellow)("⚠") + + // IconBad is the icon used when a bad answer is entered in prompt mode. + IconBad = Styler(FGRed)("✗") + + // IconSelect is the icon used to identify the currently selected item in select mode. + IconSelect = Styler(FGBold)("▸") +) diff --git a/vendor/github.com/manifoldco/promptui/styles_windows.go b/vendor/github.com/manifoldco/promptui/styles_windows.go new file mode 100644 index 000000000..36de268a6 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/styles_windows.go @@ -0,0 +1,21 @@ +package promptui + +// These are the default icons used bu promptui for select and prompts. They can either be overridden directly +// from these variable or customized through the use of custom templates +var ( + // IconInitial is the icon used when starting in prompt mode and the icon next to the label when + // starting in select mode. + IconInitial = Styler(FGBlue)("?") + + // IconGood is the icon used when a good answer is entered in prompt mode. + IconGood = Styler(FGGreen)("v") + + // IconWarn is the icon used when a good, but potentially invalid answer is entered in prompt mode. + IconWarn = Styler(FGYellow)("!") + + // IconBad is the icon used when a bad answer is entered in prompt mode. + IconBad = Styler(FGRed)("x") + + // IconSelect is the icon used to identify the currently selected item in select mode. + IconSelect = Styler(FGBold)(">") +) diff --git a/vendor/github.com/mattn/go-colorable/.travis.yml b/vendor/github.com/mattn/go-colorable/.travis.yml new file mode 100644 index 000000000..98db8f060 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - tip + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE new file mode 100644 index 000000000..91b5cef30 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md new file mode 100644 index 000000000..56729a92c --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/README.md @@ -0,0 +1,48 @@ +# go-colorable + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable) +[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable) + +Colorable writer for windows. + +For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) +This package is possible to handle escape sequence for ansi color on windows. + +## Too Bad! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) + + +## So Good! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) + +## Usage + +```go +logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) +logrus.SetOutput(colorable.NewColorableStdout()) + +logrus.Info("succeeded") +logrus.Warn("not correct") +logrus.Error("something error") +logrus.Fatal("panic") +``` + +You can compile above code on non-windows OSs. + +## Installation + +``` +$ go get github.com/mattn/go-colorable +``` + +# License + +MIT + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go new file mode 100644 index 000000000..1f28d773d --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go @@ -0,0 +1,29 @@ +// +build appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go new file mode 100644 index 000000000..887f203dc --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_others.go @@ -0,0 +1,30 @@ +// +build !windows +// +build !appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go new file mode 100644 index 000000000..e17a5474e --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -0,0 +1,884 @@ +// +build windows +// +build !appengine + +package colorable + +import ( + "bytes" + "io" + "math" + "os" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/mattn/go-isatty" +) + +const ( + foregroundBlue = 0x1 + foregroundGreen = 0x2 + foregroundRed = 0x4 + foregroundIntensity = 0x8 + foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) + backgroundBlue = 0x10 + backgroundGreen = 0x20 + backgroundRed = 0x40 + backgroundIntensity = 0x80 + backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) +) + +type wchar uint16 +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +type consoleCursorInfo struct { + size dword + visible int32 +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") + procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") + procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") + procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") + procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") +) + +// Writer provide colorable Writer to the console +type Writer struct { + out io.Writer + handle syscall.Handle + oldattr word + oldpos coord +} + +// NewColorable return new instance of Writer which handle escape sequence from File. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + if isatty.IsTerminal(file.Fd()) { + var csbi consoleScreenBufferInfo + handle := syscall.Handle(file.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} + } + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return NewColorable(os.Stdout) +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return NewColorable(os.Stderr) +} + +var color256 = map[int]int{ + 0: 0x000000, + 1: 0x800000, + 2: 0x008000, + 3: 0x808000, + 4: 0x000080, + 5: 0x800080, + 6: 0x008080, + 7: 0xc0c0c0, + 8: 0x808080, + 9: 0xff0000, + 10: 0x00ff00, + 11: 0xffff00, + 12: 0x0000ff, + 13: 0xff00ff, + 14: 0x00ffff, + 15: 0xffffff, + 16: 0x000000, + 17: 0x00005f, + 18: 0x000087, + 19: 0x0000af, + 20: 0x0000d7, + 21: 0x0000ff, + 22: 0x005f00, + 23: 0x005f5f, + 24: 0x005f87, + 25: 0x005faf, + 26: 0x005fd7, + 27: 0x005fff, + 28: 0x008700, + 29: 0x00875f, + 30: 0x008787, + 31: 0x0087af, + 32: 0x0087d7, + 33: 0x0087ff, + 34: 0x00af00, + 35: 0x00af5f, + 36: 0x00af87, + 37: 0x00afaf, + 38: 0x00afd7, + 39: 0x00afff, + 40: 0x00d700, + 41: 0x00d75f, + 42: 0x00d787, + 43: 0x00d7af, + 44: 0x00d7d7, + 45: 0x00d7ff, + 46: 0x00ff00, + 47: 0x00ff5f, + 48: 0x00ff87, + 49: 0x00ffaf, + 50: 0x00ffd7, + 51: 0x00ffff, + 52: 0x5f0000, + 53: 0x5f005f, + 54: 0x5f0087, + 55: 0x5f00af, + 56: 0x5f00d7, + 57: 0x5f00ff, + 58: 0x5f5f00, + 59: 0x5f5f5f, + 60: 0x5f5f87, + 61: 0x5f5faf, + 62: 0x5f5fd7, + 63: 0x5f5fff, + 64: 0x5f8700, + 65: 0x5f875f, + 66: 0x5f8787, + 67: 0x5f87af, + 68: 0x5f87d7, + 69: 0x5f87ff, + 70: 0x5faf00, + 71: 0x5faf5f, + 72: 0x5faf87, + 73: 0x5fafaf, + 74: 0x5fafd7, + 75: 0x5fafff, + 76: 0x5fd700, + 77: 0x5fd75f, + 78: 0x5fd787, + 79: 0x5fd7af, + 80: 0x5fd7d7, + 81: 0x5fd7ff, + 82: 0x5fff00, + 83: 0x5fff5f, + 84: 0x5fff87, + 85: 0x5fffaf, + 86: 0x5fffd7, + 87: 0x5fffff, + 88: 0x870000, + 89: 0x87005f, + 90: 0x870087, + 91: 0x8700af, + 92: 0x8700d7, + 93: 0x8700ff, + 94: 0x875f00, + 95: 0x875f5f, + 96: 0x875f87, + 97: 0x875faf, + 98: 0x875fd7, + 99: 0x875fff, + 100: 0x878700, + 101: 0x87875f, + 102: 0x878787, + 103: 0x8787af, + 104: 0x8787d7, + 105: 0x8787ff, + 106: 0x87af00, + 107: 0x87af5f, + 108: 0x87af87, + 109: 0x87afaf, + 110: 0x87afd7, + 111: 0x87afff, + 112: 0x87d700, + 113: 0x87d75f, + 114: 0x87d787, + 115: 0x87d7af, + 116: 0x87d7d7, + 117: 0x87d7ff, + 118: 0x87ff00, + 119: 0x87ff5f, + 120: 0x87ff87, + 121: 0x87ffaf, + 122: 0x87ffd7, + 123: 0x87ffff, + 124: 0xaf0000, + 125: 0xaf005f, + 126: 0xaf0087, + 127: 0xaf00af, + 128: 0xaf00d7, + 129: 0xaf00ff, + 130: 0xaf5f00, + 131: 0xaf5f5f, + 132: 0xaf5f87, + 133: 0xaf5faf, + 134: 0xaf5fd7, + 135: 0xaf5fff, + 136: 0xaf8700, + 137: 0xaf875f, + 138: 0xaf8787, + 139: 0xaf87af, + 140: 0xaf87d7, + 141: 0xaf87ff, + 142: 0xafaf00, + 143: 0xafaf5f, + 144: 0xafaf87, + 145: 0xafafaf, + 146: 0xafafd7, + 147: 0xafafff, + 148: 0xafd700, + 149: 0xafd75f, + 150: 0xafd787, + 151: 0xafd7af, + 152: 0xafd7d7, + 153: 0xafd7ff, + 154: 0xafff00, + 155: 0xafff5f, + 156: 0xafff87, + 157: 0xafffaf, + 158: 0xafffd7, + 159: 0xafffff, + 160: 0xd70000, + 161: 0xd7005f, + 162: 0xd70087, + 163: 0xd700af, + 164: 0xd700d7, + 165: 0xd700ff, + 166: 0xd75f00, + 167: 0xd75f5f, + 168: 0xd75f87, + 169: 0xd75faf, + 170: 0xd75fd7, + 171: 0xd75fff, + 172: 0xd78700, + 173: 0xd7875f, + 174: 0xd78787, + 175: 0xd787af, + 176: 0xd787d7, + 177: 0xd787ff, + 178: 0xd7af00, + 179: 0xd7af5f, + 180: 0xd7af87, + 181: 0xd7afaf, + 182: 0xd7afd7, + 183: 0xd7afff, + 184: 0xd7d700, + 185: 0xd7d75f, + 186: 0xd7d787, + 187: 0xd7d7af, + 188: 0xd7d7d7, + 189: 0xd7d7ff, + 190: 0xd7ff00, + 191: 0xd7ff5f, + 192: 0xd7ff87, + 193: 0xd7ffaf, + 194: 0xd7ffd7, + 195: 0xd7ffff, + 196: 0xff0000, + 197: 0xff005f, + 198: 0xff0087, + 199: 0xff00af, + 200: 0xff00d7, + 201: 0xff00ff, + 202: 0xff5f00, + 203: 0xff5f5f, + 204: 0xff5f87, + 205: 0xff5faf, + 206: 0xff5fd7, + 207: 0xff5fff, + 208: 0xff8700, + 209: 0xff875f, + 210: 0xff8787, + 211: 0xff87af, + 212: 0xff87d7, + 213: 0xff87ff, + 214: 0xffaf00, + 215: 0xffaf5f, + 216: 0xffaf87, + 217: 0xffafaf, + 218: 0xffafd7, + 219: 0xffafff, + 220: 0xffd700, + 221: 0xffd75f, + 222: 0xffd787, + 223: 0xffd7af, + 224: 0xffd7d7, + 225: 0xffd7ff, + 226: 0xffff00, + 227: 0xffff5f, + 228: 0xffff87, + 229: 0xffffaf, + 230: 0xffffd7, + 231: 0xffffff, + 232: 0x080808, + 233: 0x121212, + 234: 0x1c1c1c, + 235: 0x262626, + 236: 0x303030, + 237: 0x3a3a3a, + 238: 0x444444, + 239: 0x4e4e4e, + 240: 0x585858, + 241: 0x626262, + 242: 0x6c6c6c, + 243: 0x767676, + 244: 0x808080, + 245: 0x8a8a8a, + 246: 0x949494, + 247: 0x9e9e9e, + 248: 0xa8a8a8, + 249: 0xb2b2b2, + 250: 0xbcbcbc, + 251: 0xc6c6c6, + 252: 0xd0d0d0, + 253: 0xdadada, + 254: 0xe4e4e4, + 255: 0xeeeeee, +} + +// `\033]0;TITLESTR\007` +func doTitleSequence(er *bytes.Reader) error { + var c byte + var err error + + c, err = er.ReadByte() + if err != nil { + return err + } + if c != '0' && c != '2' { + return nil + } + c, err = er.ReadByte() + if err != nil { + return err + } + if c != ';' { + return nil + } + title := make([]byte, 0, 80) + for { + c, err = er.ReadByte() + if err != nil { + return err + } + if c == 0x07 || c == '\n' { + break + } + title = append(title, c) + } + if len(title) > 0 { + title8, err := syscall.UTF16PtrFromString(string(title)) + if err == nil { + procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) + } + } + return nil +} + +// Write write data on console +func (w *Writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + + if c2 == ']' { + if err := doTitleSequence(er); err != nil { + break loop + } + continue + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + var m byte + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + break + } + buf.Write([]byte(string(c))) + } + + switch m { + case 'A': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'B': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'C': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'D': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'E': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'F': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'G': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = short(n - 1) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'H', 'f': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + if buf.Len() > 0 { + token := strings.Split(buf.String(), ";") + switch len(token) { + case 1: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + csbi.cursorPosition.y = short(n1 - 1) + case 2: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + n2, err := strconv.Atoi(token[1]) + if err != nil { + continue + } + csbi.cursorPosition.x = short(n2 - 1) + csbi.cursorPosition.y = short(n1 - 1) + } + } else { + csbi.cursorPosition.y = 0 + } + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'J': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + var count, written dword + var cursor coord + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'K': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + var count, written dword + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x + 1, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x - 1) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + count = dword(csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'm': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) + continue + } + token := strings.Split(cs, ";") + for i := 0; i < len(token); i++ { + ns := token[i] + if n, err = strconv.Atoi(ns); err == nil { + switch { + case n == 0 || n == 100: + attr = w.oldattr + case 1 <= n && n <= 5: + attr |= foregroundIntensity + case n == 7: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case n == 22 || n == 25: + attr |= foregroundIntensity + case n == 27: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 30 <= n && n <= 37: + attr &= backgroundMask + if (n-30)&1 != 0 { + attr |= foregroundRed + } + if (n-30)&2 != 0 { + attr |= foregroundGreen + } + if (n-30)&4 != 0 { + attr |= foregroundBlue + } + case n == 38: // set foreground color. + if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256foreAttr == nil { + n256setup() + } + attr &= backgroundMask + attr |= n256foreAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & backgroundMask) + } + case n == 39: // reset foreground color. + attr &= backgroundMask + attr |= w.oldattr & foregroundMask + case 40 <= n && n <= 47: + attr &= foregroundMask + if (n-40)&1 != 0 { + attr |= backgroundRed + } + if (n-40)&2 != 0 { + attr |= backgroundGreen + } + if (n-40)&4 != 0 { + attr |= backgroundBlue + } + case n == 48: // set background color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256backAttr == nil { + n256setup() + } + attr &= foregroundMask + attr |= n256backAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & foregroundMask) + } + case n == 49: // reset foreground color. + attr &= foregroundMask + attr |= w.oldattr & backgroundMask + case 90 <= n && n <= 97: + attr = (attr & backgroundMask) + attr |= foregroundIntensity + if (n-90)&1 != 0 { + attr |= foregroundRed + } + if (n-90)&2 != 0 { + attr |= foregroundGreen + } + if (n-90)&4 != 0 { + attr |= foregroundBlue + } + case 100 <= n && n <= 107: + attr = (attr & foregroundMask) + attr |= backgroundIntensity + if (n-100)&1 != 0 { + attr |= backgroundRed + } + if (n-100)&2 != 0 { + attr |= backgroundGreen + } + if (n-100)&4 != 0 { + attr |= backgroundBlue + } + } + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) + } + } + case 'h': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 'l': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 's': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + w.oldpos = csbi.cursorPosition + case 'u': + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) + } + } + + return len(data), nil +} + +type consoleColor struct { + rgb int + red bool + green bool + blue bool + intensity bool +} + +func (c consoleColor) foregroundAttr() (attr word) { + if c.red { + attr |= foregroundRed + } + if c.green { + attr |= foregroundGreen + } + if c.blue { + attr |= foregroundBlue + } + if c.intensity { + attr |= foregroundIntensity + } + return +} + +func (c consoleColor) backgroundAttr() (attr word) { + if c.red { + attr |= backgroundRed + } + if c.green { + attr |= backgroundGreen + } + if c.blue { + attr |= backgroundBlue + } + if c.intensity { + attr |= backgroundIntensity + } + return +} + +var color16 = []consoleColor{ + {0x000000, false, false, false, false}, + {0x000080, false, false, true, false}, + {0x008000, false, true, false, false}, + {0x008080, false, true, true, false}, + {0x800000, true, false, false, false}, + {0x800080, true, false, true, false}, + {0x808000, true, true, false, false}, + {0xc0c0c0, true, true, true, false}, + {0x808080, false, false, false, true}, + {0x0000ff, false, false, true, true}, + {0x00ff00, false, true, false, true}, + {0x00ffff, false, true, true, true}, + {0xff0000, true, false, false, true}, + {0xff00ff, true, false, true, true}, + {0xffff00, true, true, false, true}, + {0xffffff, true, true, true, true}, +} + +type hsv struct { + h, s, v float32 +} + +func (a hsv) dist(b hsv) float32 { + dh := a.h - b.h + switch { + case dh > 0.5: + dh = 1 - dh + case dh < -0.5: + dh = -1 - dh + } + ds := a.s - b.s + dv := a.v - b.v + return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) +} + +func toHSV(rgb int) hsv { + r, g, b := float32((rgb&0xFF0000)>>16)/256.0, + float32((rgb&0x00FF00)>>8)/256.0, + float32(rgb&0x0000FF)/256.0 + min, max := minmax3f(r, g, b) + h := max - min + if h > 0 { + if max == r { + h = (g - b) / h + if h < 0 { + h += 6 + } + } else if max == g { + h = 2 + (b-r)/h + } else { + h = 4 + (r-g)/h + } + } + h /= 6.0 + s := max - min + if max != 0 { + s /= max + } + v := max + return hsv{h: h, s: s, v: v} +} + +type hsvTable []hsv + +func toHSVTable(rgbTable []consoleColor) hsvTable { + t := make(hsvTable, len(rgbTable)) + for i, c := range rgbTable { + t[i] = toHSV(c.rgb) + } + return t +} + +func (t hsvTable) find(rgb int) consoleColor { + hsv := toHSV(rgb) + n := 7 + l := float32(5.0) + for i, p := range t { + d := hsv.dist(p) + if d < l { + l, n = d, i + } + } + return color16[n] +} + +func minmax3f(a, b, c float32) (min, max float32) { + if a < b { + if b < c { + return a, c + } else if a < c { + return a, b + } else { + return c, b + } + } else { + if a < c { + return b, c + } else if b < c { + return b, a + } else { + return c, a + } + } +} + +var n256foreAttr []word +var n256backAttr []word + +func n256setup() { + n256foreAttr = make([]word, 256) + n256backAttr = make([]word, 256) + t := toHSVTable(color16) + for i, rgb := range color256 { + c := t.find(rgb) + n256foreAttr[i] = c.foregroundAttr() + n256backAttr[i] = c.backgroundAttr() + } +} diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go new file mode 100644 index 000000000..9721e16f4 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -0,0 +1,55 @@ +package colorable + +import ( + "bytes" + "io" +) + +// NonColorable hold writer but remove escape sequence. +type NonColorable struct { + out io.Writer +} + +// NewNonColorable return new instance of Writer which remove escape sequence from Writer. +func NewNonColorable(w io.Writer) io.Writer { + return &NonColorable{out: w} +} + +// Write write data on console +func (w *NonColorable) Write(data []byte) (n int, err error) { + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + break + } + buf.Write([]byte(string(c))) + } + } + + return len(data), nil +} diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml new file mode 100644 index 000000000..5597e026d --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - tip + +os: + - linux + - osx + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE new file mode 100644 index 000000000..65dc692b6 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md new file mode 100644 index 000000000..1e69004bb --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -0,0 +1,50 @@ +# go-isatty + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) +[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) + +isatty for golang + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/mattn/go-isatty" + "os" +) + +func main() { + if isatty.IsTerminal(os.Stdout.Fd()) { + fmt.Println("Is Terminal") + } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { + fmt.Println("Is Cygwin/MSYS2 Terminal") + } else { + fmt.Println("Is Not Terminal") + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-isatty +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) + +## Thanks + +* k-takata: base idea for IsCygwinTerminal + + https://github.com/k-takata/go-iscygpty diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go new file mode 100644 index 000000000..17d4f90eb --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/doc.go @@ -0,0 +1,2 @@ +// Package isatty implements interface to isatty +package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/mattn/go-isatty/isatty_appengine.go new file mode 100644 index 000000000..9584a9884 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_appengine.go @@ -0,0 +1,15 @@ +// +build appengine + +package isatty + +// IsTerminal returns true if the file descriptor is terminal which +// is always false on on appengine classic which is a sandboxed PaaS. +func IsTerminal(fd uintptr) bool { + return false +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go new file mode 100644 index 000000000..42f2514d1 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -0,0 +1,18 @@ +// +build darwin freebsd openbsd netbsd dragonfly +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TIOCGETA + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/mattn/go-isatty/isatty_linux.go new file mode 100644 index 000000000..7384cf991 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux.go @@ -0,0 +1,18 @@ +// +build linux +// +build !appengine,!ppc64,!ppc64le + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go new file mode 100644 index 000000000..44e5d2130 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go @@ -0,0 +1,19 @@ +// +build linux +// +build ppc64 ppc64le + +package isatty + +import ( + "unsafe" + + syscall "golang.org/x/sys/unix" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go new file mode 100644 index 000000000..9d8b4a599 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -0,0 +1,10 @@ +// +build !windows +// +build !appengine + +package isatty + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go new file mode 100644 index 000000000..1f0c6bf53 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -0,0 +1,16 @@ +// +build solaris +// +build !appengine + +package isatty + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func IsTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go new file mode 100644 index 000000000..af51cbcaa --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -0,0 +1,94 @@ +// +build windows +// +build !appengine + +package isatty + +import ( + "strings" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + fileNameInfo uintptr = 2 + fileTypePipe = 3 +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") + procGetFileType = kernel32.NewProc("GetFileType") +) + +func init() { + // Check if GetFileInformationByHandleEx is available. + if procGetFileInformationByHandleEx.Find() != nil { + procGetFileInformationByHandleEx = nil + } +} + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// Check pipe name is used for cygwin/msys2 pty. +// Cygwin/MSYS2 PTY has a name like: +// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master +func isCygwinPipeName(name string) bool { + token := strings.Split(name, "-") + if len(token) < 5 { + return false + } + + if token[0] != `\msys` && token[0] != `\cygwin` { + return false + } + + if token[1] == "" { + return false + } + + if !strings.HasPrefix(token[2], "pty") { + return false + } + + if token[3] != `from` && token[3] != `to` { + return false + } + + if token[4] != "master" { + return false + } + + return true +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. +func IsCygwinTerminal(fd uintptr) bool { + if procGetFileInformationByHandleEx == nil { + return false + } + + // Cygwin/msys's pty is a pipe. + ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) + if ft != fileTypePipe || e != 0 { + return false + } + + var buf [2 + syscall.MAX_PATH]uint16 + r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), + 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), + uintptr(len(buf)*2), 0, 0) + if r == 0 || e != 0 { + return false + } + + l := *(*uint32)(unsafe.Pointer(&buf)) + return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6264a8d51..93959c675 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -41,6 +41,8 @@ github.com/buger/goterm # github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b github.com/checkpoint-restore/go-criu github.com/checkpoint-restore/go-criu/rpc +# github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e +github.com/chzyer/readline # github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f github.com/containerd/cgroups/stats/v1 # github.com/containerd/containerd v1.4.1 @@ -66,7 +68,7 @@ github.com/containernetworking/plugins/pkg/utils/hwaddr github.com/containernetworking/plugins/pkg/utils/sysctl github.com/containernetworking/plugins/plugins/ipam/host-local/backend github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator -# github.com/containers/buildah v1.17.0 +# github.com/containers/buildah v1.17.1-0.20201113135631-d0c958d65eb2 github.com/containers/buildah github.com/containers/buildah/bind github.com/containers/buildah/chroot @@ -103,7 +105,7 @@ github.com/containers/common/pkg/sysinfo github.com/containers/common/version # github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon/runner/config -# github.com/containers/image/v5 v5.7.0 +# github.com/containers/image/v5 v5.8.0 github.com/containers/image/v5/copy github.com/containers/image/v5/directory github.com/containers/image/v5/directory/explicitfilepath @@ -136,6 +138,7 @@ github.com/containers/image/v5/pkg/compression github.com/containers/image/v5/pkg/compression/internal github.com/containers/image/v5/pkg/compression/types github.com/containers/image/v5/pkg/docker/config +github.com/containers/image/v5/pkg/shortnames github.com/containers/image/v5/pkg/strslice github.com/containers/image/v5/pkg/sysregistriesv2 github.com/containers/image/v5/pkg/tlsclientconfig @@ -334,6 +337,9 @@ github.com/inconshreveable/mousetrap github.com/ishidawataru/sctp # github.com/json-iterator/go v1.1.10 github.com/json-iterator/go +# github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a +github.com/juju/ansiterm +github.com/juju/ansiterm/tabwriter # github.com/klauspost/compress v1.11.2 github.com/klauspost/compress/flate github.com/klauspost/compress/fse @@ -343,6 +349,16 @@ github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd/internal/xxhash # github.com/klauspost/pgzip v1.2.5 github.com/klauspost/pgzip +# github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a +github.com/lunixbochs/vtclean +# github.com/manifoldco/promptui v0.8.0 +github.com/manifoldco/promptui +github.com/manifoldco/promptui/list +github.com/manifoldco/promptui/screenbuf +# github.com/mattn/go-colorable v0.0.9 +github.com/mattn/go-colorable +# github.com/mattn/go-isatty v0.0.4 +github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.9 github.com/mattn/go-runewidth # github.com/mattn/go-shellwords v1.0.10 -- cgit v1.2.3-54-g00ecf