From 68292ae6dacd3c55f06e6457c14ea4daaf51dbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BChnert?= Date: Fri, 16 Oct 2020 02:33:46 +0200 Subject: [PATCH] simplified and streamlined repository package entirely --- cmd/addRepo.go | 45 +- cmd/listConcept.go | 16 +- cmd/listRepo.go | 17 +- cmd/removeRepo.go | 5 +- cmd/root.go | 27 +- cmd/tidyRepo.go | 2 +- go.mod | 13 +- go.sum | 1 + pkg/concepts/concepts.go | 90 +- pkg/concepts/rendering.go | 15 +- pkg/config/config.go | 168 -- pkg/errors/errors.go | 1 + pkg/repositories/clone.go | 14 - pkg/repositories/repositories.go | 1010 +++++---- .../github.com/99designs/keyring/.gitignore | 1 - vendor/github.com/99designs/keyring/README.md | 55 - vendor/github.com/99designs/keyring/array.go | 54 - vendor/github.com/99designs/keyring/config.go | 52 - vendor/github.com/99designs/keyring/file.go | 190 -- vendor/github.com/99designs/keyring/go.mod | 20 - vendor/github.com/99designs/keyring/go.sum | 42 - .../github.com/99designs/keyring/keychain.go | 295 --- .../github.com/99designs/keyring/keyring.go | 131 -- .../github.com/99designs/keyring/kwallet.go | 234 --- .../github.com/99designs/keyring/libsecret.go | 277 --- vendor/github.com/99designs/keyring/pass.go | 160 -- vendor/github.com/99designs/keyring/prompt.go | 27 - .../github.com/99designs/keyring/wincred.go | 93 - .../github.com/dvsekhvalnov/jose2go/README.md | 938 --------- .../dvsekhvalnov/jose2go/aes/ecb.go | 68 - .../dvsekhvalnov/jose2go/aes/key_wrap.go | 113 - .../dvsekhvalnov/jose2go/aes_cbc_hmac.go | 112 - .../dvsekhvalnov/jose2go/aes_gcm.go | 98 - .../dvsekhvalnov/jose2go/aes_gcm_kw.go | 128 -- .../github.com/dvsekhvalnov/jose2go/aeskw.go | 64 - .../dvsekhvalnov/jose2go/arrays/arrays.go | 116 - .../jose2go/base64url/base64url.go | 31 - .../dvsekhvalnov/jose2go/compact/compact.go | 33 - .../dvsekhvalnov/jose2go/deflate.go | 39 - .../github.com/dvsekhvalnov/jose2go/direct.go | 39 - .../github.com/dvsekhvalnov/jose2go/ecdh.go | 157 -- .../dvsekhvalnov/jose2go/ecdh_aeskw.go | 42 - .../dvsekhvalnov/jose2go/ecdsa_using_sha.go | 76 - .../github.com/dvsekhvalnov/jose2go/hmac.go | 13 - .../dvsekhvalnov/jose2go/hmac_using_sha.go | 46 - .../github.com/dvsekhvalnov/jose2go/jose.go | 424 ---- .../jose2go/kdf/nist_sp800_56a.go | 43 - .../dvsekhvalnov/jose2go/kdf/pbkdf2.go | 63 - .../dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem | 14 - .../jose2go/keys/ecc/ec_private.key | 5 - .../jose2go/keys/ecc/ec_private.pem | 5 - .../jose2go/keys/ecc/ec_public.key | 4 - .../dvsekhvalnov/jose2go/keys/ecc/ecc.go | 88 - .../dvsekhvalnov/jose2go/padding/align.go | 23 - .../dvsekhvalnov/jose2go/padding/pkcs7.go | 38 - .../dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go | 103 - .../dvsekhvalnov/jose2go/plaintext.go | 38 - .../dvsekhvalnov/jose2go/rsa_oaep.go | 57 - .../dvsekhvalnov/jose2go/rsa_pkcs1v15.go | 41 - .../dvsekhvalnov/jose2go/rsa_using_sha.go | 50 - .../dvsekhvalnov/jose2go/rsapss_using_sha.go | 43 - vendor/github.com/dvsekhvalnov/jose2go/sha.go | 24 - .../fsnotify/fsnotify/.editorconfig | 12 + .../fsnotify/fsnotify/.gitattributes | 1 + .../github.com/fsnotify/fsnotify/.gitignore | 6 + .../github.com/fsnotify/fsnotify/.travis.yml | 36 + vendor/github.com/fsnotify/fsnotify/AUTHORS | 52 + .../github.com/fsnotify/fsnotify/CHANGELOG.md | 317 +++ .../fsnotify/fsnotify/CONTRIBUTING.md | 77 + vendor/github.com/fsnotify/fsnotify/LICENSE | 28 + vendor/github.com/fsnotify/fsnotify/README.md | 130 ++ vendor/github.com/fsnotify/fsnotify/fen.go | 37 + .../github.com/fsnotify/fsnotify/fsnotify.go | 68 + vendor/github.com/fsnotify/fsnotify/go.mod | 5 + vendor/github.com/fsnotify/fsnotify/go.sum | 2 + .../github.com/fsnotify/fsnotify/inotify.go | 337 +++ .../fsnotify/fsnotify/inotify_poller.go | 187 ++ vendor/github.com/fsnotify/fsnotify/kqueue.go | 521 +++++ .../fsnotify/fsnotify/open_mode_bsd.go | 11 + .../fsnotify/fsnotify/open_mode_darwin.go | 12 + .../github.com/fsnotify/fsnotify/windows.go | 561 +++++ .../gsterjov/go-libsecret/collection.go | 124 -- .../github.com/gsterjov/go-libsecret/item.go | 77 - .../gsterjov/go-libsecret/prompt.go | 55 - .../gsterjov/go-libsecret/secret.go | 21 - .../gsterjov/go-libsecret/service.go | 141 -- .../gsterjov/go-libsecret/session.go | 22 - vendor/github.com/hashicorp/hcl/.gitignore | 9 + vendor/github.com/hashicorp/hcl/.travis.yml | 13 + vendor/github.com/hashicorp/hcl/LICENSE | 354 ++++ vendor/github.com/hashicorp/hcl/Makefile | 18 + vendor/github.com/hashicorp/hcl/README.md | 125 ++ vendor/github.com/hashicorp/hcl/appveyor.yml | 19 + vendor/github.com/hashicorp/hcl/decoder.go | 729 +++++++ vendor/github.com/hashicorp/hcl/go.mod | 3 + vendor/github.com/hashicorp/hcl/go.sum | 2 + vendor/github.com/hashicorp/hcl/hcl.go | 11 + .../github.com/hashicorp/hcl/hcl/ast/ast.go | 219 ++ .../github.com/hashicorp/hcl/hcl/ast/walk.go | 52 + .../hashicorp/hcl/hcl/parser/error.go | 17 + .../hashicorp/hcl/hcl/parser/parser.go | 532 +++++ .../hashicorp/hcl/hcl/printer/nodes.go | 789 +++++++ .../hashicorp/hcl/hcl/printer/printer.go | 66 + .../hashicorp/hcl/hcl/scanner/scanner.go | 652 ++++++ .../hashicorp/hcl/hcl/strconv/quote.go | 241 +++ .../hashicorp/hcl/hcl/token/position.go | 46 + .../hashicorp/hcl/hcl/token/token.go | 219 ++ .../hashicorp/hcl/json/parser/flatten.go | 117 ++ .../hashicorp/hcl/json/parser/parser.go | 313 +++ .../hashicorp/hcl/json/scanner/scanner.go | 451 ++++ .../hashicorp/hcl/json/token/position.go | 46 + .../hashicorp/hcl/json/token/token.go | 118 ++ vendor/github.com/hashicorp/hcl/lex.go | 38 + vendor/github.com/hashicorp/hcl/parse.go | 39 + .../keybase/go-keychain/.travis.yml | 20 - .../github.com/keybase/go-keychain/README.md | 157 -- .../go-keychain/corefoundation_1.10.go | 359 ---- .../go-keychain/corefoundation_pre1.10.go | 344 --- .../keybase/go-keychain/datetime.go | 69 - vendor/github.com/keybase/go-keychain/ios.go | 22 - .../keybase/go-keychain/keychain_1.10.go | 532 ----- .../keybase/go-keychain/keychain_pre1.10.go | 601 ------ .../keybase/go-keychain/macos_1.10.go | 272 --- .../keybase/go-keychain/macos_pre1.10.go | 274 --- .../keybase/go-keychain/staticcheck.conf | 5 - vendor/github.com/keybase/go-keychain/util.go | 31 - .../magiconair/properties/.gitignore | 6 + .../magiconair/properties/.travis.yml | 10 + .../magiconair/properties/CHANGELOG.md | 131 ++ .../github.com/magiconair/properties/LICENSE | 25 + .../magiconair/properties/README.md | 129 ++ .../magiconair/properties/decode.go | 289 +++ .../github.com/magiconair/properties/doc.go | 156 ++ .../magiconair/properties/integrate.go | 34 + .../github.com/magiconair/properties/lex.go | 407 ++++ .../github.com/magiconair/properties/load.go | 292 +++ .../magiconair/properties/parser.go | 95 + .../magiconair/properties/properties.go | 833 ++++++++ .../magiconair/properties/rangecheck.go | 31 + .../mitchellh/mapstructure/.travis.yml | 8 + .../mitchellh/mapstructure/CHANGELOG.md | 21 + .../copy => mitchellh/mapstructure}/LICENSE | 2 +- .../mitchellh/mapstructure/README.md | 46 + .../mitchellh/mapstructure/decode_hooks.go | 217 ++ .../mitchellh/mapstructure/error.go | 50 + .../github.com/mitchellh/mapstructure/go.mod | 1 + .../mitchellh/mapstructure/mapstructure.go | 1149 ++++++++++ vendor/github.com/mtibben/percent/LICENSE | 21 - vendor/github.com/mtibben/percent/README.md | 5 - vendor/github.com/mtibben/percent/go.mod | 3 - vendor/github.com/mtibben/percent/percent.go | 64 - vendor/github.com/otiai10/copy/.gitignore | 3 - vendor/github.com/otiai10/copy/README.md | 14 - vendor/github.com/otiai10/copy/copy.go | 184 -- vendor/github.com/otiai10/copy/go.mod | 5 - vendor/github.com/otiai10/copy/go.sum | 5 - vendor/github.com/otiai10/copy/options.go | 46 - .../github.com/pelletier/go-toml/.gitignore | 2 + .../github.com/pelletier/go-toml/.travis.yml | 23 + .../go-toml}/LICENSE | 3 +- vendor/github.com/pelletier/go-toml/README.md | 131 ++ .../pelletier/go-toml/benchmark.json | 164 ++ .../github.com/pelletier/go-toml/benchmark.sh | 32 + .../pelletier/go-toml/benchmark.toml | 244 +++ .../pelletier/go-toml/benchmark.yml | 121 ++ vendor/github.com/pelletier/go-toml/doc.go | 23 + .../pelletier/go-toml/example-crlf.toml | 29 + .../github.com/pelletier/go-toml/example.toml | 29 + vendor/github.com/pelletier/go-toml/fuzz.go | 31 + vendor/github.com/pelletier/go-toml/fuzz.sh | 15 + .../pelletier/go-toml/keysparsing.go | 85 + vendor/github.com/pelletier/go-toml/lexer.go | 750 +++++++ .../github.com/pelletier/go-toml/marshal.go | 609 ++++++ .../pelletier/go-toml/marshal_test.toml | 38 + vendor/github.com/pelletier/go-toml/parser.go | 430 ++++ .../github.com/pelletier/go-toml/position.go | 29 + vendor/github.com/pelletier/go-toml/test.sh | 88 + vendor/github.com/pelletier/go-toml/token.go | 144 ++ vendor/github.com/pelletier/go-toml/toml.go | 367 ++++ .../pelletier/go-toml/tomltree_create.go | 142 ++ .../pelletier/go-toml/tomltree_write.go | 333 +++ vendor/github.com/spf13/afero/.travis.yml | 21 + vendor/github.com/spf13/afero/LICENSE.txt | 174 ++ vendor/github.com/spf13/afero/README.md | 452 ++++ vendor/github.com/spf13/afero/afero.go | 108 + vendor/github.com/spf13/afero/appveyor.yml | 15 + vendor/github.com/spf13/afero/basepath.go | 180 ++ .../github.com/spf13/afero/cacheOnReadFs.go | 290 +++ vendor/github.com/spf13/afero/const_bsds.go | 22 + .../github.com/spf13/afero/const_win_unix.go | 25 + .../github.com/spf13/afero/copyOnWriteFs.go | 292 +++ vendor/github.com/spf13/afero/go.mod | 1 + vendor/github.com/spf13/afero/httpFs.go | 110 + vendor/github.com/spf13/afero/ioutil.go | 230 ++ vendor/github.com/spf13/afero/lstater.go | 27 + vendor/github.com/spf13/afero/match.go | 110 + vendor/github.com/spf13/afero/mem/dir.go | 37 + vendor/github.com/spf13/afero/mem/dirmap.go | 43 + vendor/github.com/spf13/afero/mem/file.go | 317 +++ vendor/github.com/spf13/afero/memmap.go | 365 ++++ vendor/github.com/spf13/afero/os.go | 101 + vendor/github.com/spf13/afero/path.go | 106 + vendor/github.com/spf13/afero/readonlyfs.go | 80 + vendor/github.com/spf13/afero/regexpfs.go | 214 ++ vendor/github.com/spf13/afero/unionFile.go | 305 +++ vendor/github.com/spf13/afero/util.go | 330 +++ .../jwalterweatherman}/.gitignore | 1 - .../jwalterweatherman}/LICENSE | 5 +- .../spf13/jwalterweatherman/README.md | 148 ++ .../jwalterweatherman/default_notepad.go | 113 + .../github.com/spf13/jwalterweatherman/go.mod | 1 + .../spf13/jwalterweatherman/log_counter.go | 55 + .../spf13/jwalterweatherman/notepad.go | 194 ++ .../go-keychain => spf13/viper}/.gitignore | 7 +- vendor/github.com/spf13/viper/.travis.yml | 31 + .../keyring => spf13/viper}/LICENSE | 5 +- vendor/github.com/spf13/viper/README.md | 691 ++++++ vendor/github.com/spf13/viper/flags.go | 57 + vendor/github.com/spf13/viper/go.mod | 43 + vendor/github.com/spf13/viper/go.sum | 178 ++ vendor/github.com/spf13/viper/util.go | 221 ++ vendor/github.com/spf13/viper/viper.go | 1868 +++++++++++++++++ vendor/github.com/whilp/git-urls/.travis.yml | 7 - vendor/github.com/whilp/git-urls/LICENSE | 21 - vendor/github.com/whilp/git-urls/Makefile | 16 - vendor/github.com/whilp/git-urls/README.md | 3 - vendor/github.com/whilp/git-urls/go.mod | 3 - vendor/github.com/whilp/git-urls/urls.go | 150 -- .../zalando/go-keyring/.catwatch.yml | 1 + .../github.com/zalando/go-keyring/.travis.yml | 25 + .../github.com/zalando/go-keyring/.zappr.yml | 8 + .../zalando/go-keyring/CONTRIBUTING.md | 12 + .../jose2go => zalando/go-keyring}/LICENSE | 2 +- .../github.com/zalando/go-keyring/MAINTAINERS | 2 + .../github.com/zalando/go-keyring/README.md | 146 ++ .../github.com/zalando/go-keyring/SECURITY.md | 8 + .../zalando/go-keyring/appveyor.yml | 11 + vendor/github.com/zalando/go-keyring/go.mod | 9 + vendor/github.com/zalando/go-keyring/go.sum | 16 + .../github.com/zalando/go-keyring/keyring.go | 38 + .../zalando/go-keyring/keyring_darwin.go | 94 + .../zalando/go-keyring/keyring_fallback.go | 23 + .../zalando/go-keyring/keyring_linux.go | 120 ++ .../zalando/go-keyring/keyring_mock.go | 46 + .../zalando/go-keyring/keyring_windows.go | 52 + .../secret_service/secret_service.go | 245 +++ vendor/modules.txt | 47 +- 247 files changed, 24635 insertions(+), 9182 deletions(-) delete mode 100644 pkg/config/config.go delete mode 100644 pkg/repositories/clone.go delete mode 100644 vendor/github.com/99designs/keyring/.gitignore delete mode 100644 vendor/github.com/99designs/keyring/README.md delete mode 100644 vendor/github.com/99designs/keyring/array.go delete mode 100644 vendor/github.com/99designs/keyring/config.go delete mode 100644 vendor/github.com/99designs/keyring/file.go delete mode 100644 vendor/github.com/99designs/keyring/go.mod delete mode 100644 vendor/github.com/99designs/keyring/go.sum delete mode 100644 vendor/github.com/99designs/keyring/keychain.go delete mode 100644 vendor/github.com/99designs/keyring/keyring.go delete mode 100644 vendor/github.com/99designs/keyring/kwallet.go delete mode 100644 vendor/github.com/99designs/keyring/libsecret.go delete mode 100644 vendor/github.com/99designs/keyring/pass.go delete mode 100644 vendor/github.com/99designs/keyring/prompt.go delete mode 100644 vendor/github.com/99designs/keyring/wincred.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/README.md delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/aes/ecb.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/aes/key_wrap.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/aes_cbc_hmac.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/aes_gcm.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/aes_gcm_kw.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/aeskw.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/arrays/arrays.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/base64url/base64url.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/compact/compact.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/deflate.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/direct.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/ecdh.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/ecdh_aeskw.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/ecdsa_using_sha.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/hmac.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/hmac_using_sha.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/jose.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.key delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.pem delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_public.key delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/padding/align.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/padding/pkcs7.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/plaintext.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/rsa_oaep.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/rsa_pkcs1v15.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/rsa_using_sha.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/rsapss_using_sha.go delete mode 100644 vendor/github.com/dvsekhvalnov/jose2go/sha.go create mode 100644 vendor/github.com/fsnotify/fsnotify/.editorconfig create mode 100644 vendor/github.com/fsnotify/fsnotify/.gitattributes create mode 100644 vendor/github.com/fsnotify/fsnotify/.gitignore create mode 100644 vendor/github.com/fsnotify/fsnotify/.travis.yml create mode 100644 vendor/github.com/fsnotify/fsnotify/AUTHORS create mode 100644 vendor/github.com/fsnotify/fsnotify/CHANGELOG.md create mode 100644 vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md create mode 100644 vendor/github.com/fsnotify/fsnotify/LICENSE create mode 100644 vendor/github.com/fsnotify/fsnotify/README.md create mode 100644 vendor/github.com/fsnotify/fsnotify/fen.go create mode 100644 vendor/github.com/fsnotify/fsnotify/fsnotify.go create mode 100644 vendor/github.com/fsnotify/fsnotify/go.mod create mode 100644 vendor/github.com/fsnotify/fsnotify/go.sum create mode 100644 vendor/github.com/fsnotify/fsnotify/inotify.go create mode 100644 vendor/github.com/fsnotify/fsnotify/inotify_poller.go create mode 100644 vendor/github.com/fsnotify/fsnotify/kqueue.go create mode 100644 vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go create mode 100644 vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go create mode 100644 vendor/github.com/fsnotify/fsnotify/windows.go delete mode 100644 vendor/github.com/gsterjov/go-libsecret/collection.go delete mode 100644 vendor/github.com/gsterjov/go-libsecret/item.go delete mode 100644 vendor/github.com/gsterjov/go-libsecret/prompt.go delete mode 100644 vendor/github.com/gsterjov/go-libsecret/secret.go delete mode 100644 vendor/github.com/gsterjov/go-libsecret/service.go delete mode 100644 vendor/github.com/gsterjov/go-libsecret/session.go create mode 100644 vendor/github.com/hashicorp/hcl/.gitignore create mode 100644 vendor/github.com/hashicorp/hcl/.travis.yml create mode 100644 vendor/github.com/hashicorp/hcl/LICENSE create mode 100644 vendor/github.com/hashicorp/hcl/Makefile create mode 100644 vendor/github.com/hashicorp/hcl/README.md create mode 100644 vendor/github.com/hashicorp/hcl/appveyor.yml create mode 100644 vendor/github.com/hashicorp/hcl/decoder.go create mode 100644 vendor/github.com/hashicorp/hcl/go.mod create mode 100644 vendor/github.com/hashicorp/hcl/go.sum create mode 100644 vendor/github.com/hashicorp/hcl/hcl.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/ast/ast.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/ast/walk.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/parser/error.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/parser/parser.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/printer/printer.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/token/position.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/token/token.go create mode 100644 vendor/github.com/hashicorp/hcl/json/parser/flatten.go create mode 100644 vendor/github.com/hashicorp/hcl/json/parser/parser.go create mode 100644 vendor/github.com/hashicorp/hcl/json/scanner/scanner.go create mode 100644 vendor/github.com/hashicorp/hcl/json/token/position.go create mode 100644 vendor/github.com/hashicorp/hcl/json/token/token.go create mode 100644 vendor/github.com/hashicorp/hcl/lex.go create mode 100644 vendor/github.com/hashicorp/hcl/parse.go delete mode 100644 vendor/github.com/keybase/go-keychain/.travis.yml delete mode 100644 vendor/github.com/keybase/go-keychain/README.md delete mode 100644 vendor/github.com/keybase/go-keychain/corefoundation_1.10.go delete mode 100644 vendor/github.com/keybase/go-keychain/corefoundation_pre1.10.go delete mode 100644 vendor/github.com/keybase/go-keychain/datetime.go delete mode 100644 vendor/github.com/keybase/go-keychain/ios.go delete mode 100644 vendor/github.com/keybase/go-keychain/keychain_1.10.go delete mode 100644 vendor/github.com/keybase/go-keychain/keychain_pre1.10.go delete mode 100644 vendor/github.com/keybase/go-keychain/macos_1.10.go delete mode 100644 vendor/github.com/keybase/go-keychain/macos_pre1.10.go delete mode 100644 vendor/github.com/keybase/go-keychain/staticcheck.conf delete mode 100644 vendor/github.com/keybase/go-keychain/util.go create mode 100644 vendor/github.com/magiconair/properties/.gitignore create mode 100644 vendor/github.com/magiconair/properties/.travis.yml create mode 100644 vendor/github.com/magiconair/properties/CHANGELOG.md create mode 100644 vendor/github.com/magiconair/properties/LICENSE create mode 100644 vendor/github.com/magiconair/properties/README.md create mode 100644 vendor/github.com/magiconair/properties/decode.go create mode 100644 vendor/github.com/magiconair/properties/doc.go create mode 100644 vendor/github.com/magiconair/properties/integrate.go create mode 100644 vendor/github.com/magiconair/properties/lex.go create mode 100644 vendor/github.com/magiconair/properties/load.go create mode 100644 vendor/github.com/magiconair/properties/parser.go create mode 100644 vendor/github.com/magiconair/properties/properties.go create mode 100644 vendor/github.com/magiconair/properties/rangecheck.go create mode 100644 vendor/github.com/mitchellh/mapstructure/.travis.yml create mode 100644 vendor/github.com/mitchellh/mapstructure/CHANGELOG.md rename vendor/github.com/{otiai10/copy => mitchellh/mapstructure}/LICENSE (96%) create mode 100644 vendor/github.com/mitchellh/mapstructure/README.md create mode 100644 vendor/github.com/mitchellh/mapstructure/decode_hooks.go create mode 100644 vendor/github.com/mitchellh/mapstructure/error.go create mode 100644 vendor/github.com/mitchellh/mapstructure/go.mod create mode 100644 vendor/github.com/mitchellh/mapstructure/mapstructure.go delete mode 100644 vendor/github.com/mtibben/percent/LICENSE delete mode 100644 vendor/github.com/mtibben/percent/README.md delete mode 100644 vendor/github.com/mtibben/percent/go.mod delete mode 100644 vendor/github.com/mtibben/percent/percent.go delete mode 100644 vendor/github.com/otiai10/copy/.gitignore delete mode 100644 vendor/github.com/otiai10/copy/README.md delete mode 100644 vendor/github.com/otiai10/copy/copy.go delete mode 100644 vendor/github.com/otiai10/copy/go.mod delete mode 100644 vendor/github.com/otiai10/copy/go.sum delete mode 100644 vendor/github.com/otiai10/copy/options.go create mode 100644 vendor/github.com/pelletier/go-toml/.gitignore create mode 100644 vendor/github.com/pelletier/go-toml/.travis.yml rename vendor/github.com/{gsterjov/go-libsecret => pelletier/go-toml}/LICENSE (94%) create mode 100644 vendor/github.com/pelletier/go-toml/README.md create mode 100644 vendor/github.com/pelletier/go-toml/benchmark.json create mode 100644 vendor/github.com/pelletier/go-toml/benchmark.sh create mode 100644 vendor/github.com/pelletier/go-toml/benchmark.toml create mode 100644 vendor/github.com/pelletier/go-toml/benchmark.yml create mode 100644 vendor/github.com/pelletier/go-toml/doc.go create mode 100644 vendor/github.com/pelletier/go-toml/example-crlf.toml create mode 100644 vendor/github.com/pelletier/go-toml/example.toml create mode 100644 vendor/github.com/pelletier/go-toml/fuzz.go create mode 100644 vendor/github.com/pelletier/go-toml/fuzz.sh create mode 100644 vendor/github.com/pelletier/go-toml/keysparsing.go create mode 100644 vendor/github.com/pelletier/go-toml/lexer.go create mode 100644 vendor/github.com/pelletier/go-toml/marshal.go create mode 100644 vendor/github.com/pelletier/go-toml/marshal_test.toml create mode 100644 vendor/github.com/pelletier/go-toml/parser.go create mode 100644 vendor/github.com/pelletier/go-toml/position.go create mode 100644 vendor/github.com/pelletier/go-toml/test.sh create mode 100644 vendor/github.com/pelletier/go-toml/token.go create mode 100644 vendor/github.com/pelletier/go-toml/toml.go create mode 100644 vendor/github.com/pelletier/go-toml/tomltree_create.go create mode 100644 vendor/github.com/pelletier/go-toml/tomltree_write.go create mode 100644 vendor/github.com/spf13/afero/.travis.yml create mode 100644 vendor/github.com/spf13/afero/LICENSE.txt create mode 100644 vendor/github.com/spf13/afero/README.md create mode 100644 vendor/github.com/spf13/afero/afero.go create mode 100644 vendor/github.com/spf13/afero/appveyor.yml create mode 100644 vendor/github.com/spf13/afero/basepath.go create mode 100644 vendor/github.com/spf13/afero/cacheOnReadFs.go create mode 100644 vendor/github.com/spf13/afero/const_bsds.go create mode 100644 vendor/github.com/spf13/afero/const_win_unix.go create mode 100644 vendor/github.com/spf13/afero/copyOnWriteFs.go create mode 100644 vendor/github.com/spf13/afero/go.mod create mode 100644 vendor/github.com/spf13/afero/httpFs.go create mode 100644 vendor/github.com/spf13/afero/ioutil.go create mode 100644 vendor/github.com/spf13/afero/lstater.go create mode 100644 vendor/github.com/spf13/afero/match.go create mode 100644 vendor/github.com/spf13/afero/mem/dir.go create mode 100644 vendor/github.com/spf13/afero/mem/dirmap.go create mode 100644 vendor/github.com/spf13/afero/mem/file.go create mode 100644 vendor/github.com/spf13/afero/memmap.go create mode 100644 vendor/github.com/spf13/afero/os.go create mode 100644 vendor/github.com/spf13/afero/path.go create mode 100644 vendor/github.com/spf13/afero/readonlyfs.go create mode 100644 vendor/github.com/spf13/afero/regexpfs.go create mode 100644 vendor/github.com/spf13/afero/unionFile.go create mode 100644 vendor/github.com/spf13/afero/util.go rename vendor/github.com/{dvsekhvalnov/jose2go => spf13/jwalterweatherman}/.gitignore (97%) rename vendor/github.com/{keybase/go-keychain => spf13/jwalterweatherman}/LICENSE (96%) create mode 100644 vendor/github.com/spf13/jwalterweatherman/README.md create mode 100644 vendor/github.com/spf13/jwalterweatherman/default_notepad.go create mode 100644 vendor/github.com/spf13/jwalterweatherman/go.mod create mode 100644 vendor/github.com/spf13/jwalterweatherman/log_counter.go create mode 100644 vendor/github.com/spf13/jwalterweatherman/notepad.go rename vendor/github.com/{keybase/go-keychain => spf13/viper}/.gitignore (78%) create mode 100644 vendor/github.com/spf13/viper/.travis.yml rename vendor/github.com/{99designs/keyring => spf13/viper}/LICENSE (96%) create mode 100644 vendor/github.com/spf13/viper/README.md create mode 100644 vendor/github.com/spf13/viper/flags.go create mode 100644 vendor/github.com/spf13/viper/go.mod create mode 100644 vendor/github.com/spf13/viper/go.sum create mode 100644 vendor/github.com/spf13/viper/util.go create mode 100644 vendor/github.com/spf13/viper/viper.go delete mode 100644 vendor/github.com/whilp/git-urls/.travis.yml delete mode 100644 vendor/github.com/whilp/git-urls/LICENSE delete mode 100644 vendor/github.com/whilp/git-urls/Makefile delete mode 100644 vendor/github.com/whilp/git-urls/README.md delete mode 100644 vendor/github.com/whilp/git-urls/go.mod delete mode 100644 vendor/github.com/whilp/git-urls/urls.go create mode 100644 vendor/github.com/zalando/go-keyring/.catwatch.yml create mode 100644 vendor/github.com/zalando/go-keyring/.travis.yml create mode 100644 vendor/github.com/zalando/go-keyring/.zappr.yml create mode 100644 vendor/github.com/zalando/go-keyring/CONTRIBUTING.md rename vendor/github.com/{dvsekhvalnov/jose2go => zalando/go-keyring}/LICENSE (97%) create mode 100644 vendor/github.com/zalando/go-keyring/MAINTAINERS create mode 100644 vendor/github.com/zalando/go-keyring/README.md create mode 100644 vendor/github.com/zalando/go-keyring/SECURITY.md create mode 100644 vendor/github.com/zalando/go-keyring/appveyor.yml create mode 100644 vendor/github.com/zalando/go-keyring/go.mod create mode 100644 vendor/github.com/zalando/go-keyring/go.sum create mode 100644 vendor/github.com/zalando/go-keyring/keyring.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_darwin.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_fallback.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_linux.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_mock.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_windows.go create mode 100644 vendor/github.com/zalando/go-keyring/secret_service/secret_service.go diff --git a/cmd/addRepo.go b/cmd/addRepo.go index e7d24e2..3f51fcb 100644 --- a/cmd/addRepo.go +++ b/cmd/addRepo.go @@ -18,15 +18,15 @@ package cmd import ( "errors" "net/url" - "os" "regexp" - kableerrors "github.com/redradrat/kable/pkg/errors" "github.com/redradrat/kable/pkg/repositories" "github.com/spf13/cobra" ) +var repoAuth bool + // addRepoCmd represents the add command var addRepoCmd = &cobra.Command{ Use: "add [ID] [URL]", @@ -52,23 +52,30 @@ var addRepoCmd = &cobra.Command{ PrintMsg("Fetching repository...") name := args[0] repoUrl := args[1] - err := repositories.AddRepository(name, repoUrl, "master") - if err != nil { - if err.Error() == "authentication required" { - user, pw, err := RunAuthDialog() - if err != nil { - PrintError("unable to display authentication dialog: %s", err) - } - err = repositories.AddAuthRepository(name, repoUrl, user, pw, "master") - if err != nil { - if !errors.Is(err, kableerrors.RepositoryAlreadyExistsError) { - PrintError("unable to add repository: %s", err) - } else { - PrintSuccess("Repository already configured!") - os.Exit(0) - } - } + + var usr *string + if repoAuth { + user, pw, err := RunAuthDialog() + if err != nil { + PrintError("unable to display authentication dialog: %s", err) + } + if err := repositories.StoreRepoAuth(name, repositories.AuthPair{Username: user, Password: pw}); err != nil { + PrintError("unable to store authentication data: %s", err) } + usr = &user + } + + mod := repositories.AddRepository(repositories.Repository{ + Name: name, + GitRepository: repositories.GitRepository{ + URL: repoUrl, + GitRef: "refs/heads/master", + }, + Username: usr, + }) + err := repositories.UpdateRegistry(mod) + if err != nil { + PrintError("unable to update registry: %s", err) } PrintSuccess("Successfully added repository!") }, @@ -85,5 +92,5 @@ func init() { // Cobra supports local flags which will only run when this command // is called directly, e.g.: - // addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + addRepoCmd.Flags().BoolVar(&repoAuth, "auth", false, "Whether auth is required to be stored for this repo.") } diff --git a/cmd/listConcept.go b/cmd/listConcept.go index eace579..3453d8c 100644 --- a/cmd/listConcept.go +++ b/cmd/listConcept.go @@ -16,7 +16,7 @@ limitations under the License. package cmd import ( - concepts2 "github.com/redradrat/kable/pkg/concepts" + "github.com/redradrat/kable/pkg/concepts" "github.com/spf13/cobra" ) @@ -25,16 +25,20 @@ var listConceptsCmd = &cobra.Command{ Use: "list", Short: "List all available concepts", Run: func(cmd *cobra.Command, args []string) { - concepts, err := concepts2.ListConcepts() + cis, err := concepts.ListConcepts() if err != nil { PrintError("unable to list concepts: %s", err) } var outList [][]string - for _, pair := range concepts { + for _, ci := range cis { + c, err := concepts.GetRepoConcept(ci) + if err != nil { + PrintError("error getting concept '%s': %s", ci.String(), err) + } outList = append(outList, []string{ - pair.Path, - pair.RepoId, - pair.Concept.Meta.Maintainer.String(), + ci.Concept(), + ci.Repo(), + c.Meta.Maintainer.String(), }) } PrintTable([]string{"ID", "Repository", "Maintainer"}, outList...) diff --git a/cmd/listRepo.go b/cmd/listRepo.go index 9ec21c4..d83447b 100644 --- a/cmd/listRepo.go +++ b/cmd/listRepo.go @@ -16,8 +16,6 @@ limitations under the License. package cmd import ( - "strconv" - "github.com/redradrat/kable/pkg/repositories" "github.com/spf13/cobra" ) @@ -25,23 +23,18 @@ import ( // listReposCmd represents the list command var listReposCmd = &cobra.Command{ Use: "list", - Short: "Lists all concept repositories in the current config", + Short: "Lists all available concept repositories", Run: func(cmd *cobra.Command, args []string) { - repoMap, err := repositories.ListRepositories() + repos, err := repositories.ListRepositories() if err != nil { PrintError("cannot list repositories: %s \n", err) } - var repoSlices [][]string - for id, uri := range repoMap { - if repositories.IsInitialized(id) { - repoSlices = append(repoSlices, []string{id, uri, strconv.FormatBool(true)}) - } else { - repoSlices = append(repoSlices, []string{id, uri, strconv.FormatBool(false)}) - } + for _, repo := range repos { + repoSlices = append(repoSlices, []string{repo.Name, repo.URL, repo.GitRef}) } - PrintTable([]string{"ID", "URL", "Initialized"}, repoSlices...) + PrintTable([]string{"ID", "URL", "Ref"}, repoSlices...) }, } diff --git a/cmd/removeRepo.go b/cmd/removeRepo.go index cd1020a..d2f7003 100644 --- a/cmd/removeRepo.go +++ b/cmd/removeRepo.go @@ -45,8 +45,9 @@ var removeCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { PrintMsg("Removing repository...") - if err := repositories.RemoveRepository(args[0]); err != nil { - PrintError("unable to remove repository: %s \n", err) + mod := repositories.RemoveRepository(args[0]) + if err := repositories.UpdateRegistry(mod); err != nil { + PrintError("unable to update repository registry: %s \n", err) } PrintSuccess("Successfully removed repository!") }} diff --git a/cmd/root.go b/cmd/root.go index c5af9f8..b054679 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,14 +8,14 @@ package cmd import ( "fmt" "os" + "path/filepath" - "github.com/redradrat/kable/pkg/config" + "github.com/spf13/viper" "github.com/spf13/cobra" ) var cfgFile string -var Config *config.Config // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{} @@ -35,8 +35,8 @@ func init() { // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. - rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config/kable/settings.json)") - + //rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config/kable/settings.json)") + // // Cobra also supports local flags, which will only run // when this action is called directly. //rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") @@ -44,8 +44,21 @@ func init() { // initConfig reads in config file and ENV variables if set. func initConfig() { - _, err := config.ReadConfig(cfgFile) - if err != nil { - PrintError("Cannot read config file: %s \n", err) + viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigType("json") // REQUIRED if the config file does not have the extension in the name + viper.AddConfigPath("/etc/kable/") // path to look for the config file in + viper.AddConfigPath("$HOME/.kable") // call multiple times to add many search paths + + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + hdir, err := os.UserHomeDir() + if err != nil { + panic("unable to get user homedir") + } + err = viper.WriteConfigAs(filepath.Join(hdir, "/.kable/config.json")) + if err != nil { + panic("unable to read and write config") + } } + } diff --git a/cmd/tidyRepo.go b/cmd/tidyRepo.go index 8a5dadd..d65814d 100644 --- a/cmd/tidyRepo.go +++ b/cmd/tidyRepo.go @@ -26,7 +26,7 @@ var tidyCmd = &cobra.Command{ Short: "Clean up removed, cached repos", Run: func(cmd *cobra.Command, args []string) { PrintMsg("Tidying up cached repositories...") - err := repositories.TidyRepositories() + err := repositories.TidyCache() if err != nil { PrintError("unable to clean up repositories: %s", err) } diff --git a/go.mod b/go.mod index bffec24..2feaff0 100644 --- a/go.mod +++ b/go.mod @@ -3,26 +3,27 @@ module github.com/redradrat/kable go 1.14 require ( - github.com/99designs/keyring v1.1.6 + github.com/99designs/keyring v1.1.6 // indirect github.com/AlecAivazis/survey/v2 v2.1.1 github.com/coreos/etcd v3.3.10+incompatible // indirect github.com/fatih/color v1.9.0 github.com/ghodss/yaml v1.0.0 // indirect github.com/go-git/go-git/v5 v5.1.0 github.com/google/go-jsonnet v0.16.1-0.20200908152747-b70cbd441a39 // indirect - github.com/google/uuid v1.1.1 + github.com/google/uuid v1.1.1 // indirect github.com/grafana/tanka v0.12.0 github.com/jsonnet-bundler/jsonnet-bundler v0.4.0 github.com/labstack/echo/v4 v4.1.16 github.com/manifoldco/promptui v0.7.0 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.4 - github.com/otiai10/copy v1.2.0 + github.com/otiai10/copy v1.2.0 // indirect github.com/spf13/cobra v1.0.0 + github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.6.1 // indirect - github.com/whilp/git-urls v1.0.0 - github.com/zalando/go-keyring v0.1.0 // indirect + github.com/whilp/git-urls v1.0.0 // indirect + github.com/zalando/go-keyring v0.1.0 gopkg.in/yaml.v2 v2.3.0 // indirect k8s.io/apimachinery v0.19.2 // indirect ) diff --git a/go.sum b/go.sum index e52cb95..9f5a56f 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,7 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= diff --git a/pkg/concepts/concepts.go b/pkg/concepts/concepts.go index a2514ee..b91fe3d 100644 --- a/pkg/concepts/concepts.go +++ b/pkg/concepts/concepts.go @@ -13,9 +13,6 @@ import ( "github.com/redradrat/kable/pkg/repositories" "github.com/redradrat/kable/pkg/errors" - - "github.com/go-git/go-git/v5" - giturls "github.com/whilp/git-urls" ) const ( @@ -183,21 +180,15 @@ func (iti InputTypeIdentifier) String() string { } func GetRepoConcept(cid ConceptIdentifier) (*Concept, error) { - path, err := GetRepoConceptPath(cid) + r, err := repositories.GetRepository(cid.Repo()) if err != nil { return nil, err } - return GetConcept(path) -} - -func GetRepoConceptPath(cid ConceptIdentifier) (string, error) { - if !cid.IsValid() { - return "", errors.InvalidConceptIdentifierError - } - if !repositories.IsInitialized(cid.Repo()) { - return "", errors.RepositoryNotInitializedError + path, err := r.AbsolutePath() + if err != nil { + return nil, err } - return filepath.Join(repositories.MustGetCacheInfo(cid.Repo()).AbsolutePath(), filepath.Join(cid.Concept())), nil + return GetConcept(filepath.Join(path, cid.Concept())) } func GetConcept(path string) (*Concept, error) { @@ -218,44 +209,8 @@ type ConceptOrigin struct { Ref string `json:"ref"` } -func GetConceptOrigin(cid ConceptIdentifier) (*ConceptOrigin, error) { - if !cid.IsValid() { - return nil, errors.InvalidConceptIdentifierError - } - if !repositories.IsInitialized(cid.Repo()) { - return nil, errors.RepositoryNotInitializedError - } - repo, err := git.PlainOpen(repositories.MustGetCacheInfo(cid.Repo()).AbsolutePath()) - if err != nil { - return nil, err - } - - repoURL, err := repo.Remote(git.DefaultRemoteName) - if err != nil { - return nil, err - } - - parsedURL, err := giturls.Parse(repoURL.Config().URLs[0]) - if err != nil { - return nil, err - } - - repoID := parsedURL.Host + parsedURL.Path - - ref, err := repo.Head() - if err != nil { - return nil, err - } - - origin := ConceptOrigin{ - Repository: strings.TrimSuffix(repoID, ".git"), - Ref: ref.Hash().String(), - } - return &origin, nil -} - type ConceptRepoInfo struct { - Concept Concept + Concept string Path string RepoId string } @@ -276,39 +231,22 @@ func (mi MaintainerInfo) String() string { return strings.Join(elements, " ") } -func ListConceptsForRepo(repoid string) ([]ConceptRepoInfo, error) { - var concepts []ConceptRepoInfo - if !repositories.IsInitialized(repoid) { - return concepts, nil - } - ri, err := repositories.GetRepoIndex(repoid) - if err != nil { - return nil, err - } - for _, entry := range ri.ConceptEntries { - c, err := GetRepoConcept(NewConceptIdentifier(entry, repoid)) - if err != nil { - return concepts, err - } - concepts = append(concepts, ConceptRepoInfo{RepoId: repoid, Path: entry, Concept: *c}) - } - return concepts, nil -} - -func ListConcepts() ([]ConceptRepoInfo, error) { - var repoList []ConceptRepoInfo +func ListConcepts() ([]ConceptIdentifier, error) { + var cis []ConceptIdentifier repos, err := repositories.ListRepositories() if err != nil { return nil, err } - for id, _ := range repos { - concepts, err := ListConceptsForRepo(id) + for _, repo := range repos { + idx, err := repo.RepoIndex() if err != nil { return nil, err } - repoList = append(repoList, concepts...) + for _, path := range idx.ConceptEntries { + cis = append(cis, NewConceptIdentifier(path, repo.Name)) + } } - return repoList, nil + return cis, nil } func InitConcept(workdir, name string, conceptType ConceptType) error { diff --git a/pkg/concepts/rendering.go b/pkg/concepts/rendering.go index bf50cf8..cde9b55 100644 --- a/pkg/concepts/rendering.go +++ b/pkg/concepts/rendering.go @@ -9,6 +9,8 @@ import ( "strconv" "time" + "github.com/redradrat/kable/pkg/repositories" + "github.com/redradrat/kable/pkg/errors" ) @@ -230,17 +232,22 @@ func renderConcept(id string, avs *RenderValues, ttype TargetType, opts RenderOp return nil, errors.InvalidConceptIdentifierError } - // Get the repo path - path, err = GetRepoConceptPath(ConceptIdentifier(id)) + r, err := repositories.GetRepository(id) if err != nil { return nil, err } - // Get the origin of the the concept - origin, err = GetConceptOrigin(ConceptIdentifier(id)) + // Get the repo path + path, err = r.AbsolutePath() if err != nil { return nil, err } + + // Get the origin of the the concept + origin = &ConceptOrigin{ + Repository: r.URL, + Ref: r.GitRef, + } } var target Target diff --git a/pkg/config/config.go b/pkg/config/config.go deleted file mode 100644 index c0b1078..0000000 --- a/pkg/config/config.go +++ /dev/null @@ -1,168 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/redradrat/kable/pkg/errors" - - "github.com/mitchellh/go-homedir" -) - -const ( - ConfigFileName = "kableconfig.json" -) - -var currentConfig *Config -var ConfigPath string -var RootDir string -var UserDir string -var CurDir string -var RepoDir string -var CacheDir string -var ConceptDir string -var cfgHierarchy []string - -// Config represents the Kable currentConfig object -type Config struct { - APIVersion int `json:"apiVersion"` - UseKeychainProvider bool `json:"useKeychainProvider"` -} - -func init() { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - RootDir = "/etc/kable/" - UserDir = filepath.Join(home + "/.kable/") - RepoDir = filepath.Join(UserDir, "/repos/") - ConceptDir = filepath.Join(UserDir, "/concepts/") - CacheDir = filepath.Join(UserDir, "/cache/") - CurDir = "./" - cfgHierarchy = []string{ - filepath.Join(CurDir, ConfigFileName), - filepath.Join(UserDir, ConfigFileName), - filepath.Join(RootDir, ConfigFileName), - } -} - -// ReadConfig tries to read Config from either a file at the path that has been given as string argument, or, if cfgFile -// is an empty string, tries default locations. -// -// Default: /etc/kable/kableconfig.json, ~/.kable/kableconfig.json, ./kableconfig.json -func ReadConfig(cfgFile string) (string, error) { - var cfgFileObj *os.File - var err error - - if cfgFile != "" { - // Use config file from the flag. - ConfigPath = cfgFile - cfgFileObj, err = os.Open(cfgFile) - if err != nil { - return "", fmt.Errorf("Cannot open config file: %s \n", err) - } - } else { - // Use default cfg hierarchy. - for _, path := range cfgHierarchy { - ConfigPath = path - cfgFileObj, err = os.Open(path) - if err == nil { - break - } - if !os.IsNotExist(err) { - return "", fmt.Errorf("Cannot open config file: %s \n", err) - } - } - } - - if cfgFileObj == nil { - if err := initConfig(); err != nil { - return ConfigPath, fmt.Errorf("Cannot write config file: %s \n", err) - } - } else { - var repoConf Config - content, err := ioutil.ReadFile(ConfigPath) - if err != nil { - return ConfigPath, err - } - if err = json.Unmarshal(content, &repoConf); err != nil { - return ConfigPath, err - } - if err := setCurrentConfig(&repoConf); err != nil { - return ConfigPath, fmt.Errorf("Cannot write config file: %s \n", err) - } - } - - return ConfigPath, nil -} - -// initConfig initializes a fresh config file at ~/.config/kable/kableconfig.json, if not yet initialized -func initConfig() error { - if configSet() { - return nil - } - if err := setCurrentConfig(&Config{ - APIVersion: 1, - UseKeychainProvider: true, - }); err != nil { - return err - } - ConfigPath = filepath.Join(UserDir, ConfigFileName) - if err := writeConfig(ConfigPath); err != nil { - return err - } - return nil -} - -func writeConfig(path string) error { - if !configSet() { - return errors.ConfigNotInitializedError - } - filepath.Dir(ConfigPath) - if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { - return err - } - file, err := os.Create(path) - if err != nil { - return err - } - out, err := json.MarshalIndent(currentConfig, "", " ") - if err != nil { - return err - } - _, err = file.Write(out) - if err != nil { - return err - } - return nil -} - -// GetConfig returns the current Config Object. One first needs to Read or Init Config. -func GetConfig() (*Config, error) { - if configSet() { - return currentConfig, nil - } - return nil, errors.ConfigNotInitializedError -} - -func setCurrentConfig(config *Config) error { - if configSet() { - return errors.ConfigAlreadyInitializedError - } - currentConfig = config - return nil -} - -func configSet() bool { - if currentConfig != nil && ConfigPath != "" { - return true - } - return false -} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index cb26a52..80ad4ce 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -6,6 +6,7 @@ var ( StaleRepoCacheIndexError = errors.New("attempted to write stale cache") RepositoryInvalidError = errors.New("given repository is not a valid kable repository") RepositoryAlreadyExistsError = errors.New("repository is already configured") + RepositoryUnknownError = errors.New("repository is unknown") RepositoryNotInitializedError = errors.New("repository is not yet initialized") ConfigNotInitializedError = errors.New("currentConfig is not yet initialized") ConfigAlreadyInitializedError = errors.New("currentConfig is already initialized") diff --git a/pkg/repositories/clone.go b/pkg/repositories/clone.go deleted file mode 100644 index 4c0d52f..0000000 --- a/pkg/repositories/clone.go +++ /dev/null @@ -1,14 +0,0 @@ -package repositories - -import ( - "github.com/go-git/go-git/v5" -) - -func clone(cloner ClonerConfig) error { - out, err := git.PlainClone(cloner.BaseDir, false, &cloner.CloneOptions) - if err != nil { - return err - } - out.Config() - return nil -} diff --git a/pkg/repositories/repositories.go b/pkg/repositories/repositories.go index 3fa99dc..4c09d38 100644 --- a/pkg/repositories/repositories.go +++ b/pkg/repositories/repositories.go @@ -2,491 +2,793 @@ package repositories import ( "encoding/json" - "errors" "io/ioutil" "os" "path/filepath" - - "github.com/go-git/go-git/v5/plumbing/transport" - - "github.com/go-git/go-git/v5/plumbing/transport/http" - - "github.com/redradrat/kable/pkg/config" - - errors2 "github.com/redradrat/kable/pkg/errors" - - "github.com/google/uuid" + "strings" "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5" - "github.com/99designs/keyring" + "github.com/redradrat/kable/pkg/errors" + + "github.com/zalando/go-keyring" ) const ( - RepoIndexFilename = "kable.json" - KableKeychainId = "kablerepos" + RegistryFileName = "kable.json" + KableDirName = ".kable" + CacheDirName = "cache" + KableKeychainId = "kablerepo_" ) -var kableKeyringConfig = keyring.Config{ - ServiceName: "kable", -} - -type RepoIndex struct { - Version int `json:"version"` - ConceptEntries []string `json:"concepts"` -} - -func ListRepositories() (map[string]string, error) { - idx, err := readCacheIndex() +func homeDir() string { + out, err := os.UserHomeDir() if err != nil { - return nil, err + panic(err) } + return out +} - repoMap := map[string]string{} - for id, repo := range idx.Index { - if repo.SoftDeleted { - continue - } +var KableDir = filepath.Join(homeDir(), KableDirName) +var RepoRegistryPath = filepath.Join(KableDir, RegistryFileName) +var CacheDir = filepath.Join(KableDir, CacheDirName) - repoMap[id] = repo.URI - } - return repoMap, nil -} +//var kableKeyringConfig = keyring.Config{ +// ServiceName: "kable", +//} -type ClonerConfig struct { - git.CloneOptions - BaseDir string +type RepoRegistry struct { + Repositories Repositories `json:"repositories"` } -type RepoCache struct { - Index map[string]RepoCacheEntry - modCount uuid.UUID -} +type Repositories map[string]Repository -func NewRepoCache() RepoCache { - return RepoCache{ - Index: map[string]RepoCacheEntry{}, - modCount: uuid.UUID{}, +func (repos Repositories) List() []Repository { + out := make([]Repository, 0, len(repos)) + for _, v := range repos { + out = append(out, v) } + return out } -type RepoCacheEntry struct { - RepoDirPath string - Branch string - URI string - SoftDeleted bool -} +func Registry() (RepoRegistry, error) { + file, err := ioutil.ReadFile(RepoRegistryPath) + if err != nil { + if os.IsNotExist(err) { + if err := writeRegistry(RepoRegistry{}); err != nil { + return RepoRegistry{}, err + } + file, err = ioutil.ReadFile(RepoRegistryPath) + if err != nil { + return RepoRegistry{}, err + } + } else { + return RepoRegistry{}, err + } + } -func (rce RepoCacheEntry) AbsolutePath() string { - return filepath.Join(config.RepoDir, rce.RepoDirPath) -} + r := RepoRegistry{} + if err := json.Unmarshal(file, &r); err != nil { + return RepoRegistry{}, err + } -func writeCacheIndex(ci RepoCache) error { - return writeCacheIndexI(ci, false) + return r, nil } -func initCacheIndex() error { - rc := NewRepoCache() - return writeCacheIndexI(rc, true) -} +type RegistryModification func(RepoRegistry) RepoRegistry -func writeCacheIndexI(ci RepoCache, init bool) error { - if !init { - oldIdx, err := readCacheIndex() - if err != nil { - return err +func AddRepository(repo Repository) RegistryModification { + return func(registry RepoRegistry) RepoRegistry { + if registry.Repositories == nil { + registry.Repositories = Repositories{} } + registry.Repositories[repo.Name] = repo + return registry + } +} - if ci.modCount != oldIdx.modCount { - return errors2.StaleRepoCacheIndexError +func RemoveRepository(name string) RegistryModification { + return func(registry RepoRegistry) RepoRegistry { + if registry.Repositories == nil { + return registry } + delete(registry.Repositories, name) + return registry } +} - // Before writing, we update the modCount. - ci.modCount = uuid.New() - - out, err := json.MarshalIndent(ci, "", " ") +func UpdateRegistry(updates ...RegistryModification) error { + registry, err := Registry() if err != nil { return err } - if err := ioutil.WriteFile(filepath.Join(config.UserDir, "repo.cache"), out, 0666); err != nil { + for _, update := range updates { + registry = update(registry) + } + + if err := writeRegistry(registry); err != nil { return err } return nil } -func readCacheIndex() (RepoCache, error) { - index := NewRepoCache() - content, err := ioutil.ReadFile(filepath.Join(config.UserDir, "repo.cache")) +func writeRegistry(r RepoRegistry) error { + b, err := json.MarshalIndent(r, "", " ") if err != nil { - if !os.IsNotExist(err) { - return index, err - } - if err := initCacheIndex(); err != nil { - return index, err - } - } - if err := json.Unmarshal(content, &index); err != nil { - return index, err - } - return index, nil -} - -func AddToCacheIndex(id string, url, branch string) error { - cache, err := readCacheIndex() - - // If we get an "IsNotExist"-error here, we just assume we're initializing - if err != nil && !os.IsNotExist(err) { return err } - - cache.Index[id] = RepoCacheEntry{ - Branch: branch, - RepoDirPath: id, - URI: url, - } - - if err := writeCacheIndex(cache); err != nil { + if err := ioutil.WriteFile(RepoRegistryPath, b, 0644); err != nil { return err } - return nil } -func GetCacheInfo(id string) (*RepoCacheEntry, error) { - ci, err := readCacheIndex() - if err != nil { - return nil, err - } - out := ci.Index[id] - return &out, nil +type Repository struct { + GitRepository + Name string + Username *string } -func DeactivateRepoCache(id string) error { - for { - cache, err := readCacheIndex() - if err != nil { - return err - } - - entry := cache.Index[id] - entry.SoftDeleted = true - cache.Index[id] = entry - - if err := writeCacheIndex(cache); err != nil { - if errors.Is(err, errors2.StaleRepoCacheIndexError) { - continue - } +func maybeDelete(path string) error { + if err := os.RemoveAll(path); err != nil { + if !os.IsNotExist(err) { return err } - - return nil } - + return nil } -func ActivateRepoCache(oldid, newid string) error { - for { - cache, err := readCacheIndex() +func maybeClone(r Repository, path string, pull bool) error { + var auth *http.BasicAuth + if r.Username != nil { + a, err := getAuth(r.Name, *r.Username) if err != nil { return err } + auth = &http.BasicAuth{ + Username: a.Username, + Password: a.Password, + } + } - entry := cache.Index[oldid] - entry.SoftDeleted = false - cache.Index[newid] = entry - - if oldid != newid { - delete(cache.Index, oldid) - if err := os.Rename(filepath.Join(config.RepoDir, oldid), filepath.Join(config.RepoDir, newid)); err != nil { - return err + repo, err := git.PlainOpen(path) + if err != nil { + if err == git.ErrRepositoryNotExists { + cloneopts := &git.CloneOptions{ + URL: r.URL, + ReferenceName: plumbing.ReferenceName(r.GitRef), + SingleBranch: true, + Auth: auth, } - } - if err := writeCacheIndex(cache); err != nil { - if errors.Is(err, errors2.StaleRepoCacheIndexError) { - continue + _, err = git.PlainClone(path, false, cloneopts) + if err != nil { + return err } + return nil + } else { return err } - - return nil } -} -func RemoveFromCacheIndex(id string) error { - for { - cache, err := readCacheIndex() + if pull { + wt, err := repo.Worktree() if err != nil { return err } - delete(cache.Index, id) - if err := writeCacheIndex(cache); err != nil { - if errors.Is(err, errors2.StaleRepoCacheIndexError) { - continue - } + if err := wt.Pull(&git.PullOptions{ + ReferenceName: plumbing.ReferenceName(r.GitRef), + SingleBranch: true, + Auth: auth, + }); err != nil && err != git.NoErrAlreadyUpToDate { return err } - - return nil - } -} - -// Returns a UUID if cached, else just returns nil. -func IsCached(url, branch string) (*string, error) { - var id string - idx, err := readCacheIndex() - if err != nil { - return &id, err - } - for existingId, ref := range idx.Index { - if ref.URI == url && ref.Branch == branch { - id = existingId - return &id, nil - } } - return nil, nil + return nil } -// TidyRepositories cleans up all cached repositories that are not in use in the current config, -// and returns the names of the deleted ones. -func TidyRepositories() error { - idx, err := readCacheIndex() - if err != nil { - return err - } - - // Go through index, and delete all repositories, that are not used in the current config - fi, err := ioutil.ReadDir(config.RepoDir) - if err != nil { - return err - } - - for _, ref := range fi { - if _, exists := idx.Index[ref.Name()]; !exists || idx.Index[ref.Name()].SoftDeleted == true { - if err := os.RemoveAll(filepath.Join(config.RepoDir, ref.Name())); err != nil { - return err - } - if err := RemoveFromCacheIndex(ref.Name()); err != nil { - return err - } - } +func (r Repository) AbsolutePath() (string, error) { + path := computePath(r.URL) + if err := maybeClone(r, path, false); err != nil { + return "", err } - - return nil + return path, nil } -func AddRepository(id, url, branch string) error { - return AddAuthRepository(id, url, "", "", branch) +func computePath(url string) string { + a := strings.Split(strings.TrimSuffix(url, ".git"), "/") + return filepath.Join(CacheDir, a[len(a)-1]) } -func AddAuthRepository(id, url, user, pass, branch string) error { - idx, err := readCacheIndex() - if err != nil { - return err - } +type AuthPair struct { + Username string + Password string +} - if _, exists := idx.Index[id]; exists && !idx.Index[id].SoftDeleted { - return errors2.RepositoryAlreadyExistsError - } +func getAuth(name, user string) (*AuthPair, error) { - // Now let's first check if we have a repo cached. - cachedId, err := IsCached(url, branch) + pass, err := keyring.Get(KableKeychainId+name, user) if err != nil { - return err + return nil, err } - if cachedId != nil { - if err := ActivateRepoCache(*cachedId, id); err != nil { - return err - } - // Exit without additional cloning. - return nil - } + //ring, err := getKeyring() + //if err != nil { + // return nil, err + //} + //var pass keyring.Item + //user, err := ring.Get(name + "-user") + //if err != nil { + // if err == keyring.ErrKeyNotFound { + // return nil, err + // } + // return nil, err + //} else { + // pass, err = ring.Get(name + "-pass") + // if err != nil { + // return nil, err + // } + //} + return &AuthPair{Username: user, Password: pass}, nil +} - // If cachedId is nil, we need to clone. - if err := cloneRepo(id, url, user, pass, branch); err != nil { - return err +func (r Repository) RepoIndex() (*RepoIndex, error) { + ri := RepoIndex{} + path, err := r.AbsolutePath() + if err != nil { + return nil, err } - ring, err := keyring.Open(kableKeyringConfig) + b, err := ioutil.ReadFile(filepath.Join(path, RegistryFileName)) if err != nil { - return err + return nil, err } - // store credentials in local keyring - err = ring.Set(keyring.Item{ - Key: id + "-user", - Data: []byte(user), - Label: "kable", - }) - err = ring.Set(keyring.Item{ - Key: id + "-pass", - Data: []byte(pass), - Label: "kable", - }) - if err != nil { - return err + if err := json.Unmarshal(b, &ri); err != nil { + return nil, err } - return nil + return &ri, nil } -func RemoveRepository(id string) error { - return DeactivateRepoCache(id) +type GitRepository struct { + URL string + GitRef string } func UpdateRepositories() error { - idx, err := readCacheIndex() + repos, err := ListRepositories() if err != nil { return err } - - for id, _ := range idx.Index { - if !IsInitialized(id) { - continue - } - - repo, err := git.PlainOpen(MustGetCacheInfo(id).AbsolutePath()) - if err != nil { - return err - } - wt, err := repo.Worktree() - if err != nil { + for _, repo := range repos { + if err := maybeClone(repo, computePath(repo.URL), true); err != nil { return err } - if err := wt.Pull(&git.PullOptions{ - SingleBranch: true, - }); err != nil { - if err.Error() == "authentication required" { - // try to get auth from keychain - ring, err := keyring.Open(kableKeyringConfig) - if err != nil { - return err - } - user, err := ring.Get(id + "-user") - if err != nil { - return err - } - pass, err := ring.Get(id + "-pass") - if err != nil { - return err - } - err = wt.Pull(&git.PullOptions{ - SingleBranch: true, - Auth: &http.BasicAuth{ - Username: string(user.Data), - Password: string(pass.Data), - }, - }) - if err != nil { - if err == git.NoErrAlreadyUpToDate { - continue - } - return err - } - } - } } return nil } -func cloneRepo(name, url, user, pass, branch string) error { - repopath := filepath.Join(config.RepoDir, name) - refName := plumbing.NewBranchReferenceName(branch) - var auth transport.AuthMethod - if user != "" && pass != "" { - auth = &http.BasicAuth{ - Username: user, - Password: pass, - } - } - - err := clone(ClonerConfig{ - CloneOptions: git.CloneOptions{ - Auth: auth, - URL: url, - ReferenceName: refName, - SingleBranch: true, - }, - BaseDir: repopath, - }) +func ListRepositories() ([]Repository, error) { + r, err := Registry() if err != nil { - return err + return nil, err } + return r.Repositories.List(), nil +} - // Get the name from the Index. If we get a PathError from GetRepoIndex here, - // it means something is fishy with this repository. Probably not a kable repo. - cont, err := ioutil.ReadFile(filepath.Join(repopath, RepoIndexFilename)) +func TidyCache() error { + files, err := ioutil.ReadDir(CacheDir) if err != nil { - if err := os.RemoveAll(repopath); err != nil { - return err - } - if os.IsNotExist(err) { - return errors2.RepositoryInvalidError - } return err } - ri := RepoIndex{} - if err := json.Unmarshal(cont, &ri); err != nil { - if err := os.RemoveAll(repopath); err != nil { - return err - } - if _, ok := err.(*json.UnmarshalTypeError); ok { - return errors2.RepositoryInvalidError + for _, file := range files { + _, err := GetRepository(file.Name()) + if err != nil { + if err := os.RemoveAll(filepath.Join(CacheDir, file.Name())); err != nil { + return err + } } - return err - } - - // After a successful clone, we need to add the repo to the cache index. - if err := AddToCacheIndex(name, url, branch); err != nil { - return err } - return nil } -func IsInitialized(repoid string) bool { - initialized := true - _, err := GetRepoIndex(repoid) +func GetRepository(name string) (Repository, error) { + r, err := Registry() if err != nil { - initialized = false + return Repository{}, err + } + repo, ok := r.Repositories[name] + if !ok { + return Repository{}, errors.RepositoryUnknownError } - return initialized -} -func MustGetCacheInfo(id string) *RepoCacheEntry { - out, _ := GetCacheInfo(id) - return out + return repo, nil } -func GetRepoIndex(repoid string) (RepoIndex, error) { - index := RepoIndex{} - cacheInfo, err := GetCacheInfo(repoid) - if err != nil { - return index, err - } +type RepoIndex struct { + Version int `json:"version"` + ConceptEntries []string `json:"concepts"` +} - // Read in the file - content, err := ioutil.ReadFile(filepath.Join(cacheInfo.AbsolutePath(), RepoIndexFilename)) +func StoreRepoAuth(name string, pair AuthPair) error { + // set password + err := keyring.Set(KableKeychainId+name, pair.Username, pair.Password) if err != nil { - return index, err + return err } - // Unmarshal the index file - if err := json.Unmarshal(content, &index); err != nil { - return index, err - } - return index, nil + // store credentials in local keyring + //err = ring.Set(keyring.Item{ + // Key: name + "-user", + // Data: []byte(pair.Username), + // Label: "kable", + //}) + //err = ring.Set(keyring.Item{ + // Key: name + "-pass", + // Data: []byte(pair.Password), + // Label: "kable", + //}) + //if err != nil { + // return err + //} + return nil } -func MustGetRepoIndex(repoid string) RepoIndex { - out, _ := GetRepoIndex(repoid) - return out -} +//var ring *keyring.Keyring +// +//func getKeyring() (keyring.Keyring, error) { +// if ring != nil { +// return *ring, nil +// } +// r, err := keyring.Open(kableKeyringConfig) +// if err != nil { +// return nil, err +// } +// ring = &r +// return *ring, nil +//} + +// +//type ClonerConfig struct { +// git.CloneOptions +// BaseDir string +//} +// +//type RepoCache struct { +// Index map[string]RepoCacheEntry +// modCount uuid.UUID +//} +// +//func NewRepoCache() RepoCache { +// return RepoCache{ +// Index: map[string]RepoCacheEntry{}, +// modCount: uuid.UUID{}, +// } +//} +// +//type RepoCacheEntry struct { +// RepoDirPath string +// Branch string +// URI string +// SoftDeleted bool +//} +// +//func (rce RepoCacheEntry) AbsolutePath() string { +// return filepath.Join(config.RepoDir, rce.RepoDirPath) +//} +// +//func writeCacheIndex(ci RepoCache) error { +// return writeCacheIndexI(ci, false) +//} +// +//func initCacheIndex() error { +// rc := NewRepoCache() +// return writeCacheIndexI(rc, true) +//} +// +//func writeCacheIndexI(ci RepoCache, init bool) error { +// if !init { +// oldIdx, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// if ci.modCount != oldIdx.modCount { +// return errors.StaleRepoCacheIndexError +// } +// } +// +// // Before writing, we update the modCount. +// ci.modCount = uuid.New() +// +// out, err := json.MarshalIndent(ci, "", " ") +// if err != nil { +// return err +// } +// if err := ioutil.WriteFile(filepath.Join(config.UserDir, "repo.cache"), out, 0666); err != nil { +// return err +// } +// +// return nil +//} +// +//func readCacheIndex() (RepoCache, error) { +// index := NewRepoCache() +// content, err := ioutil.ReadFile(filepath.Join(config.UserDir, "repo.cache")) +// if err != nil { +// if !os.IsNotExist(err) { +// return index, err +// } +// if err := initCacheIndex(); err != nil { +// return index, err +// } +// } +// if err := json.Unmarshal(content, &index); err != nil { +// return index, err +// } +// return index, nil +//} +// +//func AddToCacheIndex(id string, url, branch string) error { +// cache, err := readCacheIndex() +// +// // If we get an "IsNotExist"-error here, we just assume we're initializing +// if err != nil && !os.IsNotExist(err) { +// return err +// } +// +// cache.Index[id] = RepoCacheEntry{ +// Branch: branch, +// RepoDirPath: id, +// URI: url, +// } +// +// if err := writeCacheIndex(cache); err != nil { +// return err +// } +// +// return nil +//} +// +//func GetCacheInfo(id string) (*RepoCacheEntry, error) { +// ci, err := readCacheIndex() +// if err != nil { +// return nil, err +// } +// out := ci.Index[id] +// return &out, nil +//} +// +//func DeactivateRepoCache(id string) error { +// for { +// cache, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// entry := cache.Index[id] +// entry.SoftDeleted = true +// cache.Index[id] = entry +// +// if err := writeCacheIndex(cache); err != nil { +// if errors.Is(err, errors.StaleRepoCacheIndexError) { +// continue +// } +// return err +// } +// +// return nil +// } +// +//} +// +//func ActivateRepoCache(oldid, newid string) error { +// for { +// cache, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// entry := cache.Index[oldid] +// entry.SoftDeleted = false +// cache.Index[newid] = entry +// +// if oldid != newid { +// delete(cache.Index, oldid) +// if err := os.Rename(filepath.Join(config.RepoDir, oldid), filepath.Join(config.RepoDir, newid)); err != nil { +// return err +// } +// } +// +// if err := writeCacheIndex(cache); err != nil { +// if errors.Is(err, errors.StaleRepoCacheIndexError) { +// continue +// } +// return err +// } +// +// return nil +// } +//} +// +//func RemoveFromCacheIndex(id string) error { +// for { +// cache, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// delete(cache.Index, id) +// if err := writeCacheIndex(cache); err != nil { +// if errors.Is(err, errors.StaleRepoCacheIndexError) { +// continue +// } +// return err +// } +// +// return nil +// } +//} +// +//// Returns a UUID if cached, else just returns nil. +//func IsCached(url, branch string) (*string, error) { +// var id string +// idx, err := readCacheIndex() +// if err != nil { +// return &id, err +// } +// for existingId, ref := range idx.Index { +// if ref.URI == url && ref.Branch == branch { +// id = existingId +// return &id, nil +// } +// } +// return nil, nil +// +//} +// +//// TidyRepositories cleans up all cached repositories that are not in use in the current config, +//// and returns the names of the deleted ones. +//func TidyRepositories() error { +// idx, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// // Go through index, and delete all repositories, that are not used in the current config +// fi, err := ioutil.ReadDir(config.RepoDir) +// if err != nil { +// return err +// } +// +// for _, ref := range fi { +// if _, exists := idx.Index[ref.Name()]; !exists || idx.Index[ref.Name()].SoftDeleted == true { +// if err := os.RemoveAll(filepath.Join(config.RepoDir, ref.Name())); err != nil { +// return err +// } +// if err := RemoveFromCacheIndex(ref.Name()); err != nil { +// return err +// } +// } +// } +// +// return nil +//} +// +//func AddRepository(id, url, branch string) error { +// return AddAuthRepository(id, url, "", "", branch) +//} +// +//func AddAuthRepository(id, url, user, pass, branch string) error { +// idx, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// if _, exists := idx.Index[id]; exists && !idx.Index[id].SoftDeleted { +// return errors.RepositoryAlreadyExistsError +// } +// +// // Now let's first check if we have a repo cached. +// cachedId, err := IsCached(url, branch) +// if err != nil { +// return err +// } +// +// if cachedId != nil { +// if err := ActivateRepoCache(*cachedId, id); err != nil { +// return err +// } +// // Exit without additional cloning. +// return nil +// } +// +// // If cachedId is nil, we need to maybeClone. +// if err := cloneRepo(id, url, user, pass, branch); err != nil { +// return err +// } +// +// ring, err := keyring.Open(kableKeyringConfig) +// if err != nil { +// return err +// } +// +// // store credentials in local keyring +// err = ring.Set(keyring.Item{ +// Key: id + "-user", +// Data: []byte(user), +// Label: "kable", +// }) +// err = ring.Set(keyring.Item{ +// Key: id + "-pass", +// Data: []byte(pass), +// Label: "kable", +// }) +// if err != nil { +// return err +// } +// +// return nil +//} +// +//func RemoveRepository(id string) error { +// return DeactivateRepoCache(id) +//} +// +//func UpdateRepositories() error { +// idx, err := readCacheIndex() +// if err != nil { +// return err +// } +// +// for id, _ := range idx.Index { +// if !IsInitialized(id) { +// continue +// } +// +// repo, err := git.PlainOpen(MustGetCacheInfo(id).AbsolutePath()) +// if err != nil { +// return err +// } +// wt, err := repo.Worktree() +// if err != nil { +// return err +// } +// if err := wt.Pull(&git.PullOptions{ +// SingleBranch: true, +// }); err != nil { +// if err.Error() == "authentication required" { +// // try to get auth from keychain +// ring, err := keyring.Open(kableKeyringConfig) +// if err != nil { +// return err +// } +// user, err := ring.Get(id + "-user") +// if err != nil { +// return err +// } +// pass, err := ring.Get(id + "-pass") +// if err != nil { +// return err +// } +// err = wt.Pull(&git.PullOptions{ +// SingleBranch: true, +// Auth: &http.BasicAuth{ +// Username: string(user.Data), +// Password: string(pass.Data), +// }, +// }) +// if err != nil { +// if err == git.NoErrAlreadyUpToDate { +// continue +// } +// return err +// } +// } +// } +// } +// return nil +//} +// +//func cloneRepo(name, url, user, pass, branch string) error { +// repopath := filepath.Join(config.RepoDir, name) +// refName := plumbing.NewBranchReferenceName(branch) +// var auth transport.AuthMethod +// if user != "" && pass != "" { +// auth = &http.BasicAuth{ +// Username: user, +// Password: pass, +// } +// } +// +// err := maybeClone(ClonerConfig{ +// CloneOptions: git.CloneOptions{ +// Auth: auth, +// URL: url, +// ReferenceName: refName, +// SingleBranch: true, +// }, +// BaseDir: repopath, +// }) +// if err != nil { +// return err +// } +// +// // Get the name from the Index. If we get a PathError from GetRepoIndex here, +// // it means something is fishy with this repository. Probably not a kable repo. +// cont, err := ioutil.ReadFile(filepath.Join(repopath, RegistryFileName)) +// if err != nil { +// if err := os.RemoveAll(repopath); err != nil { +// return err +// } +// if os.IsNotExist(err) { +// return errors.RepositoryInvalidError +// } +// return err +// } +// +// ri := RepoIndex{} +// if err := json.Unmarshal(cont, &ri); err != nil { +// if err := os.RemoveAll(repopath); err != nil { +// return err +// } +// if _, ok := err.(*json.UnmarshalTypeError); ok { +// return errors.RepositoryInvalidError +// } +// return err +// } +// +// // After a successful maybeClone, we need to add the repo to the cache index. +// if err := AddToCacheIndex(name, url, branch); err != nil { +// return err +// } +// +// return nil +//} +// +//func IsInitialized(repoid string) bool { +// initialized := true +// _, err := GetRepoIndex(repoid) +// if err != nil { +// initialized = false +// } +// return initialized +//} +// +//func MustGetCacheInfo(id string) *RepoCacheEntry { +// out, _ := GetCacheInfo(id) +// return out +//} +// +//func GetRepoIndex(repoid string) (RepoIndex, error) { +// index := RepoIndex{} +// cacheInfo, err := GetCacheInfo(repoid) +// if err != nil { +// return index, err +// } +// +// // Read in the file +// content, err := ioutil.ReadFile(filepath.Join(cacheInfo.AbsolutePath(), RegistryFileName)) +// if err != nil { +// return index, err +// } +// +// // Unmarshal the index file +// if err := json.Unmarshal(content, &index); err != nil { +// return index, err +// } +// return index, nil +//} +// +//func MustGetRepoIndex(repoid string) RepoIndex { +// out, _ := GetRepoIndex(repoid) +// return out +//} diff --git a/vendor/github.com/99designs/keyring/.gitignore b/vendor/github.com/99designs/keyring/.gitignore deleted file mode 100644 index 8000dd9..0000000 --- a/vendor/github.com/99designs/keyring/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.vagrant diff --git a/vendor/github.com/99designs/keyring/README.md b/vendor/github.com/99designs/keyring/README.md deleted file mode 100644 index 7b07ab7..0000000 --- a/vendor/github.com/99designs/keyring/README.md +++ /dev/null @@ -1,55 +0,0 @@ -Keyring -======= -[![Build Status](https://github.com/99designs/keyring/workflows/Continuous%20Integration/badge.svg)](https://github.com/99designs/keyring/actions) -[![Documentation](https://godoc.org/github.com/99designs/keyring?status.svg)](https://godoc.org/github.com/99designs/keyring) - -Keyring provides utility functions for and a common interface to a range of secure credential storage services. Originally developed as part of [AWS Vault](https://github.com/99designs/aws-vault), a command line tool for securely managing AWS access from developer workstations. - -Currently Keyring supports the following backends - * macOS/OSX Keychain - * Windows credential store - * [Pass](https://www.passwordstore.org/) - * [Secret Service](https://github.com/99designs/aws-vault/pull/98) - * [KDE Wallet](https://github.com/99designs/aws-vault/pull/27) - * [Encrypted File](https://github.com/99designs/aws-vault/pull/63) - -## Installing - -`go get github.com/99designs/keyring` - -## Usage - -The short version of how to use keyring is shown below. - -```go -ring, _ := keyring.Open(keyring.Config{ - ServiceName: "example", -}) - -_ = ring.Set(keyring.Item{ - Key: "foo", - Data: []byte("secret-bar"), -}) - -i, _ := ring.Get("foo") - -fmt.Printf("%s", i.Data) -``` - -For more detail on the API please check [the keyring godocs](https://godoc.org/github.com/99designs/keyring) - -## Development & Contributing - -Contributions to the keyring package are most welcome from engineers of all backgrounds and skill levels. In particular the addition of extra backends across popular operating systems would be appreciated. - -This project will adhere to the [Go Community Code of Conduct](https://golang.org/conduct) in the github provided discussion spaces, with the moderators being the 99designs engineering team. - -To make a contribution: - - * Fork the repository - * Make your changes on the fork - * Submit a pull request back to this repo with a clear description of the problem you're solving - * Ensure your PR passes all current (and new) tests - * Ideally verify that [aws-vault](https://github.com/99designs/aws-vault) works with your changes (optional) - -...and we'll do our best to get your work merged in diff --git a/vendor/github.com/99designs/keyring/array.go b/vendor/github.com/99designs/keyring/array.go deleted file mode 100644 index fca61b8..0000000 --- a/vendor/github.com/99designs/keyring/array.go +++ /dev/null @@ -1,54 +0,0 @@ -package keyring - -// ArrayKeyring is a mock/non-secure backend that meets the Keyring interface. -// It is intended to be used to aid unit testing of code that relies on the package. -// NOTE: Do not use in production code -type ArrayKeyring struct { - items map[string]Item -} - -// NewArrayKeyring returns an ArrayKeyring, optionally constructed with an initial slice -// of items -func NewArrayKeyring(initial []Item) *ArrayKeyring { - kr := &ArrayKeyring{} - for _, i := range initial { - _ = kr.Set(i) - } - return kr -} - -// Get returns an Item matching Key -func (k *ArrayKeyring) Get(key string) (Item, error) { - if i, ok := k.items[key]; ok { - return i, nil - } - return Item{}, ErrKeyNotFound -} - -// Set will store an item on the mock Keyring -func (k *ArrayKeyring) Set(i Item) error { - if k.items == nil { - k.items = map[string]Item{} - } - k.items[i.Key] = i - return nil -} - -// Remove will delete an Item from the Keyring -func (k *ArrayKeyring) Remove(key string) error { - delete(k.items, key) - return nil -} - -// Keys provides a slice of all Item keys on the Keyring -func (k *ArrayKeyring) Keys() ([]string, error) { - var keys = []string{} - for key := range k.items { - keys = append(keys, key) - } - return keys, nil -} - -func (k *ArrayKeyring) GetMetadata(_ string) (Metadata, error) { - return Metadata{}, ErrMetadataNeedsCredentials -} diff --git a/vendor/github.com/99designs/keyring/config.go b/vendor/github.com/99designs/keyring/config.go deleted file mode 100644 index 1c5d0f7..0000000 --- a/vendor/github.com/99designs/keyring/config.go +++ /dev/null @@ -1,52 +0,0 @@ -package keyring - -// Config contains configuration for keyring -type Config struct { - // AllowedBackends is a whitelist of backend providers that can be used. Nil means all available. - AllowedBackends []BackendType - - // ServiceName is a generic service name that is used by backends that support the concept - ServiceName string - - // MacOSKeychainNameKeychainName is the name of the macOS keychain that is used - KeychainName string - - // KeychainTrustApplication is whether the calling application should be trusted by default by items - KeychainTrustApplication bool - - // KeychainSynchronizable is whether the item can be synchronized to iCloud - KeychainSynchronizable bool - - // KeychainAccessibleWhenUnlocked is whether the item is accessible when the device is locked - KeychainAccessibleWhenUnlocked bool - - // KeychainPasswordFunc is an optional function used to prompt the user for a password - KeychainPasswordFunc PromptFunc - - // FilePasswordFunc is a required function used to prompt the user for a password - FilePasswordFunc PromptFunc - - // FileDir is the directory that keyring files are stored in, ~ is resolved to home dir - FileDir string - - // KWalletAppID is the application id for KWallet - KWalletAppID string - - // KWalletFolder is the folder for KWallet - KWalletFolder string - - // LibSecretCollectionName is the name collection in secret-service - LibSecretCollectionName string - - // PassDir is the pass password-store directory - PassDir string - - // PassCmd is the name of the pass executable - PassCmd string - - // PassPrefix is a string prefix to prepend to the item path stored in pass - PassPrefix string - - // WinCredPrefix is a string prefix to prepend to the key name - WinCredPrefix string -} diff --git a/vendor/github.com/99designs/keyring/file.go b/vendor/github.com/99designs/keyring/file.go deleted file mode 100644 index ea0c8bb..0000000 --- a/vendor/github.com/99designs/keyring/file.go +++ /dev/null @@ -1,190 +0,0 @@ -package keyring - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" - - jose "github.com/dvsekhvalnov/jose2go" - homedir "github.com/mitchellh/go-homedir" - "github.com/mtibben/percent" -) - -func init() { - supportedBackends[FileBackend] = opener(func(cfg Config) (Keyring, error) { - return &fileKeyring{ - dir: cfg.FileDir, - passwordFunc: cfg.FilePasswordFunc, - }, nil - }) -} - -var filenameEscape = func(s string) string { - return percent.Encode(s, "/") -} -var filenameUnescape = percent.Decode - -type fileKeyring struct { - dir string - passwordFunc PromptFunc - password string -} - -func (k *fileKeyring) resolveDir() (string, error) { - if k.dir == "" { - return "", fmt.Errorf("No directory provided for file keyring") - } - - dir := k.dir - - // expand tilde for home directory - if strings.HasPrefix(dir, "~") { - home, err := homedir.Dir() - if err != nil { - return "", err - } - dir = strings.Replace(dir, "~", home, 1) - debugf("Expanded file dir to %s", dir) - } - - stat, err := os.Stat(dir) - if os.IsNotExist(err) { - err = os.MkdirAll(dir, 0700) - } else if err != nil && !stat.IsDir() { - err = fmt.Errorf("%s is a file, not a directory", dir) - } - - return dir, err -} - -func (k *fileKeyring) unlock() error { - dir, err := k.resolveDir() - if err != nil { - return err - } - - if k.password == "" { - pwd, err := k.passwordFunc(fmt.Sprintf("Enter passphrase to unlock %s", dir)) - if err != nil { - return err - } - k.password = pwd - } - - return nil -} - -func (k *fileKeyring) Get(key string) (Item, error) { - filename, err := k.filename(key) - if err != nil { - return Item{}, err - } - - bytes, err := ioutil.ReadFile(filename) - if os.IsNotExist(err) { - return Item{}, ErrKeyNotFound - } else if err != nil { - return Item{}, err - } - - if err = k.unlock(); err != nil { - return Item{}, err - } - - payload, _, err := jose.Decode(string(bytes), k.password) - if err != nil { - return Item{}, err - } - - var decoded Item - err = json.Unmarshal([]byte(payload), &decoded) - - return decoded, err -} - -func (k *fileKeyring) GetMetadata(key string) (Metadata, error) { - filename, err := k.filename(key) - if err != nil { - return Metadata{}, err - } - - stat, err := os.Stat(filename) - if os.IsNotExist(err) { - return Metadata{}, ErrKeyNotFound - } else if err != nil { - return Metadata{}, err - } - - // For the File provider, all internal data is encrypted, not just the - // credentials. Thus we only have the timestamps. Return a nil *Item. - // - // If we want to change this ... how portable are extended file attributes - // these days? Would it break user expectations of the security model to - // leak data into those? I'm hesitant to do so. - - return Metadata{ - ModificationTime: stat.ModTime(), - }, nil -} - -func (k *fileKeyring) Set(i Item) error { - bytes, err := json.Marshal(i) - if err != nil { - return err - } - - if err = k.unlock(); err != nil { - return err - } - - token, err := jose.Encrypt(string(bytes), jose.PBES2_HS256_A128KW, jose.A256GCM, k.password, - jose.Headers(map[string]interface{}{ - "created": time.Now().String(), - })) - if err != nil { - return err - } - - filename, err := k.filename(i.Key) - if err != nil { - return err - } - return ioutil.WriteFile(filename, []byte(token), 0600) -} - -func (k *fileKeyring) filename(key string) (string, error) { - dir, err := k.resolveDir() - if err != nil { - return "", err - } - - return filepath.Join(dir, filenameEscape(key)), nil -} - -func (k *fileKeyring) Remove(key string) error { - filename, err := k.filename(key) - if err != nil { - return err - } - - return os.Remove(filename) -} - -func (k *fileKeyring) Keys() ([]string, error) { - dir, err := k.resolveDir() - if err != nil { - return nil, err - } - - var keys = []string{} - files, _ := ioutil.ReadDir(dir) - for _, f := range files { - keys = append(keys, filenameUnescape(f.Name())) - } - - return keys, nil -} diff --git a/vendor/github.com/99designs/keyring/go.mod b/vendor/github.com/99designs/keyring/go.mod deleted file mode 100644 index c80b402..0000000 --- a/vendor/github.com/99designs/keyring/go.mod +++ /dev/null @@ -1,20 +0,0 @@ -module github.com/99designs/keyring - -go 1.14 - -require ( - github.com/danieljoos/wincred v1.0.2 - github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b - github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 - github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c - github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d - github.com/kr/pretty v0.1.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 - github.com/mtibben/percent v0.2.1 - github.com/stretchr/objx v0.2.0 // indirect - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 - golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect -) - -replace github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 diff --git a/vendor/github.com/99designs/keyring/go.sum b/vendor/github.com/99designs/keyring/go.sum deleted file mode 100644 index ab8baf5..0000000 --- a/vendor/github.com/99designs/keyring/go.sum +++ /dev/null @@ -1,42 +0,0 @@ -github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= -github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= -github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= -github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b h1:HBah4D48ypg3J7Np4N+HY/ZR76fx3HEUGxDU6Uk39oQ= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -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/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= -github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -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/99designs/keyring/keychain.go b/vendor/github.com/99designs/keyring/keychain.go deleted file mode 100644 index 9caf54d..0000000 --- a/vendor/github.com/99designs/keyring/keychain.go +++ /dev/null @@ -1,295 +0,0 @@ -// +build darwin,cgo - -package keyring - -import ( - "errors" - "fmt" - - gokeychain "github.com/keybase/go-keychain" -) - -type keychain struct { - path string - service string - - passwordFunc PromptFunc - - isSynchronizable bool - isAccessibleWhenUnlocked bool - isTrusted bool -} - -func init() { - supportedBackends[KeychainBackend] = opener(func(cfg Config) (Keyring, error) { - kc := &keychain{ - service: cfg.ServiceName, - passwordFunc: cfg.KeychainPasswordFunc, - - // Set the isAccessibleWhenUnlocked to the boolean value of - // KeychainAccessibleWhenUnlocked is a shorthand for setting the accessibility value. - // See: https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked - isAccessibleWhenUnlocked: cfg.KeychainAccessibleWhenUnlocked, - } - if cfg.KeychainName != "" { - kc.path = cfg.KeychainName + ".keychain" - } - if cfg.KeychainTrustApplication { - kc.isTrusted = true - } - return kc, nil - }) -} - -func (k *keychain) Get(key string) (Item, error) { - query := gokeychain.NewItem() - query.SetSecClass(gokeychain.SecClassGenericPassword) - query.SetService(k.service) - query.SetAccount(key) - query.SetMatchLimit(gokeychain.MatchLimitOne) - query.SetReturnAttributes(true) - query.SetReturnData(true) - - if k.path != "" { - // When we are querying, we don't create by default - query.SetMatchSearchList(gokeychain.NewWithPath(k.path)) - } - - debugf("Querying keychain for service=%q, account=%q, keychain=%q", k.service, key, k.path) - results, err := gokeychain.QueryItem(query) - if err == gokeychain.ErrorItemNotFound || len(results) == 0 { - debugf("No results found") - return Item{}, ErrKeyNotFound - } - - if err != nil { - debugf("Error: %#v", err) - return Item{}, err - } - - item := Item{ - Key: key, - Data: results[0].Data, - Label: results[0].Label, - Description: results[0].Description, - } - - debugf("Found item %q", results[0].Label) - return item, nil -} - -func (k *keychain) GetMetadata(key string) (Metadata, error) { - query := gokeychain.NewItem() - query.SetSecClass(gokeychain.SecClassGenericPassword) - query.SetService(k.service) - query.SetAccount(key) - query.SetMatchLimit(gokeychain.MatchLimitOne) - query.SetReturnAttributes(true) - query.SetReturnData(false) - query.SetReturnRef(true) - - debugf("Querying keychain for metadata of service=%q, account=%q, keychain=%q", k.service, key, k.path) - results, err := gokeychain.QueryItem(query) - if err == gokeychain.ErrorItemNotFound || len(results) == 0 { - debugf("No results found") - return Metadata{}, ErrKeyNotFound - } else if err != nil { - debugf("Error: %#v", err) - return Metadata{}, err - } - - md := Metadata{ - Item: &Item{ - Key: key, - Label: results[0].Label, - Description: results[0].Description, - }, - ModificationTime: results[0].ModificationDate, - } - - debugf("Found metadata for %q", md.Item.Label) - - return md, nil -} - -func (k *keychain) updateItem(kc gokeychain.Keychain, kcItem gokeychain.Item, account string) error { - queryItem := gokeychain.NewItem() - queryItem.SetSecClass(gokeychain.SecClassGenericPassword) - queryItem.SetService(k.service) - queryItem.SetAccount(account) - queryItem.SetMatchLimit(gokeychain.MatchLimitOne) - queryItem.SetReturnAttributes(true) - - if k.path != "" { - queryItem.SetMatchSearchList(kc) - } - - results, err := gokeychain.QueryItem(queryItem) - if err != nil { - return fmt.Errorf("Failed to query keychain: %v", err) - } - if len(results) == 0 { - return errors.New("no results") - } - - // Don't call SetAccess() as this will cause multiple prompts on update, even when we are not updating the AccessList - kcItem.SetAccess(nil) - - if err := gokeychain.UpdateItem(queryItem, kcItem); err != nil { - return fmt.Errorf("Failed to update item in keychain: %v", err) - } - - return nil -} - -func (k *keychain) Set(item Item) error { - var kc gokeychain.Keychain - - // when we are setting a value, we create or open - if k.path != "" { - var err error - kc, err = k.createOrOpen() - if err != nil { - return err - } - } - - kcItem := gokeychain.NewItem() - kcItem.SetSecClass(gokeychain.SecClassGenericPassword) - kcItem.SetService(k.service) - kcItem.SetAccount(item.Key) - kcItem.SetLabel(item.Label) - kcItem.SetDescription(item.Description) - kcItem.SetData(item.Data) - - if k.path != "" { - kcItem.UseKeychain(kc) - } - - if k.isSynchronizable && !item.KeychainNotSynchronizable { - kcItem.SetSynchronizable(gokeychain.SynchronizableYes) - } - - if k.isAccessibleWhenUnlocked { - kcItem.SetAccessible(gokeychain.AccessibleWhenUnlocked) - } - - isTrusted := k.isTrusted && !item.KeychainNotTrustApplication - - if isTrusted { - debugf("Keychain item trusts keyring") - kcItem.SetAccess(&gokeychain.Access{ - Label: item.Label, - TrustedApplications: nil, - }) - } else { - debugf("Keychain item doesn't trust keyring") - kcItem.SetAccess(&gokeychain.Access{ - Label: item.Label, - TrustedApplications: []string{}, - }) - } - - debugf("Adding service=%q, label=%q, account=%q, trusted=%v to osx keychain %q", k.service, item.Label, item.Key, isTrusted, k.path) - - err := gokeychain.AddItem(kcItem) - - if err == gokeychain.ErrorDuplicateItem { - debugf("Item already exists, updating") - err = k.updateItem(kc, kcItem, item.Key) - } - - if err != nil { - return err - } - - return nil -} - -func (k *keychain) Remove(key string) error { - item := gokeychain.NewItem() - item.SetSecClass(gokeychain.SecClassGenericPassword) - item.SetService(k.service) - item.SetAccount(key) - - if k.path != "" { - kc := gokeychain.NewWithPath(k.path) - - if err := kc.Status(); err != nil { - if err == gokeychain.ErrorNoSuchKeychain { - return ErrKeyNotFound - } - return err - } - - item.SetMatchSearchList(kc) - } - - debugf("Removing keychain item service=%q, account=%q, keychain %q", k.service, key, k.path) - return gokeychain.DeleteItem(item) -} - -func (k *keychain) Keys() ([]string, error) { - query := gokeychain.NewItem() - query.SetSecClass(gokeychain.SecClassGenericPassword) - query.SetService(k.service) - query.SetMatchLimit(gokeychain.MatchLimitAll) - query.SetReturnAttributes(true) - - if k.path != "" { - kc := gokeychain.NewWithPath(k.path) - - if err := kc.Status(); err != nil { - if err == gokeychain.ErrorNoSuchKeychain { - return []string{}, nil - } - return nil, err - } - - query.SetMatchSearchList(kc) - } - - debugf("Querying keychain for service=%q, keychain=%q", k.service, k.path) - results, err := gokeychain.QueryItem(query) - if err != nil { - return nil, err - } - - debugf("Found %d results", len(results)) - accountNames := make([]string, len(results)) - for idx, r := range results { - accountNames[idx] = r.Account - } - - return accountNames, nil -} - -func (k *keychain) createOrOpen() (gokeychain.Keychain, error) { - kc := gokeychain.NewWithPath(k.path) - - debugf("Checking keychain status") - err := kc.Status() - if err == nil { - debugf("Keychain status returned nil, keychain exists") - return kc, nil - } - - debugf("Keychain status returned error: %v", err) - - if err != gokeychain.ErrorNoSuchKeychain { - return gokeychain.Keychain{}, err - } - - if k.passwordFunc == nil { - debugf("Creating keychain %s with prompt", k.path) - return gokeychain.NewKeychainWithPrompt(k.path) - } - - passphrase, err := k.passwordFunc("Enter passphrase for keychain") - if err != nil { - return gokeychain.Keychain{}, err - } - - debugf("Creating keychain %s with provided password", k.path) - return gokeychain.NewKeychain(k.path, passphrase) -} diff --git a/vendor/github.com/99designs/keyring/keyring.go b/vendor/github.com/99designs/keyring/keyring.go deleted file mode 100644 index 5349b6f..0000000 --- a/vendor/github.com/99designs/keyring/keyring.go +++ /dev/null @@ -1,131 +0,0 @@ -// Package keyring provides a uniform API over a range of desktop credential storage engines -// -// See project homepage at https://github.com/99designs/keyring for more background -package keyring - -import ( - "errors" - "log" - "time" -) - -// A BackendType is an identifier for a credential storage service -type BackendType string - -// All currently supported secure storage backends -const ( - InvalidBackend BackendType = "" - SecretServiceBackend BackendType = "secret-service" - KeychainBackend BackendType = "keychain" - KWalletBackend BackendType = "kwallet" - WinCredBackend BackendType = "wincred" - FileBackend BackendType = "file" - PassBackend BackendType = "pass" -) - -// This order makes sure the OS-specific backends -// are picked over the more generic backends. -var backendOrder = []BackendType{ - // Windows - WinCredBackend, - // MacOS - KeychainBackend, - // Linux - SecretServiceBackend, - KWalletBackend, - // General - PassBackend, - FileBackend, -} - -var supportedBackends = map[BackendType]opener{} - -// AvailableBackends provides a slice of all available backend keys on the current OS -func AvailableBackends() []BackendType { - b := []BackendType{} - for _, k := range backendOrder { - _, ok := supportedBackends[k] - if ok { - b = append(b, k) - } - } - return b -} - -type opener func(cfg Config) (Keyring, error) - -// Open will open a specific keyring backend -func Open(cfg Config) (Keyring, error) { - if cfg.AllowedBackends == nil { - cfg.AllowedBackends = AvailableBackends() - } - debugf("Considering backends: %v", cfg.AllowedBackends) - for _, backend := range cfg.AllowedBackends { - if opener, ok := supportedBackends[backend]; ok { - openBackend, err := opener(cfg) - if err != nil { - debugf("Failed backend %s: %s", backend, err) - continue - } - return openBackend, nil - } - } - return nil, ErrNoAvailImpl -} - -// Item is a thing stored on the keyring -type Item struct { - Key string - Data []byte - Label string - Description string - - // Backend specific config - KeychainNotTrustApplication bool - KeychainNotSynchronizable bool -} - -// Metadata is information about a thing stored on the keyring; retrieving -// metadata must not require authentication. The embedded Item should be -// filled in with an empty Data field. -// It's allowed for Item to be a nil pointer, indicating that all we -// have is the timestamps. -type Metadata struct { - *Item - ModificationTime time.Time -} - -// Keyring provides the uniform interface over the underlying backends -type Keyring interface { - // Returns an Item matching the key or ErrKeyNotFound - Get(key string) (Item, error) - // Returns the non-secret parts of an Item - GetMetadata(key string) (Metadata, error) - // Stores an Item on the keyring - Set(item Item) error - // Removes the item with matching key - Remove(key string) error - // Provides a slice of all keys stored on the keyring - Keys() ([]string, error) -} - -// ErrNoAvailImpl is returned by Open when a backend cannot be found -var ErrNoAvailImpl = errors.New("Specified keyring backend not available") - -// ErrKeyNotFound is returned by Keyring Get when the item is not on the keyring -var ErrKeyNotFound = errors.New("The specified item could not be found in the keyring") - -// ErrMetadataNeedsCredentials is returned when Metadata is called against a -// backend which requires credentials even to see metadata. -var ErrMetadataNeedsCredentials = errors.New("The keyring backend requires credentials for metadata access") - -var ( - // Debug specifies whether to print debugging output - Debug bool -) - -func debugf(pattern string, args ...interface{}) { - if Debug { - log.Printf("[keyring] "+pattern, args...) - } -} diff --git a/vendor/github.com/99designs/keyring/kwallet.go b/vendor/github.com/99designs/keyring/kwallet.go deleted file mode 100644 index 1e5a16e..0000000 --- a/vendor/github.com/99designs/keyring/kwallet.go +++ /dev/null @@ -1,234 +0,0 @@ -// +build linux - -package keyring - -import ( - "encoding/json" - "os" - - "github.com/godbus/dbus" -) - -const ( - dbusServiceName = "org.kde.kwalletd5" - dbusPath = "/modules/kwalletd5" -) - -func init() { - - if os.Getenv("DISABLE_KWALLET") == "1" { - return - } - - // silently fail if dbus isn't available - _, err := dbus.SessionBus() - if err != nil { - return - } - - supportedBackends[KWalletBackend] = opener(func(cfg Config) (Keyring, error) { - if cfg.ServiceName == "" { - cfg.ServiceName = "kdewallet" - } - - if cfg.KWalletAppID == "" { - cfg.KWalletAppID = "keyring" - } - - if cfg.KWalletFolder == "" { - cfg.KWalletFolder = "keyring" - } - - wallet, err := newKwallet() - if err != nil { - return nil, err - } - - ring := &kwalletKeyring{ - wallet: *wallet, - name: cfg.ServiceName, - appID: cfg.KWalletAppID, - folder: cfg.KWalletFolder, - } - - return ring, ring.openWallet() - }) -} - -type kwalletKeyring struct { - wallet kwalletBinding - name string - handle int32 - appID string - folder string -} - -func (k *kwalletKeyring) openWallet() error { - isOpen, err := k.wallet.IsOpen(k.handle) - if err != nil { - return err - } - - if !isOpen { - handle, err := k.wallet.Open(k.name, 0, k.appID) - if err != nil { - return err - } - k.handle = handle - } - - return nil -} - -func (k *kwalletKeyring) Get(key string) (Item, error) { - err := k.openWallet() - if err != nil { - return Item{}, err - } - - data, err := k.wallet.ReadEntry(k.handle, k.folder, key, k.appID) - if err != nil { - return Item{}, err - } - - item := Item{} - err = json.Unmarshal(data, &item) - if err != nil { - return Item{}, err - } - - return item, nil -} - -// GetMetadata for kwallet returns an error indicating that it's unsupported -// for this backend. -// -// The only APIs found around KWallet are for retrieving content, no indication -// found in docs for methods to use to retrieve metadata without needing unlock -// credentials. -func (k *kwalletKeyring) GetMetadata(_ string) (Metadata, error) { - return Metadata{}, ErrMetadataNeedsCredentials -} - -func (k *kwalletKeyring) Set(item Item) error { - err := k.openWallet() - if err != nil { - return err - } - - data, err := json.Marshal(item) - if err != nil { - return err - } - - err = k.wallet.WriteEntry(k.handle, k.folder, item.Key, data, k.appID) - if err != nil { - return err - } - - return nil -} - -func (k *kwalletKeyring) Remove(key string) error { - err := k.openWallet() - if err != nil { - return err - } - - err = k.wallet.RemoveEntry(k.handle, k.folder, key, k.appID) - if err != nil { - return err - } - - return nil -} - -func (k *kwalletKeyring) Keys() ([]string, error) { - err := k.openWallet() - if err != nil { - return []string{}, err - } - - entries, err := k.wallet.EntryList(k.handle, k.folder, k.appID) - if err != nil { - return []string{}, err - } - - return entries, nil -} - -func newKwallet() (*kwalletBinding, error) { - conn, err := dbus.SessionBus() - if err != nil { - return nil, err - } - - return &kwalletBinding{ - conn.Object(dbusServiceName, dbusPath), - }, nil -} - -// Dumb Dbus bindings for kwallet bindings with types -type kwalletBinding struct { - dbus dbus.BusObject -} - -// method bool org.kde.KWallet.isOpen(int handle) -func (k *kwalletBinding) IsOpen(handle int32) (bool, error) { - call := k.dbus.Call("org.kde.KWallet.isOpen", 0, handle) - if call.Err != nil { - return false, call.Err - } - - return call.Body[0].(bool), call.Err -} - -// method int org.kde.KWallet.open(QString wallet, qlonglong wId, QString appid) -func (k *kwalletBinding) Open(name string, wID int64, appid string) (int32, error) { - call := k.dbus.Call("org.kde.KWallet.open", 0, name, wID, appid) - if call.Err != nil { - return 0, call.Err - } - - return call.Body[0].(int32), call.Err -} - -// method QStringList org.kde.KWallet.entryList(int handle, QString folder, QString appid) -func (k *kwalletBinding) EntryList(handle int32, folder string, appid string) ([]string, error) { - call := k.dbus.Call("org.kde.KWallet.entryList", 0, handle, folder, appid) - if call.Err != nil { - return []string{}, call.Err - } - - return call.Body[0].([]string), call.Err -} - -// method int org.kde.KWallet.writeEntry(int handle, QString folder, QString key, QByteArray value, QString appid) -func (k *kwalletBinding) WriteEntry(handle int32, folder string, key string, value []byte, appid string) error { - call := k.dbus.Call("org.kde.KWallet.writeEntry", 0, handle, folder, key, value, appid) - if call.Err != nil { - return call.Err - } - - return call.Err -} - -// method int org.kde.KWallet.removeEntry(int handle, QString folder, QString key, QString appid) -func (k *kwalletBinding) RemoveEntry(handle int32, folder string, key string, appid string) error { - call := k.dbus.Call("org.kde.KWallet.removeEntry", 0, handle, folder, key, appid) - if call.Err != nil { - return call.Err - } - - return call.Err -} - -// method QByteArray org.kde.KWallet.readEntry(int handle, QString folder, QString key, QString appid) -func (k *kwalletBinding) ReadEntry(handle int32, folder string, key string, appid string) ([]byte, error) { - call := k.dbus.Call("org.kde.KWallet.readEntry", 0, handle, folder, key, appid) - if call.Err != nil { - return []byte{}, call.Err - } - - return call.Body[0].([]byte), call.Err -} diff --git a/vendor/github.com/99designs/keyring/libsecret.go b/vendor/github.com/99designs/keyring/libsecret.go deleted file mode 100644 index ca4913d..0000000 --- a/vendor/github.com/99designs/keyring/libsecret.go +++ /dev/null @@ -1,277 +0,0 @@ -// +build linux - -package keyring - -import ( - "encoding/json" - "errors" - - "github.com/godbus/dbus" - "github.com/gsterjov/go-libsecret" -) - -func init() { - // silently fail if dbus isn't available - _, err := dbus.SessionBus() - if err != nil { - return - } - - supportedBackends[SecretServiceBackend] = opener(func(cfg Config) (Keyring, error) { - if cfg.ServiceName == "" { - cfg.ServiceName = "secret-service" - } - if cfg.LibSecretCollectionName == "" { - cfg.LibSecretCollectionName = cfg.ServiceName - } - - service, err := libsecret.NewService() - if err != nil { - return &secretsKeyring{}, err - } - - ring := &secretsKeyring{ - name: cfg.LibSecretCollectionName, - service: service, - } - - return ring, ring.openSecrets() - }) -} - -type secretsKeyring struct { - name string - service *libsecret.Service - collection *libsecret.Collection - session *libsecret.Session -} - -type secretsError struct { - message string -} - -func (e *secretsError) Error() string { - return e.message -} - -var errCollectionNotFound = errors.New("The collection does not exist. Please add a key first") - -func (k *secretsKeyring) openSecrets() error { - session, err := k.service.Open() - if err != nil { - return err - } - k.session = session - - // get the collection if it already exists - collections, err := k.service.Collections() - if err != nil { - return err - } - - path := libsecret.DBusPath + "/collection/" + k.name - - for _, collection := range collections { - if string(collection.Path()) == path { - k.collection = &collection - return nil - } - } - - return nil -} - -func (k *secretsKeyring) openCollection() error { - if err := k.openSecrets(); err != nil { - return err - } - - if k.collection == nil { - return errCollectionNotFound - // return &secretsError{fmt.Sprintf( - // "The collection %q does not exist. Please add a key first", - // k.name, - // )} - } - - return nil -} - -func (k *secretsKeyring) Get(key string) (Item, error) { - if err := k.openCollection(); err != nil { - if err == errCollectionNotFound { - return Item{}, ErrKeyNotFound - } - return Item{}, err - } - - items, err := k.collection.SearchItems(key) - if err != nil { - return Item{}, err - } - - if len(items) == 0 { - return Item{}, ErrKeyNotFound - } - - // use the first item whenever there are multiples - // with the same profile name - item := items[0] - - locked, err := item.Locked() - if err != nil { - return Item{}, err - } - - if locked { - if err := k.service.Unlock(item); err != nil { - return Item{}, err - } - } - - secret, err := item.GetSecret(k.session) - if err != nil { - return Item{}, err - } - - // pack the secret into the item - var ret Item - if err = json.Unmarshal(secret.Value, &ret); err != nil { - return Item{}, err - } - - return ret, err -} - -// GetMetadata for libsecret returns an error indicating that it's unsupported -// for this backend. -// -// libsecret actually implements a metadata system which we could use, "Secret -// Attributes"; I found no indication in documentation of anything like an -// automatically maintained last-modification timestamp, so to use this we'd -// need to have a SetMetadata API too. Which we're not yet doing, but feel -// free to contribute patches. -func (k *secretsKeyring) GetMetadata(key string) (Metadata, error) { - return Metadata{}, ErrMetadataNeedsCredentials -} - -func (k *secretsKeyring) Set(item Item) error { - err := k.openSecrets() - if err != nil { - return err - } - - // create the collection if it doesn't already exist - if k.collection == nil { - collection, err := k.service.CreateCollection(k.name) - if err != nil { - return err - } - - k.collection = collection - } - - if err := k.ensureCollectionUnlocked(); err != nil { - return err - } - - // create the new item - data, err := json.Marshal(item) - if err != nil { - return err - } - - secret := libsecret.NewSecret(k.session, []byte{}, data, "application/json") - - if _, err := k.collection.CreateItem(item.Key, secret, true); err != nil { - return err - } - - return nil -} - -func (k *secretsKeyring) Remove(key string) error { - if err := k.openCollection(); err != nil { - if err == errCollectionNotFound { - return ErrKeyNotFound - } - return err - } - - items, err := k.collection.SearchItems(key) - if err != nil { - return err - } - - // nothing to delete - if len(items) == 0 { - return nil - } - - // we dont want to delete more than one anyway - // so just get the first item found - item := items[0] - - locked, err := item.Locked() - if err != nil { - return err - } - - if locked { - if err := k.service.Unlock(item); err != nil { - return err - } - } - - if err := item.Delete(); err != nil { - return err - } - - return nil -} - -func (k *secretsKeyring) Keys() ([]string, error) { - if err := k.openCollection(); err != nil { - if err == errCollectionNotFound { - return []string{}, nil - } - return nil, err - } - if err := k.ensureCollectionUnlocked(); err != nil { - return nil, err - } - items, err := k.collection.Items() - if err != nil { - return nil, err - } - keys := []string{} - for _, item := range items { - label, err := item.Label() - if err == nil { - keys = append(keys, label) - } else { - // err is being silently ignored here, not sure if that's good or bad - } - } - return keys, nil -} - -// deleteCollection deletes the keyring's collection if it exists. This is mainly to support testing. -func (k *secretsKeyring) deleteCollection() error { - if err := k.openCollection(); err != nil { - return err - } - return k.collection.Delete() -} - -// unlock the collection if it's locked -func (k *secretsKeyring) ensureCollectionUnlocked() error { - locked, err := k.collection.Locked() - if err != nil { - return err - } - if !locked { - return nil - } - return k.service.Unlock(k.collection) -} diff --git a/vendor/github.com/99designs/keyring/pass.go b/vendor/github.com/99designs/keyring/pass.go deleted file mode 100644 index bfe796c..0000000 --- a/vendor/github.com/99designs/keyring/pass.go +++ /dev/null @@ -1,160 +0,0 @@ -package keyring - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" -) - -func init() { - supportedBackends[PassBackend] = opener(func(cfg Config) (Keyring, error) { - pass := &passKeyring{ - passcmd: cfg.PassCmd, - dir: cfg.PassDir, - prefix: cfg.PassPrefix, - } - if cfg.PassCmd == "" { - pass.passcmd = "pass" - } - if cfg.PassDir == "" { - pass.dir = filepath.Join(os.Getenv("HOME"), ".password-store") - } - - // fail if the pass program is not available - _, err := exec.LookPath(pass.passcmd) - if err != nil { - return nil, errors.New("The pass program is not available") - } - - return pass, nil - }) -} - -type passKeyring struct { - dir string - passcmd string - prefix string -} - -func (k *passKeyring) pass(args ...string) (*exec.Cmd, error) { - cmd := exec.Command(k.passcmd, args...) - if k.dir != "" { - cmd.Env = append(os.Environ(), fmt.Sprintf("PASSWORD_STORE_DIR=%s", k.dir)) - } - cmd.Stderr = os.Stderr - - return cmd, nil -} - -func (k *passKeyring) Get(key string) (Item, error) { - if !k.itemExists(key) { - return Item{}, ErrKeyNotFound - } - - name := filepath.Join(k.prefix, key) - cmd, err := k.pass("show", name) - if err != nil { - return Item{}, err - } - - output, err := cmd.Output() - if err != nil { - return Item{}, err - } - - var decoded Item - err = json.Unmarshal(output, &decoded) - - return decoded, err -} - -func (k *passKeyring) GetMetadata(key string) (Metadata, error) { - return Metadata{}, nil -} - -func (k *passKeyring) Set(i Item) error { - bytes, err := json.Marshal(i) - if err != nil { - return err - } - - name := filepath.Join(k.prefix, i.Key) - cmd, err := k.pass("insert", "-m", "-f", name) - if err != nil { - return err - } - - cmd.Stdin = strings.NewReader(string(bytes)) - - err = cmd.Run() - if err != nil { - return err - } - - return nil -} - -func (k *passKeyring) Remove(key string) error { - if !k.itemExists(key) { - return ErrKeyNotFound - } - - name := filepath.Join(k.prefix, key) - cmd, err := k.pass("rm", "-f", name) - if err != nil { - return err - } - - err = cmd.Run() - if err != nil { - return err - } - - return nil -} - -func (k *passKeyring) itemExists(key string) bool { - var path = filepath.Join(k.dir, k.prefix, key+".gpg") - _, err := os.Stat(path) - if err != nil { - return false - } - return true -} - -func (k *passKeyring) Keys() ([]string, error) { - var keys = []string{} - var path = filepath.Join(k.dir, k.prefix) - - info, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return keys, nil - } - return keys, err - } - if !info.IsDir() { - return keys, fmt.Errorf("%s is not a directory", path) - } - - err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if !info.IsDir() && filepath.Ext(p) == ".gpg" { - name := strings.TrimPrefix(p, path) - if name[0] == os.PathSeparator { - name = name[1:] - } - keys = append(keys, name[:len(name)-4]) - } - return nil - }) - - return keys, err -} diff --git a/vendor/github.com/99designs/keyring/prompt.go b/vendor/github.com/99designs/keyring/prompt.go deleted file mode 100644 index d5da409..0000000 --- a/vendor/github.com/99designs/keyring/prompt.go +++ /dev/null @@ -1,27 +0,0 @@ -package keyring - -import ( - "fmt" - "os" - - "golang.org/x/crypto/ssh/terminal" -) - -// PromptFunc is a function used to prompt the user for a password -type PromptFunc func(string) (string, error) - -func terminalPrompt(prompt string) (string, error) { - fmt.Printf("%s: ", prompt) - b, err := terminal.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - return "", err - } - fmt.Println() - return string(b), nil -} - -func fixedStringPrompt(value string) PromptFunc { - return func(_ string) (string, error) { - return value, nil - } -} diff --git a/vendor/github.com/99designs/keyring/wincred.go b/vendor/github.com/99designs/keyring/wincred.go deleted file mode 100644 index b3dc01a..0000000 --- a/vendor/github.com/99designs/keyring/wincred.go +++ /dev/null @@ -1,93 +0,0 @@ -// +build windows - -package keyring - -import ( - "strings" - - "github.com/danieljoos/wincred" -) - -type windowsKeyring struct { - name string - prefix string -} - -func init() { - supportedBackends[WinCredBackend] = opener(func(cfg Config) (Keyring, error) { - name := cfg.ServiceName - if name == "" { - name = "default" - } - - prefix := cfg.WinCredPrefix - if prefix == "" { - prefix = "keyring" - } - - return &windowsKeyring{ - name: name, - prefix: prefix, - }, nil - }) -} - -func (k *windowsKeyring) Get(key string) (Item, error) { - cred, err := wincred.GetGenericCredential(k.credentialName(key)) - if err != nil { - if err.Error() == "Element not found." { - return Item{}, ErrKeyNotFound - } - return Item{}, err - } - - item := Item{ - Key: key, - Data: cred.CredentialBlob, - } - - return item, nil -} - -// GetMetadata for pass returns an error indicating that it's unsupported -// for this backend. -// TODO: This is a stub. Look into whether pass would support metadata in a usable way for keyring. -func (k *windowsKeyring) GetMetadata(_ string) (Metadata, error) { - return Metadata{}, ErrMetadataNeedsCredentials -} - -func (k *windowsKeyring) Set(item Item) error { - cred := wincred.NewGenericCredential(k.credentialName(item.Key)) - cred.CredentialBlob = item.Data - return cred.Write() -} - -func (k *windowsKeyring) Remove(key string) error { - cred, err := wincred.GetGenericCredential(k.credentialName(key)) - if err != nil { - if err.Error() == "Element not found." { - return ErrKeyNotFound - } - return err - } - return cred.Delete() -} - -func (k *windowsKeyring) Keys() ([]string, error) { - results := []string{} - - if creds, err := wincred.List(); err == nil { - for _, cred := range creds { - prefix := k.credentialName("") - if strings.HasPrefix(cred.TargetName, prefix) { - results = append(results, strings.TrimPrefix(cred.TargetName, prefix)) - } - } - } - - return results, nil -} - -func (k *windowsKeyring) credentialName(key string) string { - return k.prefix + ":" + k.name + ":" + key -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/README.md b/vendor/github.com/dvsekhvalnov/jose2go/README.md deleted file mode 100644 index 9df801c..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/README.md +++ /dev/null @@ -1,938 +0,0 @@ -# Golang (GO) Javascript Object Signing and Encryption (JOSE) and JSON Web Token (JWT) implementation - -[![GoDoc](https://godoc.org/github.com/dvsekhvalnov/jose2go?status.svg)](http://godoc.org/github.com/dvsekhvalnov/jose2go) - -Pure Golang (GO) library for generating, decoding and encrypting [JSON Web Tokens](https://tools.ietf.org/html/rfc7519). Zero dependency, relies only -on standard library. - -Supports full suite of signing, encryption and compression algorithms defined by [JSON Web Algorithms](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31) as of July 4, 2014 version. - -Extensively unit tested and cross tested (100+ tests) for compatibility with [jose.4.j](https://bitbucket.org/b_c/jose4j/wiki/Home), [Nimbus-JOSE-JWT](https://bitbucket.org/nimbusds/nimbus-jose-jwt/wiki/Home), [json-jwt](https://github.com/nov/json-jwt) and -[jose-jwt](https://github.com/dvsekhvalnov/jose-jwt) libraries. - - -## Status -Used in production. GA ready. Current version is 1.5. - -## Important -v1.5 bug fix release - -v1.4 changes default behavior of inserting `typ=JWT` header if not overriden. As of 1.4 no -extra headers added by library automatically. To mimic pre 1.4 behaviour use: -```Go -token, err := jose.Sign(..., jose.Header("typ", "JWT")) - -//or - -token, err := jose.Encrypt(..., jose.Header("typ", "JWT")) -``` - -v1.3 fixed potential Invalid Curve Attack on NIST curves within ECDH key management. -Upgrade strongly recommended. - -v1.2 breaks `jose.Decode` interface by returning 3 values instead of 2. - -v1.2 deprecates `jose.Compress` method in favor of using configuration options to `jose.Encrypt`, -the method will be removed in next release. - -### Migration to v1.2 -Pre v1.2 decoding: - -```Go -payload,err := jose.Decode(token,sharedKey) -``` - -Should be updated to v1.2: - -```Go -payload, headers, err := jose.Decode(token,sharedKey) -``` - -Pre v1.2 compression: - -```Go -token,err := jose.Compress(payload,jose.DIR,jose.A128GCM,jose.DEF, key) -``` - -Should be update to v1.2: - -```Go -token, err := jose.Encrypt(payload, jose.DIR, jose.A128GCM, key, jose.Zip(jose.DEF)) -``` - -## Supported JWA algorithms - -**Signing** -- HMAC signatures with HS256, HS384 and HS512. -- RSASSA-PKCS1-V1_5 signatures with RS256, RS384 and RS512. -- RSASSA-PSS signatures (probabilistic signature scheme with appendix) with PS256, PS384 and PS512. -- ECDSA signatures with ES256, ES384 and ES512. -- NONE (unprotected) plain text algorithm without integrity protection - -**Encryption** -- RSAES OAEP (using SHA-1 and MGF1 with SHA-1) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- RSAES OAEP 256 (using SHA-256 and MGF1 with SHA-256) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- RSAES-PKCS1-V1_5 encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- A128KW, A192KW, A256KW encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- A128GCMKW, A192GCMKW, A256GCMKW encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- ECDH-ES with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM -- Direct symmetric key encryption with pre-shared key A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM and A256GCM - -**Compression** -- DEFLATE compression - -## Installation -### Grab package from github -`go get github.com/dvsekhvalnov/jose2go` or `go get -u github.com/dvsekhvalnov/jose2go` to update to latest version - -### Import package -```Go -import ( - "github.com/dvsekhvalnov/jose2go" -) -``` - -## Usage -#### Creating Plaintext (unprotected) Tokens - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - token,err := jose.Sign(payload,jose.NONE, nil) - - if(err==nil) { - //go use token - fmt.Printf("\nPlaintext = %v\n",token) - } -} -``` - -### Creating signed tokens -#### HS-256, HS-384 and HS-512 -Signing with HS256, HS384, HS512 expecting `[]byte` array key of corresponding length: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - key := []byte{97,48,97,50,97,98,100,56,45,54,49,54,50,45,52,49,99,51,45,56,51,100,54,45,49,99,102,53,53,57,98,52,54,97,102,99} - - token,err := jose.Sign(payload,jose.HS256,key) - - if(err==nil) { - //go use token - fmt.Printf("\nHS256 = %v\n",token) - } -} -``` - -#### RS-256, RS-384 and RS-512, PS-256, PS-384 and PS-512 -Signing with RS256, RS384, RS512, PS256, PS384, PS512 expecting `*rsa.PrivateKey` private key of corresponding length. **jose2go** [provides convenient utils](#dealing-with-keys) to construct `*rsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `Rsa.ReadPrivate([]byte)` under `jose2go/keys/rsa` package. - -```Go -package main - -import ( - "fmt" - "io/ioutil" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - keyBytes,err := ioutil.ReadFile("private.key") - - if(err!=nil) { - panic("invalid key file") - } - - privateKey,e:=Rsa.ReadPrivate(keyBytes) - - if(e!=nil) { - panic("invalid key format") - } - - token,err := jose.Sign(payload,jose.RS256, privateKey) - - if(err==nil) { - //go use token - fmt.Printf("\nRS256 = %v\n",token) - } -} -``` - -#### ES-256, ES-384 and ES-512 -ES256, ES384, ES512 ECDSA signatures expecting `*ecdsa.PrivateKey` private elliptic curve key of corresponding length. **jose2go** [provides convenient utils](#dealing-with-keys) to construct `*ecdsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `ecc.ReadPrivate([]byte)` or directly from `X,Y,D` parameters: `ecc.NewPrivate(x,y,d []byte)` under `jose2go/keys/ecc` package. - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello":"world"}` - - privateKey:=ecc.NewPrivate([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}, - []byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}, - []byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 }) - - token,err := jose.Sign(payload, jose.ES256, privateKey) - - if(err==nil) { - //go use token - fmt.Printf("\ntoken = %v\n",token) - } -} -``` - -### Creating encrypted tokens -#### RSA-OAEP-256, RSA-OAEP and RSA1\_5 key management algorithm -RSA-OAEP-256, RSA-OAEP and RSA1_5 key management expecting `*rsa.PublicKey` public key of corresponding length. - -```Go -package main - -import ( - "fmt" - "io/ioutil" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - keyBytes,err := ioutil.ReadFile("public.key") - - if(err!=nil) { - panic("invalid key file") - } - - publicKey,e:=Rsa.ReadPublic(keyBytes) - - if(e!=nil) { - panic("invalid key format") - } - - //OR: - //token,err := jose.Encrypt(payload, jose.RSA1_5, jose.A256GCM, publicKey) - token,err := jose.Encrypt(payload, jose.RSA_OAEP, jose.A256GCM, publicKey) - - if(err==nil) { - //go use token - fmt.Printf("\ntoken = %v\n",token) - } -} -``` - -#### AES Key Wrap key management family of algorithms -AES128KW, AES192KW and AES256KW key management requires `[]byte` array key of corresponding length - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133} - - token,err := jose.Encrypt(payload,jose.A128KW,jose.A128GCM,sharedKey) - - if(err==nil) { - //go use token - fmt.Printf("\nA128KW A128GCM = %v\n",token) - } -} -``` - -#### AES GCM Key Wrap key management family of algorithms -AES128GCMKW, AES192GCMKW and AES256GCMKW key management requires `[]byte` array key of corresponding length - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133} - - token,err := jose.Encrypt(payload,jose.A128GCMKW,jose.A128GCM,sharedKey) - - if(err==nil) { - //go use token - fmt.Printf("\nA128GCMKW A128GCM = %v\n",token) - } -} -``` - -#### ECDH-ES and ECDH-ES with AES Key Wrap key management family of algorithms -ECDH-ES and ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW key management requires `*ecdsa.PublicKey` elliptic curve key of corresponding length. **jose2go** [provides convenient utils](#dealing-with-keys) to construct `*ecdsa.PublicKey` instance from PEM encoded PKCS1 X509 certificate or PKIX data: `ecc.ReadPublic([]byte)` or directly from `X,Y` parameters: `ecc.NewPublic(x,y []byte)`under `jose2go/keys/ecc` package: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello":"world"}` - - publicKey:=ecc.NewPublic([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}, - []byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}) - - token,err := jose.Encrypt(payload, jose.ECDH_ES, jose.A128CBC_HS256, publicKey) - - if(err==nil) { - //go use token - fmt.Printf("\ntoken = %v\n",token) - } -} -``` - -#### PBES2 using HMAC SHA with AES Key Wrap key management family of algorithms -PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW key management requires `string` passphrase from which actual key will be derived - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - passphrase := `top secret` - - token,err := jose.Encrypt(payload,jose.PBES2_HS256_A128KW,jose.A256GCM,passphrase) - - if(err==nil) { - //go use token - fmt.Printf("\nPBES2_HS256_A128KW A256GCM = %v\n",token) - } -} -``` - -#### DIR direct pre-shared symmetric key management -Direct key management with pre-shared symmetric keys expecting `[]byte` array key of corresponding length: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133} - - token,err := jose.Encrypt(payload,jose.DIR,jose.A128GCM,sharedKey) - - if(err==nil) { - //go use token - fmt.Printf("\nDIR A128GCM = %v\n",token) - } -} -``` - -### Creating compressed & encrypted tokens -#### DEFLATE compression -**jose2go** supports optional DEFLATE compression of payload before encrypting, can be used with all supported encryption and key management algorithms: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := `{"hello": "world"}` - - sharedKey := []byte{194, 164, 235, 6, 138, 248, 171, 239, 24, 216, 11, 22, 137, 199, 215, 133} - - token, err := jose.Encrypt(payload, jose.DIR, jose.A128GCM, sharedKey, jose.Zip(jose.DEF)) - - if err == nil { - //go use token - fmt.Printf("\nDIR A128GCM DEFLATED= %v\n", token) - } -} -``` - -### Verifying, Decoding and Decompressing tokens -Decoding json web tokens is fully symmetric to creating signed or encrypted tokens (with respect to public/private cryptography), decompressing deflated payloads is handled automatically: - -As of v1.2 decode method defined as `jose.Decode() payload string, headers map[string]interface{}, err error` and returns both payload as unprocessed string and headers as map. - -**HS256, HS384, HS512** signatures, **A128KW, A192KW, A256KW**,**A128GCMKW, A192GCMKW, A256GCMKW** and **DIR** key management algorithm expecting `[]byte` array key: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := "eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.chIoYWrQMA8XL5nFz6oLDJyvgHk2KA4BrFGrKymjC8E" - - sharedKey :=[]byte{97,48,97,50,97,98,100,56,45,54,49,54,50,45,52,49,99,51,45,56,51,100,54,45,49,99,102,53,53,57,98,52,54,97,102,99} - - payload, headers, err := jose.Decode(token,sharedKey) - - if(err==nil) { - //go use token - fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers - fmt.Printf("\nheaders = %v\n",headers) - } -} -``` - -**RS256, RS384, RS512**,**PS256, PS384, PS512** signatures expecting `*rsa.PublicKey` public key of corresponding length. **jose2go** [provides convenient utils](#dealing-with-keys) to construct `*rsa.PublicKey` instance from PEM encoded PKCS1 X509 certificate or PKIX data: `Rsa.ReadPublic([]byte)` under `jose2go/keys/rsa` package: - -```Go -package main - -import ( - "fmt" - "io/ioutil" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw" - - keyBytes, err := ioutil.ReadFile("public.key") - - if(err!=nil) { - panic("invalid key file") - } - - publicKey, e:=Rsa.ReadPublic(keyBytes) - - if(e!=nil) { - panic("invalid key format") - } - - payload, headers, err := jose.Decode(token, publicKey) - - if(err==nil) { - //go use token - fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers - fmt.Printf("\nheaders = %v\n",headers) - } -} -``` - -**RSA-OAEP-256**, **RSA-OAEP** and **RSA1_5** key management algorithms expecting `*rsa.PrivateKey` private key of corresponding length: - -```Go -package main - -import ( - "fmt" - "io/ioutil" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2R0NNIn0.ixD3WVOkvaxeLKi0kyVqTzM6W2EW25SHHYCAr9473Xq528xSK0AVux6kUtv7QMkQKgkMvO8X4VdvonyGkDZTK2jgYUiI06dz7I1sjWJIbyNVrANbBsmBiwikwB-9DLEaKuM85Lwu6gnzbOF6B9R0428ckxmITCPDrzMaXwYZHh46FiSg9djChUTex0pHGhNDiEIgaINpsmqsOFX1L2Y7KM2ZR7wtpR3kidMV3JlxHdKheiPKnDx_eNcdoE-eogPbRGFdkhEE8Dyass1ZSxt4fP27NwsIer5pc0b922_3XWdi1r1TL_fLvGktHLvt6HK6IruXFHpU4x5Z2gTXWxEIog.zzTNmovBowdX2_hi.QSPSgXn0w25ugvzmu2TnhePn.0I3B9BE064HFNP2E0I7M9g" - - keyBytes, err := ioutil.ReadFile("private.key") - - if(err!=nil) { - panic("invalid key file") - } - - privateKey, e:=Rsa.ReadPrivate(keyBytes) - - if(e!=nil) { - panic("invalid key format") - } - - payload, headers, err := jose.Decode(token, privateKey) - - if(err==nil) { - //go use payload - fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers - fmt.Printf("\nheaders = %v\n",headers) - } -} -``` - -**PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW** key management algorithms expects `string` passpharase as a key - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := `eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJlZWpFZTF0YmJVbU5XV2s2In0.J2HTgltxH3p7A2zDgQWpZPgA2CHTSnDmMhlZWeSOMoZ0YvhphCeg-w.FzYG5AOptknu7jsG.L8jAxfxZhDNIqb0T96YWoznQ.yNeOfQWUbm8KuDGZ_5lL_g` - - passphrase := `top secret` - - payload, headers, err := jose.Decode(token,passphrase) - - if(err==nil) { - //go use token - fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers - fmt.Printf("\nheaders = %v\n",headers) - } -} -``` - -**ES256, ES284, ES512** signatures expecting `*ecdsa.PublicKey` public elliptic curve key of corresponding length. **jose2go** [provides convenient utils](#dealing-with-keys) to construct `*ecdsa.PublicKey` instance from PEM encoded PKCS1 X509 certificate or PKIX data: `ecc.ReadPublic([]byte)` or directly from `X,Y` parameters: `ecc.NewPublic(x,y []byte)`under `jose2go/keys/ecc` package: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := "eyJhbGciOiJFUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.EVnmDMlz-oi05AQzts-R3aqWvaBlwVZddWkmaaHyMx5Phb2NSLgyI0kccpgjjAyo1S5KCB3LIMPfmxCX_obMKA" - - publicKey:=ecc.NewPublic([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}, - []byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}) - - payload, headers, err := jose.Decode(token, publicKey) - - if(err==nil) { - //go use token - fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers - fmt.Printf("\nheaders = %v\n",headers) - } -} -``` - -**ECDH-ES** and **ECDH-ES+A128KW**, **ECDH-ES+A192KW**, **ECDH-ES+A256KW** key management expecting `*ecdsa.PrivateKey` private elliptic curve key of corresponding length. **jose2go** [provides convenient utils](#dealing-with-keys) to construct `*ecdsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `ecc.ReadPrivate([]byte)` or directly from `X,Y,D` parameters: `ecc.NewPrivate(x,y,d []byte)` under `jose2go/keys/ecc` package: - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImVwayI6eyJrdHkiOiJFQyIsIngiOiItVk1LTG5NeW9IVHRGUlpGNnFXNndkRm5BN21KQkdiNzk4V3FVMFV3QVhZIiwieSI6ImhQQWNReTgzVS01Qjl1U21xbnNXcFZzbHVoZGJSZE1nbnZ0cGdmNVhXTjgiLCJjcnYiOiJQLTI1NiJ9fQ..UA3N2j-TbYKKD361AxlXUA.XxFur_nY1GauVp5W_KO2DEHfof5s7kUwvOgghiNNNmnB4Vxj5j8VRS8vMOb51nYy2wqmBb2gBf1IHDcKZdACkCOMqMIcpBvhyqbuKiZPLHiilwSgVV6ubIV88X0vK0C8ZPe5lEyRudbgFjdlTnf8TmsvuAsdtPn9dXwDjUR23bD2ocp8UGAV0lKqKzpAw528vTfD0gwMG8gt_op8yZAxqqLLljMuZdTnjofAfsW2Rq3Z6GyLUlxR51DAUlQKi6UpsKMJoXTrm1Jw8sXBHpsRqA.UHCYOtnqk4SfhAknCnymaQ" - - privateKey:=ecc.NewPrivate([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}, - []byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}, - []byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 }) - - payload, headers, err := jose.Decode(token, privateKey) - - if(err==nil) { - //go use token - fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers - fmt.Printf("\nheaders = %v\n",headers) - } -} -``` - -### Adding extra headers -It's possible to pass additional headers while encoding token. **jose2go** provides convenience configuration helpers: `Header(name string, value interface{})` and `Headers(headers map[string]interface{})` that can be passed to `Sign(..)` and `Encrypt(..)` calls. - -Note: **jose2go** do not allow to override `alg`, `enc` and `zip` headers. - -Example of signing with extra headers: -```Go - token, err := jose.Sign(payload, jose.ES256, key, - jose.Header("keyid", "111-222-333"), - jose.Header("trans-id", "aaa-bbb")) -``` - -Encryption with extra headers: -```Go -token, err := jose.Encrypt(payload, jose.DIR, jose.A128GCM, sharedKey, - jose.Headers(map[string]interface{}{"keyid": "111-22-33", "cty": "text/plain"})) -``` - -### Two phase validation -In some cases validation (decoding) key can be unknown prior to examining token content. For instance one can use different keys per token issuer or rely on headers information to determine which key to use, do logging or other things. - -**jose2go** allows to pass `func(headers map[string]interface{}, payload string) key interface{}` callback instead of key to `jose.Decode(..)`. Callback will be executed prior to decoding and integrity validation and will recieve parsed headers and payload as is (for encrypted tokens it will be cipher text). Callback should return key to be used for actual decoding process or `error` if decoding should be stopped, given error object will be returned from `jose.Decode(..)` call. - -Example of decoding token with callback: - -```Go -package main - -import ( - "crypto/rsa" - "fmt" - "github.com/dvsekhvalnov/jose2go" - "github.com/dvsekhvalnov/jose2go/keys/rsa" - "io/ioutil" - "errors" -) - -func main() { - - token := "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw" - - payload, _, err := jose.Decode(token, - func(headers map[string]interface{}, payload string) interface{} { - //log something - fmt.Printf("\nHeaders before decoding: %v\n", headers) - fmt.Printf("\nPayload before decoding: %v\n", payload) - - //lookup key based on keyid header as en example - //or lookup based on something from payload, e.g. 'iss' claim for instance - key := FindKey(headers['keyid']) - - if(key==nil) { - return errors.New("Key not found") - } - - return key; - }) - - if err == nil { - //go use token - fmt.Printf("\ndecoded payload = %v\n", payload) - } -} -``` - -### Working with binary payload -In addition to work with string payloads (typical use-case) `jose2go` supports -encoding and decoding of raw binary data. `jose.DecodeBytes`, `jose.SignBytes` -and `jose.EncryptBytes` functions provides similar interface but accepting -`[]byte` payloads. - -Examples: - -```Go -package main - -import ( - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - token := `eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJlZWpFZTF0YmJVbU5XV2s2In0.J2HTgltxH3p7A2zDgQWpZPgA2CHTSnDmMhlZWeSOMoZ0YvhphCeg-w.FzYG5AOptknu7jsG.L8jAxfxZhDNIqb0T96YWoznQ.yNeOfQWUbm8KuDGZ_5lL_g` - - passphrase := `top secret` - - payload, headers, err := jose.DecodeBytes(token,passphrase) - - if(err==nil) { - //go use token - //payload = []byte{....} - } -} -``` - -```Go -package main - -import ( - "fmt" - "io/ioutil" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := []byte {0x01, 0x02, 0x03, 0x04} - - keyBytes,err := ioutil.ReadFile("private.key") - - if(err!=nil) { - panic("invalid key file") - } - - privateKey,e:=Rsa.ReadPrivate(keyBytes) - - if(e!=nil) { - panic("invalid key format") - } - - token,err := jose.SignBytes(payload,jose.RS256, privateKey) - - if(err==nil) { - //go use token - fmt.Printf("\nRS256 = %v\n",token) - } -} -``` - -```Go -package main - -import ( - "fmt" - "io/ioutil" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "github.com/dvsekhvalnov/jose2go" -) - -func main() { - - payload := []byte {0x01, 0x02, 0x03, 0x04} - - keyBytes,err := ioutil.ReadFile("public.key") - - if(err!=nil) { - panic("invalid key file") - } - - publicKey,e:=Rsa.ReadPublic(keyBytes) - - if(e!=nil) { - panic("invalid key format") - } - - token,err := jose.EncryptBytes(payload, jose.RSA_OAEP, jose.A256GCM, publicKey) - - if(err==nil) { - //go use token - fmt.Printf("\ntoken = %v\n",token) - } -} -``` -### Dealing with keys -**jose2go** provides several helper methods to simplify loading & importing of elliptic and rsa keys. Import `jose2go/keys/rsa` or `jose2go/keys/ecc` respectively: - -#### RSA keys -1. `Rsa.ReadPrivate(raw []byte) (key *rsa.PrivateKey,err error)` attempts to parse RSA private key from PKCS1 or PKCS8 format (`BEGIN RSA PRIVATE KEY` and `BEGIN PRIVATE KEY` headers) - -```Go -package main - -import ( - "fmt" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "io/ioutil" -) - -func main() { - - keyBytes, _ := ioutil.ReadFile("private.key") - - privateKey, err:=Rsa.ReadPrivate(keyBytes) - - if(err!=nil) { - panic("invalid key format") - } - - fmt.Printf("privateKey = %v\n",privateKey) -} -``` - -2. `Rsa.ReadPublic(raw []byte) (key *rsa.PublicKey,err error)` attempts to parse RSA public key from PKIX key format or PKCS1 X509 certificate (`BEGIN PUBLIC KEY` and `BEGIN CERTIFICATE` headers) - -```Go -package main - -import ( - "fmt" - Rsa "github.com/dvsekhvalnov/jose2go/keys/rsa" - "io/ioutil" -) - -func main() { - - keyBytes, _ := ioutil.ReadFile("public.cer") - - publicKey, err:=Rsa.ReadPublic(keyBytes) - - if(err!=nil) { - panic("invalid key format") - } - - fmt.Printf("publicKey = %v\n",publicKey) -} -``` - -#### ECC keys -1. `ecc.ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error)` attemps to parse elliptic curve private key from PKCS1 or PKCS8 format (`BEGIN EC PRIVATE KEY` and `BEGIN PRIVATE KEY` headers) - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "io/ioutil" -) - -func main() { - - keyBytes, _ := ioutil.ReadFile("ec-private.pem") - - ecPrivKey, err:=ecc.ReadPrivate(keyBytes) - - if(err!=nil) { - panic("invalid key format") - } - - fmt.Printf("ecPrivKey = %v\n",ecPrivKey) -} -``` - -2. `ecc.ReadPublic(raw []byte) (key *ecdsa.PublicKey,err error)` attemps to parse elliptic curve public key from PKCS1 X509 or PKIX format (`BEGIN PUBLIC KEY` and `BEGIN CERTIFICATE` headers) - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "io/ioutil" -) - -func main() { - - keyBytes, _ := ioutil.ReadFile("ec-public.key") - - ecPubKey, err:=ecc.ReadPublic(keyBytes) - - if(err!=nil) { - panic("invalid key format") - } - - fmt.Printf("ecPubKey = %v\n",ecPubKey) -} -``` - -3. `ecc.NewPublic(x,y []byte) (*ecdsa.PublicKey)` constructs elliptic public key from (X,Y) represented as bytes. Supported are NIST curves P-256,P-384 and P-521. Curve detected automatically by input length. - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" -) - -func main() { - - ecPubKey:=ecc.NewPublic([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}, - []byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}) - - fmt.Printf("ecPubKey = %v\n",ecPubKey) -} -``` - -4. `ecc.NewPrivate(x,y,d []byte) (*ecdsa.PrivateKey)` constructs elliptic private key from (X,Y) and D represented as bytes. Supported are NIST curves P-256,P-384 and P-521. Curve detected automatically by input length. - -```Go -package main - -import ( - "fmt" - "github.com/dvsekhvalnov/jose2go/keys/ecc" -) - -func main() { - - ecPrivKey:=ecc.NewPrivate([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}, - []byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}, - []byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 }) - - fmt.Printf("ecPrivKey = %v\n",ecPrivKey) -} -``` - -### More examples -Checkout `jose_test.go` for more examples. - -## Changelog -### 1.2 -- interface to access token headers after decoding -- interface to provide extra headers for token encoding -- two-phase validation support - -### 1.1 -- security and bug fixes - -### 1.0 -- initial stable version with full suite JOSE spec support diff --git a/vendor/github.com/dvsekhvalnov/jose2go/aes/ecb.go b/vendor/github.com/dvsekhvalnov/jose2go/aes/ecb.go deleted file mode 100644 index ec9af99..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/aes/ecb.go +++ /dev/null @@ -1,68 +0,0 @@ -// Package aes contains provides AES Key Wrap and ECB mode implementations -package aes - -import ( - "crypto/cipher" -) - -type ecb struct { - b cipher.Block -} - -type ecbEncrypter ecb -type ecbDecrypter ecb - -// NewECBEncrypter creates BlockMode for AES encryption in ECB mode -func NewECBEncrypter(b cipher.Block) cipher.BlockMode { - return &ecbEncrypter{b: b} -} - -// NewECBDecrypter creates BlockMode for AES decryption in ECB mode -func NewECBDecrypter(b cipher.Block) cipher.BlockMode { - return &ecbDecrypter{b: b} -} - -func (x *ecbEncrypter) BlockSize() int { return x.b.BlockSize() } -func (x *ecbDecrypter) BlockSize() int { return x.b.BlockSize() } - -func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { - bs := x.BlockSize() - - if len(src)%bs != 0 { - panic("ecbDecrypter.CryptBlocks(): input not full blocks") - } - - if len(dst) < len(src) { - panic("ecbDecrypter.CryptBlocks(): output smaller than input") - } - - if len(src) == 0 { - return - } - - for len(src) > 0 { - x.b.Decrypt(dst, src) - src = src[bs:] - } -} - -func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { - bs := x.BlockSize() - - if len(src)%bs != 0 { - panic("ecbEncrypter.CryptBlocks(): input not full blocks") - } - - if len(dst) < len(src) { - panic("ecbEncrypter.CryptBlocks(): output smaller than input") - } - - if len(src) == 0 { - return - } - - for len(src) > 0 { - x.b.Encrypt(dst, src) - src = src[bs:] - } -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/aes/key_wrap.go b/vendor/github.com/dvsekhvalnov/jose2go/aes/key_wrap.go deleted file mode 100644 index 8e5963e..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/aes/key_wrap.go +++ /dev/null @@ -1,113 +0,0 @@ -package aes - -import ( - "github.com/dvsekhvalnov/jose2go/arrays" - "crypto/cipher" - "crypto/aes" - "crypto/hmac" - "errors" -) - -var defaultIV=[]byte { 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 } - -// KeyWrap encrypts provided key (CEK) with KEK key using AES Key Wrap (rfc 3394) algorithm -func KeyWrap(cek,kek []byte) ([]byte,error) { - // 1) Initialize variables - a := defaultIV // Set A = IV, an initial value - r := arrays.Slice(cek, 8) // For i = 1 to n - // R[0][i] = P[i] - n := uint64(len(r)) - - // 2) Calculate intermediate values. - var j,i,t uint64 - - for j = 0; j < 6; j++ { // For j = 0 to 5 - for i = 0; i < n; i++ { // For i=1 to n - t = n * j + i + 1; - b,e := aesEnc(kek, arrays.Concat(a, r[i])) // B=AES(K, A | R[i]) - - if e!=nil { return nil, e } - - a = b[:len(b)/2] // A=MSB(64,B) ^ t where t = (n*j)+i - r[i] = b[len(b)/2:] // R[i] = LSB(64, B) - a = arrays.Xor(a, arrays.UInt64ToBytes(t)) - } - } - - // 3) Output the results - c := make([][]byte, n+1, n+1) - c[0] = a; // Set C[0] = A - for i = 1; i <= n; i++ { // For i = 1 to n - c[i] = r[i - 1] // C[i] = R[i] - } - - return arrays.Unwrap(c),nil -} - -// KeyUnwrap decrypts previously encrypted key (CEK) with KEK key using AES Key Wrap (rfc 3394) algorithm -func KeyUnwrap(encryptedCek, kek []byte) ([]byte,error) { - // 1) Initialize variables - c := arrays.Slice(encryptedCek, 8); - a := c[0]; // Set A = C[0] - r := make([][]byte,len(c) - 1); - - for i := 1; i < len(c); i++ { // For i = 1 to n - r[i - 1] = c[i]; // R[i] = C[i] - } - - n := uint64(len(r)) - - // 2) Calculate intermediate values - var t,j uint64 - - for j = 6; j > 0; j-- { // For j = 5 to 0 - for i := n; i > 0; i-- { // For i = n to 1 - t = n * (j-1) + i; - a = arrays.Xor(a, arrays.UInt64ToBytes(t)) - b,e := aesDec(kek, arrays.Concat(a, r[i-1])) // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i - - if e!=nil { return nil,e } - - a = b[:len(b)/2] // A = MSB(64, B) - r[i-1] = b[len(b)/2:] // R[i] = LSB(64, B) - } - } - - // 3) Output the results - if (!hmac.Equal(defaultIV, a)) { // If A is an appropriate initial value - return nil, errors.New("aes.KeyUnwrap(): integrity check failed.") - } - - // For i = 1 to n - return arrays.Unwrap(r),nil // P[i] = R[i] - -} - -func aesEnc(kek, plainText []byte) (cipherText []byte, err error) { - var block cipher.Block - - if block, err = aes.NewCipher(kek);err!=nil { - return nil,err - } - - cipherText = make([]byte, len(plainText)) - - NewECBEncrypter(block).CryptBlocks(cipherText,plainText) - - return cipherText,nil -} - -func aesDec(kek, cipherText []byte) (plainText []byte,err error) { - - var block cipher.Block - - if block, err = aes.NewCipher(kek);err!=nil { - return nil,err - } - - plainText = make([]byte, len(cipherText)) - - NewECBDecrypter(block).CryptBlocks(plainText,cipherText) - - return plainText,nil -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/aes_cbc_hmac.go b/vendor/github.com/dvsekhvalnov/jose2go/aes_cbc_hmac.go deleted file mode 100644 index a2217e1..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/aes_cbc_hmac.go +++ /dev/null @@ -1,112 +0,0 @@ -package jose - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/hmac" - "errors" - "fmt" - "github.com/dvsekhvalnov/jose2go/arrays" - "github.com/dvsekhvalnov/jose2go/padding" -) - -// AES CBC with HMAC authenticated encryption algorithm implementation -type AesCbcHmac struct { - keySizeBits int -} - -func init() { - RegisterJwe(&AesCbcHmac{keySizeBits: 256}) - RegisterJwe(&AesCbcHmac{keySizeBits: 384}) - RegisterJwe(&AesCbcHmac{keySizeBits: 512}) -} - -func (alg *AesCbcHmac) Name() string { - switch alg.keySizeBits { - case 256: - return A128CBC_HS256 - case 384: - return A192CBC_HS384 - default: - return A256CBC_HS512 - } -} - -func (alg *AesCbcHmac) KeySizeBits() int { - return alg.keySizeBits -} - -func (alg *AesCbcHmac) SetKeySizeBits(bits int) { - alg.keySizeBits = bits -} - -func (alg *AesCbcHmac) Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error) { - - cekSizeBits := len(cek) << 3 - if cekSizeBits != alg.keySizeBits { - return nil, nil, nil, errors.New(fmt.Sprintf("AesCbcHmac.Encrypt(): expected key of size %v bits, but was given %v bits.", alg.keySizeBits, cekSizeBits)) - } - - hmacKey := cek[0 : len(cek)/2] - aesKey := cek[len(cek)/2:] - - if iv, err = arrays.Random(16); err != nil { - return nil, nil, nil, err - } - - var block cipher.Block - - if block, err = aes.NewCipher(aesKey); err != nil { - return nil, nil, nil, err - } - - padded := padding.AddPkcs7(plainText, 16) - - cipherText = make([]byte, len(padded), cap(padded)) - mode := cipher.NewCBCEncrypter(block, iv) - mode.CryptBlocks(cipherText, padded) - - authTag = alg.computeAuthTag(aad, iv, cipherText, hmacKey) - - return iv, cipherText, authTag, nil -} - -func (alg *AesCbcHmac) Decrypt(aad, cek, iv, cipherText, authTag []byte) (plainText []byte, err error) { - - cekSizeBits := len(cek) << 3 - - if cekSizeBits != alg.keySizeBits { - return nil, errors.New(fmt.Sprintf("AesCbcHmac.Decrypt(): expected key of size %v bits, but was given %v bits.", alg.keySizeBits, cekSizeBits)) - } - - hmacKey := cek[0 : len(cek)/2] - aesKey := cek[len(cek)/2:] - - // Check MAC - expectedAuthTag := alg.computeAuthTag(aad, iv, cipherText, hmacKey) - - if !hmac.Equal(expectedAuthTag, authTag) { - return nil, errors.New("AesCbcHmac.Decrypt(): Authentication tag do not match.") - } - - var block cipher.Block - - if block, err = aes.NewCipher(aesKey); err == nil { - mode := cipher.NewCBCDecrypter(block, iv) - - var padded []byte = make([]byte, len(cipherText), cap(cipherText)) - mode.CryptBlocks(padded, cipherText) - - return padding.RemovePkcs7(padded, 16), nil - } - - return nil, err -} - -func (alg *AesCbcHmac) computeAuthTag(aad []byte, iv []byte, cipherText []byte, hmacKey []byte) (signature []byte) { - al := arrays.UInt64ToBytes(uint64(len(aad) << 3)) - hmacInput := arrays.Concat(aad, iv, cipherText, al) - hmac := calculateHmac(alg.keySizeBits, hmacInput, hmacKey) - - return hmac[0 : len(hmac)/2] -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/aes_gcm.go b/vendor/github.com/dvsekhvalnov/jose2go/aes_gcm.go deleted file mode 100644 index 57fc861..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/aes_gcm.go +++ /dev/null @@ -1,98 +0,0 @@ -package jose - -import ( - "fmt" - "errors" - "crypto/aes" - "crypto/cipher" - "github.com/dvsekhvalnov/jose2go/arrays" -) - -// AES GCM authenticated encryption algorithm implementation -type AesGcm struct{ - keySizeBits int -} - -func init() { - RegisterJwe(&AesGcm{keySizeBits:128}) - RegisterJwe(&AesGcm{keySizeBits:192}) - RegisterJwe(&AesGcm{keySizeBits:256}) -} - -func (alg *AesGcm) Name() string { - switch alg.keySizeBits { - case 128: return A128GCM - case 192: return A192GCM - default: return A256GCM - } -} - -func (alg *AesGcm) KeySizeBits() int { - return alg.keySizeBits -} - -func (alg *AesGcm) Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error) { - - cekSizeBits := len(cek)<<3 - - if cekSizeBits != alg.keySizeBits { - return nil,nil,nil, errors.New(fmt.Sprintf("AesGcm.Encrypt(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, cekSizeBits)) - } - - if iv,err = arrays.Random(12);err!=nil { - return nil,nil,nil,err - } - - var block cipher.Block - - if block, err = aes.NewCipher(cek);err!=nil { - return nil,nil,nil,err - } - - var aesgcm cipher.AEAD - - if aesgcm,err = cipher.NewGCM(block);err!=nil { - return nil,nil,nil,err - } - - cipherWithTag := aesgcm.Seal(nil, iv, plainText, aad) - - cipherText=cipherWithTag[:len(cipherWithTag)-aesgcm.Overhead()] - authTag=cipherWithTag[len(cipherWithTag)-aesgcm.Overhead():] - - return iv, cipherText, authTag, nil -} - -func (alg *AesGcm) Decrypt(aad, cek, iv, cipherText, authTag []byte) (plainText []byte, err error) { - - cekSizeBits := len(cek)<<3 - - if cekSizeBits != alg.keySizeBits { - return nil, errors.New(fmt.Sprintf("AesGcm.Decrypt(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, cekSizeBits)) - } - - var block cipher.Block - - if block, err = aes.NewCipher(cek);err!=nil { - return nil,err - } - - var aesgcm cipher.AEAD - - if aesgcm,err = cipher.NewGCM(block);err!=nil { - return nil,err - } - - cipherWithTag:=append(cipherText,authTag...) - - if nonceSize := len(iv); nonceSize != aesgcm.NonceSize() { - return nil, errors.New(fmt.Sprintf("AesGcm.Decrypt(): expected nonce of size %v bits, but was given %v bits.", aesgcm.NonceSize()<<3, nonceSize<<3)) - } - - if plainText,err = aesgcm.Open(nil, iv, cipherWithTag, aad);err!=nil { - return nil,err - } - - return plainText,nil -} - diff --git a/vendor/github.com/dvsekhvalnov/jose2go/aes_gcm_kw.go b/vendor/github.com/dvsekhvalnov/jose2go/aes_gcm_kw.go deleted file mode 100644 index e6f5681..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/aes_gcm_kw.go +++ /dev/null @@ -1,128 +0,0 @@ -package jose - -import ( - "errors" - "fmt" - "github.com/dvsekhvalnov/jose2go/base64url" - "github.com/dvsekhvalnov/jose2go/arrays" - "crypto/aes" - "crypto/cipher" -) - -func init() { - RegisterJwa(&AesGcmKW{ keySizeBits: 128}) - RegisterJwa(&AesGcmKW{ keySizeBits: 192}) - RegisterJwa(&AesGcmKW{ keySizeBits: 256}) -} - -// AES GCM Key Wrap key management algorithm implementation -type AesGcmKW struct { - keySizeBits int -} - -func (alg *AesGcmKW) Name() string { - switch alg.keySizeBits { - case 128: return A128GCMKW - case 192: return A192GCMKW - default: return A256GCMKW - } -} - -func (alg *AesGcmKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - if kek,ok:=key.([]byte); ok { - - kekSizeBits := len(kek) << 3 - - if kekSizeBits != alg.keySizeBits { - return nil,nil, errors.New(fmt.Sprintf("AesGcmKW.WrapNewKey(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, kekSizeBits)) - } - - if cek,err = arrays.Random(cekSizeBits>>3);err!=nil { - return nil,nil,err - } - - var iv []byte - - if iv,err = arrays.Random(12);err!=nil { - return nil,nil,err - } - - var block cipher.Block - - if block, err = aes.NewCipher(kek);err!=nil { - return nil,nil,err - } - - var aesgcm cipher.AEAD - - if aesgcm,err = cipher.NewGCM(block);err!=nil { - return nil,nil,err - } - - cipherWithTag := aesgcm.Seal(nil, iv, cek, nil) - - cipherText := cipherWithTag[:len(cipherWithTag)-aesgcm.Overhead()] - authTag := cipherWithTag[len(cipherWithTag)-aesgcm.Overhead():] - - header["iv"]=base64url.Encode(iv) - header["tag"]=base64url.Encode(authTag) - - return cek,cipherText,nil - } - - return nil,nil,errors.New("AesGcmKW.WrapNewKey(): expected key to be '[]byte' array") -} - -func (alg *AesGcmKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - if kek,ok:=key.([]byte); ok { - - kekSizeBits := len(kek) << 3 - - if kekSizeBits != alg.keySizeBits { - return nil,errors.New(fmt.Sprintf("AesGcmKW.Unwrap(): expected key of size %v bits, but was given %v bits.", alg.keySizeBits, kekSizeBits)) - } - - var iv,tag string - - if iv,ok = header["iv"].(string);!ok { - return nil,errors.New("AesGcmKW.Unwrap(): expected 'iv' param in JWT header, but was not found.") - } - - if tag,ok = header["tag"].(string);!ok { - return nil,errors.New("AesGcmKW.Unwrap(): expected 'tag' param in JWT header, but was not found.") - } - - var ivBytes,tagBytes []byte - - if ivBytes,err = base64url.Decode(iv);err!=nil { - return nil,err - } - - if tagBytes,err = base64url.Decode(tag);err!=nil { - return nil,err - } - - var block cipher.Block - - if block, err = aes.NewCipher(kek);err!=nil { - return nil,err - } - - var aesgcm cipher.AEAD - - if aesgcm,err = cipher.NewGCM(block);err!=nil { - return nil,err - } - - cipherAndTag:=append(encryptedCek,tagBytes...) - - if cek,err = aesgcm.Open(nil, ivBytes,cipherAndTag , nil);err!=nil { - fmt.Printf("err = %v\n",err) - return nil,err - } - - return cek,nil - } - - return nil,errors.New("AesGcmKW.Unwrap(): expected key to be '[]byte' array") -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/aeskw.go b/vendor/github.com/dvsekhvalnov/jose2go/aeskw.go deleted file mode 100644 index c5b3e4f..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/aeskw.go +++ /dev/null @@ -1,64 +0,0 @@ -package jose - -import ( - "errors" - "fmt" - "github.com/dvsekhvalnov/jose2go/aes" - "github.com/dvsekhvalnov/jose2go/arrays" -) - -func init() { - RegisterJwa(&AesKW{ keySizeBits: 128}) - RegisterJwa(&AesKW{ keySizeBits: 192}) - RegisterJwa(&AesKW{ keySizeBits: 256}) -} - -// AES Key Wrap key management algorithm implementation -type AesKW struct { - keySizeBits int -} - -func (alg *AesKW) Name() string { - switch alg.keySizeBits { - case 128: return A128KW - case 192: return A192KW - default: return A256KW - } -} - -func (alg *AesKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - if kek,ok:=key.([]byte); ok { - - kekSizeBits := len(kek) << 3 - - if kekSizeBits != alg.keySizeBits { - return nil,nil, errors.New(fmt.Sprintf("AesKW.WrapNewKey(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, kekSizeBits)) - } - - if cek,err = arrays.Random(cekSizeBits>>3);err==nil { - encryptedCek,err=aes.KeyWrap(cek,kek) - return - } - - return nil,nil,err - - } - - return nil,nil,errors.New("AesKW.WrapNewKey(): expected key to be '[]byte' array") -} - -func (alg *AesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - - if kek,ok:=key.([]byte); ok { - - kekSizeBits := len(kek) << 3 - - if kekSizeBits != alg.keySizeBits { - return nil,errors.New(fmt.Sprintf("AesKW.Unwrap(): expected key of size %v bits, but was given %v bits.", alg.keySizeBits, kekSizeBits)) - } - - return aes.KeyUnwrap(encryptedCek, kek) - } - - return nil,errors.New("AesKW.Unwrap(): expected key to be '[]byte' array") -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/arrays/arrays.go b/vendor/github.com/dvsekhvalnov/jose2go/arrays/arrays.go deleted file mode 100644 index 17ff0bd..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/arrays/arrays.go +++ /dev/null @@ -1,116 +0,0 @@ -// Package arrays provides various byte array utilities -package arrays - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "fmt" - "github.com/dvsekhvalnov/jose2go/base64url" -) - -// Xor is doing byte by byte exclusive or of 2 byte arrays -func Xor(left, right []byte) []byte { - result := make([]byte, len(left)) - - for i := 0; i < len(left); i++ { - result[i] = left[i] ^ right[i] - } - - return result -} - -// Slice is splitting input byte array into slice of subarrays. Each of count length. -func Slice(arr []byte, count int) [][]byte { - - sliceCount := len(arr) / count - result := make([][]byte, sliceCount) - - for i := 0; i < sliceCount; i++ { - start := i * count - end := i*count + count - - result[i] = arr[start:end] - } - - return result -} - -// Random generates byte array with random data of byteCount length -func Random(byteCount int) ([]byte, error) { - data := make([]byte, byteCount) - - if _, err := rand.Read(data); err != nil { - return nil, err - } - - return data, nil -} - -// Concat combine several arrays into single one, resulting slice = A1 | A2 | A3 | ... | An -func Concat(arrays ...[]byte) []byte { - var result []byte = arrays[0] - - for _, arr := range arrays[1:] { - result = append(result, arr...) - } - - return result -} - -// Unwrap same thing as Contact, just different interface, combines several array into single one -func Unwrap(arrays [][]byte) []byte { - var result []byte = arrays[0] - - for _, arr := range arrays[1:] { - result = append(result, arr...) - } - - return result -} - -// UInt64ToBytes unwrap uint64 value to byte array of length 8 using big endian -func UInt64ToBytes(value uint64) []byte { - result := make([]byte, 8) - binary.BigEndian.PutUint64(result, value) - - return result -} - -// UInt32ToBytes unwrap uint32 value to byte array of length 4 using big endian -func UInt32ToBytes(value uint32) []byte { - result := make([]byte, 4) - binary.BigEndian.PutUint32(result, value) - - return result -} - -// Dump produces printable debug representation of byte array as string -func Dump(arr []byte) string { - var buf bytes.Buffer - - buf.WriteString("(") - buf.WriteString(fmt.Sprintf("%v", len(arr))) - buf.WriteString(" bytes)[") - - for idx, b := range arr { - buf.WriteString(fmt.Sprintf("%v", b)) - if idx != len(arr)-1 { - buf.WriteString(", ") - } - } - - buf.WriteString("], Hex: [") - - for idx, b := range arr { - buf.WriteString(fmt.Sprintf("%X", b)) - if idx != len(arr)-1 { - buf.WriteString(" ") - } - } - - buf.WriteString("], Base64Url:") - buf.WriteString(base64url.Encode(arr)) - - return buf.String() -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/base64url/base64url.go b/vendor/github.com/dvsekhvalnov/jose2go/base64url/base64url.go deleted file mode 100644 index 7229a85..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/base64url/base64url.go +++ /dev/null @@ -1,31 +0,0 @@ -// package base64url provides base64url encoding/decoding support -package base64url - -import ( - "strings" - "encoding/base64" -) - -// Decode decodes base64url string to byte array -func Decode(data string) ([]byte,error) { - data = strings.Replace(data, "-", "+", -1) // 62nd char of encoding - data = strings.Replace(data, "_", "/", -1) // 63rd char of encoding - - switch(len(data) % 4) { // Pad with trailing '='s - case 0: // no padding - case 2: data+="==" // 2 pad chars - case 3: data+="=" // 1 pad char - } - - return base64.StdEncoding.DecodeString(data) -} - -// Encode encodes given byte array to base64url string -func Encode(data []byte) string { - result := base64.StdEncoding.EncodeToString(data) - result = strings.Replace(result, "+", "-", -1) // 62nd char of encoding - result = strings.Replace(result, "/", "_", -1) // 63rd char of encoding - result = strings.Replace(result, "=", "", -1) // Remove any trailing '='s - - return result -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/compact/compact.go b/vendor/github.com/dvsekhvalnov/jose2go/compact/compact.go deleted file mode 100644 index 1e9dd2f..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/compact/compact.go +++ /dev/null @@ -1,33 +0,0 @@ -// package compact provides function to work with json compact serialization format -package compact - -import ( - "github.com/dvsekhvalnov/jose2go/base64url" - "strings" -) - -// Parse splitting & decoding compact serialized json web token, returns slice of byte arrays, each representing part of token -func Parse(token string) (result [][]byte, e error) { - parts := strings.Split(token, ".") - - result = make([][]byte, len(parts)) - - for i, part := range parts { - if result[i], e = base64url.Decode(part); e != nil { - return nil, e - } - } - - return result, nil -} - -// Serialize converts given parts into compact serialization format -func Serialize(parts ...[]byte) string { - result := make([]string, len(parts)) - - for i, part := range parts { - result[i] = base64url.Encode(part) - } - - return strings.Join(result, ".") -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/deflate.go b/vendor/github.com/dvsekhvalnov/jose2go/deflate.go deleted file mode 100644 index c788f5b..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/deflate.go +++ /dev/null @@ -1,39 +0,0 @@ -package jose - -import ( - "bytes" - "compress/flate" - "io/ioutil" -) - -func init() { - RegisterJwc(new(Deflate)) -} - -// Deflate compression algorithm implementation -type Deflate struct {} - -func (alg *Deflate) Name() string { - return DEF -} - -func (alg *Deflate) Compress(plainText []byte) []byte { - var buf bytes.Buffer - deflate,_ := flate.NewWriter(&buf, 8) //level=DEFLATED - - deflate.Write(plainText) - deflate.Close() - - return buf.Bytes() -} - -func (alg *Deflate) Decompress(compressedText []byte) []byte { - - enflated,_ := ioutil.ReadAll( - flate.NewReader( - bytes.NewReader(compressedText))) - - return enflated -} - - diff --git a/vendor/github.com/dvsekhvalnov/jose2go/direct.go b/vendor/github.com/dvsekhvalnov/jose2go/direct.go deleted file mode 100644 index 51f0a2f..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/direct.go +++ /dev/null @@ -1,39 +0,0 @@ -package jose - -import ( - "errors" -) - -func init() { - RegisterJwa(new(Direct)) -} - -// Direct (pre-shared) key management algorithm implementation -type Direct struct{ -} - -func (alg *Direct) Name() string { - return DIR -} - -func (alg *Direct) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - - if cek,ok:=key.([]byte); ok { - return cek,[]byte{},nil - } - - return nil,nil,errors.New("Direct.WrapNewKey(): expected key to be '[]byte' array") -} - -func (alg *Direct) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - - if(len(encryptedCek)!=0) { - return nil, errors.New("Direct.Unwrap(): expected empty encrypted CEK") - } - - if cek,ok:=key.([]byte); ok { - return cek,nil - } - - return nil,errors.New("Direct.Unwrap(): expected key to be '[]byte' array") -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go b/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go deleted file mode 100644 index c3512fb..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go +++ /dev/null @@ -1,157 +0,0 @@ -package jose - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/sha256" - "errors" - "fmt" - "github.com/dvsekhvalnov/jose2go/arrays" - "github.com/dvsekhvalnov/jose2go/base64url" - "github.com/dvsekhvalnov/jose2go/kdf" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go/padding" - "math/big" -) - -func init() { - RegisterJwa(&Ecdh{directAgreement: true}) -} - -// Elliptic curve Diffie–Hellman key management (key agreement) algorithm implementation -type Ecdh struct { - directAgreement bool -} - -func (alg *Ecdh) Name() string { - return ECDH_ES -} - -func (alg *Ecdh) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - - if pubKey, ok := key.(*ecdsa.PublicKey); ok { - - if _, ok := header[alg.idHeader()].(string); !ok { - return nil, nil, errors.New(fmt.Sprintf("Ecdh.WrapNewKey(): expected '%v' param in JWT header, but was not found.", alg.idHeader())) - } - - var d []byte - var x, y *big.Int - - if d, x, y, err = elliptic.GenerateKey(pubKey.Curve, rand.Reader); err != nil { - return nil, nil, err - } - - ephemeral := ecc.NewPrivate(x.Bytes(), y.Bytes(), d) - - xBytes := padding.Align(x.Bytes(), pubKey.Curve.Params().BitSize) - yBytes := padding.Align(y.Bytes(), pubKey.Curve.Params().BitSize) - - epk := map[string]string{ - "kty": "EC", - "x": base64url.Encode(xBytes), - "y": base64url.Encode(yBytes), - "crv": name(pubKey.Curve), - } - - header["epk"] = epk - - return alg.deriveKey(pubKey, ephemeral, cekSizeBits, header), nil, nil - } - - return nil, nil, errors.New("Ecdh.WrapNewKey(): expected key to be '*ecdsa.PublicKey'") -} - -func (alg *Ecdh) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - - if privKey, ok := key.(*ecdsa.PrivateKey); ok { - - var epk map[string]interface{} - - if epk, ok = header["epk"].(map[string]interface{}); !ok { - return nil, errors.New("Ecdh.Unwrap(): expected 'epk' param in JWT header, but was not found.") - } - - if _, ok := header[alg.idHeader()].(string); !ok { - return nil, errors.New(fmt.Sprintf("Ecdh.Unwrap(): expected '%v' param in JWT header, but was not found.", alg.idHeader())) - } - - var x, y, crv string - var xBytes, yBytes []byte - - if x, ok = epk["x"].(string); !ok { - return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'x' was not found.") - } - - if y, ok = epk["y"].(string); !ok { - return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'y' was not found.") - } - - if crv, ok = epk["crv"].(string); !ok { - return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'crv' was not found.") - } - - if crv != "P-256" && crv != "P-384" && crv != "P-521" { - return nil, errors.New(fmt.Sprintf("Ecdh.Unwrap(): unknown or unsupported curve %v", crv)) - } - - if xBytes, err = base64url.Decode(x); err != nil { - return nil, err - } - if yBytes, err = base64url.Decode(y); err != nil { - return nil, err - } - - pubKey := ecc.NewPublic(xBytes, yBytes) - - if !privKey.Curve.IsOnCurve(pubKey.X, pubKey.Y) { - return nil, errors.New(fmt.Sprintf("Ephemeral public key received in header is invalid for reciever's private key.")) - } - - return alg.deriveKey(pubKey, privKey, cekSizeBits, header), nil - } - - return nil, errors.New("Ecdh.Unwrap(): expected key to be '*ecdsa.PrivateKey'") -} - -func (alg *Ecdh) deriveKey(pubKey *ecdsa.PublicKey, privKey *ecdsa.PrivateKey, keySizeBits int, header map[string]interface{}) []byte { - - var enc, apv, apu []byte - var err error - - enc = []byte(header[alg.idHeader()].(string)) - - if a, ok := header["apv"].(string); !ok { - if apv, err = base64url.Decode(a); err != nil { - apv = nil - } - } - - if a, ok := header["apu"].(string); !ok { - if apu, err = base64url.Decode(a); err != nil { - apu = nil - } - } - - z, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes()) - zBytes := padding.Align(z.Bytes(), privKey.Curve.Params().BitSize) - - return kdf.DeriveConcatKDF(keySizeBits, zBytes, prependDatalen(enc), prependDatalen(apu), prependDatalen(apv), arrays.UInt32ToBytes(uint32(keySizeBits)), nil, sha256.New()) -} - -func (alg *Ecdh) idHeader() string { - if alg.directAgreement { - return "enc" - } - - return "alg" -} - -func name(curve elliptic.Curve) string { - return fmt.Sprintf("P-%v", curve.Params().BitSize) -} - -func prependDatalen(bytes []byte) []byte { - return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))), bytes) -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/ecdh_aeskw.go b/vendor/github.com/dvsekhvalnov/jose2go/ecdh_aeskw.go deleted file mode 100644 index bff3480..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/ecdh_aeskw.go +++ /dev/null @@ -1,42 +0,0 @@ -package jose - -func init() { - RegisterJwa(&EcdhAesKW{ keySizeBits: 128, aesKW: &AesKW{ keySizeBits: 128}, ecdh: &Ecdh{directAgreement:false}}) - RegisterJwa(&EcdhAesKW{ keySizeBits: 192, aesKW: &AesKW{ keySizeBits: 192}, ecdh: &Ecdh{directAgreement:false}}) - RegisterJwa(&EcdhAesKW{ keySizeBits: 256, aesKW: &AesKW{ keySizeBits: 256}, ecdh: &Ecdh{directAgreement:false}}) -} - -// Elliptic curve Diffie–Hellman with AES Key Wrap key management algorithm implementation -type EcdhAesKW struct{ - keySizeBits int - aesKW JwaAlgorithm - ecdh JwaAlgorithm -} - -func (alg *EcdhAesKW) Name() string { - switch alg.keySizeBits { - case 128: return ECDH_ES_A128KW - case 192: return ECDH_ES_A192KW - default: return ECDH_ES_A256KW - } -} - -func (alg *EcdhAesKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - var kek []byte - - if kek,_,err=alg.ecdh.WrapNewKey(alg.keySizeBits, key, header);err!=nil { - return nil,nil,err - } - - return alg.aesKW.WrapNewKey(cekSizeBits,kek,header) -} - -func (alg *EcdhAesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - var kek []byte - - if kek,err=alg.ecdh.Unwrap(nil, key, alg.keySizeBits, header);err!=nil { - return nil,err - } - - return alg.aesKW.Unwrap(encryptedCek,kek,cekSizeBits,header) -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/ecdsa_using_sha.go b/vendor/github.com/dvsekhvalnov/jose2go/ecdsa_using_sha.go deleted file mode 100644 index 23ac6ee..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/ecdsa_using_sha.go +++ /dev/null @@ -1,76 +0,0 @@ -package jose - -import ( - "crypto/rand" - "math/big" - "crypto/ecdsa" - "errors" - "github.com/dvsekhvalnov/jose2go/arrays" - "github.com/dvsekhvalnov/jose2go/padding" - "fmt" -) - -func init() { - RegisterJws(&EcdsaUsingSha{keySizeBits: 256, hashSizeBits: 256}) - RegisterJws(&EcdsaUsingSha{keySizeBits: 384, hashSizeBits: 384}) - RegisterJws(&EcdsaUsingSha{keySizeBits: 521, hashSizeBits: 512}) -} - -// ECDSA signing algorithm implementation -type EcdsaUsingSha struct{ - keySizeBits int - hashSizeBits int -} - -func (alg *EcdsaUsingSha) Name() string { - switch alg.keySizeBits { - case 256: return ES256 - case 384: return ES384 - default: return ES512 - } -} - -func (alg *EcdsaUsingSha) Verify(securedInput, signature []byte, key interface{}) error { - - if pubKey,ok:=key.(*ecdsa.PublicKey);ok { - - if sizeBits:=pubKey.Curve.Params().BitSize;sizeBits!=alg.keySizeBits { - return errors.New(fmt.Sprintf("EcdsaUsingSha.Verify(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits,sizeBits)) - } - - r:=new(big.Int).SetBytes(signature[:len(signature)/2]) - s:=new(big.Int).SetBytes(signature[len(signature)/2:]) - - if ok:=ecdsa.Verify(pubKey, sha(alg.hashSizeBits, securedInput), r,s); ok { - return nil - } - - return errors.New("EcdsaUsingSha.Verify(): Signature is not valid.") - } - - return errors.New("EcdsaUsingSha.Verify(): expects key to be '*ecdsa.PublicKey'") -} - -func (alg *EcdsaUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) { - - if privKey,ok := key.(*ecdsa.PrivateKey);ok { - - if sizeBits:=privKey.Curve.Params().BitSize;sizeBits!=alg.keySizeBits { - return nil,errors.New(fmt.Sprintf("EcdsaUsingSha.Sign(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits,sizeBits)) - } - - var r,s *big.Int - - if r,s,err = ecdsa.Sign(rand.Reader, privKey, sha(alg.hashSizeBits, securedInput));err==nil { - - rBytes:=padding.Align(r.Bytes(), alg.keySizeBits) - sBytes:=padding.Align(s.Bytes(), alg.keySizeBits) - - return arrays.Concat(rBytes,sBytes),nil - } - - return nil, err - } - - return nil,errors.New("EcdsaUsingSha.Sign(): expects key to be '*ecdsa.PrivateKey'") -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/hmac.go b/vendor/github.com/dvsekhvalnov/jose2go/hmac.go deleted file mode 100644 index d3726b7..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/hmac.go +++ /dev/null @@ -1,13 +0,0 @@ -package jose - -import ( - "crypto/hmac" - "hash" -) - -func calculateHmac(keySizeBits int, securedInput []byte, key []byte) []byte { - hasher := hmac.New(func() hash.Hash { return hashAlg(keySizeBits)}, key) - hasher.Write(securedInput) - - return hasher.Sum(nil) -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/hmac_using_sha.go b/vendor/github.com/dvsekhvalnov/jose2go/hmac_using_sha.go deleted file mode 100644 index e0b664f..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/hmac_using_sha.go +++ /dev/null @@ -1,46 +0,0 @@ -package jose - -import ( - "crypto/hmac" - "errors" -) - -func init() { - RegisterJws(&HmacUsingSha{keySizeBits: 256}) - RegisterJws(&HmacUsingSha{keySizeBits: 384}) - RegisterJws(&HmacUsingSha{keySizeBits: 512}) -} - -// HMAC with SHA signing algorithm implementation -type HmacUsingSha struct{ - keySizeBits int -} - -func (alg *HmacUsingSha) Name() string { - switch alg.keySizeBits { - case 256: return HS256 - case 384: return HS384 - default: return HS512 - } -} - -func (alg *HmacUsingSha) Verify(securedInput, signature []byte, key interface{}) error { - - actualSig,_ := alg.Sign(securedInput, key) - - if !hmac.Equal(signature, actualSig) { - return errors.New("HmacUsingSha.Verify(): Signature is invalid") - } - - return nil -} - -func (alg *HmacUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) { - //TODO: assert min key size - - if pubKey,ok:=key.([]byte); ok { - return calculateHmac(alg.keySizeBits, securedInput, pubKey),nil - } - - return nil,errors.New("HmacUsingSha.Sign(): expects key to be '[]byte' array") -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/jose.go b/vendor/github.com/dvsekhvalnov/jose2go/jose.go deleted file mode 100644 index 1f1c19e..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/jose.go +++ /dev/null @@ -1,424 +0,0 @@ -//Package jose provides high level functions for producing (signing, encrypting and -// compressing) or consuming (decoding) Json Web Tokens using Java Object Signing and Encryption spec -package jose - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/dvsekhvalnov/jose2go/compact" -) - -const ( - NONE = "none" //plaintext (unprotected) without signature / encryption - - HS256 = "HS256" //HMAC using SHA-256 hash - HS384 = "HS384" //HMAC using SHA-384 hash - HS512 = "HS512" //HMAC using SHA-512 hash - RS256 = "RS256" //RSASSA-PKCS-v1_5 using SHA-256 hash - RS384 = "RS384" //RSASSA-PKCS-v1_5 using SHA-384 hash - RS512 = "RS512" //RSASSA-PKCS-v1_5 using SHA-512 hash - PS256 = "PS256" //RSASSA-PSS using SHA-256 hash - PS384 = "PS384" //RSASSA-PSS using SHA-384 hash - PS512 = "PS512" //RSASSA-PSS using SHA-512 hash - ES256 = "ES256" //ECDSA using P-256 curve and SHA-256 hash - ES384 = "ES384" //ECDSA using P-384 curve and SHA-384 hash - ES512 = "ES512" //ECDSA using P-521 curve and SHA-512 hash - - A128CBC_HS256 = "A128CBC-HS256" //AES in CBC mode with PKCS #5 (NIST.800-38A) padding with HMAC using 256 bit key - A192CBC_HS384 = "A192CBC-HS384" //AES in CBC mode with PKCS #5 (NIST.800-38A) padding with HMAC using 384 bit key - A256CBC_HS512 = "A256CBC-HS512" //AES in CBC mode with PKCS #5 (NIST.800-38A) padding with HMAC using 512 bit key - A128GCM = "A128GCM" //AES in GCM mode with 128 bit key - A192GCM = "A192GCM" //AES in GCM mode with 192 bit key - A256GCM = "A256GCM" //AES in GCM mode with 256 bit key - - DIR = "dir" //Direct use of pre-shared symmetric key - RSA1_5 = "RSA1_5" //RSAES with PKCS #1 v1.5 padding, RFC 3447 - RSA_OAEP = "RSA-OAEP" //RSAES using Optimal Assymetric Encryption Padding, RFC 3447 - RSA_OAEP_256 = "RSA-OAEP-256" //RSAES using Optimal Assymetric Encryption Padding with SHA-256, RFC 3447 - A128KW = "A128KW" //AES Key Wrap Algorithm using 128 bit keys, RFC 3394 - A192KW = "A192KW" //AES Key Wrap Algorithm using 192 bit keys, RFC 3394 - A256KW = "A256KW" //AES Key Wrap Algorithm using 256 bit keys, RFC 3394 - A128GCMKW = "A128GCMKW" //AES GCM Key Wrap Algorithm using 128 bit keys - A192GCMKW = "A192GCMKW" //AES GCM Key Wrap Algorithm using 192 bit keys - A256GCMKW = "A256GCMKW" //AES GCM Key Wrap Algorithm using 256 bit keys - PBES2_HS256_A128KW = "PBES2-HS256+A128KW" //Password Based Encryption using PBES2 schemes with HMAC-SHA and AES Key Wrap using 128 bit key - PBES2_HS384_A192KW = "PBES2-HS384+A192KW" //Password Based Encryption using PBES2 schemes with HMAC-SHA and AES Key Wrap using 192 bit key - PBES2_HS512_A256KW = "PBES2-HS512+A256KW" //Password Based Encryption using PBES2 schemes with HMAC-SHA and AES Key Wrap using 256 bit key - ECDH_ES = "ECDH-ES" //Elliptic Curve Diffie Hellman key agreement - ECDH_ES_A128KW = "ECDH-ES+A128KW" //Elliptic Curve Diffie Hellman key agreement with AES Key Wrap using 128 bit key - ECDH_ES_A192KW = "ECDH-ES+A192KW" //Elliptic Curve Diffie Hellman key agreement with AES Key Wrap using 192 bit key - ECDH_ES_A256KW = "ECDH-ES+A256KW" //Elliptic Curve Diffie Hellman key agreement with AES Key Wrap using 256 bit key - - DEF = "DEF" //DEFLATE compression, RFC 1951 -) - -var jwsHashers = map[string]JwsAlgorithm{} -var jweEncryptors = map[string]JweEncryption{} -var jwaAlgorithms = map[string]JwaAlgorithm{} -var jwcCompressors = map[string]JwcAlgorithm{} - -// RegisterJwe register new encryption algorithm -func RegisterJwe(alg JweEncryption) { - jweEncryptors[alg.Name()] = alg -} - -// RegisterJwa register new key management algorithm -func RegisterJwa(alg JwaAlgorithm) { - jwaAlgorithms[alg.Name()] = alg -} - -// RegisterJws register new signing algorithm -func RegisterJws(alg JwsAlgorithm) { - jwsHashers[alg.Name()] = alg -} - -// RegisterJwc register new compression algorithm -func RegisterJwc(alg JwcAlgorithm) { - jwcCompressors[alg.Name()] = alg -} - -// JweEncryption is a contract for implementing encryption algorithm -type JweEncryption interface { - Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error) - Decrypt(aad, cek, iv, cipherText, authTag []byte) (plainText []byte, err error) - KeySizeBits() int - Name() string -} - -// JwaAlgorithm is a contract for implementing key management algorithm -type JwaAlgorithm interface { - WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) - Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) - Name() string -} - -// JwsAlgorithm is a contract for implementing signing algorithm -type JwsAlgorithm interface { - Verify(securedInput, signature []byte, key interface{}) error - Sign(securedInput []byte, key interface{}) (signature []byte, err error) - Name() string -} - -// JwcAlgorithm is a contract for implementing compression algorithm -type JwcAlgorithm interface { - Compress(plainText []byte) []byte - Decompress(compressedText []byte) []byte - Name() string -} - -func Zip(alg string) func(cfg *JoseConfig) { - return func(cfg *JoseConfig) { - cfg.CompressionAlg = alg - } -} - -func Header(name string, value interface{}) func(cfg *JoseConfig) { - return func(cfg *JoseConfig) { - cfg.Headers[name] = value - } -} - -func Headers(headers map[string]interface{}) func(cfg *JoseConfig) { - return func(cfg *JoseConfig) { - for k, v := range headers { - cfg.Headers[k] = v - } - } -} - -type JoseConfig struct { - CompressionAlg string - Headers map[string]interface{} -} - -// Sign produces signed JWT token given arbitrary string payload, signature algorithm to use (see constants for list of supported algs), signing key and extra options (see option functions) -// Signing key is of different type for different signing alg, see specific -// signing alg implementation documentation. -// -// It returns 3 parts signed JWT token as string and not nil error if something went wrong. -func Sign(payload string, signingAlg string, key interface{}, options ...func(*JoseConfig)) (token string, err error) { - return SignBytes([]byte(payload), signingAlg, key, options...) -} - -// Sign produces signed JWT token given arbitrary binary payload, signature algorithm to use (see constants for list of supported algs), signing key and extra options (see option functions) -// Signing key is of different type for different signing alg, see specific -// signing alg implementation documentation. -// -// It returns 3 parts signed JWT token as string and not nil error if something went wrong. -func SignBytes(payload []byte, signingAlg string, key interface{}, options ...func(*JoseConfig)) (token string, err error) { - if signer, ok := jwsHashers[signingAlg]; ok { - - cfg := &JoseConfig{CompressionAlg: "", Headers: make(map[string]interface{})} - - //apply extra options - for _, option := range options { - option(cfg) - } - - //make sure defaults and requires are managed by us - cfg.Headers["alg"] = signingAlg - - paloadBytes := payload - var header []byte - var signature []byte - - if header, err = json.Marshal(cfg.Headers); err == nil { - securedInput := []byte(compact.Serialize(header, paloadBytes)) - - if signature, err = signer.Sign(securedInput, key); err == nil { - return compact.Serialize(header, paloadBytes, signature), nil - } - } - - return "", err - } - - return "", errors.New(fmt.Sprintf("jwt.Sign(): unknown algorithm: '%v'", signingAlg)) -} - -// Encrypt produces encrypted JWT token given arbitrary string payload, key management and encryption algorithms to use (see constants for list of supported algs) and management key. -// Management key is of different type for different key management alg, see specific -// key management alg implementation documentation. -// -// It returns 5 parts encrypted JWT token as string and not nil error if something went wrong. -func Encrypt(payload string, alg string, enc string, key interface{}, options ...func(*JoseConfig)) (token string, err error) { - return EncryptBytes([]byte(payload), alg, enc, key, options...) -} - -// Encrypt produces encrypted JWT token given arbitrary binary payload, key management and encryption algorithms to use (see constants for list of supported algs) and management key. -// Management key is of different type for different key management alg, see specific -// key management alg implementation documentation. -// -// It returns 5 parts encrypted JWT token as string and not nil error if something went wrong. -func EncryptBytes(payload []byte, alg string, enc string, key interface{}, options ...func(*JoseConfig)) (token string, err error) { - - cfg := &JoseConfig{CompressionAlg: "", Headers: make(map[string]interface{})} - - //apply extra options - for _, option := range options { - option(cfg) - } - - //make sure required headers are managed by us - cfg.Headers["alg"] = alg - cfg.Headers["enc"] = enc - - byteContent := payload - - if cfg.CompressionAlg != "" { - if zipAlg, ok := jwcCompressors[cfg.CompressionAlg]; ok { - byteContent = zipAlg.Compress([]byte(payload)) - cfg.Headers["zip"] = cfg.CompressionAlg - } else { - return "", errors.New(fmt.Sprintf("jwt.Compress(): Unknown compression method '%v'", cfg.CompressionAlg)) - } - - } else { - delete(cfg.Headers, "zip") //we not allow to manage 'zip' header manually for encryption - } - - return encrypt(byteContent, cfg.Headers, key) -} - -// This method is DEPRICATED and subject to be removed in next version. -// Use Encrypt(..) with Zip option instead. -// -// Compress produces encrypted & comressed JWT token given arbitrary payload, key management , encryption and compression algorithms to use (see constants for list of supported algs) and management key. -// Management key is of different type for different key management alg, see specific -// key management alg implementation documentation. -// -// It returns 5 parts encrypted & compressed JWT token as string and not nil error if something went wrong. -func Compress(payload string, alg string, enc string, zip string, key interface{}) (token string, err error) { - - if zipAlg, ok := jwcCompressors[zip]; ok { - compressed := zipAlg.Compress([]byte(payload)) - - jwtHeader := map[string]interface{}{ - "enc": enc, - "alg": alg, - "zip": zip, - } - - return encrypt(compressed, jwtHeader, key) - } - - return "", errors.New(fmt.Sprintf("jwt.Compress(): Unknown compression method '%v'", zip)) -} - -// Decode verifies, decrypts and decompresses given JWT token using management key. -// Management key is of different type for different key management or signing algorithms, see specific alg implementation documentation. -// -// Returns decoded payload as a string, headers and not nil error if something went wrong. -func Decode(token string, key interface{}) (string, map[string]interface{}, error) { - - payload, headers, err := DecodeBytes(token, key) - - if err != nil { - return "", nil, err - } - - return string(payload), headers, nil -} - -// Decode verifies, decrypts and decompresses given JWT token using management key. -// Management key is of different type for different key management or signing algorithms, see specific alg implementation documentation. -// -// Returns decoded payload as a raw bytes, headers and not nil error if something went wrong. -func DecodeBytes(token string, key interface{}) ([]byte, map[string]interface{}, error) { - parts, err := compact.Parse(token) - - if err != nil { - return nil, nil, err - } - - if len(parts) == 3 { - return verify(parts, key) - } - - if len(parts) == 5 { - return decrypt(parts, key) - } - - return nil, nil, errors.New(fmt.Sprintf("jwt.DecodeBytes() expects token of 3 or 5 parts, but was given: %v parts", len(parts))) -} - -func encrypt(payload []byte, jwtHeader map[string]interface{}, key interface{}) (token string, err error) { - var ok bool - var keyMgmtAlg JwaAlgorithm - var encAlg JweEncryption - - alg := jwtHeader["alg"].(string) - enc := jwtHeader["enc"].(string) - - if keyMgmtAlg, ok = jwaAlgorithms[alg]; !ok { - return "", errors.New(fmt.Sprintf("jwt.encrypt(): Unknown key management algorithm '%v'", alg)) - } - - if encAlg, ok = jweEncryptors[enc]; !ok { - return "", errors.New(fmt.Sprintf("jwt.encrypt(): Unknown encryption algorithm '%v'", enc)) - } - - var cek, encryptedCek, header, iv, cipherText, authTag []byte - - if cek, encryptedCek, err = keyMgmtAlg.WrapNewKey(encAlg.KeySizeBits(), key, jwtHeader); err != nil { - return "", err - } - - if header, err = json.Marshal(jwtHeader); err != nil { - return "", err - } - - if iv, cipherText, authTag, err = encAlg.Encrypt([]byte(compact.Serialize(header)), payload, cek); err != nil { - return "", err - } - - return compact.Serialize(header, encryptedCek, iv, cipherText, authTag), nil -} - -func verify(parts [][]byte, key interface{}) (plainText []byte, headers map[string]interface{}, err error) { - - header, payload, signature := parts[0], parts[1], parts[2] - - secured := []byte(compact.Serialize(header, payload)) - - var jwtHeader map[string]interface{} - - if err = json.Unmarshal(header, &jwtHeader); err != nil { - return nil, nil, err - } - - if alg, ok := jwtHeader["alg"].(string); ok { - if verifier, ok := jwsHashers[alg]; ok { - if key, err = retrieveActualKey(jwtHeader, string(payload), key); err != nil { - return nil, nil, err - } - - if err = verifier.Verify(secured, signature, key); err == nil { - return payload, jwtHeader, nil - } - - return nil, nil, err - } - - return nil, nil, errors.New(fmt.Sprintf("jwt.Decode(): Unknown algorithm: '%v'", alg)) - } - - return nil, nil, errors.New(fmt.Sprint("jwt.Decode(): required 'alg' header is missing or of invalid type")) -} - -func decrypt(parts [][]byte, key interface{}) (plainText []byte, headers map[string]interface{}, err error) { - - header, encryptedCek, iv, cipherText, authTag := parts[0], parts[1], parts[2], parts[3], parts[4] - - var jwtHeader map[string]interface{} - - if e := json.Unmarshal(header, &jwtHeader); e != nil { - return nil, nil, e - } - - var keyMgmtAlg JwaAlgorithm - var encAlg JweEncryption - var zipAlg JwcAlgorithm - var cek, plainBytes []byte - var ok bool - var alg, enc string - - if alg, ok = jwtHeader["alg"].(string); !ok { - return nil, nil, errors.New(fmt.Sprint("jwt.Decode(): required 'alg' header is missing or of invalid type")) - } - - if enc, ok = jwtHeader["enc"].(string); !ok { - return nil, nil, errors.New(fmt.Sprint("jwt.Decode(): required 'enc' header is missing or of invalid type")) - } - - aad := []byte(compact.Serialize(header)) - - if keyMgmtAlg, ok = jwaAlgorithms[alg]; ok { - if encAlg, ok = jweEncryptors[enc]; ok { - - if key, err = retrieveActualKey(jwtHeader, string(cipherText), key); err != nil { - return nil, nil, err - } - - if cek, err = keyMgmtAlg.Unwrap(encryptedCek, key, encAlg.KeySizeBits(), jwtHeader); err == nil { - if plainBytes, err = encAlg.Decrypt(aad, cek, iv, cipherText, authTag); err == nil { - - if zip, compressed := jwtHeader["zip"].(string); compressed { - - if zipAlg, ok = jwcCompressors[zip]; !ok { - return nil, nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown compression algorithm '%v'", zip)) - } - - plainBytes = zipAlg.Decompress(plainBytes) - } - - return plainBytes, jwtHeader, nil - } - - return nil, nil, err - } - - return nil, nil, err - } - - return nil, nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown encryption algorithm '%v'", enc)) - } - - return nil, nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown key management algorithm '%v'", alg)) -} - -func retrieveActualKey(headers map[string]interface{}, payload string, key interface{}) (interface{}, error) { - if keyCallback, ok := key.(func(headers map[string]interface{}, payload string) interface{}); ok { - result := keyCallback(headers, payload) - - if err, ok := result.(error); ok { - return nil, err - } - - return result, nil - } - - return key, nil -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a.go b/vendor/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a.go deleted file mode 100644 index dd76790..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a.go +++ /dev/null @@ -1,43 +0,0 @@ -package kdf - -import ( - "hash" - "math" - "github.com/dvsekhvalnov/jose2go/arrays" -) - -const ( - MaxInt = int(^uint(0)>>1); -) - -// DeriveConcatKDF implements NIST SP 800-56A Concatenation Key Derivation Function. Derives -// key material of keydatalen bits size given Z (sharedSecret), OtherInfo (AlgorithmID | -// PartyUInfo | PartyVInfo | SuppPubInfo | SuppPrivInfo) and hash function -func DeriveConcatKDF(keydatalen int, sharedSecret, algId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo []byte, h hash.Hash) []byte { - - otherInfo := arrays.Concat(algId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo) - - keyLenBytes := keydatalen >> 3 - - reps := int(math.Ceil(float64(keyLenBytes) / float64(h.Size()))) - - if reps > MaxInt { - panic("kdf.DeriveConcatKDF: too much iterations (more than 2^32-1).") - } - - dk:=make([]byte, 0, keyLenBytes) - - for counter := 1;counter <= reps;counter++ { - h.Reset() - - counterBytes:=arrays.UInt32ToBytes(uint32(counter)) - - h.Write(counterBytes) - h.Write(sharedSecret) - h.Write(otherInfo) - - dk = h.Sum(dk) - } - - return dk[:keyLenBytes] -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2.go b/vendor/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2.go deleted file mode 100644 index aec58e7..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2.go +++ /dev/null @@ -1,63 +0,0 @@ -// package kdf contains implementations of various key derivation functions -package kdf - -import ( - "crypto/hmac" - "fmt" - "hash" - "math" - - "github.com/dvsekhvalnov/jose2go/arrays" -) - -// DerivePBKDF2 implements Password Based Key Derivation Function 2, RFC 2898. Derives key of keyBitLength size, given password, salt, iteration count and hash function -func DerivePBKDF2(password, salt []byte, iterationCount, keyBitLength int, h func() hash.Hash) []byte { - - prf := hmac.New(h, password) - hLen := prf.Size() - dkLen := keyBitLength >> 3 //size of derived key in bytes - - l := int(math.Ceil(float64(dkLen) / float64(hLen))) // l = CEIL (dkLen / hLen) - r := dkLen - (l-1)*hLen - - // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop. - if dkLen > MaxInt { - panic(fmt.Sprintf("kdf.DerivePBKDF2: expects derived key size to be not more that (2^32-1) bits, but was requested %v bits.", keyBitLength)) - } - - dk := make([]byte, 0, dkLen) - - for i := 0; i < l; i++ { - - t := f(salt, iterationCount, i+1, prf) // T_l = F (P, S, c, l) - - if i == (l - 1) { - t = t[:r] - } // truncate last block to r bits - - dk = append(dk, t...) // DK = T_1 || T_2 || ... || T_l<0..r-1> - } - - return dk -} - -func f(salt []byte, iterationCount, blockIndex int, prf hash.Hash) []byte { - - prf.Reset() - prf.Write(salt) - prf.Write(arrays.UInt32ToBytes(uint32(blockIndex))) - - u := prf.Sum(nil) // U_1 = PRF (P, S || INT (i)) - - result := u - - for i := 2; i <= iterationCount; i++ { - prf.Reset() - prf.Write(u) - - u = prf.Sum(nil) // U_c = PRF (P, U_{c-1}) . - result = arrays.Xor(result, u) // U_1 \xor U_2 \xor ... \xor U_c - } - - return result -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem deleted file mode 100644 index 384cb46..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICJjCCAc6gAwIBAgIJAOCtH/xv+cfpMAkGByqGSM49BAEwRTELMAkGA1UEBhMC -QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDAeFw0xNDA4MTIxMTU5MTVaFw0xODA1MDgxMTU5MTVaMEUxCzAJ -BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l -dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASqGi1p -Eudyh+Nw//zeAGIYSQG/H/q/x6Xz2TIg3GN4hbD5BS1f4/vEitpui+TpmtJyggzo -x5D55N9kjq0X/hyyo4GnMIGkMB0GA1UdDgQWBBQvGSEecx9JdDECRIorVpeWy7oA -ujB1BgNVHSMEbjBsgBQvGSEecx9JdDECRIorVpeWy7oAuqFJpEcwRTELMAkGA1UE -BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp -ZGdpdHMgUHR5IEx0ZIIJAOCtH/xv+cfpMAwGA1UdEwQFMAMBAf8wCQYHKoZIzj0E -AQNHADBEAiAUQheZrGjbsy6PfpWGZEhTFzqvBVXtbqtz+6aTkOCrCQIgLfvw9C+0 -SDn/abV4NtgYOM0OLkoNRTCIzzguHxhhaJ4= ------END CERTIFICATE----- diff --git a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.key b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.key deleted file mode 100644 index 399b84b..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIA/32XgQpS/tKRhw8jSdr8ivBmMyD/vbG5KT3s3XPArcoAoGCCqGSM49 -AwEHoUQDQgAEqhotaRLncofjcP/83gBiGEkBvx/6v8el89kyINxjeIWw+QUtX+P7 -xIrabovk6ZrScoIM6MeQ+eTfZI6tF/4csg== ------END EC PRIVATE KEY----- diff --git a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.pem b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.pem deleted file mode 100644 index 5247be2..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgD/fZeBClL+0pGHDy -NJ2vyK8GYzIP+9sbkpPezdc8CtyhRANCAASqGi1pEudyh+Nw//zeAGIYSQG/H/q/ -x6Xz2TIg3GN4hbD5BS1f4/vEitpui+TpmtJyggzox5D55N9kjq0X/hyy ------END PRIVATE KEY----- diff --git a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_public.key b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_public.key deleted file mode 100644 index bc2345d..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_public.key +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqhotaRLncofjcP/83gBiGEkBvx/6 -v8el89kyINxjeIWw+QUtX+P7xIrabovk6ZrScoIM6MeQ+eTfZI6tF/4csg== ------END PUBLIC KEY----- diff --git a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go b/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go deleted file mode 100644 index b1debe4..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go +++ /dev/null @@ -1,88 +0,0 @@ -//package ecc provides helpers for creating elliptic curve leys -package ecc - -import ( - "math/big" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/x509" - "encoding/pem" - "errors" -) - -// ReadPublic loads ecdsa.PublicKey from given PKCS1 X509 or PKIX blobs -func ReadPublic(raw []byte) (key *ecdsa.PublicKey,err error) { - var encoded *pem.Block - - if encoded, _ = pem.Decode(raw); encoded == nil { - return nil, errors.New("Ecc.ReadPublic(): Key must be PEM encoded PKCS1 X509 certificate or PKIX EC public key") - } - - var parsedKey interface{} - var cert *x509.Certificate - - if parsedKey, err = x509.ParsePKIXPublicKey(encoded.Bytes); err != nil { - if cert,err = x509.ParseCertificate(encoded.Bytes);err!=nil { - return nil, err - } - - parsedKey=cert.PublicKey - } - - var ok bool - - if key, ok = parsedKey.(*ecdsa.PublicKey); !ok { - return nil, errors.New("Ecc.ReadPublic(): Key is not a valid *ecdsa.PublicKey") - } - - return key, nil -} - -// ReadPrivate loads ecdsa.PrivateKey from given PKCS1 or PKCS8 blobs -func ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error) { - var encoded *pem.Block - - if encoded, _ = pem.Decode(raw); encoded == nil { - return nil, errors.New("Ecc.ReadPrivate(): Key must be PEM encoded PKCS1 or PKCS8 EC private key") - } - - var parsedKey interface{} - - if parsedKey,err=x509.ParseECPrivateKey(encoded.Bytes);err!=nil { - if parsedKey, err = x509.ParsePKCS8PrivateKey(encoded.Bytes);err!=nil { - return nil,err - } - } - - var ok bool - - if key,ok=parsedKey.(*ecdsa.PrivateKey);!ok { - return nil, errors.New("Ecc.ReadPrivate(): Key is not valid *ecdsa.PrivateKey") - } - - return key,nil -} - -// NewPublic constructs ecdsa.PublicKey from given (X,Y) -func NewPublic(x,y []byte) (*ecdsa.PublicKey) { - return &ecdsa.PublicKey{ Curve: curve(len(x)), - X:new(big.Int).SetBytes(x), - Y:new(big.Int).SetBytes(y) } -} - -// NewPrivate constructs ecdsa.PrivateKey from given (X,Y) and D -func NewPrivate(x,y,d []byte) (*ecdsa.PrivateKey) { - return &ecdsa.PrivateKey {D:new(big.Int).SetBytes(d), - PublicKey: ecdsa.PublicKey{ Curve:curve(len(x)), - X:new(big.Int).SetBytes(x), - Y:new(big.Int).SetBytes(y)}} -} - -func curve(size int) (elliptic.Curve) { - switch size { - case 32: return elliptic.P256() - case 48: return elliptic.P384() - case 65,66: return elliptic.P521() //adjust for P-521 curve, which can be 65 or 66 bytes - default: return nil //unsupported curve - } -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/padding/align.go b/vendor/github.com/dvsekhvalnov/jose2go/padding/align.go deleted file mode 100644 index 0ef601f..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/padding/align.go +++ /dev/null @@ -1,23 +0,0 @@ -// package padding provides various padding algorithms -package padding - -import ( - "bytes" -) - -// Align left pads given byte array with zeros till it have at least bitSize length. -func Align(data []byte, bitSize int) []byte { - - actual:=len(data) - required:=bitSize >> 3 - - if (bitSize % 8) > 0 { - required++ //extra byte if needed - } - - if (actual >= required) { - return data - } - - return append(bytes.Repeat([]byte{0}, required-actual), data...) -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/padding/pkcs7.go b/vendor/github.com/dvsekhvalnov/jose2go/padding/pkcs7.go deleted file mode 100644 index 2f64e7e..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/padding/pkcs7.go +++ /dev/null @@ -1,38 +0,0 @@ -package padding - -import ( - "bytes" -) - -// AddPkcs7 pads given byte array using pkcs7 padding schema till it has blockSize length in bytes -func AddPkcs7(data []byte, blockSize int) []byte { - - var paddingCount int - - if paddingCount = blockSize - (len(data) % blockSize);paddingCount == 0 { - paddingCount=blockSize - } - - return append(data, bytes.Repeat([]byte{byte(paddingCount)}, paddingCount)...) -} - -// RemovePkcs7 removes pkcs7 padding from previously padded byte array -func RemovePkcs7(padded []byte, blockSize int) []byte { - - dataLen:=len(padded) - paddingCount:=int(padded[dataLen-1]) - - if(paddingCount > blockSize || paddingCount <= 0) { - return padded //data is not padded (or not padded correctly), return as is - } - - padding := padded[dataLen-paddingCount : dataLen-1] - - for _, b := range padding { - if int(b) != paddingCount { - return padded //data is not padded (or not padded correcly), return as is - } - } - - return padded[:len(padded)-paddingCount] //return data - padding -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go b/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go deleted file mode 100644 index baeaf9c..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go +++ /dev/null @@ -1,103 +0,0 @@ -package jose - -import ( - "crypto/sha256" - "crypto/sha512" - "errors" - "hash" - - "github.com/dvsekhvalnov/jose2go/arrays" - "github.com/dvsekhvalnov/jose2go/base64url" - "github.com/dvsekhvalnov/jose2go/kdf" -) - -func init() { - RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 128, aesKW: &AesKW{keySizeBits: 128}}) - RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 192, aesKW: &AesKW{keySizeBits: 192}}) - RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 256, aesKW: &AesKW{keySizeBits: 256}}) -} - -// PBSE2 with HMAC key management algorithm implementation -type Pbse2HmacAesKW struct { - keySizeBits int - aesKW JwaAlgorithm -} - -func (alg *Pbse2HmacAesKW) Name() string { - switch alg.keySizeBits { - case 128: - return PBES2_HS256_A128KW - case 192: - return PBES2_HS384_A192KW - default: - return PBES2_HS512_A256KW - } -} - -func (alg *Pbse2HmacAesKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - if passphrase, ok := key.(string); ok { - - algId := []byte(header["alg"].(string)) - - iterationCount := 8192 - var saltInput []byte - - if saltInput, err = arrays.Random(12); err != nil { - return nil, nil, err - } - - header["p2c"] = iterationCount - header["p2s"] = base64url.Encode(saltInput) - - salt := arrays.Concat(algId, []byte{0}, saltInput) - - kek := kdf.DerivePBKDF2([]byte(passphrase), salt, iterationCount, alg.keySizeBits, alg.prf) - return alg.aesKW.WrapNewKey(cekSizeBits, kek, header) - } - - return nil, nil, errors.New("Pbse2HmacAesKW.WrapNewKey(): expected key to be 'string' array") -} - -func (alg *Pbse2HmacAesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - - if passphrase, ok := key.(string); ok { - - var p2s string - var p2c float64 - - if p2c, ok = header["p2c"].(float64); !ok { - return nil, errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2c' param in JWT header, but was not found.") - } - - if p2s, ok = header["p2s"].(string); !ok { - return nil, errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2s' param in JWT header, but was not found.") - } - - var saltInput []byte - - algId := []byte(header["alg"].(string)) - - if saltInput, err = base64url.Decode(p2s); err != nil { - return nil, err - } - - salt := arrays.Concat(algId, []byte{0}, saltInput) - - kek := kdf.DerivePBKDF2([]byte(passphrase), salt, int(p2c), alg.keySizeBits, alg.prf) - - return alg.aesKW.Unwrap(encryptedCek, kek, cekSizeBits, header) - } - - return nil, errors.New("Pbse2HmacAesKW.Unwrap(): expected key to be 'string' array") -} - -func (alg *Pbse2HmacAesKW) prf() hash.Hash { - switch alg.keySizeBits { - case 128: - return sha256.New() - case 192: - return sha512.New384() - default: - return sha512.New() - } -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/plaintext.go b/vendor/github.com/dvsekhvalnov/jose2go/plaintext.go deleted file mode 100644 index 761ce5a..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/plaintext.go +++ /dev/null @@ -1,38 +0,0 @@ -package jose - -import ( - "errors" -) - -// Plaintext (no signing) signing algorithm implementation -type Plaintext struct{} - -func init() { - RegisterJws(new(Plaintext)) -} - -func (alg *Plaintext) Name() string { - return NONE -} - -func (alg *Plaintext) Verify(securedInput []byte, signature []byte, key interface{}) error { - - if key != nil { - return errors.New("Plaintext.Verify() expects key to be nil") - } - - if len(signature) != 0 { - return errors.New("Plaintext.Verify() expects signature to be empty.") - } - - return nil -} - -func (alg *Plaintext) Sign(securedInput []byte, key interface{}) (signature []byte, err error) { - - if key != nil { - return nil, errors.New("Plaintext.Verify() expects key to be nil") - } - - return []byte{}, nil -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/rsa_oaep.go b/vendor/github.com/dvsekhvalnov/jose2go/rsa_oaep.go deleted file mode 100644 index b0d1b52..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/rsa_oaep.go +++ /dev/null @@ -1,57 +0,0 @@ -package jose - -import ( - "errors" - "crypto/rsa" - "crypto/rand" - "hash" - "crypto/sha1" - "crypto/sha256" - "github.com/dvsekhvalnov/jose2go/arrays" -) - -// RS-AES using OAEP key management algorithm implementation -func init() { - RegisterJwa(&RsaOaep {shaSizeBits:1}) - RegisterJwa(&RsaOaep {shaSizeBits:256}) -} - -type RsaOaep struct{ - shaSizeBits int - // func shaF() hash.Hash -} - -func (alg *RsaOaep) Name() string { - switch alg.shaSizeBits { - case 1: return RSA_OAEP - default: return RSA_OAEP_256 - } -} - -func (alg *RsaOaep) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - if pubKey,ok:=key.(*rsa.PublicKey);ok { - if cek,err = arrays.Random(cekSizeBits>>3);err==nil { - encryptedCek,err=rsa.EncryptOAEP(alg.sha(),rand.Reader,pubKey,cek,nil) - return - } - - return nil,nil,err - } - - return nil,nil,errors.New("RsaOaep.WrapNewKey(): expected key to be '*rsa.PublicKey'") -} - -func (alg *RsaOaep) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - if privKey,ok:=key.(*rsa.PrivateKey);ok { - return rsa.DecryptOAEP(alg.sha(), rand.Reader, privKey, encryptedCek, nil) - } - - return nil,errors.New("RsaOaep.Unwrap(): expected key to be '*rsa.PrivateKey'") -} - -func (alg *RsaOaep) sha() hash.Hash { - switch alg.shaSizeBits { - case 1: return sha1.New() - default: return sha256.New() - } -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/rsa_pkcs1v15.go b/vendor/github.com/dvsekhvalnov/jose2go/rsa_pkcs1v15.go deleted file mode 100644 index 10dac06..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/rsa_pkcs1v15.go +++ /dev/null @@ -1,41 +0,0 @@ -package jose - -import ( - "errors" - "crypto/rsa" - "crypto/rand" - "github.com/dvsekhvalnov/jose2go/arrays" -) - -func init() { - RegisterJwa(new(RsaPkcs1v15)) -} - -// RS-AES using PKCS #1 v1.5 padding key management algorithm implementation -type RsaPkcs1v15 struct{ -} - -func (alg *RsaPkcs1v15) Name() string { - return RSA1_5 -} - -func (alg *RsaPkcs1v15) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - if pubKey,ok:=key.(*rsa.PublicKey);ok { - if cek,err = arrays.Random(cekSizeBits>>3);err==nil { - encryptedCek,err=rsa.EncryptPKCS1v15(rand.Reader,pubKey,cek) - return - } - - return nil,nil,err - } - - return nil,nil,errors.New("RsaPkcs1v15.WrapNewKey(): expected key to be '*rsa.PublicKey'") -} - -func (alg *RsaPkcs1v15) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - if privKey,ok:=key.(*rsa.PrivateKey);ok { - return rsa.DecryptPKCS1v15(rand.Reader,privKey,encryptedCek) - } - - return nil,errors.New("RsaPkcs1v15.Unwrap(): expected key to be '*rsa.PrivateKey'") -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/rsa_using_sha.go b/vendor/github.com/dvsekhvalnov/jose2go/rsa_using_sha.go deleted file mode 100644 index 647a45a..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/rsa_using_sha.go +++ /dev/null @@ -1,50 +0,0 @@ -package jose - -import ( - "crypto/rand" - "crypto/rsa" - "errors" -) - -func init() { - RegisterJws(&RsaUsingSha{keySizeBits: 256}) - RegisterJws(&RsaUsingSha{keySizeBits: 384}) - RegisterJws(&RsaUsingSha{keySizeBits: 512}) -} - -// RSA using SHA signature algorithm implementation -type RsaUsingSha struct{ - keySizeBits int -} - -func (alg *RsaUsingSha) Name() string { - switch alg.keySizeBits { - case 256: return RS256 - case 384: return RS384 - default: return RS512 - } -} - -func (alg *RsaUsingSha) Verify(securedInput, signature []byte, key interface{}) error { - - if pubKey,ok:=key.(*rsa.PublicKey);ok { - return rsa.VerifyPKCS1v15(pubKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput), signature) - } - - return errors.New("RsaUsingSha.Verify(): expects key to be '*rsa.PublicKey'") -} - -func (alg *RsaUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) { - - if privKey,ok:=key.(*rsa.PrivateKey);ok { - return rsa.SignPKCS1v15(rand.Reader, privKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput)) - } - - return nil,errors.New("RsaUsingSha.Sign(): expects key to be '*rsa.PrivateKey'") -} - -func sha(keySizeBits int, input []byte) (hash []byte) { - hasher := hashAlg(keySizeBits) - hasher.Write(input) - return hasher.Sum(nil) -} \ No newline at end of file diff --git a/vendor/github.com/dvsekhvalnov/jose2go/rsapss_using_sha.go b/vendor/github.com/dvsekhvalnov/jose2go/rsapss_using_sha.go deleted file mode 100644 index fc111db..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/rsapss_using_sha.go +++ /dev/null @@ -1,43 +0,0 @@ -package jose - -import ( - "crypto/rand" - "crypto/rsa" - "errors" -) - -func init() { - RegisterJws(&RsaPssUsingSha{keySizeBits: 256, saltSizeBytes: 32}) - RegisterJws(&RsaPssUsingSha{keySizeBits: 384, saltSizeBytes: 48}) - RegisterJws(&RsaPssUsingSha{keySizeBits: 512, saltSizeBytes: 64}) -} - -// RSA with PSS using SHA signing algorithm implementation -type RsaPssUsingSha struct{ - keySizeBits int - saltSizeBytes int -} - -func (alg *RsaPssUsingSha) Name() string { - switch alg.keySizeBits { - case 256: return PS256 - case 384: return PS384 - default: return PS512 - } -} - -func (alg *RsaPssUsingSha) Verify(securedInput, signature []byte, key interface{}) error { - if pubKey,ok:=key.(*rsa.PublicKey);ok { - return rsa.VerifyPSS(pubKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput), signature, &rsa.PSSOptions{SaltLength:alg.saltSizeBytes}) - } - - return errors.New("RsaPssUsingSha.Verify(): expects key to be '*rsa.PublicKey'") -} - -func (alg *RsaPssUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) { - if privKey,ok:=key.(*rsa.PrivateKey);ok { - return rsa.SignPSS(rand.Reader, privKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput), &rsa.PSSOptions{SaltLength:alg.saltSizeBytes}) - } - - return nil,errors.New("RsaPssUsingSha.Sign(): expects key to be '*rsa.PrivateKey'") -} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/sha.go b/vendor/github.com/dvsekhvalnov/jose2go/sha.go deleted file mode 100644 index 13b7b17..0000000 --- a/vendor/github.com/dvsekhvalnov/jose2go/sha.go +++ /dev/null @@ -1,24 +0,0 @@ -package jose - -import ( - "hash" - "crypto" - "crypto/sha256" - "crypto/sha512" -) - -func hashFunc(keySizeBits int) crypto.Hash { - switch keySizeBits { - case 256: return crypto.SHA256 - case 384: return crypto.SHA384 - default: return crypto.SHA512 - } -} - -func hashAlg(keySizeBits int) hash.Hash { - switch keySizeBits { - case 256: return sha256.New() - case 384: return sha512.New384() - default: return sha512.New() - } -} \ No newline at end of file diff --git a/vendor/github.com/fsnotify/fsnotify/.editorconfig b/vendor/github.com/fsnotify/fsnotify/.editorconfig new file mode 100644 index 0000000..fad8958 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.go] +indent_style = tab +indent_size = 4 +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/github.com/fsnotify/fsnotify/.gitattributes b/vendor/github.com/fsnotify/fsnotify/.gitattributes new file mode 100644 index 0000000..32f1001 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitattributes @@ -0,0 +1 @@ +go.sum linguist-generated diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore new file mode 100644 index 0000000..4cd0cba --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitignore @@ -0,0 +1,6 @@ +# Setup a Global .gitignore for OS and editor generated files: +# https://help.github.com/articles/ignoring-files +# git config --global core.excludesfile ~/.gitignore_global + +.vagrant +*.sublime-project diff --git a/vendor/github.com/fsnotify/fsnotify/.travis.yml b/vendor/github.com/fsnotify/fsnotify/.travis.yml new file mode 100644 index 0000000..a9c3016 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.travis.yml @@ -0,0 +1,36 @@ +sudo: false +language: go + +go: + - "stable" + - "1.11.x" + - "1.10.x" + - "1.9.x" + +matrix: + include: + - go: "stable" + env: GOLINT=true + allow_failures: + - go: tip + fast_finish: true + + +before_install: + - if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi + +script: + - go test --race ./... + +after_script: + - test -z "$(gofmt -s -l -w . | tee /dev/stderr)" + - if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi + - go vet ./... + +os: + - linux + - osx + - windows + +notifications: + email: false diff --git a/vendor/github.com/fsnotify/fsnotify/AUTHORS b/vendor/github.com/fsnotify/fsnotify/AUTHORS new file mode 100644 index 0000000..5ab5d41 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/AUTHORS @@ -0,0 +1,52 @@ +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# You can update this list using the following command: +# +# $ git shortlog -se | awk '{print $2 " " $3 " " $4}' + +# Please keep the list sorted. + +Aaron L +Adrien Bustany +Amit Krishnan +Anmol Sethi +Bjørn Erik Pedersen +Bruno Bigras +Caleb Spare +Case Nelson +Chris Howey +Christoffer Buchholz +Daniel Wagner-Hall +Dave Cheney +Evan Phoenix +Francisco Souza +Hari haran +John C Barstow +Kelvin Fo +Ken-ichirou MATSUZAWA +Matt Layher +Nathan Youngman +Nickolai Zeldovich +Patrick +Paul Hammond +Pawel Knap +Pieter Droogendijk +Pursuit92 +Riku Voipio +Rob Figueiredo +Rodrigo Chiossi +Slawek Ligus +Soge Zhang +Tiffany Jernigan +Tilak Sharma +Tom Payne +Travis Cline +Tudor Golubenco +Vahe Khachikyan +Yukang +bronze1man +debrando +henrikedwards +铁哥 diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md new file mode 100644 index 0000000..be4d7ea --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md @@ -0,0 +1,317 @@ +# Changelog + +## v1.4.7 / 2018-01-09 + +* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine) +* Tests: Fix missing verb on format string (thanks @rchiossi) +* Linux: Fix deadlock in Remove (thanks @aarondl) +* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne) +* Docs: Moved FAQ into the README (thanks @vahe) +* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich) +* Docs: replace references to OS X with macOS + +## v1.4.2 / 2016-10-10 + +* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack) + +## v1.4.1 / 2016-10-04 + +* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack) + +## v1.4.0 / 2016-10-01 + +* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie) + +## v1.3.1 / 2016-06-28 + +* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc) + +## v1.3.0 / 2016-04-19 + +* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135) + +## v1.2.10 / 2016-03-02 + +* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj) + +## v1.2.9 / 2016-01-13 + +kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep) + +## v1.2.8 / 2015-12-17 + +* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test) +* inotify: fix race in test +* enable race detection for continuous integration (Linux, Mac, Windows) + +## v1.2.5 / 2015-10-17 + +* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki) +* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken) +* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie) +* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion) + +## v1.2.1 / 2015-10-14 + +* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx) + +## v1.2.0 / 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59) + +## v1.1.1 / 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## v1.1.0 / 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## v1.0.4 / 2014-09-07 + +* kqueue: add dragonfly to the build tags. +* Rename source code files, rearrange code so exported APIs are at the top. +* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang) + +## v1.0.3 / 2014-08-19 + +* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36) + +## v1.0.2 / 2014-08-17 + +* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) +* [Fix] Make ./path and path equivalent. (thanks @zhsso) + +## v1.0.0 / 2014-08-15 + +* [API] Remove AddWatch on Windows, use Add. +* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30) +* Minor updates based on feedback from golint. + +## dev / 2014-07-09 + +* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify). +* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) + +## dev / 2014-07-04 + +* kqueue: fix incorrect mutex used in Close() +* Update example to demonstrate usage of Op. + +## dev / 2014-06-28 + +* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4) +* Fix for String() method on Event (thanks Alex Brainman) +* Don't build on Plan 9 or Solaris (thanks @4ad) + +## dev / 2014-06-21 + +* Events channel of type Event rather than *Event. +* [internal] use syscall constants directly for inotify and kqueue. +* [internal] kqueue: rename events to kevents and fileEvent to event. + +## dev / 2014-06-19 + +* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). +* [internal] remove cookie from Event struct (unused). +* [internal] Event struct has the same definition across every OS. +* [internal] remove internal watch and removeWatch methods. + +## dev / 2014-06-12 + +* [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). +* [API] Pluralized channel names: Events and Errors. +* [API] Renamed FileEvent struct to Event. +* [API] Op constants replace methods like IsCreate(). + +## dev / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## dev / 2014-05-23 + +* [API] Remove current implementation of WatchFlags. + * current implementation doesn't take advantage of OS for efficiency + * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes + * no tests for the current implementation + * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) + +## v0.9.3 / 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## v0.9.2 / 2014-08-17 + +* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) + +## v0.9.1 / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## v0.9.0 / 2014-01-17 + +* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) +* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) +* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. + +## v0.8.12 / 2013-11-13 + +* [API] Remove FD_SET and friends from Linux adapter + +## v0.8.11 / 2013-11-02 + +* [Doc] Add Changelog [#72][] (thanks @nathany) +* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond) + +## v0.8.10 / 2013-10-19 + +* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) +* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) +* [Doc] specify OS-specific limits in README (thanks @debrando) + +## v0.8.9 / 2013-09-08 + +* [Doc] Contributing (thanks @nathany) +* [Doc] update package path in example code [#63][] (thanks @paulhammond) +* [Doc] GoCI badge in README (Linux only) [#60][] +* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) + +## v0.8.8 / 2013-06-17 + +* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) + +## v0.8.7 / 2013-06-03 + +* [API] Make syscall flags internal +* [Fix] inotify: ignore event changes +* [Fix] race in symlink test [#45][] (reported by @srid) +* [Fix] tests on Windows +* lower case error messages + +## v0.8.6 / 2013-05-23 + +* kqueue: Use EVT_ONLY flag on Darwin +* [Doc] Update README with full example + +## v0.8.5 / 2013-05-09 + +* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) + +## v0.8.4 / 2013-04-07 + +* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) + +## v0.8.3 / 2013-03-13 + +* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) +* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) + +## v0.8.2 / 2013-02-07 + +* [Doc] add Authors +* [Fix] fix data races for map access [#29][] (thanks @fsouza) + +## v0.8.1 / 2013-01-09 + +* [Fix] Windows path separators +* [Doc] BSD License + +## v0.8.0 / 2012-11-09 + +* kqueue: directory watching improvements (thanks @vmirage) +* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) +* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) + +## v0.7.4 / 2012-10-09 + +* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) +* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) +* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) +* [Fix] kqueue: modify after recreation of file + +## v0.7.3 / 2012-09-27 + +* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) +* [Fix] kqueue: no longer get duplicate CREATE events + +## v0.7.2 / 2012-09-01 + +* kqueue: events for created directories + +## v0.7.1 / 2012-07-14 + +* [Fix] for renaming files + +## v0.7.0 / 2012-07-02 + +* [Feature] FSNotify flags +* [Fix] inotify: Added file name back to event path + +## v0.6.0 / 2012-06-06 + +* kqueue: watch files after directory created (thanks @tmc) + +## v0.5.1 / 2012-05-22 + +* [Fix] inotify: remove all watches before Close() + +## v0.5.0 / 2012-05-03 + +* [API] kqueue: return errors during watch instead of sending over channel +* kqueue: match symlink behavior on Linux +* inotify: add `DELETE_SELF` (requested by @taralx) +* [Fix] kqueue: handle EINTR (reported by @robfig) +* [Doc] Godoc example [#1][] (thanks @davecheney) + +## v0.4.0 / 2012-03-30 + +* Go 1 released: build with go tool +* [Feature] Windows support using winfsnotify +* Windows does not have attribute change notifications +* Roll attribute notifications into IsModify + +## v0.3.0 / 2012-02-19 + +* kqueue: add files when watch directory + +## v0.2.0 / 2011-12-30 + +* update to latest Go weekly code + +## v0.1.0 / 2011-10-19 + +* kqueue: add watch on file creation to match inotify +* kqueue: create file event +* inotify: ignore `IN_IGNORED` events +* event String() +* linux: common FileEvent functions +* initial commit + +[#79]: https://github.com/howeyc/fsnotify/pull/79 +[#77]: https://github.com/howeyc/fsnotify/pull/77 +[#72]: https://github.com/howeyc/fsnotify/issues/72 +[#71]: https://github.com/howeyc/fsnotify/issues/71 +[#70]: https://github.com/howeyc/fsnotify/issues/70 +[#63]: https://github.com/howeyc/fsnotify/issues/63 +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#60]: https://github.com/howeyc/fsnotify/issues/60 +[#59]: https://github.com/howeyc/fsnotify/issues/59 +[#49]: https://github.com/howeyc/fsnotify/issues/49 +[#45]: https://github.com/howeyc/fsnotify/issues/45 +[#40]: https://github.com/howeyc/fsnotify/issues/40 +[#36]: https://github.com/howeyc/fsnotify/issues/36 +[#33]: https://github.com/howeyc/fsnotify/issues/33 +[#29]: https://github.com/howeyc/fsnotify/issues/29 +[#25]: https://github.com/howeyc/fsnotify/issues/25 +[#24]: https://github.com/howeyc/fsnotify/issues/24 +[#21]: https://github.com/howeyc/fsnotify/issues/21 diff --git a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md new file mode 100644 index 0000000..828a60b --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing + +## Issues + +* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues). +* Please indicate the platform you are using fsnotify on. +* A code example to reproduce the problem is appreciated. + +## Pull Requests + +### Contributor License Agreement + +fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). + +Please indicate that you have signed the CLA in your pull request. + +### How fsnotify is Developed + +* Development is done on feature branches. +* Tests are run on BSD, Linux, macOS and Windows. +* Pull requests are reviewed and [applied to master][am] using [hub][]. + * Maintainers may modify or squash commits rather than asking contributors to. +* To issue a new release, the maintainers will: + * Update the CHANGELOG + * Tag a version, which will become available through gopkg.in. + +### How to Fork + +For smooth sailing, always use the original import path. Installing with `go get` makes this easy. + +1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Ensure everything works and the tests pass (see below) +4. Commit your changes (`git commit -am 'Add some feature'`) + +Contribute upstream: + +1. Fork fsnotify on GitHub +2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) +3. Push to the branch (`git push fork my-new-feature`) +4. Create a new Pull Request on GitHub + +This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/). + +### Testing + +fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows. + +Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. + +To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. + +* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) +* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. +* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password) +* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`. +* When you're done, you will want to halt or destroy the Vagrant boxes. + +Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. + +Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). + +### Maintainers + +Help maintaining fsnotify is welcome. To be a maintainer: + +* Submit a pull request and sign the CLA as above. +* You must be able to run the test suite on Mac, Windows, Linux and BSD. + +To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. + +All code changes should be internal pull requests. + +Releases are tagged using [Semantic Versioning](http://semver.org/). + +[hub]: https://github.com/github/hub +[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs diff --git a/vendor/github.com/fsnotify/fsnotify/LICENSE b/vendor/github.com/fsnotify/fsnotify/LICENSE new file mode 100644 index 0000000..e180c8f --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2012-2019 fsnotify 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/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md new file mode 100644 index 0000000..b2629e5 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/README.md @@ -0,0 +1,130 @@ +# File system notifications for Go + +[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) + +fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running: + +```console +go get -u golang.org/x/sys/... +``` + +Cross platform: Windows, Linux, BSD and macOS. + +| Adapter | OS | Status | +| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| inotify | Linux 2.6.27 or later, Android\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| kqueue | BSD, macOS, iOS\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| ReadDirectoryChangesW | Windows | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | +| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) | +| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) | +| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | +| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | + +\* Android and iOS are untested. + +Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information. + +## API stability + +fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA). + +All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number. + +Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`. + +## Usage + +```go +package main + +import ( + "log" + + "github.com/fsnotify/fsnotify" +) + +func main() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Println("event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("modified file:", event.Name) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + err = watcher.Add("/tmp/foo") + if err != nil { + log.Fatal(err) + } + <-done +} +``` + +## Contributing + +Please refer to [CONTRIBUTING][] before opening an issue or pull request. + +## Example + +See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go). + +## FAQ + +**When a file is moved to another directory is it still being watched?** + +No (it shouldn't be, unless you are watching where it was moved to). + +**When I watch a directory, are all subdirectories watched as well?** + +No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]). + +**Do I have to watch the Error and Event channels in a separate goroutine?** + +As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7]) + +**Why am I receiving multiple events for the same file on OS X?** + +Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]). + +**How many files can be watched at once?** + +There are OS-specific limits as to how many watches can be created: +* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error. +* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error. + +**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?** + +fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications. + +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#18]: https://github.com/fsnotify/fsnotify/issues/18 +[#11]: https://github.com/fsnotify/fsnotify/issues/11 +[#7]: https://github.com/howeyc/fsnotify/issues/7 + +[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md + +## Related Projects + +* [notify](https://github.com/rjeczalik/notify) +* [fsevents](https://github.com/fsnotify/fsevents) + diff --git a/vendor/github.com/fsnotify/fsnotify/fen.go b/vendor/github.com/fsnotify/fsnotify/fen.go new file mode 100644 index 0000000..ced39cb --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fen.go @@ -0,0 +1,37 @@ +// Copyright 2010 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 fsnotify + +import ( + "errors" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + return nil, errors.New("FEN based watcher not yet supported for fsnotify\n") +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + return nil +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + return nil +} diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go new file mode 100644 index 0000000..89cab04 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go @@ -0,0 +1,68 @@ +// Copyright 2012 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 !plan9 + +// Package fsnotify provides a platform-independent interface for file system notifications. +package fsnotify + +import ( + "bytes" + "errors" + "fmt" +) + +// Event represents a single file system notification. +type Event struct { + Name string // Relative path to the file or directory. + Op Op // File operation that triggered the event. +} + +// Op describes a set of file operations. +type Op uint32 + +// These are the generalized file operations that can trigger a notification. +const ( + Create Op = 1 << iota + Write + Remove + Rename + Chmod +) + +func (op Op) String() string { + // Use a buffer for efficient string concatenation + var buffer bytes.Buffer + + if op&Create == Create { + buffer.WriteString("|CREATE") + } + if op&Remove == Remove { + buffer.WriteString("|REMOVE") + } + if op&Write == Write { + buffer.WriteString("|WRITE") + } + if op&Rename == Rename { + buffer.WriteString("|RENAME") + } + if op&Chmod == Chmod { + buffer.WriteString("|CHMOD") + } + if buffer.Len() == 0 { + return "" + } + return buffer.String()[1:] // Strip leading pipe +} + +// String returns a string representation of the event in the form +// "file: REMOVE|WRITE|..." +func (e Event) String() string { + return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) +} + +// Common errors that can be reported by a watcher +var ( + ErrEventOverflow = errors.New("fsnotify queue overflow") +) diff --git a/vendor/github.com/fsnotify/fsnotify/go.mod b/vendor/github.com/fsnotify/fsnotify/go.mod new file mode 100644 index 0000000..ff11e13 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/go.mod @@ -0,0 +1,5 @@ +module github.com/fsnotify/fsnotify + +go 1.13 + +require golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 diff --git a/vendor/github.com/fsnotify/fsnotify/go.sum b/vendor/github.com/fsnotify/fsnotify/go.sum new file mode 100644 index 0000000..f60af98 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/fsnotify/fsnotify/inotify.go b/vendor/github.com/fsnotify/fsnotify/inotify.go new file mode 100644 index 0000000..d9fd1b8 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/inotify.go @@ -0,0 +1,337 @@ +// Copyright 2010 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 linux + +package fsnotify + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + "unsafe" + + "golang.org/x/sys/unix" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + mu sync.Mutex // Map access + fd int + poller *fdPoller + watches map[string]*watch // Map of inotify watches (key: path) + paths map[int]string // Map of watched paths (key: watch descriptor) + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + // Create inotify fd + fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) + if fd == -1 { + return nil, errno + } + // Create epoll + poller, err := newFdPoller(fd) + if err != nil { + unix.Close(fd) + return nil, err + } + w := &Watcher{ + fd: fd, + poller: poller, + watches: make(map[string]*watch), + paths: make(map[int]string), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + doneResp: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + if w.isClosed() { + return nil + } + + // Send 'close' signal to goroutine, and set the Watcher to closed. + close(w.done) + + // Wake up goroutine + w.poller.wake() + + // Wait for goroutine to close + <-w.doneResp + + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + name = filepath.Clean(name) + if w.isClosed() { + return errors.New("inotify instance already closed") + } + + const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | + unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | + unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF + + var flags uint32 = agnosticEvents + + w.mu.Lock() + defer w.mu.Unlock() + watchEntry := w.watches[name] + if watchEntry != nil { + flags |= watchEntry.flags | unix.IN_MASK_ADD + } + wd, errno := unix.InotifyAddWatch(w.fd, name, flags) + if wd == -1 { + return errno + } + + if watchEntry == nil { + w.watches[name] = &watch{wd: uint32(wd), flags: flags} + w.paths[wd] = name + } else { + watchEntry.wd = uint32(wd) + watchEntry.flags = flags + } + + return nil +} + +// Remove stops watching the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + name = filepath.Clean(name) + + // Fetch the watch. + w.mu.Lock() + defer w.mu.Unlock() + watch, ok := w.watches[name] + + // Remove it from inotify. + if !ok { + return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) + } + + // We successfully removed the watch if InotifyRmWatch doesn't return an + // error, we need to clean up our internal state to ensure it matches + // inotify's kernel state. + delete(w.paths, int(watch.wd)) + delete(w.watches, name) + + // inotify_rm_watch will return EINVAL if the file has been deleted; + // the inotify will already have been removed. + // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously + // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE + // so that EINVAL means that the wd is being rm_watch()ed or its file removed + // by another thread and we have not received IN_IGNORE event. + success, errno := unix.InotifyRmWatch(w.fd, watch.wd) + if success == -1 { + // TODO: Perhaps it's not helpful to return an error here in every case. + // the only two possible errors are: + // EBADF, which happens when w.fd is not a valid file descriptor of any kind. + // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. + // Watch descriptors are invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. + return errno + } + + return nil +} + +type watch struct { + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) +} + +// readEvents reads from the inotify file descriptor, converts the +// received events into Event objects and sends them via the Events channel +func (w *Watcher) readEvents() { + var ( + buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events + n int // Number of bytes read with read() + errno error // Syscall errno + ok bool // For poller.wait + ) + + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + defer unix.Close(w.fd) + defer w.poller.close() + + for { + // See if we have been closed. + if w.isClosed() { + return + } + + ok, errno = w.poller.wait() + if errno != nil { + select { + case w.Errors <- errno: + case <-w.done: + return + } + continue + } + + if !ok { + continue + } + + n, errno = unix.Read(w.fd, buf[:]) + // If a signal interrupted execution, see if we've been asked to close, and try again. + // http://man7.org/linux/man-pages/man7/signal.7.html : + // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" + if errno == unix.EINTR { + continue + } + + // unix.Read might have been woken up by Close. If so, we're done. + if w.isClosed() { + return + } + + if n < unix.SizeofInotifyEvent { + var err error + if n == 0 { + // If EOF is received. This should really never happen. + err = io.EOF + } else if n < 0 { + // If an error occurred while reading. + err = errno + } else { + // Read was too short. + err = errors.New("notify: short read in readEvents()") + } + select { + case w.Errors <- err: + case <-w.done: + return + } + continue + } + + var offset uint32 + // We don't know how many events we just read into the buffer + // While the offset points to at least one whole event... + for offset <= uint32(n-unix.SizeofInotifyEvent) { + // Point "raw" to the event in the buffer + raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) + + mask := uint32(raw.Mask) + nameLen := uint32(raw.Len) + + if mask&unix.IN_Q_OVERFLOW != 0 { + select { + case w.Errors <- ErrEventOverflow: + case <-w.done: + return + } + } + + // If the event happened to the watched directory or the watched file, the kernel + // doesn't append the filename to the event, but we would like to always fill the + // the "Name" field with a valid filename. We retrieve the path of the watch from + // the "paths" map. + w.mu.Lock() + name, ok := w.paths[int(raw.Wd)] + // IN_DELETE_SELF occurs when the file/directory being watched is removed. + // This is a sign to clean up the maps, otherwise we are no longer in sync + // with the inotify kernel state which has already deleted the watch + // automatically. + if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { + delete(w.paths, int(raw.Wd)) + delete(w.watches, name) + } + w.mu.Unlock() + + if nameLen > 0 { + // Point "bytes" at the first byte of the filename + bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent])) + // The filename is padded with NULL bytes. TrimRight() gets rid of those. + name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") + } + + event := newEvent(name, mask) + + // Send the events that are not ignored on the events channel + if !event.ignoreLinux(mask) { + select { + case w.Events <- event: + case <-w.done: + return + } + } + + // Move to the next event in the buffer + offset += unix.SizeofInotifyEvent + nameLen + } + } +} + +// Certain types of events can be "ignored" and not sent over the Events +// channel. Such as events marked ignore by the kernel, or MODIFY events +// against files that do not exist. +func (e *Event) ignoreLinux(mask uint32) bool { + // Ignore anything the inotify API says to ignore + if mask&unix.IN_IGNORED == unix.IN_IGNORED { + return true + } + + // If the event is not a DELETE or RENAME, the file must exist. + // Otherwise the event is ignored. + // *Note*: this was put in place because it was seen that a MODIFY + // event was sent after the DELETE. This ignores that MODIFY and + // assumes a DELETE will come or has come if the file doesn't exist. + if !(e.Op&Remove == Remove || e.Op&Rename == Rename) { + _, statErr := os.Lstat(e.Name) + return os.IsNotExist(statErr) + } + return false +} + +// newEvent returns an platform-independent Event based on an inotify mask. +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { + e.Op |= Create + } + if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { + e.Op |= Remove + } + if mask&unix.IN_MODIFY == unix.IN_MODIFY { + e.Op |= Write + } + if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { + e.Op |= Rename + } + if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { + e.Op |= Chmod + } + return e +} diff --git a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go new file mode 100644 index 0000000..b33f2b4 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go @@ -0,0 +1,187 @@ +// Copyright 2015 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 linux + +package fsnotify + +import ( + "errors" + + "golang.org/x/sys/unix" +) + +type fdPoller struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + epfd int // Epoll file descriptor + pipe [2]int // Pipe for waking up +} + +func emptyPoller(fd int) *fdPoller { + poller := new(fdPoller) + poller.fd = fd + poller.epfd = -1 + poller.pipe[0] = -1 + poller.pipe[1] = -1 + return poller +} + +// Create a new inotify poller. +// This creates an inotify handler, and an epoll handler. +func newFdPoller(fd int) (*fdPoller, error) { + var errno error + poller := emptyPoller(fd) + defer func() { + if errno != nil { + poller.close() + } + }() + poller.fd = fd + + // Create epoll fd + poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC) + if poller.epfd == -1 { + return nil, errno + } + // Create pipe; pipe[0] is the read end, pipe[1] the write end. + errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC) + if errno != nil { + return nil, errno + } + + // Register inotify fd with epoll + event := unix.EpollEvent{ + Fd: int32(poller.fd), + Events: unix.EPOLLIN, + } + errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event) + if errno != nil { + return nil, errno + } + + // Register pipe fd with epoll + event = unix.EpollEvent{ + Fd: int32(poller.pipe[0]), + Events: unix.EPOLLIN, + } + errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event) + if errno != nil { + return nil, errno + } + + return poller, nil +} + +// Wait using epoll. +// Returns true if something is ready to be read, +// false if there is not. +func (poller *fdPoller) wait() (bool, error) { + // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. + // I don't know whether epoll_wait returns the number of events returned, + // or the total number of events ready. + // I decided to catch both by making the buffer one larger than the maximum. + events := make([]unix.EpollEvent, 7) + for { + n, errno := unix.EpollWait(poller.epfd, events, -1) + if n == -1 { + if errno == unix.EINTR { + continue + } + return false, errno + } + if n == 0 { + // If there are no events, try again. + continue + } + if n > 6 { + // This should never happen. More events were returned than should be possible. + return false, errors.New("epoll_wait returned more events than I know what to do with") + } + ready := events[:n] + epollhup := false + epollerr := false + epollin := false + for _, event := range ready { + if event.Fd == int32(poller.fd) { + if event.Events&unix.EPOLLHUP != 0 { + // This should not happen, but if it does, treat it as a wakeup. + epollhup = true + } + if event.Events&unix.EPOLLERR != 0 { + // If an error is waiting on the file descriptor, we should pretend + // something is ready to read, and let unix.Read pick up the error. + epollerr = true + } + if event.Events&unix.EPOLLIN != 0 { + // There is data to read. + epollin = true + } + } + if event.Fd == int32(poller.pipe[0]) { + if event.Events&unix.EPOLLHUP != 0 { + // Write pipe descriptor was closed, by us. This means we're closing down the + // watcher, and we should wake up. + } + if event.Events&unix.EPOLLERR != 0 { + // If an error is waiting on the pipe file descriptor. + // This is an absolute mystery, and should never ever happen. + return false, errors.New("Error on the pipe descriptor.") + } + if event.Events&unix.EPOLLIN != 0 { + // This is a regular wakeup, so we have to clear the buffer. + err := poller.clearWake() + if err != nil { + return false, err + } + } + } + } + + if epollhup || epollerr || epollin { + return true, nil + } + return false, nil + } +} + +// Close the write end of the poller. +func (poller *fdPoller) wake() error { + buf := make([]byte, 1) + n, errno := unix.Write(poller.pipe[1], buf) + if n == -1 { + if errno == unix.EAGAIN { + // Buffer is full, poller will wake. + return nil + } + return errno + } + return nil +} + +func (poller *fdPoller) clearWake() error { + // You have to be woken up a LOT in order to get to 100! + buf := make([]byte, 100) + n, errno := unix.Read(poller.pipe[0], buf) + if n == -1 { + if errno == unix.EAGAIN { + // Buffer is empty, someone else cleared our wake. + return nil + } + return errno + } + return nil +} + +// Close all poller file descriptors, but not the one passed to it. +func (poller *fdPoller) close() { + if poller.pipe[1] != -1 { + unix.Close(poller.pipe[1]) + } + if poller.pipe[0] != -1 { + unix.Close(poller.pipe[0]) + } + if poller.epfd != -1 { + unix.Close(poller.epfd) + } +} diff --git a/vendor/github.com/fsnotify/fsnotify/kqueue.go b/vendor/github.com/fsnotify/fsnotify/kqueue.go new file mode 100644 index 0000000..86e76a3 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/kqueue.go @@ -0,0 +1,521 @@ +// Copyright 2010 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 freebsd openbsd netbsd dragonfly darwin + +package fsnotify + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sync" + "time" + + "golang.org/x/sys/unix" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + + kq int // File descriptor (as returned by the kqueue() syscall). + + mu sync.Mutex // Protects access to watcher data + watches map[string]int // Map of watched file descriptors (key: path). + externalWatches map[string]bool // Map of watches added by user of the library. + dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. + paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. + fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). + isClosed bool // Set to true when Close() is first called +} + +type pathInfo struct { + name string + isDir bool +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + kq, err := kqueue() + if err != nil { + return nil, err + } + + w := &Watcher{ + kq: kq, + watches: make(map[string]int), + dirFlags: make(map[string]uint32), + paths: make(map[int]pathInfo), + fileExists: make(map[string]bool), + externalWatches: make(map[string]bool), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() + return nil + } + w.isClosed = true + + // copy paths to remove while locked + var pathsToRemove = make([]string, 0, len(w.watches)) + for name := range w.watches { + pathsToRemove = append(pathsToRemove, name) + } + w.mu.Unlock() + // unlock before calling Remove, which also locks + + for _, name := range pathsToRemove { + w.Remove(name) + } + + // send a "quit" message to the reader goroutine + close(w.done) + + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + w.mu.Lock() + w.externalWatches[name] = true + w.mu.Unlock() + _, err := w.addWatch(name, noteAllEvents) + return err +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + name = filepath.Clean(name) + w.mu.Lock() + watchfd, ok := w.watches[name] + w.mu.Unlock() + if !ok { + return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) + } + + const registerRemove = unix.EV_DELETE + if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { + return err + } + + unix.Close(watchfd) + + w.mu.Lock() + isDir := w.paths[watchfd].isDir + delete(w.watches, name) + delete(w.paths, watchfd) + delete(w.dirFlags, name) + w.mu.Unlock() + + // Find all watched paths that are in this directory that are not external. + if isDir { + var pathsToRemove []string + w.mu.Lock() + for _, path := range w.paths { + wdir, _ := filepath.Split(path.name) + if filepath.Clean(wdir) == name { + if !w.externalWatches[path.name] { + pathsToRemove = append(pathsToRemove, path.name) + } + } + } + w.mu.Unlock() + for _, name := range pathsToRemove { + // Since these are internal, not much sense in propagating error + // to the user, as that will just confuse them with an error about + // a path they did not explicitly watch themselves. + w.Remove(name) + } + } + + return nil +} + +// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) +const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME + +// keventWaitTime to block on each read from kevent +var keventWaitTime = durationToTimespec(100 * time.Millisecond) + +// addWatch adds name to the watched file set. +// The flags are interpreted as described in kevent(2). +// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks. +func (w *Watcher) addWatch(name string, flags uint32) (string, error) { + var isDir bool + // Make ./name and name equivalent + name = filepath.Clean(name) + + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() + return "", errors.New("kevent instance already closed") + } + watchfd, alreadyWatching := w.watches[name] + // We already have a watch, but we can still override flags. + if alreadyWatching { + isDir = w.paths[watchfd].isDir + } + w.mu.Unlock() + + if !alreadyWatching { + fi, err := os.Lstat(name) + if err != nil { + return "", err + } + + // Don't watch sockets. + if fi.Mode()&os.ModeSocket == os.ModeSocket { + return "", nil + } + + // Don't watch named pipes. + if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { + return "", nil + } + + // Follow Symlinks + // Unfortunately, Linux can add bogus symlinks to watch list without + // issue, and Windows can't do symlinks period (AFAIK). To maintain + // consistency, we will act like everything is fine. There will simply + // be no file events for broken symlinks. + // Hence the returns of nil on errors. + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + name, err = filepath.EvalSymlinks(name) + if err != nil { + return "", nil + } + + w.mu.Lock() + _, alreadyWatching = w.watches[name] + w.mu.Unlock() + + if alreadyWatching { + return name, nil + } + + fi, err = os.Lstat(name) + if err != nil { + return "", nil + } + } + + watchfd, err = unix.Open(name, openMode, 0700) + if watchfd == -1 { + return "", err + } + + isDir = fi.IsDir() + } + + const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE + if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { + unix.Close(watchfd) + return "", err + } + + if !alreadyWatching { + w.mu.Lock() + w.watches[name] = watchfd + w.paths[watchfd] = pathInfo{name: name, isDir: isDir} + w.mu.Unlock() + } + + if isDir { + // Watch the directory if it has not been watched before, + // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + w.mu.Lock() + + watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && + (!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) + // Store flags so this watch can be updated later + w.dirFlags[name] = flags + w.mu.Unlock() + + if watchDir { + if err := w.watchDirectoryFiles(name); err != nil { + return "", err + } + } + } + return name, nil +} + +// readEvents reads from kqueue and converts the received kevents into +// Event values that it sends down the Events channel. +func (w *Watcher) readEvents() { + eventBuffer := make([]unix.Kevent_t, 10) + +loop: + for { + // See if there is a message on the "done" channel + select { + case <-w.done: + break loop + default: + } + + // Get new events + kevents, err := read(w.kq, eventBuffer, &keventWaitTime) + // EINTR is okay, the syscall was interrupted before timeout expired. + if err != nil && err != unix.EINTR { + select { + case w.Errors <- err: + case <-w.done: + break loop + } + continue + } + + // Flush the events we received to the Events channel + for len(kevents) > 0 { + kevent := &kevents[0] + watchfd := int(kevent.Ident) + mask := uint32(kevent.Fflags) + w.mu.Lock() + path := w.paths[watchfd] + w.mu.Unlock() + event := newEvent(path.name, mask) + + if path.isDir && !(event.Op&Remove == Remove) { + // Double check to make sure the directory exists. This can happen when + // we do a rm -fr on a recursively watched folders and we receive a + // modification event first but the folder has been deleted and later + // receive the delete event + if _, err := os.Lstat(event.Name); os.IsNotExist(err) { + // mark is as delete event + event.Op |= Remove + } + } + + if event.Op&Rename == Rename || event.Op&Remove == Remove { + w.Remove(event.Name) + w.mu.Lock() + delete(w.fileExists, event.Name) + w.mu.Unlock() + } + + if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { + w.sendDirectoryChangeEvents(event.Name) + } else { + // Send the event on the Events channel. + select { + case w.Events <- event: + case <-w.done: + break loop + } + } + + if event.Op&Remove == Remove { + // Look for a file that may have overwritten this. + // For example, mv f1 f2 will delete f2, then create f2. + if path.isDir { + fileDir := filepath.Clean(event.Name) + w.mu.Lock() + _, found := w.watches[fileDir] + w.mu.Unlock() + if found { + // make sure the directory exists before we watch for changes. When we + // do a recursive watch and perform rm -fr, the parent directory might + // have gone missing, ignore the missing directory and let the + // upcoming delete event remove the watch from the parent directory. + if _, err := os.Lstat(fileDir); err == nil { + w.sendDirectoryChangeEvents(fileDir) + } + } + } else { + filePath := filepath.Clean(event.Name) + if fileInfo, err := os.Lstat(filePath); err == nil { + w.sendFileCreatedEventIfNew(filePath, fileInfo) + } + } + } + + // Move to next event + kevents = kevents[1:] + } + } + + // cleanup + err := unix.Close(w.kq) + if err != nil { + // only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors. + select { + case w.Errors <- err: + default: + } + } + close(w.Events) + close(w.Errors) +} + +// newEvent returns an platform-independent Event based on kqueue Fflags. +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { + e.Op |= Remove + } + if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { + e.Op |= Write + } + if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { + e.Op |= Rename + } + if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { + e.Op |= Chmod + } + return e +} + +func newCreateEvent(name string) Event { + return Event{Name: name, Op: Create} +} + +// watchDirectoryFiles to mimic inotify when adding a watch on a directory +func (w *Watcher) watchDirectoryFiles(dirPath string) error { + // Get all files + files, err := ioutil.ReadDir(dirPath) + if err != nil { + return err + } + + for _, fileInfo := range files { + filePath := filepath.Join(dirPath, fileInfo.Name()) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + } + + return nil +} + +// sendDirectoryEvents searches the directory for newly created files +// and sends them over the event channel. This functionality is to have +// the BSD version of fsnotify match Linux inotify which provides a +// create event for files created in a watched directory. +func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { + // Get all files + files, err := ioutil.ReadDir(dirPath) + if err != nil { + select { + case w.Errors <- err: + case <-w.done: + return + } + } + + // Search for new files + for _, fileInfo := range files { + filePath := filepath.Join(dirPath, fileInfo.Name()) + err := w.sendFileCreatedEventIfNew(filePath, fileInfo) + + if err != nil { + return + } + } +} + +// sendFileCreatedEvent sends a create event if the file isn't already being tracked. +func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { + w.mu.Lock() + _, doesExist := w.fileExists[filePath] + w.mu.Unlock() + if !doesExist { + // Send create event + select { + case w.Events <- newCreateEvent(filePath): + case <-w.done: + return + } + } + + // like watchDirectoryFiles (but without doing another ReadDir) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + + return nil +} + +func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) { + if fileInfo.IsDir() { + // mimic Linux providing delete events for subdirectories + // but preserve the flags used if currently watching subdirectory + w.mu.Lock() + flags := w.dirFlags[name] + w.mu.Unlock() + + flags |= unix.NOTE_DELETE | unix.NOTE_RENAME + return w.addWatch(name, flags) + } + + // watch file to mimic Linux inotify + return w.addWatch(name, noteAllEvents) +} + +// kqueue creates a new kernel event queue and returns a descriptor. +func kqueue() (kq int, err error) { + kq, err = unix.Kqueue() + if kq == -1 { + return kq, err + } + return kq, nil +} + +// register events with the queue +func register(kq int, fds []int, flags int, fflags uint32) error { + changes := make([]unix.Kevent_t, len(fds)) + + for i, fd := range fds { + // SetKevent converts int to the platform-specific types: + unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags) + changes[i].Fflags = fflags + } + + // register the events + success, err := unix.Kevent(kq, changes, nil, nil) + if success == -1 { + return err + } + return nil +} + +// read retrieves pending events, or waits until an event occurs. +// A timeout of nil blocks indefinitely, while 0 polls the queue. +func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) { + n, err := unix.Kevent(kq, nil, events, timeout) + if err != nil { + return nil, err + } + return events[0:n], nil +} + +// durationToTimespec prepares a timeout value +func durationToTimespec(d time.Duration) unix.Timespec { + return unix.NsecToTimespec(d.Nanoseconds()) +} diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go new file mode 100644 index 0000000..2306c46 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go @@ -0,0 +1,11 @@ +// 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 freebsd openbsd netbsd dragonfly + +package fsnotify + +import "golang.org/x/sys/unix" + +const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go new file mode 100644 index 0000000..870c4d6 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go @@ -0,0 +1,12 @@ +// 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 + +package fsnotify + +import "golang.org/x/sys/unix" + +// note: this constant is not defined on BSD +const openMode = unix.O_EVTONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/windows.go b/vendor/github.com/fsnotify/fsnotify/windows.go new file mode 100644 index 0000000..09436f3 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/windows.go @@ -0,0 +1,561 @@ +// 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 fsnotify + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "syscall" + "unsafe" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + isClosed bool // Set to true when Close() is first called + mu sync.Mutex // Map access + port syscall.Handle // Handle to completion port + watches watchMap // Map of watches (key: i-number) + input chan *input // Inputs to the reader are sent on this channel + quit chan chan<- error +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) + if e != nil { + return nil, os.NewSyscallError("CreateIoCompletionPort", e) + } + w := &Watcher{ + port: port, + watches: make(watchMap), + input: make(chan *input, 1), + Events: make(chan Event, 50), + Errors: make(chan error), + quit: make(chan chan<- error, 1), + } + go w.readEvents() + return w, nil +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + if w.isClosed { + return nil + } + w.isClosed = true + + // Send "quit" message to the reader goroutine + ch := make(chan error) + w.quit <- ch + if err := w.wakeupReader(); err != nil { + return err + } + return <-ch +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + if w.isClosed { + return errors.New("watcher already closed") + } + in := &input{ + op: opAddWatch, + path: filepath.Clean(name), + flags: sysFSALLEVENTS, + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + in := &input{ + op: opRemoveWatch, + path: filepath.Clean(name), + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +const ( + // Options for AddWatch + sysFSONESHOT = 0x80000000 + sysFSONLYDIR = 0x1000000 + + // Events + sysFSACCESS = 0x1 + sysFSALLEVENTS = 0xfff + sysFSATTRIB = 0x4 + sysFSCLOSE = 0x18 + sysFSCREATE = 0x100 + sysFSDELETE = 0x200 + sysFSDELETESELF = 0x400 + sysFSMODIFY = 0x2 + sysFSMOVE = 0xc0 + sysFSMOVEDFROM = 0x40 + sysFSMOVEDTO = 0x80 + sysFSMOVESELF = 0x800 + + // Special events + sysFSIGNORED = 0x8000 + sysFSQOVERFLOW = 0x4000 +) + +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { + e.Op |= Create + } + if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { + e.Op |= Remove + } + if mask&sysFSMODIFY == sysFSMODIFY { + e.Op |= Write + } + if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { + e.Op |= Rename + } + if mask&sysFSATTRIB == sysFSATTRIB { + e.Op |= Chmod + } + return e +} + +const ( + opAddWatch = iota + opRemoveWatch +) + +const ( + provisional uint64 = 1 << (32 + iota) +) + +type input struct { + op int + path string + flags uint32 + reply chan error +} + +type inode struct { + handle syscall.Handle + volume uint32 + index uint64 +} + +type watch struct { + ov syscall.Overlapped + ino *inode // i-number + path string // Directory path + mask uint64 // Directory itself is being watched with these notify flags + names map[string]uint64 // Map of names being watched and their notify flags + rename string // Remembers the old name while renaming a file + buf [4096]byte +} + +type indexMap map[uint64]*watch +type watchMap map[uint32]indexMap + +func (w *Watcher) wakeupReader() error { + e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) + if e != nil { + return os.NewSyscallError("PostQueuedCompletionStatus", e) + } + return nil +} + +func getDir(pathname string) (dir string, err error) { + attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) + if e != nil { + return "", os.NewSyscallError("GetFileAttributes", e) + } + if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { + dir = pathname + } else { + dir, _ = filepath.Split(pathname) + dir = filepath.Clean(dir) + } + return +} + +func getIno(path string) (ino *inode, err error) { + h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), + syscall.FILE_LIST_DIRECTORY, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, + nil, syscall.OPEN_EXISTING, + syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) + if e != nil { + return nil, os.NewSyscallError("CreateFile", e) + } + var fi syscall.ByHandleFileInformation + if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { + syscall.CloseHandle(h) + return nil, os.NewSyscallError("GetFileInformationByHandle", e) + } + ino = &inode{ + handle: h, + volume: fi.VolumeSerialNumber, + index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), + } + return ino, nil +} + +// Must run within the I/O thread. +func (m watchMap) get(ino *inode) *watch { + if i := m[ino.volume]; i != nil { + return i[ino.index] + } + return nil +} + +// Must run within the I/O thread. +func (m watchMap) set(ino *inode, watch *watch) { + i := m[ino.volume] + if i == nil { + i = make(indexMap) + m[ino.volume] = i + } + i[ino.index] = watch +} + +// Must run within the I/O thread. +func (w *Watcher) addWatch(pathname string, flags uint64) error { + dir, err := getDir(pathname) + if err != nil { + return err + } + if flags&sysFSONLYDIR != 0 && pathname != dir { + return nil + } + ino, err := getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watchEntry := w.watches.get(ino) + w.mu.Unlock() + if watchEntry == nil { + if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { + syscall.CloseHandle(ino.handle) + return os.NewSyscallError("CreateIoCompletionPort", e) + } + watchEntry = &watch{ + ino: ino, + path: dir, + names: make(map[string]uint64), + } + w.mu.Lock() + w.watches.set(ino, watchEntry) + w.mu.Unlock() + flags |= provisional + } else { + syscall.CloseHandle(ino.handle) + } + if pathname == dir { + watchEntry.mask |= flags + } else { + watchEntry.names[filepath.Base(pathname)] |= flags + } + if err = w.startRead(watchEntry); err != nil { + return err + } + if pathname == dir { + watchEntry.mask &= ^provisional + } else { + watchEntry.names[filepath.Base(pathname)] &= ^provisional + } + return nil +} + +// Must run within the I/O thread. +func (w *Watcher) remWatch(pathname string) error { + dir, err := getDir(pathname) + if err != nil { + return err + } + ino, err := getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watch := w.watches.get(ino) + w.mu.Unlock() + if watch == nil { + return fmt.Errorf("can't remove non-existent watch for: %s", pathname) + } + if pathname == dir { + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) + watch.mask = 0 + } else { + name := filepath.Base(pathname) + w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + return w.startRead(watch) +} + +// Must run within the I/O thread. +func (w *Watcher) deleteWatch(watch *watch) { + for name, mask := range watch.names { + if mask&provisional == 0 { + w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) + } + delete(watch.names, name) + } + if watch.mask != 0 { + if watch.mask&provisional == 0 { + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) + } + watch.mask = 0 + } +} + +// Must run within the I/O thread. +func (w *Watcher) startRead(watch *watch) error { + if e := syscall.CancelIo(watch.ino.handle); e != nil { + w.Errors <- os.NewSyscallError("CancelIo", e) + w.deleteWatch(watch) + } + mask := toWindowsFlags(watch.mask) + for _, m := range watch.names { + mask |= toWindowsFlags(m) + } + if mask == 0 { + if e := syscall.CloseHandle(watch.ino.handle); e != nil { + w.Errors <- os.NewSyscallError("CloseHandle", e) + } + w.mu.Lock() + delete(w.watches[watch.ino.volume], watch.ino.index) + w.mu.Unlock() + return nil + } + e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], + uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) + if e != nil { + err := os.NewSyscallError("ReadDirectoryChanges", e) + if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { + // Watched directory was probably removed + if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) { + if watch.mask&sysFSONESHOT != 0 { + watch.mask = 0 + } + } + err = nil + } + w.deleteWatch(watch) + w.startRead(watch) + return err + } + return nil +} + +// readEvents reads from the I/O completion port, converts the +// received events into Event objects and sends them via the Events channel. +// Entry point to the I/O thread. +func (w *Watcher) readEvents() { + var ( + n, key uint32 + ov *syscall.Overlapped + ) + runtime.LockOSThread() + + for { + e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) + watch := (*watch)(unsafe.Pointer(ov)) + + if watch == nil { + select { + case ch := <-w.quit: + w.mu.Lock() + var indexes []indexMap + for _, index := range w.watches { + indexes = append(indexes, index) + } + w.mu.Unlock() + for _, index := range indexes { + for _, watch := range index { + w.deleteWatch(watch) + w.startRead(watch) + } + } + var err error + if e := syscall.CloseHandle(w.port); e != nil { + err = os.NewSyscallError("CloseHandle", e) + } + close(w.Events) + close(w.Errors) + ch <- err + return + case in := <-w.input: + switch in.op { + case opAddWatch: + in.reply <- w.addWatch(in.path, uint64(in.flags)) + case opRemoveWatch: + in.reply <- w.remWatch(in.path) + } + default: + } + continue + } + + switch e { + case syscall.ERROR_MORE_DATA: + if watch == nil { + w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") + } else { + // The i/o succeeded but the buffer is full. + // In theory we should be building up a full packet. + // In practice we can get away with just carrying on. + n = uint32(unsafe.Sizeof(watch.buf)) + } + case syscall.ERROR_ACCESS_DENIED: + // Watched directory was probably removed + w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) + w.deleteWatch(watch) + w.startRead(watch) + continue + case syscall.ERROR_OPERATION_ABORTED: + // CancelIo was called on this handle + continue + default: + w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e) + continue + case nil: + } + + var offset uint32 + for { + if n == 0 { + w.Events <- newEvent("", sysFSQOVERFLOW) + w.Errors <- errors.New("short read in readEvents()") + break + } + + // Point "raw" to the event in the buffer + raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) + buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) + name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) + fullname := filepath.Join(watch.path, name) + + var mask uint64 + switch raw.Action { + case syscall.FILE_ACTION_REMOVED: + mask = sysFSDELETESELF + case syscall.FILE_ACTION_MODIFIED: + mask = sysFSMODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + watch.rename = name + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + if watch.names[watch.rename] != 0 { + watch.names[name] |= watch.names[watch.rename] + delete(watch.names, watch.rename) + mask = sysFSMOVESELF + } + } + + sendNameEvent := func() { + if w.sendEvent(fullname, watch.names[name]&mask) { + if watch.names[name]&sysFSONESHOT != 0 { + delete(watch.names, name) + } + } + } + if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { + sendNameEvent() + } + if raw.Action == syscall.FILE_ACTION_REMOVED { + w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { + if watch.mask&sysFSONESHOT != 0 { + watch.mask = 0 + } + } + if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { + fullname = filepath.Join(watch.path, watch.rename) + sendNameEvent() + } + + // Move to the next event in the buffer + if raw.NextEntryOffset == 0 { + break + } + offset += raw.NextEntryOffset + + // Error! + if offset >= n { + w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") + break + } + } + + if err := w.startRead(watch); err != nil { + w.Errors <- err + } + } +} + +func (w *Watcher) sendEvent(name string, mask uint64) bool { + if mask == 0 { + return false + } + event := newEvent(name, uint32(mask)) + select { + case ch := <-w.quit: + w.quit <- ch + case w.Events <- event: + } + return true +} + +func toWindowsFlags(mask uint64) uint32 { + var m uint32 + if mask&sysFSACCESS != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS + } + if mask&sysFSMODIFY != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE + } + if mask&sysFSATTRIB != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES + } + if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME + } + return m +} + +func toFSnotifyFlags(action uint32) uint64 { + switch action { + case syscall.FILE_ACTION_ADDED: + return sysFSCREATE + case syscall.FILE_ACTION_REMOVED: + return sysFSDELETE + case syscall.FILE_ACTION_MODIFIED: + return sysFSMODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + return sysFSMOVEDFROM + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + return sysFSMOVEDTO + } + return 0 +} diff --git a/vendor/github.com/gsterjov/go-libsecret/collection.go b/vendor/github.com/gsterjov/go-libsecret/collection.go deleted file mode 100644 index a835ae5..0000000 --- a/vendor/github.com/gsterjov/go-libsecret/collection.go +++ /dev/null @@ -1,124 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Collection struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) *Collection { - return &Collection{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (collection Collection) Path() dbus.ObjectPath { - return collection.dbus.Path() -} - - -// READ Array Items; -func (collection *Collection) Items() ([]Item, error) { - val, err := collection.dbus.GetProperty("org.freedesktop.Secret.Collection.Items") - if err != nil { - return []Item{}, err - } - - items := []Item{} - for _, path := range val.Value().([]dbus.ObjectPath) { - items = append(items, *NewItem(collection.conn, path)) - } - - return items, nil -} - - -// Delete (OUT ObjectPath prompt); -func (collection *Collection) Delete() error { - var prompt dbus.ObjectPath - - err := collection.dbus.Call("org.freedesktop.Secret.Collection.Delete", 0).Store(&prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(collection.conn, prompt) - - _, err := prompt.Prompt() - if err != nil { - return err - } - } - - return nil -} - - -// SearchItems (IN Dict attributes, OUT Array results); -func (collection *Collection) SearchItems(profile string) ([]Item, error) { - attributes := make(map[string]string) - attributes["profile"] = profile - - var paths []dbus.ObjectPath - - err := collection.dbus.Call("org.freedesktop.Secret.Collection.SearchItems", 0, attributes).Store(&paths) - if err != nil { - return []Item{}, err - } - - items := []Item{} - for _, path := range paths { - items = append(items, *NewItem(collection.conn, path)) - } - - return items, nil -} - - -// CreateItem (IN Dict properties, IN Secret secret, IN Boolean replace, OUT ObjectPath item, OUT ObjectPath prompt); -func (collection *Collection) CreateItem(label string, secret *Secret, replace bool) (*Item, error) { - properties := make(map[string]dbus.Variant) - attributes := make(map[string]string) - - attributes["profile"] = label - properties["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label) - properties["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attributes) - - var path dbus.ObjectPath - var prompt dbus.ObjectPath - - err := collection.dbus.Call("org.freedesktop.Secret.Collection.CreateItem", 0, properties, secret, replace).Store(&path, &prompt) - if err != nil { - return &Item{}, err - } - - if isPrompt(prompt) { - prompt := NewPrompt(collection.conn, prompt) - - result, err := prompt.Prompt() - if err != nil { - return &Item{}, err - } - - path = result.Value().(dbus.ObjectPath) - } - - return NewItem(collection.conn, path), nil -} - - -// READ Boolean Locked; -func (collection *Collection) Locked() (bool, error) { - val, err := collection.dbus.GetProperty("org.freedesktop.Secret.Collection.Locked") - if err != nil { - return true, err - } - - return val.Value().(bool), nil -} diff --git a/vendor/github.com/gsterjov/go-libsecret/item.go b/vendor/github.com/gsterjov/go-libsecret/item.go deleted file mode 100644 index f0e6ad3..0000000 --- a/vendor/github.com/gsterjov/go-libsecret/item.go +++ /dev/null @@ -1,77 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Item struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewItem(conn *dbus.Conn, path dbus.ObjectPath) *Item { - return &Item{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (item Item) Path() dbus.ObjectPath { - return item.dbus.Path() -} - - -// READWRITE String Label; -func (item *Item) Label() (string, error) { - val, err := item.dbus.GetProperty("org.freedesktop.Secret.Item.Label") - if err != nil { - return "", err - } - - return val.Value().(string), nil -} - - -// READ Boolean Locked; -func (item *Item) Locked() (bool, error) { - val, err := item.dbus.GetProperty("org.freedesktop.Secret.Item.Locked") - if err != nil { - return true, err - } - - return val.Value().(bool), nil -} - - -// GetSecret (IN ObjectPath session, OUT Secret secret); -func (item *Item) GetSecret(session *Session) (*Secret, error) { - secret := Secret{} - - err := item.dbus.Call("org.freedesktop.Secret.Item.GetSecret", 0, session.Path()).Store(&secret) - if err != nil { - return &Secret{}, err - } - - return &secret, nil -} - - -// Delete (OUT ObjectPath Prompt); -func (item *Item) Delete() error { - var prompt dbus.ObjectPath - - err := item.dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(item.conn, prompt) - if _, err := prompt.Prompt(); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/gsterjov/go-libsecret/prompt.go b/vendor/github.com/gsterjov/go-libsecret/prompt.go deleted file mode 100644 index 2c7d3f8..0000000 --- a/vendor/github.com/gsterjov/go-libsecret/prompt.go +++ /dev/null @@ -1,55 +0,0 @@ -package libsecret - -import ( - "github.com/godbus/dbus" - "strings" -) - - -type Prompt struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) *Prompt { - return &Prompt{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (prompt Prompt) Path() dbus.ObjectPath { - return prompt.dbus.Path() -} - - -func isPrompt(path dbus.ObjectPath) bool { - promptPath := DBusPath + "/prompt/" - return strings.HasPrefix(string(path), promptPath) -} - - -// Prompt (IN String window-id); -func (prompt *Prompt) Prompt() (*dbus.Variant, error) { - // prompts are asynchronous so we connect to the signal - // and block with a channel until we get a response - c := make(chan *dbus.Signal, 10) - defer close(c) - - prompt.conn.Signal(c) - defer prompt.conn.RemoveSignal(c) - - err := prompt.dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store() - if err != nil { - return &dbus.Variant{}, err - } - - for { - if result := <-c; result.Path == prompt.Path() { - value := result.Body[1].(dbus.Variant) - return &value, nil - } - } -} diff --git a/vendor/github.com/gsterjov/go-libsecret/secret.go b/vendor/github.com/gsterjov/go-libsecret/secret.go deleted file mode 100644 index 09393be..0000000 --- a/vendor/github.com/gsterjov/go-libsecret/secret.go +++ /dev/null @@ -1,21 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Secret struct { - Session dbus.ObjectPath - Parameters []byte - Value []byte - ContentType string -} - - -func NewSecret(session *Session, params []byte, value []byte, contentType string) *Secret { - return &Secret{ - Session: session.Path(), - Parameters: params, - Value: value, - ContentType: contentType, - } -} diff --git a/vendor/github.com/gsterjov/go-libsecret/service.go b/vendor/github.com/gsterjov/go-libsecret/service.go deleted file mode 100644 index bfe0f27..0000000 --- a/vendor/github.com/gsterjov/go-libsecret/service.go +++ /dev/null @@ -1,141 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -const ( - DBusServiceName = "org.freedesktop.secrets" - DBusPath = "/org/freedesktop/secrets" -) - -type DBusObject interface { - Path() dbus.ObjectPath -} - - -type Service struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewService() (*Service, error) { - conn, err := dbus.SessionBus() - if err != nil { - return &Service{}, err - } - - return &Service{ - conn: conn, - dbus: conn.Object(DBusServiceName, DBusPath), - }, nil -} - - -func (service Service) Path() dbus.ObjectPath { - return service.dbus.Path() -} - - -// OpenSession (IN String algorithm, IN Variant input, OUT Variant output, OUT ObjectPath result); -func (service *Service) Open() (*Session, error) { - var output dbus.Variant - var path dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant("")).Store(&output, &path) - if err != nil { - return &Session{}, err - } - - return NewSession(service.conn, path), nil -} - - -// READ Array Collections; -func (service *Service) Collections() ([]Collection, error) { - val, err := service.dbus.GetProperty("org.freedesktop.Secret.Service.Collections") - if err != nil { - return []Collection{}, err - } - - collections := []Collection{} - for _, path := range val.Value().([]dbus.ObjectPath) { - collections = append(collections, *NewCollection(service.conn, path)) - } - - return collections, nil -} - - -// CreateCollection (IN Dict properties, IN String alias, OUT ObjectPath collection, OUT ObjectPath prompt); -func (service *Service) CreateCollection(label string) (*Collection, error) { - properties := make(map[string]dbus.Variant) - properties["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label) - - var path dbus.ObjectPath - var prompt dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, properties, "").Store(&path, &prompt) - if err != nil { - return &Collection{}, err - } - - if isPrompt(prompt) { - prompt := NewPrompt(service.conn, prompt) - - result, err := prompt.Prompt() - if err != nil { - return &Collection{}, err - } - - path = result.Value().(dbus.ObjectPath) - } - - return NewCollection(service.conn, path), nil -} - - -// Unlock (IN Array objects, OUT Array unlocked, OUT ObjectPath prompt); -func (service *Service) Unlock(object DBusObject) error { - objects := []dbus.ObjectPath{object.Path()} - - var unlocked []dbus.ObjectPath - var prompt dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, objects).Store(&unlocked, &prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(service.conn, prompt) - if _, err := prompt.Prompt(); err != nil { - return err - } - } - - return nil -} - - -// Lock (IN Array objects, OUT Array locked, OUT ObjectPath Prompt); -func (service *Service) Lock(object DBusObject) error { - objects := []dbus.ObjectPath{object.Path()} - - var locked []dbus.ObjectPath - var prompt dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.Lock", 0, objects).Store(&locked, &prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(service.conn, prompt) - if _, err := prompt.Prompt(); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/gsterjov/go-libsecret/session.go b/vendor/github.com/gsterjov/go-libsecret/session.go deleted file mode 100644 index 8cef0ce..0000000 --- a/vendor/github.com/gsterjov/go-libsecret/session.go +++ /dev/null @@ -1,22 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Session struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewSession(conn *dbus.Conn, path dbus.ObjectPath) *Session { - return &Session{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (session Session) Path() dbus.ObjectPath { - return session.dbus.Path() -} diff --git a/vendor/github.com/hashicorp/hcl/.gitignore b/vendor/github.com/hashicorp/hcl/.gitignore new file mode 100644 index 0000000..822fa09 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/.gitignore @@ -0,0 +1,9 @@ +y.output + +# ignore intellij files +.idea +*.iml +*.ipr +*.iws + +*.test diff --git a/vendor/github.com/hashicorp/hcl/.travis.yml b/vendor/github.com/hashicorp/hcl/.travis.yml new file mode 100644 index 0000000..cb63a32 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/.travis.yml @@ -0,0 +1,13 @@ +sudo: false + +language: go + +go: + - 1.x + - tip + +branches: + only: + - master + +script: make test diff --git a/vendor/github.com/hashicorp/hcl/LICENSE b/vendor/github.com/hashicorp/hcl/LICENSE new file mode 100644 index 0000000..c33dcc7 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/hashicorp/hcl/Makefile b/vendor/github.com/hashicorp/hcl/Makefile new file mode 100644 index 0000000..9fafd50 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/Makefile @@ -0,0 +1,18 @@ +TEST?=./... + +default: test + +fmt: generate + go fmt ./... + +test: generate + go get -t ./... + go test $(TEST) $(TESTARGS) + +generate: + go generate ./... + +updatedeps: + go get -u golang.org/x/tools/cmd/stringer + +.PHONY: default generate test updatedeps diff --git a/vendor/github.com/hashicorp/hcl/README.md b/vendor/github.com/hashicorp/hcl/README.md new file mode 100644 index 0000000..c822332 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/README.md @@ -0,0 +1,125 @@ +# HCL + +[![GoDoc](https://godoc.org/github.com/hashicorp/hcl?status.png)](https://godoc.org/github.com/hashicorp/hcl) [![Build Status](https://travis-ci.org/hashicorp/hcl.svg?branch=master)](https://travis-ci.org/hashicorp/hcl) + +HCL (HashiCorp Configuration Language) is a configuration language built +by HashiCorp. The goal of HCL is to build a structured configuration language +that is both human and machine friendly for use with command-line tools, but +specifically targeted towards DevOps tools, servers, etc. + +HCL is also fully JSON compatible. That is, JSON can be used as completely +valid input to a system expecting HCL. This helps makes systems +interoperable with other systems. + +HCL is heavily inspired by +[libucl](https://github.com/vstakhov/libucl), +nginx configuration, and others similar. + +## Why? + +A common question when viewing HCL is to ask the question: why not +JSON, YAML, etc.? + +Prior to HCL, the tools we built at [HashiCorp](http://www.hashicorp.com) +used a variety of configuration languages from full programming languages +such as Ruby to complete data structure languages such as JSON. What we +learned is that some people wanted human-friendly configuration languages +and some people wanted machine-friendly languages. + +JSON fits a nice balance in this, but is fairly verbose and most +importantly doesn't support comments. With YAML, we found that beginners +had a really hard time determining what the actual structure was, and +ended up guessing more often than not whether to use a hyphen, colon, etc. +in order to represent some configuration key. + +Full programming languages such as Ruby enable complex behavior +a configuration language shouldn't usually allow, and also forces +people to learn some set of Ruby. + +Because of this, we decided to create our own configuration language +that is JSON-compatible. Our configuration language (HCL) is designed +to be written and modified by humans. The API for HCL allows JSON +as an input so that it is also machine-friendly (machines can generate +JSON instead of trying to generate HCL). + +Our goal with HCL is not to alienate other configuration languages. +It is instead to provide HCL as a specialized language for our tools, +and JSON as the interoperability layer. + +## Syntax + +For a complete grammar, please see the parser itself. A high-level overview +of the syntax and grammar is listed here. + + * Single line comments start with `#` or `//` + + * Multi-line comments are wrapped in `/*` and `*/`. Nested block comments + are not allowed. A multi-line comment (also known as a block comment) + terminates at the first `*/` found. + + * Values are assigned with the syntax `key = value` (whitespace doesn't + matter). The value can be any primitive: a string, number, boolean, + object, or list. + + * Strings are double-quoted and can contain any UTF-8 characters. + Example: `"Hello, World"` + + * Multi-line strings start with `<- + echo %Path% + + go version + + go env + + go get -t ./... + +build_script: +- cmd: go test -v ./... diff --git a/vendor/github.com/hashicorp/hcl/decoder.go b/vendor/github.com/hashicorp/hcl/decoder.go new file mode 100644 index 0000000..bed9ebb --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/decoder.go @@ -0,0 +1,729 @@ +package hcl + +import ( + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/parser" + "github.com/hashicorp/hcl/hcl/token" +) + +// This is the tag to use with structures to have settings for HCL +const tagName = "hcl" + +var ( + // nodeType holds a reference to the type of ast.Node + nodeType reflect.Type = findNodeType() +) + +// Unmarshal accepts a byte slice as input and writes the +// data to the value pointed to by v. +func Unmarshal(bs []byte, v interface{}) error { + root, err := parse(bs) + if err != nil { + return err + } + + return DecodeObject(v, root) +} + +// Decode reads the given input and decodes it into the structure +// given by `out`. +func Decode(out interface{}, in string) error { + obj, err := Parse(in) + if err != nil { + return err + } + + return DecodeObject(out, obj) +} + +// DecodeObject is a lower-level version of Decode. It decodes a +// raw Object into the given output. +func DecodeObject(out interface{}, n ast.Node) error { + val := reflect.ValueOf(out) + if val.Kind() != reflect.Ptr { + return errors.New("result must be a pointer") + } + + // If we have the file, we really decode the root node + if f, ok := n.(*ast.File); ok { + n = f.Node + } + + var d decoder + return d.decode("root", n, val.Elem()) +} + +type decoder struct { + stack []reflect.Kind +} + +func (d *decoder) decode(name string, node ast.Node, result reflect.Value) error { + k := result + + // If we have an interface with a valid value, we use that + // for the check. + if result.Kind() == reflect.Interface { + elem := result.Elem() + if elem.IsValid() { + k = elem + } + } + + // Push current onto stack unless it is an interface. + if k.Kind() != reflect.Interface { + d.stack = append(d.stack, k.Kind()) + + // Schedule a pop + defer func() { + d.stack = d.stack[:len(d.stack)-1] + }() + } + + switch k.Kind() { + case reflect.Bool: + return d.decodeBool(name, node, result) + case reflect.Float32, reflect.Float64: + return d.decodeFloat(name, node, result) + case reflect.Int, reflect.Int32, reflect.Int64: + return d.decodeInt(name, node, result) + case reflect.Interface: + // When we see an interface, we make our own thing + return d.decodeInterface(name, node, result) + case reflect.Map: + return d.decodeMap(name, node, result) + case reflect.Ptr: + return d.decodePtr(name, node, result) + case reflect.Slice: + return d.decodeSlice(name, node, result) + case reflect.String: + return d.decodeString(name, node, result) + case reflect.Struct: + return d.decodeStruct(name, node, result) + default: + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: unknown kind to decode into: %s", name, k.Kind()), + } + } +} + +func (d *decoder) decodeBool(name string, node ast.Node, result reflect.Value) error { + switch n := node.(type) { + case *ast.LiteralType: + if n.Token.Type == token.BOOL { + v, err := strconv.ParseBool(n.Token.Text) + if err != nil { + return err + } + + result.Set(reflect.ValueOf(v)) + return nil + } + } + + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: unknown type %T", name, node), + } +} + +func (d *decoder) decodeFloat(name string, node ast.Node, result reflect.Value) error { + switch n := node.(type) { + case *ast.LiteralType: + if n.Token.Type == token.FLOAT || n.Token.Type == token.NUMBER { + v, err := strconv.ParseFloat(n.Token.Text, 64) + if err != nil { + return err + } + + result.Set(reflect.ValueOf(v).Convert(result.Type())) + return nil + } + } + + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: unknown type %T", name, node), + } +} + +func (d *decoder) decodeInt(name string, node ast.Node, result reflect.Value) error { + switch n := node.(type) { + case *ast.LiteralType: + switch n.Token.Type { + case token.NUMBER: + v, err := strconv.ParseInt(n.Token.Text, 0, 0) + if err != nil { + return err + } + + if result.Kind() == reflect.Interface { + result.Set(reflect.ValueOf(int(v))) + } else { + result.SetInt(v) + } + return nil + case token.STRING: + v, err := strconv.ParseInt(n.Token.Value().(string), 0, 0) + if err != nil { + return err + } + + if result.Kind() == reflect.Interface { + result.Set(reflect.ValueOf(int(v))) + } else { + result.SetInt(v) + } + return nil + } + } + + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: unknown type %T", name, node), + } +} + +func (d *decoder) decodeInterface(name string, node ast.Node, result reflect.Value) error { + // When we see an ast.Node, we retain the value to enable deferred decoding. + // Very useful in situations where we want to preserve ast.Node information + // like Pos + if result.Type() == nodeType && result.CanSet() { + result.Set(reflect.ValueOf(node)) + return nil + } + + var set reflect.Value + redecode := true + + // For testing types, ObjectType should just be treated as a list. We + // set this to a temporary var because we want to pass in the real node. + testNode := node + if ot, ok := node.(*ast.ObjectType); ok { + testNode = ot.List + } + + switch n := testNode.(type) { + case *ast.ObjectList: + // If we're at the root or we're directly within a slice, then we + // decode objects into map[string]interface{}, otherwise we decode + // them into lists. + if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice { + var temp map[string]interface{} + tempVal := reflect.ValueOf(temp) + result := reflect.MakeMap( + reflect.MapOf( + reflect.TypeOf(""), + tempVal.Type().Elem())) + + set = result + } else { + var temp []map[string]interface{} + tempVal := reflect.ValueOf(temp) + result := reflect.MakeSlice( + reflect.SliceOf(tempVal.Type().Elem()), 0, len(n.Items)) + set = result + } + case *ast.ObjectType: + // If we're at the root or we're directly within a slice, then we + // decode objects into map[string]interface{}, otherwise we decode + // them into lists. + if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice { + var temp map[string]interface{} + tempVal := reflect.ValueOf(temp) + result := reflect.MakeMap( + reflect.MapOf( + reflect.TypeOf(""), + tempVal.Type().Elem())) + + set = result + } else { + var temp []map[string]interface{} + tempVal := reflect.ValueOf(temp) + result := reflect.MakeSlice( + reflect.SliceOf(tempVal.Type().Elem()), 0, 1) + set = result + } + case *ast.ListType: + var temp []interface{} + tempVal := reflect.ValueOf(temp) + result := reflect.MakeSlice( + reflect.SliceOf(tempVal.Type().Elem()), 0, 0) + set = result + case *ast.LiteralType: + switch n.Token.Type { + case token.BOOL: + var result bool + set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) + case token.FLOAT: + var result float64 + set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) + case token.NUMBER: + var result int + set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) + case token.STRING, token.HEREDOC: + set = reflect.Indirect(reflect.New(reflect.TypeOf(""))) + default: + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: cannot decode into interface: %T", name, node), + } + } + default: + return fmt.Errorf( + "%s: cannot decode into interface: %T", + name, node) + } + + // Set the result to what its supposed to be, then reset + // result so we don't reflect into this method anymore. + result.Set(set) + + if redecode { + // Revisit the node so that we can use the newly instantiated + // thing and populate it. + if err := d.decode(name, node, result); err != nil { + return err + } + } + + return nil +} + +func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) error { + if item, ok := node.(*ast.ObjectItem); ok { + node = &ast.ObjectList{Items: []*ast.ObjectItem{item}} + } + + if ot, ok := node.(*ast.ObjectType); ok { + node = ot.List + } + + n, ok := node.(*ast.ObjectList) + if !ok { + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: not an object type for map (%T)", name, node), + } + } + + // If we have an interface, then we can address the interface, + // but not the slice itself, so get the element but set the interface + set := result + if result.Kind() == reflect.Interface { + result = result.Elem() + } + + resultType := result.Type() + resultElemType := resultType.Elem() + resultKeyType := resultType.Key() + if resultKeyType.Kind() != reflect.String { + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: map must have string keys", name), + } + } + + // Make a map if it is nil + resultMap := result + if result.IsNil() { + resultMap = reflect.MakeMap( + reflect.MapOf(resultKeyType, resultElemType)) + } + + // Go through each element and decode it. + done := make(map[string]struct{}) + for _, item := range n.Items { + if item.Val == nil { + continue + } + + // github.com/hashicorp/terraform/issue/5740 + if len(item.Keys) == 0 { + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: map must have string keys", name), + } + } + + // Get the key we're dealing with, which is the first item + keyStr := item.Keys[0].Token.Value().(string) + + // If we've already processed this key, then ignore it + if _, ok := done[keyStr]; ok { + continue + } + + // Determine the value. If we have more than one key, then we + // get the objectlist of only these keys. + itemVal := item.Val + if len(item.Keys) > 1 { + itemVal = n.Filter(keyStr) + done[keyStr] = struct{}{} + } + + // Make the field name + fieldName := fmt.Sprintf("%s.%s", name, keyStr) + + // Get the key/value as reflection values + key := reflect.ValueOf(keyStr) + val := reflect.Indirect(reflect.New(resultElemType)) + + // If we have a pre-existing value in the map, use that + oldVal := resultMap.MapIndex(key) + if oldVal.IsValid() { + val.Set(oldVal) + } + + // Decode! + if err := d.decode(fieldName, itemVal, val); err != nil { + return err + } + + // Set the value on the map + resultMap.SetMapIndex(key, val) + } + + // Set the final map if we can + set.Set(resultMap) + return nil +} + +func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error { + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + resultType := result.Type() + resultElemType := resultType.Elem() + val := reflect.New(resultElemType) + if err := d.decode(name, node, reflect.Indirect(val)); err != nil { + return err + } + + result.Set(val) + return nil +} + +func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) error { + // If we have an interface, then we can address the interface, + // but not the slice itself, so get the element but set the interface + set := result + if result.Kind() == reflect.Interface { + result = result.Elem() + } + // Create the slice if it isn't nil + resultType := result.Type() + resultElemType := resultType.Elem() + if result.IsNil() { + resultSliceType := reflect.SliceOf(resultElemType) + result = reflect.MakeSlice( + resultSliceType, 0, 0) + } + + // Figure out the items we'll be copying into the slice + var items []ast.Node + switch n := node.(type) { + case *ast.ObjectList: + items = make([]ast.Node, len(n.Items)) + for i, item := range n.Items { + items[i] = item + } + case *ast.ObjectType: + items = []ast.Node{n} + case *ast.ListType: + items = n.List + default: + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("unknown slice type: %T", node), + } + } + + for i, item := range items { + fieldName := fmt.Sprintf("%s[%d]", name, i) + + // Decode + val := reflect.Indirect(reflect.New(resultElemType)) + + // if item is an object that was decoded from ambiguous JSON and + // flattened, make sure it's expanded if it needs to decode into a + // defined structure. + item := expandObject(item, val) + + if err := d.decode(fieldName, item, val); err != nil { + return err + } + + // Append it onto the slice + result = reflect.Append(result, val) + } + + set.Set(result) + return nil +} + +// expandObject detects if an ambiguous JSON object was flattened to a List which +// should be decoded into a struct, and expands the ast to properly deocode. +func expandObject(node ast.Node, result reflect.Value) ast.Node { + item, ok := node.(*ast.ObjectItem) + if !ok { + return node + } + + elemType := result.Type() + + // our target type must be a struct + switch elemType.Kind() { + case reflect.Ptr: + switch elemType.Elem().Kind() { + case reflect.Struct: + //OK + default: + return node + } + case reflect.Struct: + //OK + default: + return node + } + + // A list value will have a key and field name. If it had more fields, + // it wouldn't have been flattened. + if len(item.Keys) != 2 { + return node + } + + keyToken := item.Keys[0].Token + item.Keys = item.Keys[1:] + + // we need to un-flatten the ast enough to decode + newNode := &ast.ObjectItem{ + Keys: []*ast.ObjectKey{ + &ast.ObjectKey{ + Token: keyToken, + }, + }, + Val: &ast.ObjectType{ + List: &ast.ObjectList{ + Items: []*ast.ObjectItem{item}, + }, + }, + } + + return newNode +} + +func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error { + switch n := node.(type) { + case *ast.LiteralType: + switch n.Token.Type { + case token.NUMBER: + result.Set(reflect.ValueOf(n.Token.Text).Convert(result.Type())) + return nil + case token.STRING, token.HEREDOC: + result.Set(reflect.ValueOf(n.Token.Value()).Convert(result.Type())) + return nil + } + } + + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: unknown type for string %T", name, node), + } +} + +func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) error { + var item *ast.ObjectItem + if it, ok := node.(*ast.ObjectItem); ok { + item = it + node = it.Val + } + + if ot, ok := node.(*ast.ObjectType); ok { + node = ot.List + } + + // Handle the special case where the object itself is a literal. Previously + // the yacc parser would always ensure top-level elements were arrays. The new + // parser does not make the same guarantees, thus we need to convert any + // top-level literal elements into a list. + if _, ok := node.(*ast.LiteralType); ok && item != nil { + node = &ast.ObjectList{Items: []*ast.ObjectItem{item}} + } + + list, ok := node.(*ast.ObjectList) + if !ok { + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: not an object type for struct (%T)", name, node), + } + } + + // This slice will keep track of all the structs we'll be decoding. + // There can be more than one struct if there are embedded structs + // that are squashed. + structs := make([]reflect.Value, 1, 5) + structs[0] = result + + // Compile the list of all the fields that we're going to be decoding + // from all the structs. + type field struct { + field reflect.StructField + val reflect.Value + } + fields := []field{} + for len(structs) > 0 { + structVal := structs[0] + structs = structs[1:] + + structType := structVal.Type() + for i := 0; i < structType.NumField(); i++ { + fieldType := structType.Field(i) + tagParts := strings.Split(fieldType.Tag.Get(tagName), ",") + + // Ignore fields with tag name "-" + if tagParts[0] == "-" { + continue + } + + if fieldType.Anonymous { + fieldKind := fieldType.Type.Kind() + if fieldKind != reflect.Struct { + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: unsupported type to struct: %s", + fieldType.Name, fieldKind), + } + } + + // We have an embedded field. We "squash" the fields down + // if specified in the tag. + squash := false + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + + if squash { + structs = append( + structs, result.FieldByName(fieldType.Name)) + continue + } + } + + // Normal struct field, store it away + fields = append(fields, field{fieldType, structVal.Field(i)}) + } + } + + usedKeys := make(map[string]struct{}) + decodedFields := make([]string, 0, len(fields)) + decodedFieldsVal := make([]reflect.Value, 0) + unusedKeysVal := make([]reflect.Value, 0) + for _, f := range fields { + field, fieldValue := f.field, f.val + if !fieldValue.IsValid() { + // This should never happen + panic("field is not valid") + } + + // If we can't set the field, then it is unexported or something, + // and we just continue onwards. + if !fieldValue.CanSet() { + continue + } + + fieldName := field.Name + + tagValue := field.Tag.Get(tagName) + tagParts := strings.SplitN(tagValue, ",", 2) + if len(tagParts) >= 2 { + switch tagParts[1] { + case "decodedFields": + decodedFieldsVal = append(decodedFieldsVal, fieldValue) + continue + case "key": + if item == nil { + return &parser.PosError{ + Pos: node.Pos(), + Err: fmt.Errorf("%s: %s asked for 'key', impossible", + name, fieldName), + } + } + + fieldValue.SetString(item.Keys[0].Token.Value().(string)) + continue + case "unusedKeys": + unusedKeysVal = append(unusedKeysVal, fieldValue) + continue + } + } + + if tagParts[0] != "" { + fieldName = tagParts[0] + } + + // Determine the element we'll use to decode. If it is a single + // match (only object with the field), then we decode it exactly. + // If it is a prefix match, then we decode the matches. + filter := list.Filter(fieldName) + + prefixMatches := filter.Children() + matches := filter.Elem() + if len(matches.Items) == 0 && len(prefixMatches.Items) == 0 { + continue + } + + // Track the used key + usedKeys[fieldName] = struct{}{} + + // Create the field name and decode. We range over the elements + // because we actually want the value. + fieldName = fmt.Sprintf("%s.%s", name, fieldName) + if len(prefixMatches.Items) > 0 { + if err := d.decode(fieldName, prefixMatches, fieldValue); err != nil { + return err + } + } + for _, match := range matches.Items { + var decodeNode ast.Node = match.Val + if ot, ok := decodeNode.(*ast.ObjectType); ok { + decodeNode = &ast.ObjectList{Items: ot.List.Items} + } + + if err := d.decode(fieldName, decodeNode, fieldValue); err != nil { + return err + } + } + + decodedFields = append(decodedFields, field.Name) + } + + if len(decodedFieldsVal) > 0 { + // Sort it so that it is deterministic + sort.Strings(decodedFields) + + for _, v := range decodedFieldsVal { + v.Set(reflect.ValueOf(decodedFields)) + } + } + + return nil +} + +// findNodeType returns the type of ast.Node +func findNodeType() reflect.Type { + var nodeContainer struct { + Node ast.Node + } + value := reflect.ValueOf(nodeContainer).FieldByName("Node") + return value.Type() +} diff --git a/vendor/github.com/hashicorp/hcl/go.mod b/vendor/github.com/hashicorp/hcl/go.mod new file mode 100644 index 0000000..4debbbe --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/go.mod @@ -0,0 +1,3 @@ +module github.com/hashicorp/hcl + +require github.com/davecgh/go-spew v1.1.1 diff --git a/vendor/github.com/hashicorp/hcl/go.sum b/vendor/github.com/hashicorp/hcl/go.sum new file mode 100644 index 0000000..b5e2922 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/go.sum @@ -0,0 +1,2 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vendor/github.com/hashicorp/hcl/hcl.go b/vendor/github.com/hashicorp/hcl/hcl.go new file mode 100644 index 0000000..575a20b --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl.go @@ -0,0 +1,11 @@ +// Package hcl decodes HCL into usable Go structures. +// +// hcl input can come in either pure HCL format or JSON format. +// It can be parsed into an AST, and then decoded into a structure, +// or it can be decoded directly from a string into a structure. +// +// If you choose to parse HCL into a raw AST, the benefit is that you +// can write custom visitor implementations to implement custom +// semantic checks. By default, HCL does not perform any semantic +// checks. +package hcl diff --git a/vendor/github.com/hashicorp/hcl/hcl/ast/ast.go b/vendor/github.com/hashicorp/hcl/hcl/ast/ast.go new file mode 100644 index 0000000..6e5ef65 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/ast/ast.go @@ -0,0 +1,219 @@ +// Package ast declares the types used to represent syntax trees for HCL +// (HashiCorp Configuration Language) +package ast + +import ( + "fmt" + "strings" + + "github.com/hashicorp/hcl/hcl/token" +) + +// Node is an element in the abstract syntax tree. +type Node interface { + node() + Pos() token.Pos +} + +func (File) node() {} +func (ObjectList) node() {} +func (ObjectKey) node() {} +func (ObjectItem) node() {} +func (Comment) node() {} +func (CommentGroup) node() {} +func (ObjectType) node() {} +func (LiteralType) node() {} +func (ListType) node() {} + +// File represents a single HCL file +type File struct { + Node Node // usually a *ObjectList + Comments []*CommentGroup // list of all comments in the source +} + +func (f *File) Pos() token.Pos { + return f.Node.Pos() +} + +// ObjectList represents a list of ObjectItems. An HCL file itself is an +// ObjectList. +type ObjectList struct { + Items []*ObjectItem +} + +func (o *ObjectList) Add(item *ObjectItem) { + o.Items = append(o.Items, item) +} + +// Filter filters out the objects with the given key list as a prefix. +// +// The returned list of objects contain ObjectItems where the keys have +// this prefix already stripped off. This might result in objects with +// zero-length key lists if they have no children. +// +// If no matches are found, an empty ObjectList (non-nil) is returned. +func (o *ObjectList) Filter(keys ...string) *ObjectList { + var result ObjectList + for _, item := range o.Items { + // If there aren't enough keys, then ignore this + if len(item.Keys) < len(keys) { + continue + } + + match := true + for i, key := range item.Keys[:len(keys)] { + key := key.Token.Value().(string) + if key != keys[i] && !strings.EqualFold(key, keys[i]) { + match = false + break + } + } + if !match { + continue + } + + // Strip off the prefix from the children + newItem := *item + newItem.Keys = newItem.Keys[len(keys):] + result.Add(&newItem) + } + + return &result +} + +// Children returns further nested objects (key length > 0) within this +// ObjectList. This should be used with Filter to get at child items. +func (o *ObjectList) Children() *ObjectList { + var result ObjectList + for _, item := range o.Items { + if len(item.Keys) > 0 { + result.Add(item) + } + } + + return &result +} + +// Elem returns items in the list that are direct element assignments +// (key length == 0). This should be used with Filter to get at elements. +func (o *ObjectList) Elem() *ObjectList { + var result ObjectList + for _, item := range o.Items { + if len(item.Keys) == 0 { + result.Add(item) + } + } + + return &result +} + +func (o *ObjectList) Pos() token.Pos { + // always returns the uninitiliazed position + return o.Items[0].Pos() +} + +// ObjectItem represents a HCL Object Item. An item is represented with a key +// (or keys). It can be an assignment or an object (both normal and nested) +type ObjectItem struct { + // keys is only one length long if it's of type assignment. If it's a + // nested object it can be larger than one. In that case "assign" is + // invalid as there is no assignments for a nested object. + Keys []*ObjectKey + + // assign contains the position of "=", if any + Assign token.Pos + + // val is the item itself. It can be an object,list, number, bool or a + // string. If key length is larger than one, val can be only of type + // Object. + Val Node + + LeadComment *CommentGroup // associated lead comment + LineComment *CommentGroup // associated line comment +} + +func (o *ObjectItem) Pos() token.Pos { + // I'm not entirely sure what causes this, but removing this causes + // a test failure. We should investigate at some point. + if len(o.Keys) == 0 { + return token.Pos{} + } + + return o.Keys[0].Pos() +} + +// ObjectKeys are either an identifier or of type string. +type ObjectKey struct { + Token token.Token +} + +func (o *ObjectKey) Pos() token.Pos { + return o.Token.Pos +} + +// LiteralType represents a literal of basic type. Valid types are: +// token.NUMBER, token.FLOAT, token.BOOL and token.STRING +type LiteralType struct { + Token token.Token + + // comment types, only used when in a list + LeadComment *CommentGroup + LineComment *CommentGroup +} + +func (l *LiteralType) Pos() token.Pos { + return l.Token.Pos +} + +// ListStatement represents a HCL List type +type ListType struct { + Lbrack token.Pos // position of "[" + Rbrack token.Pos // position of "]" + List []Node // the elements in lexical order +} + +func (l *ListType) Pos() token.Pos { + return l.Lbrack +} + +func (l *ListType) Add(node Node) { + l.List = append(l.List, node) +} + +// ObjectType represents a HCL Object Type +type ObjectType struct { + Lbrace token.Pos // position of "{" + Rbrace token.Pos // position of "}" + List *ObjectList // the nodes in lexical order +} + +func (o *ObjectType) Pos() token.Pos { + return o.Lbrace +} + +// Comment node represents a single //, # style or /*- style commment +type Comment struct { + Start token.Pos // position of / or # + Text string +} + +func (c *Comment) Pos() token.Pos { + return c.Start +} + +// CommentGroup node represents a sequence of comments with no other tokens and +// no empty lines between. +type CommentGroup struct { + List []*Comment // len(List) > 0 +} + +func (c *CommentGroup) Pos() token.Pos { + return c.List[0].Pos() +} + +//------------------------------------------------------------------- +// GoStringer +//------------------------------------------------------------------- + +func (o *ObjectKey) GoString() string { return fmt.Sprintf("*%#v", *o) } +func (o *ObjectList) GoString() string { return fmt.Sprintf("*%#v", *o) } diff --git a/vendor/github.com/hashicorp/hcl/hcl/ast/walk.go b/vendor/github.com/hashicorp/hcl/hcl/ast/walk.go new file mode 100644 index 0000000..ba07ad4 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/ast/walk.go @@ -0,0 +1,52 @@ +package ast + +import "fmt" + +// WalkFunc describes a function to be called for each node during a Walk. The +// returned node can be used to rewrite the AST. Walking stops the returned +// bool is false. +type WalkFunc func(Node) (Node, bool) + +// Walk traverses an AST in depth-first order: It starts by calling fn(node); +// node must not be nil. If fn returns true, Walk invokes fn recursively for +// each of the non-nil children of node, followed by a call of fn(nil). The +// returned node of fn can be used to rewrite the passed node to fn. +func Walk(node Node, fn WalkFunc) Node { + rewritten, ok := fn(node) + if !ok { + return rewritten + } + + switch n := node.(type) { + case *File: + n.Node = Walk(n.Node, fn) + case *ObjectList: + for i, item := range n.Items { + n.Items[i] = Walk(item, fn).(*ObjectItem) + } + case *ObjectKey: + // nothing to do + case *ObjectItem: + for i, k := range n.Keys { + n.Keys[i] = Walk(k, fn).(*ObjectKey) + } + + if n.Val != nil { + n.Val = Walk(n.Val, fn) + } + case *LiteralType: + // nothing to do + case *ListType: + for i, l := range n.List { + n.List[i] = Walk(l, fn) + } + case *ObjectType: + n.List = Walk(n.List, fn).(*ObjectList) + default: + // should we panic here? + fmt.Printf("unknown type: %T\n", n) + } + + fn(nil) + return rewritten +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/parser/error.go b/vendor/github.com/hashicorp/hcl/hcl/parser/error.go new file mode 100644 index 0000000..5c99381 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/parser/error.go @@ -0,0 +1,17 @@ +package parser + +import ( + "fmt" + + "github.com/hashicorp/hcl/hcl/token" +) + +// PosError is a parse error that contains a position. +type PosError struct { + Pos token.Pos + Err error +} + +func (e *PosError) Error() string { + return fmt.Sprintf("At %s: %s", e.Pos, e.Err) +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go new file mode 100644 index 0000000..64c83bc --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go @@ -0,0 +1,532 @@ +// Package parser implements a parser for HCL (HashiCorp Configuration +// Language) +package parser + +import ( + "bytes" + "errors" + "fmt" + "strings" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/scanner" + "github.com/hashicorp/hcl/hcl/token" +) + +type Parser struct { + sc *scanner.Scanner + + // Last read token + tok token.Token + commaPrev token.Token + + comments []*ast.CommentGroup + leadComment *ast.CommentGroup // last lead comment + lineComment *ast.CommentGroup // last line comment + + enableTrace bool + indent int + n int // buffer size (max = 1) +} + +func newParser(src []byte) *Parser { + return &Parser{ + sc: scanner.New(src), + } +} + +// Parse returns the fully parsed source and returns the abstract syntax tree. +func Parse(src []byte) (*ast.File, error) { + // normalize all line endings + // since the scanner and output only work with "\n" line endings, we may + // end up with dangling "\r" characters in the parsed data. + src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1) + + p := newParser(src) + return p.Parse() +} + +var errEofToken = errors.New("EOF token found") + +// Parse returns the fully parsed source and returns the abstract syntax tree. +func (p *Parser) Parse() (*ast.File, error) { + f := &ast.File{} + var err, scerr error + p.sc.Error = func(pos token.Pos, msg string) { + scerr = &PosError{Pos: pos, Err: errors.New(msg)} + } + + f.Node, err = p.objectList(false) + if scerr != nil { + return nil, scerr + } + if err != nil { + return nil, err + } + + f.Comments = p.comments + return f, nil +} + +// objectList parses a list of items within an object (generally k/v pairs). +// The parameter" obj" tells this whether to we are within an object (braces: +// '{', '}') or just at the top level. If we're within an object, we end +// at an RBRACE. +func (p *Parser) objectList(obj bool) (*ast.ObjectList, error) { + defer un(trace(p, "ParseObjectList")) + node := &ast.ObjectList{} + + for { + if obj { + tok := p.scan() + p.unscan() + if tok.Type == token.RBRACE { + break + } + } + + n, err := p.objectItem() + if err == errEofToken { + break // we are finished + } + + // we don't return a nil node, because might want to use already + // collected items. + if err != nil { + return node, err + } + + node.Add(n) + + // object lists can be optionally comma-delimited e.g. when a list of maps + // is being expressed, so a comma is allowed here - it's simply consumed + tok := p.scan() + if tok.Type != token.COMMA { + p.unscan() + } + } + return node, nil +} + +func (p *Parser) consumeComment() (comment *ast.Comment, endline int) { + endline = p.tok.Pos.Line + + // count the endline if it's multiline comment, ie starting with /* + if len(p.tok.Text) > 1 && p.tok.Text[1] == '*' { + // don't use range here - no need to decode Unicode code points + for i := 0; i < len(p.tok.Text); i++ { + if p.tok.Text[i] == '\n' { + endline++ + } + } + } + + comment = &ast.Comment{Start: p.tok.Pos, Text: p.tok.Text} + p.tok = p.sc.Scan() + return +} + +func (p *Parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) { + var list []*ast.Comment + endline = p.tok.Pos.Line + + for p.tok.Type == token.COMMENT && p.tok.Pos.Line <= endline+n { + var comment *ast.Comment + comment, endline = p.consumeComment() + list = append(list, comment) + } + + // add comment group to the comments list + comments = &ast.CommentGroup{List: list} + p.comments = append(p.comments, comments) + + return +} + +// objectItem parses a single object item +func (p *Parser) objectItem() (*ast.ObjectItem, error) { + defer un(trace(p, "ParseObjectItem")) + + keys, err := p.objectKey() + if len(keys) > 0 && err == errEofToken { + // We ignore eof token here since it is an error if we didn't + // receive a value (but we did receive a key) for the item. + err = nil + } + if len(keys) > 0 && err != nil && p.tok.Type == token.RBRACE { + // This is a strange boolean statement, but what it means is: + // We have keys with no value, and we're likely in an object + // (since RBrace ends an object). For this, we set err to nil so + // we continue and get the error below of having the wrong value + // type. + err = nil + + // Reset the token type so we don't think it completed fine. See + // objectType which uses p.tok.Type to check if we're done with + // the object. + p.tok.Type = token.EOF + } + if err != nil { + return nil, err + } + + o := &ast.ObjectItem{ + Keys: keys, + } + + if p.leadComment != nil { + o.LeadComment = p.leadComment + p.leadComment = nil + } + + switch p.tok.Type { + case token.ASSIGN: + o.Assign = p.tok.Pos + o.Val, err = p.object() + if err != nil { + return nil, err + } + case token.LBRACE: + o.Val, err = p.objectType() + if err != nil { + return nil, err + } + default: + keyStr := make([]string, 0, len(keys)) + for _, k := range keys { + keyStr = append(keyStr, k.Token.Text) + } + + return nil, &PosError{ + Pos: p.tok.Pos, + Err: fmt.Errorf( + "key '%s' expected start of object ('{') or assignment ('=')", + strings.Join(keyStr, " ")), + } + } + + // key=#comment + // val + if p.lineComment != nil { + o.LineComment, p.lineComment = p.lineComment, nil + } + + // do a look-ahead for line comment + p.scan() + if len(keys) > 0 && o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil { + o.LineComment = p.lineComment + p.lineComment = nil + } + p.unscan() + return o, nil +} + +// objectKey parses an object key and returns a ObjectKey AST +func (p *Parser) objectKey() ([]*ast.ObjectKey, error) { + keyCount := 0 + keys := make([]*ast.ObjectKey, 0) + + for { + tok := p.scan() + switch tok.Type { + case token.EOF: + // It is very important to also return the keys here as well as + // the error. This is because we need to be able to tell if we + // did parse keys prior to finding the EOF, or if we just found + // a bare EOF. + return keys, errEofToken + case token.ASSIGN: + // assignment or object only, but not nested objects. this is not + // allowed: `foo bar = {}` + if keyCount > 1 { + return nil, &PosError{ + Pos: p.tok.Pos, + Err: fmt.Errorf("nested object expected: LBRACE got: %s", p.tok.Type), + } + } + + if keyCount == 0 { + return nil, &PosError{ + Pos: p.tok.Pos, + Err: errors.New("no object keys found!"), + } + } + + return keys, nil + case token.LBRACE: + var err error + + // If we have no keys, then it is a syntax error. i.e. {{}} is not + // allowed. + if len(keys) == 0 { + err = &PosError{ + Pos: p.tok.Pos, + Err: fmt.Errorf("expected: IDENT | STRING got: %s", p.tok.Type), + } + } + + // object + return keys, err + case token.IDENT, token.STRING: + keyCount++ + keys = append(keys, &ast.ObjectKey{Token: p.tok}) + case token.ILLEGAL: + return keys, &PosError{ + Pos: p.tok.Pos, + Err: fmt.Errorf("illegal character"), + } + default: + return keys, &PosError{ + Pos: p.tok.Pos, + Err: fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", p.tok.Type), + } + } + } +} + +// object parses any type of object, such as number, bool, string, object or +// list. +func (p *Parser) object() (ast.Node, error) { + defer un(trace(p, "ParseType")) + tok := p.scan() + + switch tok.Type { + case token.NUMBER, token.FLOAT, token.BOOL, token.STRING, token.HEREDOC: + return p.literalType() + case token.LBRACE: + return p.objectType() + case token.LBRACK: + return p.listType() + case token.COMMENT: + // implement comment + case token.EOF: + return nil, errEofToken + } + + return nil, &PosError{ + Pos: tok.Pos, + Err: fmt.Errorf("Unknown token: %+v", tok), + } +} + +// objectType parses an object type and returns a ObjectType AST +func (p *Parser) objectType() (*ast.ObjectType, error) { + defer un(trace(p, "ParseObjectType")) + + // we assume that the currently scanned token is a LBRACE + o := &ast.ObjectType{ + Lbrace: p.tok.Pos, + } + + l, err := p.objectList(true) + + // if we hit RBRACE, we are good to go (means we parsed all Items), if it's + // not a RBRACE, it's an syntax error and we just return it. + if err != nil && p.tok.Type != token.RBRACE { + return nil, err + } + + // No error, scan and expect the ending to be a brace + if tok := p.scan(); tok.Type != token.RBRACE { + return nil, &PosError{ + Pos: tok.Pos, + Err: fmt.Errorf("object expected closing RBRACE got: %s", tok.Type), + } + } + + o.List = l + o.Rbrace = p.tok.Pos // advanced via parseObjectList + return o, nil +} + +// listType parses a list type and returns a ListType AST +func (p *Parser) listType() (*ast.ListType, error) { + defer un(trace(p, "ParseListType")) + + // we assume that the currently scanned token is a LBRACK + l := &ast.ListType{ + Lbrack: p.tok.Pos, + } + + needComma := false + for { + tok := p.scan() + if needComma { + switch tok.Type { + case token.COMMA, token.RBRACK: + default: + return nil, &PosError{ + Pos: tok.Pos, + Err: fmt.Errorf( + "error parsing list, expected comma or list end, got: %s", + tok.Type), + } + } + } + switch tok.Type { + case token.BOOL, token.NUMBER, token.FLOAT, token.STRING, token.HEREDOC: + node, err := p.literalType() + if err != nil { + return nil, err + } + + // If there is a lead comment, apply it + if p.leadComment != nil { + node.LeadComment = p.leadComment + p.leadComment = nil + } + + l.Add(node) + needComma = true + case token.COMMA: + // get next list item or we are at the end + // do a look-ahead for line comment + p.scan() + if p.lineComment != nil && len(l.List) > 0 { + lit, ok := l.List[len(l.List)-1].(*ast.LiteralType) + if ok { + lit.LineComment = p.lineComment + l.List[len(l.List)-1] = lit + p.lineComment = nil + } + } + p.unscan() + + needComma = false + continue + case token.LBRACE: + // Looks like a nested object, so parse it out + node, err := p.objectType() + if err != nil { + return nil, &PosError{ + Pos: tok.Pos, + Err: fmt.Errorf( + "error while trying to parse object within list: %s", err), + } + } + l.Add(node) + needComma = true + case token.LBRACK: + node, err := p.listType() + if err != nil { + return nil, &PosError{ + Pos: tok.Pos, + Err: fmt.Errorf( + "error while trying to parse list within list: %s", err), + } + } + l.Add(node) + case token.RBRACK: + // finished + l.Rbrack = p.tok.Pos + return l, nil + default: + return nil, &PosError{ + Pos: tok.Pos, + Err: fmt.Errorf("unexpected token while parsing list: %s", tok.Type), + } + } + } +} + +// literalType parses a literal type and returns a LiteralType AST +func (p *Parser) literalType() (*ast.LiteralType, error) { + defer un(trace(p, "ParseLiteral")) + + return &ast.LiteralType{ + Token: p.tok, + }, nil +} + +// scan returns the next token from the underlying scanner. If a token has +// been unscanned then read that instead. In the process, it collects any +// comment groups encountered, and remembers the last lead and line comments. +func (p *Parser) scan() token.Token { + // If we have a token on the buffer, then return it. + if p.n != 0 { + p.n = 0 + return p.tok + } + + // Otherwise read the next token from the scanner and Save it to the buffer + // in case we unscan later. + prev := p.tok + p.tok = p.sc.Scan() + + if p.tok.Type == token.COMMENT { + var comment *ast.CommentGroup + var endline int + + // fmt.Printf("p.tok.Pos.Line = %+v prev: %d endline %d \n", + // p.tok.Pos.Line, prev.Pos.Line, endline) + if p.tok.Pos.Line == prev.Pos.Line { + // The comment is on same line as the previous token; it + // cannot be a lead comment but may be a line comment. + comment, endline = p.consumeCommentGroup(0) + if p.tok.Pos.Line != endline { + // The next token is on a different line, thus + // the last comment group is a line comment. + p.lineComment = comment + } + } + + // consume successor comments, if any + endline = -1 + for p.tok.Type == token.COMMENT { + comment, endline = p.consumeCommentGroup(1) + } + + if endline+1 == p.tok.Pos.Line && p.tok.Type != token.RBRACE { + switch p.tok.Type { + case token.RBRACE, token.RBRACK: + // Do not count for these cases + default: + // The next token is following on the line immediately after the + // comment group, thus the last comment group is a lead comment. + p.leadComment = comment + } + } + + } + + return p.tok +} + +// unscan pushes the previously read token back onto the buffer. +func (p *Parser) unscan() { + p.n = 1 +} + +// ---------------------------------------------------------------------------- +// Parsing support + +func (p *Parser) printTrace(a ...interface{}) { + if !p.enableTrace { + return + } + + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = len(dots) + fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column) + + i := 2 * p.indent + for i > n { + fmt.Print(dots) + i -= n + } + // i <= n + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *Parser, msg string) *Parser { + p.printTrace(msg, "(") + p.indent++ + return p +} + +// Usage pattern: defer un(trace(p, "...")) +func un(p *Parser) { + p.indent-- + p.printTrace(")") +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go new file mode 100644 index 0000000..7c038d1 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go @@ -0,0 +1,789 @@ +package printer + +import ( + "bytes" + "fmt" + "sort" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/token" +) + +const ( + blank = byte(' ') + newline = byte('\n') + tab = byte('\t') + infinity = 1 << 30 // offset or line +) + +var ( + unindent = []byte("\uE123") // in the private use space +) + +type printer struct { + cfg Config + prev token.Pos + + comments []*ast.CommentGroup // may be nil, contains all comments + standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node) + + enableTrace bool + indentTrace int +} + +type ByPosition []*ast.CommentGroup + +func (b ByPosition) Len() int { return len(b) } +func (b ByPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) } + +// collectComments comments all standalone comments which are not lead or line +// comment +func (p *printer) collectComments(node ast.Node) { + // first collect all comments. This is already stored in + // ast.File.(comments) + ast.Walk(node, func(nn ast.Node) (ast.Node, bool) { + switch t := nn.(type) { + case *ast.File: + p.comments = t.Comments + return nn, false + } + return nn, true + }) + + standaloneComments := make(map[token.Pos]*ast.CommentGroup, 0) + for _, c := range p.comments { + standaloneComments[c.Pos()] = c + } + + // next remove all lead and line comments from the overall comment map. + // This will give us comments which are standalone, comments which are not + // assigned to any kind of node. + ast.Walk(node, func(nn ast.Node) (ast.Node, bool) { + switch t := nn.(type) { + case *ast.LiteralType: + if t.LeadComment != nil { + for _, comment := range t.LeadComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + + if t.LineComment != nil { + for _, comment := range t.LineComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + case *ast.ObjectItem: + if t.LeadComment != nil { + for _, comment := range t.LeadComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + + if t.LineComment != nil { + for _, comment := range t.LineComment.List { + if _, ok := standaloneComments[comment.Pos()]; ok { + delete(standaloneComments, comment.Pos()) + } + } + } + } + + return nn, true + }) + + for _, c := range standaloneComments { + p.standaloneComments = append(p.standaloneComments, c) + } + + sort.Sort(ByPosition(p.standaloneComments)) +} + +// output prints creates b printable HCL output and returns it. +func (p *printer) output(n interface{}) []byte { + var buf bytes.Buffer + + switch t := n.(type) { + case *ast.File: + // File doesn't trace so we add the tracing here + defer un(trace(p, "File")) + return p.output(t.Node) + case *ast.ObjectList: + defer un(trace(p, "ObjectList")) + + var index int + for { + // Determine the location of the next actual non-comment + // item. If we're at the end, the next item is at "infinity" + var nextItem token.Pos + if index != len(t.Items) { + nextItem = t.Items[index].Pos() + } else { + nextItem = token.Pos{Offset: infinity, Line: infinity} + } + + // Go through the standalone comments in the file and print out + // the comments that we should be for this object item. + for _, c := range p.standaloneComments { + // Go through all the comments in the group. The group + // should be printed together, not separated by double newlines. + printed := false + newlinePrinted := false + for _, comment := range c.List { + // We only care about comments after the previous item + // we've printed so that comments are printed in the + // correct locations (between two objects for example). + // And before the next item. + if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) { + // if we hit the end add newlines so we can print the comment + // we don't do this if prev is invalid which means the + // beginning of the file since the first comment should + // be at the first line. + if !newlinePrinted && p.prev.IsValid() && index == len(t.Items) { + buf.Write([]byte{newline, newline}) + newlinePrinted = true + } + + // Write the actual comment. + buf.WriteString(comment.Text) + buf.WriteByte(newline) + + // Set printed to true to note that we printed something + printed = true + } + } + + // If we're not at the last item, write a new line so + // that there is a newline separating this comment from + // the next object. + if printed && index != len(t.Items) { + buf.WriteByte(newline) + } + } + + if index == len(t.Items) { + break + } + + buf.Write(p.output(t.Items[index])) + if index != len(t.Items)-1 { + // Always write a newline to separate us from the next item + buf.WriteByte(newline) + + // Need to determine if we're going to separate the next item + // with a blank line. The logic here is simple, though there + // are a few conditions: + // + // 1. The next object is more than one line away anyways, + // so we need an empty line. + // + // 2. The next object is not a "single line" object, so + // we need an empty line. + // + // 3. This current object is not a single line object, + // so we need an empty line. + current := t.Items[index] + next := t.Items[index+1] + if next.Pos().Line != t.Items[index].Pos().Line+1 || + !p.isSingleLineObject(next) || + !p.isSingleLineObject(current) { + buf.WriteByte(newline) + } + } + index++ + } + case *ast.ObjectKey: + buf.WriteString(t.Token.Text) + case *ast.ObjectItem: + p.prev = t.Pos() + buf.Write(p.objectItem(t)) + case *ast.LiteralType: + buf.Write(p.literalType(t)) + case *ast.ListType: + buf.Write(p.list(t)) + case *ast.ObjectType: + buf.Write(p.objectType(t)) + default: + fmt.Printf(" unknown type: %T\n", n) + } + + return buf.Bytes() +} + +func (p *printer) literalType(lit *ast.LiteralType) []byte { + result := []byte(lit.Token.Text) + switch lit.Token.Type { + case token.HEREDOC: + // Clear the trailing newline from heredocs + if result[len(result)-1] == '\n' { + result = result[:len(result)-1] + } + + // Poison lines 2+ so that we don't indent them + result = p.heredocIndent(result) + case token.STRING: + // If this is a multiline string, poison lines 2+ so we don't + // indent them. + if bytes.IndexRune(result, '\n') >= 0 { + result = p.heredocIndent(result) + } + } + + return result +} + +// objectItem returns the printable HCL form of an object item. An object type +// starts with one/multiple keys and has a value. The value might be of any +// type. +func (p *printer) objectItem(o *ast.ObjectItem) []byte { + defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text))) + var buf bytes.Buffer + + if o.LeadComment != nil { + for _, comment := range o.LeadComment.List { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + } + } + + // If key and val are on different lines, treat line comments like lead comments. + if o.LineComment != nil && o.Val.Pos().Line != o.Keys[0].Pos().Line { + for _, comment := range o.LineComment.List { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + } + } + + for i, k := range o.Keys { + buf.WriteString(k.Token.Text) + buf.WriteByte(blank) + + // reach end of key + if o.Assign.IsValid() && i == len(o.Keys)-1 && len(o.Keys) == 1 { + buf.WriteString("=") + buf.WriteByte(blank) + } + } + + buf.Write(p.output(o.Val)) + + if o.LineComment != nil && o.Val.Pos().Line == o.Keys[0].Pos().Line { + buf.WriteByte(blank) + for _, comment := range o.LineComment.List { + buf.WriteString(comment.Text) + } + } + + return buf.Bytes() +} + +// objectType returns the printable HCL form of an object type. An object type +// begins with a brace and ends with a brace. +func (p *printer) objectType(o *ast.ObjectType) []byte { + defer un(trace(p, "ObjectType")) + var buf bytes.Buffer + buf.WriteString("{") + + var index int + var nextItem token.Pos + var commented, newlinePrinted bool + for { + // Determine the location of the next actual non-comment + // item. If we're at the end, the next item is the closing brace + if index != len(o.List.Items) { + nextItem = o.List.Items[index].Pos() + } else { + nextItem = o.Rbrace + } + + // Go through the standalone comments in the file and print out + // the comments that we should be for this object item. + for _, c := range p.standaloneComments { + printed := false + var lastCommentPos token.Pos + for _, comment := range c.List { + // We only care about comments after the previous item + // we've printed so that comments are printed in the + // correct locations (between two objects for example). + // And before the next item. + if comment.Pos().After(p.prev) && comment.Pos().Before(nextItem) { + // If there are standalone comments and the initial newline has not + // been printed yet, do it now. + if !newlinePrinted { + newlinePrinted = true + buf.WriteByte(newline) + } + + // add newline if it's between other printed nodes + if index > 0 { + commented = true + buf.WriteByte(newline) + } + + // Store this position + lastCommentPos = comment.Pos() + + // output the comment itself + buf.Write(p.indent(p.heredocIndent([]byte(comment.Text)))) + + // Set printed to true to note that we printed something + printed = true + + /* + if index != len(o.List.Items) { + buf.WriteByte(newline) // do not print on the end + } + */ + } + } + + // Stuff to do if we had comments + if printed { + // Always write a newline + buf.WriteByte(newline) + + // If there is another item in the object and our comment + // didn't hug it directly, then make sure there is a blank + // line separating them. + if nextItem != o.Rbrace && nextItem.Line != lastCommentPos.Line+1 { + buf.WriteByte(newline) + } + } + } + + if index == len(o.List.Items) { + p.prev = o.Rbrace + break + } + + // At this point we are sure that it's not a totally empty block: print + // the initial newline if it hasn't been printed yet by the previous + // block about standalone comments. + if !newlinePrinted { + buf.WriteByte(newline) + newlinePrinted = true + } + + // check if we have adjacent one liner items. If yes we'll going to align + // the comments. + var aligned []*ast.ObjectItem + for _, item := range o.List.Items[index:] { + // we don't group one line lists + if len(o.List.Items) == 1 { + break + } + + // one means a oneliner with out any lead comment + // two means a oneliner with lead comment + // anything else might be something else + cur := lines(string(p.objectItem(item))) + if cur > 2 { + break + } + + curPos := item.Pos() + + nextPos := token.Pos{} + if index != len(o.List.Items)-1 { + nextPos = o.List.Items[index+1].Pos() + } + + prevPos := token.Pos{} + if index != 0 { + prevPos = o.List.Items[index-1].Pos() + } + + // fmt.Println("DEBUG ----------------") + // fmt.Printf("prev = %+v prevPos: %s\n", prev, prevPos) + // fmt.Printf("cur = %+v curPos: %s\n", cur, curPos) + // fmt.Printf("next = %+v nextPos: %s\n", next, nextPos) + + if curPos.Line+1 == nextPos.Line { + aligned = append(aligned, item) + index++ + continue + } + + if curPos.Line-1 == prevPos.Line { + aligned = append(aligned, item) + index++ + + // finish if we have a new line or comment next. This happens + // if the next item is not adjacent + if curPos.Line+1 != nextPos.Line { + break + } + continue + } + + break + } + + // put newlines if the items are between other non aligned items. + // newlines are also added if there is a standalone comment already, so + // check it too + if !commented && index != len(aligned) { + buf.WriteByte(newline) + } + + if len(aligned) >= 1 { + p.prev = aligned[len(aligned)-1].Pos() + + items := p.alignedItems(aligned) + buf.Write(p.indent(items)) + } else { + p.prev = o.List.Items[index].Pos() + + buf.Write(p.indent(p.objectItem(o.List.Items[index]))) + index++ + } + + buf.WriteByte(newline) + } + + buf.WriteString("}") + return buf.Bytes() +} + +func (p *printer) alignedItems(items []*ast.ObjectItem) []byte { + var buf bytes.Buffer + + // find the longest key and value length, needed for alignment + var longestKeyLen int // longest key length + var longestValLen int // longest value length + for _, item := range items { + key := len(item.Keys[0].Token.Text) + val := len(p.output(item.Val)) + + if key > longestKeyLen { + longestKeyLen = key + } + + if val > longestValLen { + longestValLen = val + } + } + + for i, item := range items { + if item.LeadComment != nil { + for _, comment := range item.LeadComment.List { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + } + } + + for i, k := range item.Keys { + keyLen := len(k.Token.Text) + buf.WriteString(k.Token.Text) + for i := 0; i < longestKeyLen-keyLen+1; i++ { + buf.WriteByte(blank) + } + + // reach end of key + if i == len(item.Keys)-1 && len(item.Keys) == 1 { + buf.WriteString("=") + buf.WriteByte(blank) + } + } + + val := p.output(item.Val) + valLen := len(val) + buf.Write(val) + + if item.Val.Pos().Line == item.Keys[0].Pos().Line && item.LineComment != nil { + for i := 0; i < longestValLen-valLen+1; i++ { + buf.WriteByte(blank) + } + + for _, comment := range item.LineComment.List { + buf.WriteString(comment.Text) + } + } + + // do not print for the last item + if i != len(items)-1 { + buf.WriteByte(newline) + } + } + + return buf.Bytes() +} + +// list returns the printable HCL form of an list type. +func (p *printer) list(l *ast.ListType) []byte { + if p.isSingleLineList(l) { + return p.singleLineList(l) + } + + var buf bytes.Buffer + buf.WriteString("[") + buf.WriteByte(newline) + + var longestLine int + for _, item := range l.List { + // for now we assume that the list only contains literal types + if lit, ok := item.(*ast.LiteralType); ok { + lineLen := len(lit.Token.Text) + if lineLen > longestLine { + longestLine = lineLen + } + } + } + + haveEmptyLine := false + for i, item := range l.List { + // If we have a lead comment, then we want to write that first + leadComment := false + if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil { + leadComment = true + + // Ensure an empty line before every element with a + // lead comment (except the first item in a list). + if !haveEmptyLine && i != 0 { + buf.WriteByte(newline) + } + + for _, comment := range lit.LeadComment.List { + buf.Write(p.indent([]byte(comment.Text))) + buf.WriteByte(newline) + } + } + + // also indent each line + val := p.output(item) + curLen := len(val) + buf.Write(p.indent(val)) + + // if this item is a heredoc, then we output the comma on + // the next line. This is the only case this happens. + comma := []byte{','} + if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC { + buf.WriteByte(newline) + comma = p.indent(comma) + } + + buf.Write(comma) + + if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil { + // if the next item doesn't have any comments, do not align + buf.WriteByte(blank) // align one space + for i := 0; i < longestLine-curLen; i++ { + buf.WriteByte(blank) + } + + for _, comment := range lit.LineComment.List { + buf.WriteString(comment.Text) + } + } + + buf.WriteByte(newline) + + // Ensure an empty line after every element with a + // lead comment (except the first item in a list). + haveEmptyLine = leadComment && i != len(l.List)-1 + if haveEmptyLine { + buf.WriteByte(newline) + } + } + + buf.WriteString("]") + return buf.Bytes() +} + +// isSingleLineList returns true if: +// * they were previously formatted entirely on one line +// * they consist entirely of literals +// * there are either no heredoc strings or the list has exactly one element +// * there are no line comments +func (printer) isSingleLineList(l *ast.ListType) bool { + for _, item := range l.List { + if item.Pos().Line != l.Lbrack.Line { + return false + } + + lit, ok := item.(*ast.LiteralType) + if !ok { + return false + } + + if lit.Token.Type == token.HEREDOC && len(l.List) != 1 { + return false + } + + if lit.LineComment != nil { + return false + } + } + + return true +} + +// singleLineList prints a simple single line list. +// For a definition of "simple", see isSingleLineList above. +func (p *printer) singleLineList(l *ast.ListType) []byte { + buf := &bytes.Buffer{} + + buf.WriteString("[") + for i, item := range l.List { + if i != 0 { + buf.WriteString(", ") + } + + // Output the item itself + buf.Write(p.output(item)) + + // The heredoc marker needs to be at the end of line. + if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC { + buf.WriteByte(newline) + } + } + + buf.WriteString("]") + return buf.Bytes() +} + +// indent indents the lines of the given buffer for each non-empty line +func (p *printer) indent(buf []byte) []byte { + var prefix []byte + if p.cfg.SpacesWidth != 0 { + for i := 0; i < p.cfg.SpacesWidth; i++ { + prefix = append(prefix, blank) + } + } else { + prefix = []byte{tab} + } + + var res []byte + bol := true + for _, c := range buf { + if bol && c != '\n' { + res = append(res, prefix...) + } + + res = append(res, c) + bol = c == '\n' + } + return res +} + +// unindent removes all the indentation from the tombstoned lines +func (p *printer) unindent(buf []byte) []byte { + var res []byte + for i := 0; i < len(buf); i++ { + skip := len(buf)-i <= len(unindent) + if !skip { + skip = !bytes.Equal(unindent, buf[i:i+len(unindent)]) + } + if skip { + res = append(res, buf[i]) + continue + } + + // We have a marker. we have to backtrace here and clean out + // any whitespace ahead of our tombstone up to a \n + for j := len(res) - 1; j >= 0; j-- { + if res[j] == '\n' { + break + } + + res = res[:j] + } + + // Skip the entire unindent marker + i += len(unindent) - 1 + } + + return res +} + +// heredocIndent marks all the 2nd and further lines as unindentable +func (p *printer) heredocIndent(buf []byte) []byte { + var res []byte + bol := false + for _, c := range buf { + if bol && c != '\n' { + res = append(res, unindent...) + } + res = append(res, c) + bol = c == '\n' + } + return res +} + +// isSingleLineObject tells whether the given object item is a single +// line object such as "obj {}". +// +// A single line object: +// +// * has no lead comments (hence multi-line) +// * has no assignment +// * has no values in the stanza (within {}) +// +func (p *printer) isSingleLineObject(val *ast.ObjectItem) bool { + // If there is a lead comment, can't be one line + if val.LeadComment != nil { + return false + } + + // If there is assignment, we always break by line + if val.Assign.IsValid() { + return false + } + + // If it isn't an object type, then its not a single line object + ot, ok := val.Val.(*ast.ObjectType) + if !ok { + return false + } + + // If the object has no items, it is single line! + return len(ot.List.Items) == 0 +} + +func lines(txt string) int { + endline := 1 + for i := 0; i < len(txt); i++ { + if txt[i] == '\n' { + endline++ + } + } + return endline +} + +// ---------------------------------------------------------------------------- +// Tracing support + +func (p *printer) printTrace(a ...interface{}) { + if !p.enableTrace { + return + } + + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = len(dots) + i := 2 * p.indentTrace + for i > n { + fmt.Print(dots) + i -= n + } + // i <= n + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *printer, msg string) *printer { + p.printTrace(msg, "(") + p.indentTrace++ + return p +} + +// Usage pattern: defer un(trace(p, "...")) +func un(p *printer) { + p.indentTrace-- + p.printTrace(")") +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go new file mode 100644 index 0000000..6617ab8 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go @@ -0,0 +1,66 @@ +// Package printer implements printing of AST nodes to HCL format. +package printer + +import ( + "bytes" + "io" + "text/tabwriter" + + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/parser" +) + +var DefaultConfig = Config{ + SpacesWidth: 2, +} + +// A Config node controls the output of Fprint. +type Config struct { + SpacesWidth int // if set, it will use spaces instead of tabs for alignment +} + +func (c *Config) Fprint(output io.Writer, node ast.Node) error { + p := &printer{ + cfg: *c, + comments: make([]*ast.CommentGroup, 0), + standaloneComments: make([]*ast.CommentGroup, 0), + // enableTrace: true, + } + + p.collectComments(node) + + if _, err := output.Write(p.unindent(p.output(node))); err != nil { + return err + } + + // flush tabwriter, if any + var err error + if tw, _ := output.(*tabwriter.Writer); tw != nil { + err = tw.Flush() + } + + return err +} + +// Fprint "pretty-prints" an HCL node to output +// It calls Config.Fprint with default settings. +func Fprint(output io.Writer, node ast.Node) error { + return DefaultConfig.Fprint(output, node) +} + +// Format formats src HCL and returns the result. +func Format(src []byte) ([]byte, error) { + node, err := parser.Parse(src) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + if err := DefaultConfig.Fprint(&buf, node); err != nil { + return nil, err + } + + // Add trailing newline to result + buf.WriteString("\n") + return buf.Bytes(), nil +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go b/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go new file mode 100644 index 0000000..624a18f --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go @@ -0,0 +1,652 @@ +// Package scanner implements a scanner for HCL (HashiCorp Configuration +// Language) source text. +package scanner + +import ( + "bytes" + "fmt" + "os" + "regexp" + "unicode" + "unicode/utf8" + + "github.com/hashicorp/hcl/hcl/token" +) + +// eof represents a marker rune for the end of the reader. +const eof = rune(0) + +// Scanner defines a lexical scanner +type Scanner struct { + buf *bytes.Buffer // Source buffer for advancing and scanning + src []byte // Source buffer for immutable access + + // Source Position + srcPos token.Pos // current position + prevPos token.Pos // previous position, used for peek() method + + lastCharLen int // length of last character in bytes + lastLineLen int // length of last line in characters (for correct column reporting) + + tokStart int // token text start position + tokEnd int // token text end position + + // Error is called for each error encountered. If no Error + // function is set, the error is reported to os.Stderr. + Error func(pos token.Pos, msg string) + + // ErrorCount is incremented by one for each error encountered. + ErrorCount int + + // tokPos is the start position of most recently scanned token; set by + // Scan. The Filename field is always left untouched by the Scanner. If + // an error is reported (via Error) and Position is invalid, the scanner is + // not inside a token. + tokPos token.Pos +} + +// New creates and initializes a new instance of Scanner using src as +// its source content. +func New(src []byte) *Scanner { + // even though we accept a src, we read from a io.Reader compatible type + // (*bytes.Buffer). So in the future we might easily change it to streaming + // read. + b := bytes.NewBuffer(src) + s := &Scanner{ + buf: b, + src: src, + } + + // srcPosition always starts with 1 + s.srcPos.Line = 1 + return s +} + +// next reads the next rune from the bufferred reader. Returns the rune(0) if +// an error occurs (or io.EOF is returned). +func (s *Scanner) next() rune { + ch, size, err := s.buf.ReadRune() + if err != nil { + // advance for error reporting + s.srcPos.Column++ + s.srcPos.Offset += size + s.lastCharLen = size + return eof + } + + // remember last position + s.prevPos = s.srcPos + + s.srcPos.Column++ + s.lastCharLen = size + s.srcPos.Offset += size + + if ch == utf8.RuneError && size == 1 { + s.err("illegal UTF-8 encoding") + return ch + } + + if ch == '\n' { + s.srcPos.Line++ + s.lastLineLen = s.srcPos.Column + s.srcPos.Column = 0 + } + + if ch == '\x00' { + s.err("unexpected null character (0x00)") + return eof + } + + if ch == '\uE123' { + s.err("unicode code point U+E123 reserved for internal use") + return utf8.RuneError + } + + // debug + // fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column) + return ch +} + +// unread unreads the previous read Rune and updates the source position +func (s *Scanner) unread() { + if err := s.buf.UnreadRune(); err != nil { + panic(err) // this is user fault, we should catch it + } + s.srcPos = s.prevPos // put back last position +} + +// peek returns the next rune without advancing the reader. +func (s *Scanner) peek() rune { + peek, _, err := s.buf.ReadRune() + if err != nil { + return eof + } + + s.buf.UnreadRune() + return peek +} + +// Scan scans the next token and returns the token. +func (s *Scanner) Scan() token.Token { + ch := s.next() + + // skip white space + for isWhitespace(ch) { + ch = s.next() + } + + var tok token.Type + + // token text markings + s.tokStart = s.srcPos.Offset - s.lastCharLen + + // token position, initial next() is moving the offset by one(size of rune + // actually), though we are interested with the starting point + s.tokPos.Offset = s.srcPos.Offset - s.lastCharLen + if s.srcPos.Column > 0 { + // common case: last character was not a '\n' + s.tokPos.Line = s.srcPos.Line + s.tokPos.Column = s.srcPos.Column + } else { + // last character was a '\n' + // (we cannot be at the beginning of the source + // since we have called next() at least once) + s.tokPos.Line = s.srcPos.Line - 1 + s.tokPos.Column = s.lastLineLen + } + + switch { + case isLetter(ch): + tok = token.IDENT + lit := s.scanIdentifier() + if lit == "true" || lit == "false" { + tok = token.BOOL + } + case isDecimal(ch): + tok = s.scanNumber(ch) + default: + switch ch { + case eof: + tok = token.EOF + case '"': + tok = token.STRING + s.scanString() + case '#', '/': + tok = token.COMMENT + s.scanComment(ch) + case '.': + tok = token.PERIOD + ch = s.peek() + if isDecimal(ch) { + tok = token.FLOAT + ch = s.scanMantissa(ch) + ch = s.scanExponent(ch) + } + case '<': + tok = token.HEREDOC + s.scanHeredoc() + case '[': + tok = token.LBRACK + case ']': + tok = token.RBRACK + case '{': + tok = token.LBRACE + case '}': + tok = token.RBRACE + case ',': + tok = token.COMMA + case '=': + tok = token.ASSIGN + case '+': + tok = token.ADD + case '-': + if isDecimal(s.peek()) { + ch := s.next() + tok = s.scanNumber(ch) + } else { + tok = token.SUB + } + default: + s.err("illegal char") + } + } + + // finish token ending + s.tokEnd = s.srcPos.Offset + + // create token literal + var tokenText string + if s.tokStart >= 0 { + tokenText = string(s.src[s.tokStart:s.tokEnd]) + } + s.tokStart = s.tokEnd // ensure idempotency of tokenText() call + + return token.Token{ + Type: tok, + Pos: s.tokPos, + Text: tokenText, + } +} + +func (s *Scanner) scanComment(ch rune) { + // single line comments + if ch == '#' || (ch == '/' && s.peek() != '*') { + if ch == '/' && s.peek() != '/' { + s.err("expected '/' for comment") + return + } + + ch = s.next() + for ch != '\n' && ch >= 0 && ch != eof { + ch = s.next() + } + if ch != eof && ch >= 0 { + s.unread() + } + return + } + + // be sure we get the character after /* This allows us to find comment's + // that are not erminated + if ch == '/' { + s.next() + ch = s.next() // read character after "/*" + } + + // look for /* - style comments + for { + if ch < 0 || ch == eof { + s.err("comment not terminated") + break + } + + ch0 := ch + ch = s.next() + if ch0 == '*' && ch == '/' { + break + } + } +} + +// scanNumber scans a HCL number definition starting with the given rune +func (s *Scanner) scanNumber(ch rune) token.Type { + if ch == '0' { + // check for hexadecimal, octal or float + ch = s.next() + if ch == 'x' || ch == 'X' { + // hexadecimal + ch = s.next() + found := false + for isHexadecimal(ch) { + ch = s.next() + found = true + } + + if !found { + s.err("illegal hexadecimal number") + } + + if ch != eof { + s.unread() + } + + return token.NUMBER + } + + // now it's either something like: 0421(octal) or 0.1231(float) + illegalOctal := false + for isDecimal(ch) { + ch = s.next() + if ch == '8' || ch == '9' { + // this is just a possibility. For example 0159 is illegal, but + // 0159.23 is valid. So we mark a possible illegal octal. If + // the next character is not a period, we'll print the error. + illegalOctal = true + } + } + + if ch == 'e' || ch == 'E' { + ch = s.scanExponent(ch) + return token.FLOAT + } + + if ch == '.' { + ch = s.scanFraction(ch) + + if ch == 'e' || ch == 'E' { + ch = s.next() + ch = s.scanExponent(ch) + } + return token.FLOAT + } + + if illegalOctal { + s.err("illegal octal number") + } + + if ch != eof { + s.unread() + } + return token.NUMBER + } + + s.scanMantissa(ch) + ch = s.next() // seek forward + if ch == 'e' || ch == 'E' { + ch = s.scanExponent(ch) + return token.FLOAT + } + + if ch == '.' { + ch = s.scanFraction(ch) + if ch == 'e' || ch == 'E' { + ch = s.next() + ch = s.scanExponent(ch) + } + return token.FLOAT + } + + if ch != eof { + s.unread() + } + return token.NUMBER +} + +// scanMantissa scans the mantissa beginning from the rune. It returns the next +// non decimal rune. It's used to determine wheter it's a fraction or exponent. +func (s *Scanner) scanMantissa(ch rune) rune { + scanned := false + for isDecimal(ch) { + ch = s.next() + scanned = true + } + + if scanned && ch != eof { + s.unread() + } + return ch +} + +// scanFraction scans the fraction after the '.' rune +func (s *Scanner) scanFraction(ch rune) rune { + if ch == '.' { + ch = s.peek() // we peek just to see if we can move forward + ch = s.scanMantissa(ch) + } + return ch +} + +// scanExponent scans the remaining parts of an exponent after the 'e' or 'E' +// rune. +func (s *Scanner) scanExponent(ch rune) rune { + if ch == 'e' || ch == 'E' { + ch = s.next() + if ch == '-' || ch == '+' { + ch = s.next() + } + ch = s.scanMantissa(ch) + } + return ch +} + +// scanHeredoc scans a heredoc string +func (s *Scanner) scanHeredoc() { + // Scan the second '<' in example: '<= len(identBytes) && identRegexp.Match(s.src[lineStart:s.srcPos.Offset-s.lastCharLen]) { + break + } + + // Not an anchor match, record the start of a new line + lineStart = s.srcPos.Offset + } + + if ch == eof { + s.err("heredoc not terminated") + return + } + } + + return +} + +// scanString scans a quoted string +func (s *Scanner) scanString() { + braces := 0 + for { + // '"' opening already consumed + // read character after quote + ch := s.next() + + if (ch == '\n' && braces == 0) || ch < 0 || ch == eof { + s.err("literal not terminated") + return + } + + if ch == '"' && braces == 0 { + break + } + + // If we're going into a ${} then we can ignore quotes for awhile + if braces == 0 && ch == '$' && s.peek() == '{' { + braces++ + s.next() + } else if braces > 0 && ch == '{' { + braces++ + } + if braces > 0 && ch == '}' { + braces-- + } + + if ch == '\\' { + s.scanEscape() + } + } + + return +} + +// scanEscape scans an escape sequence +func (s *Scanner) scanEscape() rune { + // http://en.cppreference.com/w/cpp/language/escape + ch := s.next() // read character after '/' + switch ch { + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"': + // nothing to do + case '0', '1', '2', '3', '4', '5', '6', '7': + // octal notation + ch = s.scanDigits(ch, 8, 3) + case 'x': + // hexademical notation + ch = s.scanDigits(s.next(), 16, 2) + case 'u': + // universal character name + ch = s.scanDigits(s.next(), 16, 4) + case 'U': + // universal character name + ch = s.scanDigits(s.next(), 16, 8) + default: + s.err("illegal char escape") + } + return ch +} + +// scanDigits scans a rune with the given base for n times. For example an +// octal notation \184 would yield in scanDigits(ch, 8, 3) +func (s *Scanner) scanDigits(ch rune, base, n int) rune { + start := n + for n > 0 && digitVal(ch) < base { + ch = s.next() + if ch == eof { + // If we see an EOF, we halt any more scanning of digits + // immediately. + break + } + + n-- + } + if n > 0 { + s.err("illegal char escape") + } + + if n != start && ch != eof { + // we scanned all digits, put the last non digit char back, + // only if we read anything at all + s.unread() + } + + return ch +} + +// scanIdentifier scans an identifier and returns the literal string +func (s *Scanner) scanIdentifier() string { + offs := s.srcPos.Offset - s.lastCharLen + ch := s.next() + for isLetter(ch) || isDigit(ch) || ch == '-' || ch == '.' { + ch = s.next() + } + + if ch != eof { + s.unread() // we got identifier, put back latest char + } + + return string(s.src[offs:s.srcPos.Offset]) +} + +// recentPosition returns the position of the character immediately after the +// character or token returned by the last call to Scan. +func (s *Scanner) recentPosition() (pos token.Pos) { + pos.Offset = s.srcPos.Offset - s.lastCharLen + switch { + case s.srcPos.Column > 0: + // common case: last character was not a '\n' + pos.Line = s.srcPos.Line + pos.Column = s.srcPos.Column + case s.lastLineLen > 0: + // last character was a '\n' + // (we cannot be at the beginning of the source + // since we have called next() at least once) + pos.Line = s.srcPos.Line - 1 + pos.Column = s.lastLineLen + default: + // at the beginning of the source + pos.Line = 1 + pos.Column = 1 + } + return +} + +// err prints the error of any scanning to s.Error function. If the function is +// not defined, by default it prints them to os.Stderr +func (s *Scanner) err(msg string) { + s.ErrorCount++ + pos := s.recentPosition() + + if s.Error != nil { + s.Error(pos, msg) + return + } + + fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg) +} + +// isHexadecimal returns true if the given rune is a letter +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) +} + +// isDigit returns true if the given rune is a decimal digit +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) +} + +// isDecimal returns true if the given rune is a decimal number +func isDecimal(ch rune) bool { + return '0' <= ch && ch <= '9' +} + +// isHexadecimal returns true if the given rune is an hexadecimal number +func isHexadecimal(ch rune) bool { + return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' +} + +// isWhitespace returns true if the rune is a space, tab, newline or carriage return +func isWhitespace(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' +} + +// digitVal returns the integer value of a given octal,decimal or hexadecimal rune +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch - '0') + case 'a' <= ch && ch <= 'f': + return int(ch - 'a' + 10) + case 'A' <= ch && ch <= 'F': + return int(ch - 'A' + 10) + } + return 16 // larger than any legal digit val +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go b/vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go new file mode 100644 index 0000000..5f981ea --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go @@ -0,0 +1,241 @@ +package strconv + +import ( + "errors" + "unicode/utf8" +) + +// ErrSyntax indicates that a value does not have the right syntax for the target type. +var ErrSyntax = errors.New("invalid syntax") + +// Unquote interprets s as a single-quoted, double-quoted, +// or backquoted Go string literal, returning the string value +// that s quotes. (If s is single-quoted, it would be a Go +// character literal; Unquote returns the corresponding +// one-character string.) +func Unquote(s string) (t string, err error) { + n := len(s) + if n < 2 { + return "", ErrSyntax + } + quote := s[0] + if quote != s[n-1] { + return "", ErrSyntax + } + s = s[1 : n-1] + + if quote != '"' { + return "", ErrSyntax + } + if !contains(s, '$') && !contains(s, '{') && contains(s, '\n') { + return "", ErrSyntax + } + + // Is it trivial? Avoid allocation. + if !contains(s, '\\') && !contains(s, quote) && !contains(s, '$') { + switch quote { + case '"': + return s, nil + case '\'': + r, size := utf8.DecodeRuneInString(s) + if size == len(s) && (r != utf8.RuneError || size != 1) { + return s, nil + } + } + } + + var runeTmp [utf8.UTFMax]byte + buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. + for len(s) > 0 { + // If we're starting a '${}' then let it through un-unquoted. + // Specifically: we don't unquote any characters within the `${}` + // section. + if s[0] == '$' && len(s) > 1 && s[1] == '{' { + buf = append(buf, '$', '{') + s = s[2:] + + // Continue reading until we find the closing brace, copying as-is + braces := 1 + for len(s) > 0 && braces > 0 { + r, size := utf8.DecodeRuneInString(s) + if r == utf8.RuneError { + return "", ErrSyntax + } + + s = s[size:] + + n := utf8.EncodeRune(runeTmp[:], r) + buf = append(buf, runeTmp[:n]...) + + switch r { + case '{': + braces++ + case '}': + braces-- + } + } + if braces != 0 { + return "", ErrSyntax + } + if len(s) == 0 { + // If there's no string left, we're done! + break + } else { + // If there's more left, we need to pop back up to the top of the loop + // in case there's another interpolation in this string. + continue + } + } + + if s[0] == '\n' { + return "", ErrSyntax + } + + c, multibyte, ss, err := unquoteChar(s, quote) + if err != nil { + return "", err + } + s = ss + if c < utf8.RuneSelf || !multibyte { + buf = append(buf, byte(c)) + } else { + n := utf8.EncodeRune(runeTmp[:], c) + buf = append(buf, runeTmp[:n]...) + } + if quote == '\'' && len(s) != 0 { + // single-quoted must be single character + return "", ErrSyntax + } + } + return string(buf), nil +} + +// contains reports whether the string contains the byte c. +func contains(s string, c byte) bool { + for i := 0; i < len(s); i++ { + if s[i] == c { + return true + } + } + return false +} + +func unhex(b byte) (v rune, ok bool) { + c := rune(b) + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + return +} + +func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { + // easy cases + switch c := s[0]; { + case c == quote && (quote == '\'' || quote == '"'): + err = ErrSyntax + return + case c >= utf8.RuneSelf: + r, size := utf8.DecodeRuneInString(s) + return r, true, s[size:], nil + case c != '\\': + return rune(s[0]), false, s[1:], nil + } + + // hard case: c is backslash + if len(s) <= 1 { + err = ErrSyntax + return + } + c := s[1] + s = s[2:] + + switch c { + case 'a': + value = '\a' + case 'b': + value = '\b' + case 'f': + value = '\f' + case 'n': + value = '\n' + case 'r': + value = '\r' + case 't': + value = '\t' + case 'v': + value = '\v' + case 'x', 'u', 'U': + n := 0 + switch c { + case 'x': + n = 2 + case 'u': + n = 4 + case 'U': + n = 8 + } + var v rune + if len(s) < n { + err = ErrSyntax + return + } + for j := 0; j < n; j++ { + x, ok := unhex(s[j]) + if !ok { + err = ErrSyntax + return + } + v = v<<4 | x + } + s = s[n:] + if c == 'x' { + // single-byte string, possibly not UTF-8 + value = v + break + } + if v > utf8.MaxRune { + err = ErrSyntax + return + } + value = v + multibyte = true + case '0', '1', '2', '3', '4', '5', '6', '7': + v := rune(c) - '0' + if len(s) < 2 { + err = ErrSyntax + return + } + for j := 0; j < 2; j++ { // one digit already; two more + x := rune(s[j]) - '0' + if x < 0 || x > 7 { + err = ErrSyntax + return + } + v = (v << 3) | x + } + s = s[2:] + if v > 255 { + err = ErrSyntax + return + } + value = v + case '\\': + value = '\\' + case '\'', '"': + if c != quote { + err = ErrSyntax + return + } + value = rune(c) + default: + err = ErrSyntax + return + } + tail = s + return +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/token/position.go b/vendor/github.com/hashicorp/hcl/hcl/token/position.go new file mode 100644 index 0000000..59c1bb7 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/token/position.go @@ -0,0 +1,46 @@ +package token + +import "fmt" + +// Pos describes an arbitrary source position +// including the file, line, and column location. +// A Position is valid if the line number is > 0. +type Pos struct { + Filename string // filename, if any + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (character count) +} + +// IsValid returns true if the position is valid. +func (p *Pos) IsValid() bool { return p.Line > 0 } + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// line:column valid position without file name +// file invalid position with file name +// - invalid position without file name +func (p Pos) String() string { + s := p.Filename + if p.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d:%d", p.Line, p.Column) + } + if s == "" { + s = "-" + } + return s +} + +// Before reports whether the position p is before u. +func (p Pos) Before(u Pos) bool { + return u.Offset > p.Offset || u.Line > p.Line +} + +// After reports whether the position p is after u. +func (p Pos) After(u Pos) bool { + return u.Offset < p.Offset || u.Line < p.Line +} diff --git a/vendor/github.com/hashicorp/hcl/hcl/token/token.go b/vendor/github.com/hashicorp/hcl/hcl/token/token.go new file mode 100644 index 0000000..e37c066 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/hcl/token/token.go @@ -0,0 +1,219 @@ +// Package token defines constants representing the lexical tokens for HCL +// (HashiCorp Configuration Language) +package token + +import ( + "fmt" + "strconv" + "strings" + + hclstrconv "github.com/hashicorp/hcl/hcl/strconv" +) + +// Token defines a single HCL token which can be obtained via the Scanner +type Token struct { + Type Type + Pos Pos + Text string + JSON bool +} + +// Type is the set of lexical tokens of the HCL (HashiCorp Configuration Language) +type Type int + +const ( + // Special tokens + ILLEGAL Type = iota + EOF + COMMENT + + identifier_beg + IDENT // literals + literal_beg + NUMBER // 12345 + FLOAT // 123.45 + BOOL // true,false + STRING // "abc" + HEREDOC // < 0 { + // Pop the current item + n := len(frontier) + item := frontier[n-1] + frontier = frontier[:n-1] + + switch v := item.Val.(type) { + case *ast.ObjectType: + items, frontier = flattenObjectType(v, item, items, frontier) + case *ast.ListType: + items, frontier = flattenListType(v, item, items, frontier) + default: + items = append(items, item) + } + } + + // Reverse the list since the frontier model runs things backwards + for i := len(items)/2 - 1; i >= 0; i-- { + opp := len(items) - 1 - i + items[i], items[opp] = items[opp], items[i] + } + + // Done! Set the original items + list.Items = items + return n, true + }) +} + +func flattenListType( + ot *ast.ListType, + item *ast.ObjectItem, + items []*ast.ObjectItem, + frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) { + // If the list is empty, keep the original list + if len(ot.List) == 0 { + items = append(items, item) + return items, frontier + } + + // All the elements of this object must also be objects! + for _, subitem := range ot.List { + if _, ok := subitem.(*ast.ObjectType); !ok { + items = append(items, item) + return items, frontier + } + } + + // Great! We have a match go through all the items and flatten + for _, elem := range ot.List { + // Add it to the frontier so that we can recurse + frontier = append(frontier, &ast.ObjectItem{ + Keys: item.Keys, + Assign: item.Assign, + Val: elem, + LeadComment: item.LeadComment, + LineComment: item.LineComment, + }) + } + + return items, frontier +} + +func flattenObjectType( + ot *ast.ObjectType, + item *ast.ObjectItem, + items []*ast.ObjectItem, + frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) { + // If the list has no items we do not have to flatten anything + if ot.List.Items == nil { + items = append(items, item) + return items, frontier + } + + // All the elements of this object must also be objects! + for _, subitem := range ot.List.Items { + if _, ok := subitem.Val.(*ast.ObjectType); !ok { + items = append(items, item) + return items, frontier + } + } + + // Great! We have a match go through all the items and flatten + for _, subitem := range ot.List.Items { + // Copy the new key + keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys)) + copy(keys, item.Keys) + copy(keys[len(item.Keys):], subitem.Keys) + + // Add it to the frontier so that we can recurse + frontier = append(frontier, &ast.ObjectItem{ + Keys: keys, + Assign: item.Assign, + Val: subitem.Val, + LeadComment: item.LeadComment, + LineComment: item.LineComment, + }) + } + + return items, frontier +} diff --git a/vendor/github.com/hashicorp/hcl/json/parser/parser.go b/vendor/github.com/hashicorp/hcl/json/parser/parser.go new file mode 100644 index 0000000..125a5f0 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/json/parser/parser.go @@ -0,0 +1,313 @@ +package parser + +import ( + "errors" + "fmt" + + "github.com/hashicorp/hcl/hcl/ast" + hcltoken "github.com/hashicorp/hcl/hcl/token" + "github.com/hashicorp/hcl/json/scanner" + "github.com/hashicorp/hcl/json/token" +) + +type Parser struct { + sc *scanner.Scanner + + // Last read token + tok token.Token + commaPrev token.Token + + enableTrace bool + indent int + n int // buffer size (max = 1) +} + +func newParser(src []byte) *Parser { + return &Parser{ + sc: scanner.New(src), + } +} + +// Parse returns the fully parsed source and returns the abstract syntax tree. +func Parse(src []byte) (*ast.File, error) { + p := newParser(src) + return p.Parse() +} + +var errEofToken = errors.New("EOF token found") + +// Parse returns the fully parsed source and returns the abstract syntax tree. +func (p *Parser) Parse() (*ast.File, error) { + f := &ast.File{} + var err, scerr error + p.sc.Error = func(pos token.Pos, msg string) { + scerr = fmt.Errorf("%s: %s", pos, msg) + } + + // The root must be an object in JSON + object, err := p.object() + if scerr != nil { + return nil, scerr + } + if err != nil { + return nil, err + } + + // We make our final node an object list so it is more HCL compatible + f.Node = object.List + + // Flatten it, which finds patterns and turns them into more HCL-like + // AST trees. + flattenObjects(f.Node) + + return f, nil +} + +func (p *Parser) objectList() (*ast.ObjectList, error) { + defer un(trace(p, "ParseObjectList")) + node := &ast.ObjectList{} + + for { + n, err := p.objectItem() + if err == errEofToken { + break // we are finished + } + + // we don't return a nil node, because might want to use already + // collected items. + if err != nil { + return node, err + } + + node.Add(n) + + // Check for a followup comma. If it isn't a comma, then we're done + if tok := p.scan(); tok.Type != token.COMMA { + break + } + } + + return node, nil +} + +// objectItem parses a single object item +func (p *Parser) objectItem() (*ast.ObjectItem, error) { + defer un(trace(p, "ParseObjectItem")) + + keys, err := p.objectKey() + if err != nil { + return nil, err + } + + o := &ast.ObjectItem{ + Keys: keys, + } + + switch p.tok.Type { + case token.COLON: + pos := p.tok.Pos + o.Assign = hcltoken.Pos{ + Filename: pos.Filename, + Offset: pos.Offset, + Line: pos.Line, + Column: pos.Column, + } + + o.Val, err = p.objectValue() + if err != nil { + return nil, err + } + } + + return o, nil +} + +// objectKey parses an object key and returns a ObjectKey AST +func (p *Parser) objectKey() ([]*ast.ObjectKey, error) { + keyCount := 0 + keys := make([]*ast.ObjectKey, 0) + + for { + tok := p.scan() + switch tok.Type { + case token.EOF: + return nil, errEofToken + case token.STRING: + keyCount++ + keys = append(keys, &ast.ObjectKey{ + Token: p.tok.HCLToken(), + }) + case token.COLON: + // If we have a zero keycount it means that we never got + // an object key, i.e. `{ :`. This is a syntax error. + if keyCount == 0 { + return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type) + } + + // Done + return keys, nil + case token.ILLEGAL: + return nil, errors.New("illegal") + default: + return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type) + } + } +} + +// object parses any type of object, such as number, bool, string, object or +// list. +func (p *Parser) objectValue() (ast.Node, error) { + defer un(trace(p, "ParseObjectValue")) + tok := p.scan() + + switch tok.Type { + case token.NUMBER, token.FLOAT, token.BOOL, token.NULL, token.STRING: + return p.literalType() + case token.LBRACE: + return p.objectType() + case token.LBRACK: + return p.listType() + case token.EOF: + return nil, errEofToken + } + + return nil, fmt.Errorf("Expected object value, got unknown token: %+v", tok) +} + +// object parses any type of object, such as number, bool, string, object or +// list. +func (p *Parser) object() (*ast.ObjectType, error) { + defer un(trace(p, "ParseType")) + tok := p.scan() + + switch tok.Type { + case token.LBRACE: + return p.objectType() + case token.EOF: + return nil, errEofToken + } + + return nil, fmt.Errorf("Expected object, got unknown token: %+v", tok) +} + +// objectType parses an object type and returns a ObjectType AST +func (p *Parser) objectType() (*ast.ObjectType, error) { + defer un(trace(p, "ParseObjectType")) + + // we assume that the currently scanned token is a LBRACE + o := &ast.ObjectType{} + + l, err := p.objectList() + + // if we hit RBRACE, we are good to go (means we parsed all Items), if it's + // not a RBRACE, it's an syntax error and we just return it. + if err != nil && p.tok.Type != token.RBRACE { + return nil, err + } + + o.List = l + return o, nil +} + +// listType parses a list type and returns a ListType AST +func (p *Parser) listType() (*ast.ListType, error) { + defer un(trace(p, "ParseListType")) + + // we assume that the currently scanned token is a LBRACK + l := &ast.ListType{} + + for { + tok := p.scan() + switch tok.Type { + case token.NUMBER, token.FLOAT, token.STRING: + node, err := p.literalType() + if err != nil { + return nil, err + } + + l.Add(node) + case token.COMMA: + continue + case token.LBRACE: + node, err := p.objectType() + if err != nil { + return nil, err + } + + l.Add(node) + case token.BOOL: + // TODO(arslan) should we support? not supported by HCL yet + case token.LBRACK: + // TODO(arslan) should we support nested lists? Even though it's + // written in README of HCL, it's not a part of the grammar + // (not defined in parse.y) + case token.RBRACK: + // finished + return l, nil + default: + return nil, fmt.Errorf("unexpected token while parsing list: %s", tok.Type) + } + + } +} + +// literalType parses a literal type and returns a LiteralType AST +func (p *Parser) literalType() (*ast.LiteralType, error) { + defer un(trace(p, "ParseLiteral")) + + return &ast.LiteralType{ + Token: p.tok.HCLToken(), + }, nil +} + +// scan returns the next token from the underlying scanner. If a token has +// been unscanned then read that instead. +func (p *Parser) scan() token.Token { + // If we have a token on the buffer, then return it. + if p.n != 0 { + p.n = 0 + return p.tok + } + + p.tok = p.sc.Scan() + return p.tok +} + +// unscan pushes the previously read token back onto the buffer. +func (p *Parser) unscan() { + p.n = 1 +} + +// ---------------------------------------------------------------------------- +// Parsing support + +func (p *Parser) printTrace(a ...interface{}) { + if !p.enableTrace { + return + } + + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = len(dots) + fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column) + + i := 2 * p.indent + for i > n { + fmt.Print(dots) + i -= n + } + // i <= n + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *Parser, msg string) *Parser { + p.printTrace(msg, "(") + p.indent++ + return p +} + +// Usage pattern: defer un(trace(p, "...")) +func un(p *Parser) { + p.indent-- + p.printTrace(")") +} diff --git a/vendor/github.com/hashicorp/hcl/json/scanner/scanner.go b/vendor/github.com/hashicorp/hcl/json/scanner/scanner.go new file mode 100644 index 0000000..fe3f0f0 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/json/scanner/scanner.go @@ -0,0 +1,451 @@ +package scanner + +import ( + "bytes" + "fmt" + "os" + "unicode" + "unicode/utf8" + + "github.com/hashicorp/hcl/json/token" +) + +// eof represents a marker rune for the end of the reader. +const eof = rune(0) + +// Scanner defines a lexical scanner +type Scanner struct { + buf *bytes.Buffer // Source buffer for advancing and scanning + src []byte // Source buffer for immutable access + + // Source Position + srcPos token.Pos // current position + prevPos token.Pos // previous position, used for peek() method + + lastCharLen int // length of last character in bytes + lastLineLen int // length of last line in characters (for correct column reporting) + + tokStart int // token text start position + tokEnd int // token text end position + + // Error is called for each error encountered. If no Error + // function is set, the error is reported to os.Stderr. + Error func(pos token.Pos, msg string) + + // ErrorCount is incremented by one for each error encountered. + ErrorCount int + + // tokPos is the start position of most recently scanned token; set by + // Scan. The Filename field is always left untouched by the Scanner. If + // an error is reported (via Error) and Position is invalid, the scanner is + // not inside a token. + tokPos token.Pos +} + +// New creates and initializes a new instance of Scanner using src as +// its source content. +func New(src []byte) *Scanner { + // even though we accept a src, we read from a io.Reader compatible type + // (*bytes.Buffer). So in the future we might easily change it to streaming + // read. + b := bytes.NewBuffer(src) + s := &Scanner{ + buf: b, + src: src, + } + + // srcPosition always starts with 1 + s.srcPos.Line = 1 + return s +} + +// next reads the next rune from the bufferred reader. Returns the rune(0) if +// an error occurs (or io.EOF is returned). +func (s *Scanner) next() rune { + ch, size, err := s.buf.ReadRune() + if err != nil { + // advance for error reporting + s.srcPos.Column++ + s.srcPos.Offset += size + s.lastCharLen = size + return eof + } + + if ch == utf8.RuneError && size == 1 { + s.srcPos.Column++ + s.srcPos.Offset += size + s.lastCharLen = size + s.err("illegal UTF-8 encoding") + return ch + } + + // remember last position + s.prevPos = s.srcPos + + s.srcPos.Column++ + s.lastCharLen = size + s.srcPos.Offset += size + + if ch == '\n' { + s.srcPos.Line++ + s.lastLineLen = s.srcPos.Column + s.srcPos.Column = 0 + } + + // debug + // fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column) + return ch +} + +// unread unreads the previous read Rune and updates the source position +func (s *Scanner) unread() { + if err := s.buf.UnreadRune(); err != nil { + panic(err) // this is user fault, we should catch it + } + s.srcPos = s.prevPos // put back last position +} + +// peek returns the next rune without advancing the reader. +func (s *Scanner) peek() rune { + peek, _, err := s.buf.ReadRune() + if err != nil { + return eof + } + + s.buf.UnreadRune() + return peek +} + +// Scan scans the next token and returns the token. +func (s *Scanner) Scan() token.Token { + ch := s.next() + + // skip white space + for isWhitespace(ch) { + ch = s.next() + } + + var tok token.Type + + // token text markings + s.tokStart = s.srcPos.Offset - s.lastCharLen + + // token position, initial next() is moving the offset by one(size of rune + // actually), though we are interested with the starting point + s.tokPos.Offset = s.srcPos.Offset - s.lastCharLen + if s.srcPos.Column > 0 { + // common case: last character was not a '\n' + s.tokPos.Line = s.srcPos.Line + s.tokPos.Column = s.srcPos.Column + } else { + // last character was a '\n' + // (we cannot be at the beginning of the source + // since we have called next() at least once) + s.tokPos.Line = s.srcPos.Line - 1 + s.tokPos.Column = s.lastLineLen + } + + switch { + case isLetter(ch): + lit := s.scanIdentifier() + if lit == "true" || lit == "false" { + tok = token.BOOL + } else if lit == "null" { + tok = token.NULL + } else { + s.err("illegal char") + } + case isDecimal(ch): + tok = s.scanNumber(ch) + default: + switch ch { + case eof: + tok = token.EOF + case '"': + tok = token.STRING + s.scanString() + case '.': + tok = token.PERIOD + ch = s.peek() + if isDecimal(ch) { + tok = token.FLOAT + ch = s.scanMantissa(ch) + ch = s.scanExponent(ch) + } + case '[': + tok = token.LBRACK + case ']': + tok = token.RBRACK + case '{': + tok = token.LBRACE + case '}': + tok = token.RBRACE + case ',': + tok = token.COMMA + case ':': + tok = token.COLON + case '-': + if isDecimal(s.peek()) { + ch := s.next() + tok = s.scanNumber(ch) + } else { + s.err("illegal char") + } + default: + s.err("illegal char: " + string(ch)) + } + } + + // finish token ending + s.tokEnd = s.srcPos.Offset + + // create token literal + var tokenText string + if s.tokStart >= 0 { + tokenText = string(s.src[s.tokStart:s.tokEnd]) + } + s.tokStart = s.tokEnd // ensure idempotency of tokenText() call + + return token.Token{ + Type: tok, + Pos: s.tokPos, + Text: tokenText, + } +} + +// scanNumber scans a HCL number definition starting with the given rune +func (s *Scanner) scanNumber(ch rune) token.Type { + zero := ch == '0' + pos := s.srcPos + + s.scanMantissa(ch) + ch = s.next() // seek forward + if ch == 'e' || ch == 'E' { + ch = s.scanExponent(ch) + return token.FLOAT + } + + if ch == '.' { + ch = s.scanFraction(ch) + if ch == 'e' || ch == 'E' { + ch = s.next() + ch = s.scanExponent(ch) + } + return token.FLOAT + } + + if ch != eof { + s.unread() + } + + // If we have a larger number and this is zero, error + if zero && pos != s.srcPos { + s.err("numbers cannot start with 0") + } + + return token.NUMBER +} + +// scanMantissa scans the mantissa beginning from the rune. It returns the next +// non decimal rune. It's used to determine wheter it's a fraction or exponent. +func (s *Scanner) scanMantissa(ch rune) rune { + scanned := false + for isDecimal(ch) { + ch = s.next() + scanned = true + } + + if scanned && ch != eof { + s.unread() + } + return ch +} + +// scanFraction scans the fraction after the '.' rune +func (s *Scanner) scanFraction(ch rune) rune { + if ch == '.' { + ch = s.peek() // we peek just to see if we can move forward + ch = s.scanMantissa(ch) + } + return ch +} + +// scanExponent scans the remaining parts of an exponent after the 'e' or 'E' +// rune. +func (s *Scanner) scanExponent(ch rune) rune { + if ch == 'e' || ch == 'E' { + ch = s.next() + if ch == '-' || ch == '+' { + ch = s.next() + } + ch = s.scanMantissa(ch) + } + return ch +} + +// scanString scans a quoted string +func (s *Scanner) scanString() { + braces := 0 + for { + // '"' opening already consumed + // read character after quote + ch := s.next() + + if ch == '\n' || ch < 0 || ch == eof { + s.err("literal not terminated") + return + } + + if ch == '"' { + break + } + + // If we're going into a ${} then we can ignore quotes for awhile + if braces == 0 && ch == '$' && s.peek() == '{' { + braces++ + s.next() + } else if braces > 0 && ch == '{' { + braces++ + } + if braces > 0 && ch == '}' { + braces-- + } + + if ch == '\\' { + s.scanEscape() + } + } + + return +} + +// scanEscape scans an escape sequence +func (s *Scanner) scanEscape() rune { + // http://en.cppreference.com/w/cpp/language/escape + ch := s.next() // read character after '/' + switch ch { + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"': + // nothing to do + case '0', '1', '2', '3', '4', '5', '6', '7': + // octal notation + ch = s.scanDigits(ch, 8, 3) + case 'x': + // hexademical notation + ch = s.scanDigits(s.next(), 16, 2) + case 'u': + // universal character name + ch = s.scanDigits(s.next(), 16, 4) + case 'U': + // universal character name + ch = s.scanDigits(s.next(), 16, 8) + default: + s.err("illegal char escape") + } + return ch +} + +// scanDigits scans a rune with the given base for n times. For example an +// octal notation \184 would yield in scanDigits(ch, 8, 3) +func (s *Scanner) scanDigits(ch rune, base, n int) rune { + for n > 0 && digitVal(ch) < base { + ch = s.next() + n-- + } + if n > 0 { + s.err("illegal char escape") + } + + // we scanned all digits, put the last non digit char back + s.unread() + return ch +} + +// scanIdentifier scans an identifier and returns the literal string +func (s *Scanner) scanIdentifier() string { + offs := s.srcPos.Offset - s.lastCharLen + ch := s.next() + for isLetter(ch) || isDigit(ch) || ch == '-' { + ch = s.next() + } + + if ch != eof { + s.unread() // we got identifier, put back latest char + } + + return string(s.src[offs:s.srcPos.Offset]) +} + +// recentPosition returns the position of the character immediately after the +// character or token returned by the last call to Scan. +func (s *Scanner) recentPosition() (pos token.Pos) { + pos.Offset = s.srcPos.Offset - s.lastCharLen + switch { + case s.srcPos.Column > 0: + // common case: last character was not a '\n' + pos.Line = s.srcPos.Line + pos.Column = s.srcPos.Column + case s.lastLineLen > 0: + // last character was a '\n' + // (we cannot be at the beginning of the source + // since we have called next() at least once) + pos.Line = s.srcPos.Line - 1 + pos.Column = s.lastLineLen + default: + // at the beginning of the source + pos.Line = 1 + pos.Column = 1 + } + return +} + +// err prints the error of any scanning to s.Error function. If the function is +// not defined, by default it prints them to os.Stderr +func (s *Scanner) err(msg string) { + s.ErrorCount++ + pos := s.recentPosition() + + if s.Error != nil { + s.Error(pos, msg) + return + } + + fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg) +} + +// isHexadecimal returns true if the given rune is a letter +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) +} + +// isHexadecimal returns true if the given rune is a decimal digit +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) +} + +// isHexadecimal returns true if the given rune is a decimal number +func isDecimal(ch rune) bool { + return '0' <= ch && ch <= '9' +} + +// isHexadecimal returns true if the given rune is an hexadecimal number +func isHexadecimal(ch rune) bool { + return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' +} + +// isWhitespace returns true if the rune is a space, tab, newline or carriage return +func isWhitespace(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' +} + +// digitVal returns the integer value of a given octal,decimal or hexadecimal rune +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch - '0') + case 'a' <= ch && ch <= 'f': + return int(ch - 'a' + 10) + case 'A' <= ch && ch <= 'F': + return int(ch - 'A' + 10) + } + return 16 // larger than any legal digit val +} diff --git a/vendor/github.com/hashicorp/hcl/json/token/position.go b/vendor/github.com/hashicorp/hcl/json/token/position.go new file mode 100644 index 0000000..59c1bb7 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/json/token/position.go @@ -0,0 +1,46 @@ +package token + +import "fmt" + +// Pos describes an arbitrary source position +// including the file, line, and column location. +// A Position is valid if the line number is > 0. +type Pos struct { + Filename string // filename, if any + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (character count) +} + +// IsValid returns true if the position is valid. +func (p *Pos) IsValid() bool { return p.Line > 0 } + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// line:column valid position without file name +// file invalid position with file name +// - invalid position without file name +func (p Pos) String() string { + s := p.Filename + if p.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d:%d", p.Line, p.Column) + } + if s == "" { + s = "-" + } + return s +} + +// Before reports whether the position p is before u. +func (p Pos) Before(u Pos) bool { + return u.Offset > p.Offset || u.Line > p.Line +} + +// After reports whether the position p is after u. +func (p Pos) After(u Pos) bool { + return u.Offset < p.Offset || u.Line < p.Line +} diff --git a/vendor/github.com/hashicorp/hcl/json/token/token.go b/vendor/github.com/hashicorp/hcl/json/token/token.go new file mode 100644 index 0000000..95a0c3e --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/json/token/token.go @@ -0,0 +1,118 @@ +package token + +import ( + "fmt" + "strconv" + + hcltoken "github.com/hashicorp/hcl/hcl/token" +) + +// Token defines a single HCL token which can be obtained via the Scanner +type Token struct { + Type Type + Pos Pos + Text string +} + +// Type is the set of lexical tokens of the HCL (HashiCorp Configuration Language) +type Type int + +const ( + // Special tokens + ILLEGAL Type = iota + EOF + + identifier_beg + literal_beg + NUMBER // 12345 + FLOAT // 123.45 + BOOL // true,false + STRING // "abc" + NULL // null + literal_end + identifier_end + + operator_beg + LBRACK // [ + LBRACE // { + COMMA // , + PERIOD // . + COLON // : + + RBRACK // ] + RBRACE // } + + operator_end +) + +var tokens = [...]string{ + ILLEGAL: "ILLEGAL", + + EOF: "EOF", + + NUMBER: "NUMBER", + FLOAT: "FLOAT", + BOOL: "BOOL", + STRING: "STRING", + NULL: "NULL", + + LBRACK: "LBRACK", + LBRACE: "LBRACE", + COMMA: "COMMA", + PERIOD: "PERIOD", + COLON: "COLON", + + RBRACK: "RBRACK", + RBRACE: "RBRACE", +} + +// String returns the string corresponding to the token tok. +func (t Type) String() string { + s := "" + if 0 <= t && t < Type(len(tokens)) { + s = tokens[t] + } + if s == "" { + s = "token(" + strconv.Itoa(int(t)) + ")" + } + return s +} + +// IsIdentifier returns true for tokens corresponding to identifiers and basic +// type literals; it returns false otherwise. +func (t Type) IsIdentifier() bool { return identifier_beg < t && t < identifier_end } + +// IsLiteral returns true for tokens corresponding to basic type literals; it +// returns false otherwise. +func (t Type) IsLiteral() bool { return literal_beg < t && t < literal_end } + +// IsOperator returns true for tokens corresponding to operators and +// delimiters; it returns false otherwise. +func (t Type) IsOperator() bool { return operator_beg < t && t < operator_end } + +// String returns the token's literal text. Note that this is only +// applicable for certain token types, such as token.IDENT, +// token.STRING, etc.. +func (t Token) String() string { + return fmt.Sprintf("%s %s %s", t.Pos.String(), t.Type.String(), t.Text) +} + +// HCLToken converts this token to an HCL token. +// +// The token type must be a literal type or this will panic. +func (t Token) HCLToken() hcltoken.Token { + switch t.Type { + case BOOL: + return hcltoken.Token{Type: hcltoken.BOOL, Text: t.Text} + case FLOAT: + return hcltoken.Token{Type: hcltoken.FLOAT, Text: t.Text} + case NULL: + return hcltoken.Token{Type: hcltoken.STRING, Text: ""} + case NUMBER: + return hcltoken.Token{Type: hcltoken.NUMBER, Text: t.Text} + case STRING: + return hcltoken.Token{Type: hcltoken.STRING, Text: t.Text, JSON: true} + default: + panic(fmt.Sprintf("unimplemented HCLToken for type: %s", t.Type)) + } +} diff --git a/vendor/github.com/hashicorp/hcl/lex.go b/vendor/github.com/hashicorp/hcl/lex.go new file mode 100644 index 0000000..d9993c2 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/lex.go @@ -0,0 +1,38 @@ +package hcl + +import ( + "unicode" + "unicode/utf8" +) + +type lexModeValue byte + +const ( + lexModeUnknown lexModeValue = iota + lexModeHcl + lexModeJson +) + +// lexMode returns whether we're going to be parsing in JSON +// mode or HCL mode. +func lexMode(v []byte) lexModeValue { + var ( + r rune + w int + offset int + ) + + for { + r, w = utf8.DecodeRune(v[offset:]) + offset += w + if unicode.IsSpace(r) { + continue + } + if r == '{' { + return lexModeJson + } + break + } + + return lexModeHcl +} diff --git a/vendor/github.com/hashicorp/hcl/parse.go b/vendor/github.com/hashicorp/hcl/parse.go new file mode 100644 index 0000000..1fca53c --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/parse.go @@ -0,0 +1,39 @@ +package hcl + +import ( + "fmt" + + "github.com/hashicorp/hcl/hcl/ast" + hclParser "github.com/hashicorp/hcl/hcl/parser" + jsonParser "github.com/hashicorp/hcl/json/parser" +) + +// ParseBytes accepts as input byte slice and returns ast tree. +// +// Input can be either JSON or HCL +func ParseBytes(in []byte) (*ast.File, error) { + return parse(in) +} + +// ParseString accepts input as a string and returns ast tree. +func ParseString(input string) (*ast.File, error) { + return parse([]byte(input)) +} + +func parse(in []byte) (*ast.File, error) { + switch lexMode(in) { + case lexModeHcl: + return hclParser.Parse(in) + case lexModeJson: + return jsonParser.Parse(in) + } + + return nil, fmt.Errorf("unknown config format") +} + +// Parse parses the given input and returns the root object. +// +// The input format can be either HCL or JSON. +func Parse(input string) (*ast.File, error) { + return parse([]byte(input)) +} diff --git a/vendor/github.com/keybase/go-keychain/.travis.yml b/vendor/github.com/keybase/go-keychain/.travis.yml deleted file mode 100644 index e246704..0000000 --- a/vendor/github.com/keybase/go-keychain/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: go - -os: - - osx - - linux - -before_install: - - go get golang.org/x/lint/golint - -script: - - if [[ "$(go version)" =~ go1.1[12] ]]; then go get honnef.co/go/tools/cmd/staticcheck && staticcheck ./... && echo "staticcheck complete"; fi - - go vet ./... - - golint ./... - - go test -tags skipsecretserviceintegrationtests ./... - -go: - - 1.10.x - - 1.11.x - - 1.12.x - - master diff --git a/vendor/github.com/keybase/go-keychain/README.md b/vendor/github.com/keybase/go-keychain/README.md deleted file mode 100644 index 6cd0e64..0000000 --- a/vendor/github.com/keybase/go-keychain/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# Go Keychain - -A library for accessing the Keychain for macOS, iOS, and Linux in Go (golang). - -Requires macOS 10.9 or greater and iOS 8 or greater. On Linux, communicates to -a provider of the DBUS SecretService spec like gnome-keyring or ksecretservice. - -```go -import "github.com/keybase/go-keychain" -``` - - -## Mac/iOS Usage - -The API is meant to mirror the macOS/iOS Keychain API and is not necessarily idiomatic go. - -#### Add Item - -```go -item := keychain.NewItem() -item.SetSecClass(keychain.SecClassGenericPassword) -item.SetService("MyService") -item.SetAccount("gabriel") -item.SetLabel("A label") -item.SetAccessGroup("A123456789.group.com.mycorp") -item.SetData([]byte("toomanysecrets")) -item.SetSynchronizable(keychain.SynchronizableNo) -item.SetAccessible(keychain.AccessibleWhenUnlocked) -err := keychain.AddItem(item) - -if err == keychain.ErrorDuplicateItem { - // Duplicate -} -``` - -#### Query Item - -Query for multiple results, returning attributes: - -```go -query := keychain.NewItem() -query.SetSecClass(keychain.SecClassGenericPassword) -query.SetService(service) -query.SetAccount(account) -query.SetAccessGroup(accessGroup) -query.SetMatchLimit(keychain.MatchLimitAll) -query.SetReturnAttributes(true) -results, err := keychain.QueryItem(query) -if err != nil { - // Error -} else { - for _, r := range results { - fmt.Printf("%#v\n", r) - } -} -``` - -Query for a single result, returning data: - -```go -query := keychain.NewItem() -query.SetSecClass(keychain.SecClassGenericPassword) -query.SetService(service) -query.SetAccount(account) -query.SetAccessGroup(accessGroup) -query.SetMatchLimit(keychain.MatchLimitOne) -query.SetReturnData(true) -results, err := keychain.QueryItem(query) -if err != nil { - // Error -} else if len(results) != 1 { - // Not found -} else { - password := string(results[0].Data) -} -``` - -#### Delete Item - -Delete a generic password item with service and account: - -```go -item := keychain.NewItem() -item.SetSecClass(keychain.SecClassGenericPassword) -item.SetService(service) -item.SetAccount(account) -err := keychain.DeleteItem(item) -``` - -### Other - -There are some convenience methods for generic password: - -```go -// Create generic password item with service, account, label, password, access group -item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp") -item.SetSynchronizable(keychain.SynchronizableNo) -item.SetAccessible(keychain.AccessibleWhenUnlocked) -err := keychain.AddItem(item) -if err == keychain.ErrorDuplicateItem { - // Duplicate -} - -accounts, err := keychain.GetGenericPasswordAccounts("MyService") -// Should have 1 account == "gabriel" - -err := keychain.DeleteGenericPasswordItem("MyService", "gabriel") -if err == keychain.ErrorNotFound { - // Not found -} -``` - -### OS X - -Creating a new keychain and add an item to it: - -```go - -// Add a new key chain into ~/Application Support/Keychains, with the provided password -k, err := keychain.NewKeychain("mykeychain.keychain", "my keychain password") -if err != nil { - // Error creating -} - -// Create generic password item with service, account, label, password, access group -item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp") -item.UseKeychain(k) -err := keychain.AddItem(item) -if err != nil { - // Error creating -} -``` - -Using a Keychain at path: - -```go -k, err := keychain.NewWithPath("mykeychain.keychain") -``` - -Set a trusted applications for item (OS X only): - -```go -item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp") -trustedApplications := []string{"/Applications/Mail.app"} -item.SetAccess(&keychain.Access{Label: "Mail", TrustedApplications: trustedApplications}) -err := keychain.AddItem(item) -``` - -## iOS - -Bindable package in `bind`. iOS project in `ios`. Run that project to test iOS. - -To re-generate framework: - -``` -(cd bind && gomobile bind -target=ios -tags=ios -o ../ios/bind.framework) -``` diff --git a/vendor/github.com/keybase/go-keychain/corefoundation_1.10.go b/vendor/github.com/keybase/go-keychain/corefoundation_1.10.go deleted file mode 100644 index 076fc72..0000000 --- a/vendor/github.com/keybase/go-keychain/corefoundation_1.10.go +++ /dev/null @@ -1,359 +0,0 @@ -// +build darwin ios -// +build go1.10 - -package keychain - -/* -#cgo LDFLAGS: -framework CoreFoundation - -#include - -// Can't cast a *uintptr to *unsafe.Pointer in Go, and casting -// C.CFTypeRef to unsafe.Pointer is unsafe in Go, so have shim functions to -// do the casting in C (where it's safe). - -// We add a suffix to the C functions below, because we copied this -// file from go-kext, which means that any project that depends on this -// package and go-kext would run into duplicate symbol errors otherwise. -// -// TODO: Move this file into its own package depended on by go-kext -// and this package. - -CFDictionaryRef CFDictionaryCreateSafe2(CFAllocatorRef allocator, const uintptr_t *keys, const uintptr_t *values, CFIndex numValues, const CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks) { - return CFDictionaryCreate(allocator, (const void **)keys, (const void **)values, numValues, keyCallBacks, valueCallBacks); -} - -CFArrayRef CFArrayCreateSafe2(CFAllocatorRef allocator, const uintptr_t *values, CFIndex numValues, const CFArrayCallBacks *callBacks) { - return CFArrayCreate(allocator, (const void **)values, numValues, callBacks); -} -*/ -import "C" -import ( - "errors" - "fmt" - "math" - "reflect" - "unicode/utf8" - "unsafe" -) - -// Release releases memory pointed to by a CFTypeRef. -func Release(ref C.CFTypeRef) { - C.CFRelease(ref) -} - -// BytesToCFData will return a CFDataRef and if non-nil, must be released with -// Release(ref). -func BytesToCFData(b []byte) (C.CFDataRef, error) { - if uint64(len(b)) > math.MaxUint32 { - return 0, errors.New("Data is too large") - } - var p *C.UInt8 - if len(b) > 0 { - p = (*C.UInt8)(&b[0]) - } - cfData := C.CFDataCreate(C.kCFAllocatorDefault, p, C.CFIndex(len(b))) - if cfData == 0 { - return 0, fmt.Errorf("CFDataCreate failed") - } - return cfData, nil -} - -// CFDataToBytes converts CFData to bytes. -func CFDataToBytes(cfData C.CFDataRef) ([]byte, error) { - return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData))), nil -} - -// MapToCFDictionary will return a CFDictionaryRef and if non-nil, must be -// released with Release(ref). -func MapToCFDictionary(m map[C.CFTypeRef]C.CFTypeRef) (C.CFDictionaryRef, error) { - var keys, values []C.uintptr_t - for key, value := range m { - keys = append(keys, C.uintptr_t(key)) - values = append(values, C.uintptr_t(value)) - } - numValues := len(values) - var keysPointer, valuesPointer *C.uintptr_t - if numValues > 0 { - keysPointer = &keys[0] - valuesPointer = &values[0] - } - cfDict := C.CFDictionaryCreateSafe2(C.kCFAllocatorDefault, keysPointer, valuesPointer, C.CFIndex(numValues), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) - if cfDict == 0 { - return 0, fmt.Errorf("CFDictionaryCreate failed") - } - return cfDict, nil -} - -// CFDictionaryToMap converts CFDictionaryRef to a map. -func CFDictionaryToMap(cfDict C.CFDictionaryRef) (m map[C.CFTypeRef]C.CFTypeRef) { - count := C.CFDictionaryGetCount(cfDict) - if count > 0 { - keys := make([]C.CFTypeRef, count) - values := make([]C.CFTypeRef, count) - C.CFDictionaryGetKeysAndValues(cfDict, (*unsafe.Pointer)(unsafe.Pointer(&keys[0])), (*unsafe.Pointer)(unsafe.Pointer(&values[0]))) - m = make(map[C.CFTypeRef]C.CFTypeRef, count) - for i := C.CFIndex(0); i < count; i++ { - m[keys[i]] = values[i] - } - } - return -} - -// StringToCFString will return a CFStringRef and if non-nil, must be released with -// Release(ref). -func StringToCFString(s string) (C.CFStringRef, error) { - if !utf8.ValidString(s) { - return 0, errors.New("Invalid UTF-8 string") - } - if uint64(len(s)) > math.MaxUint32 { - return 0, errors.New("String is too large") - } - - bytes := []byte(s) - var p *C.UInt8 - if len(bytes) > 0 { - p = (*C.UInt8)(&bytes[0]) - } - return C.CFStringCreateWithBytes(C.kCFAllocatorDefault, p, C.CFIndex(len(s)), C.kCFStringEncodingUTF8, C.false), nil -} - -// CFStringToString converts a CFStringRef to a string. -func CFStringToString(s C.CFStringRef) string { - p := C.CFStringGetCStringPtr(s, C.kCFStringEncodingUTF8) - if p != nil { - return C.GoString(p) - } - length := C.CFStringGetLength(s) - if length == 0 { - return "" - } - maxBufLen := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8) - if maxBufLen == 0 { - return "" - } - buf := make([]byte, maxBufLen) - var usedBufLen C.CFIndex - _ = C.CFStringGetBytes(s, C.CFRange{0, length}, C.kCFStringEncodingUTF8, C.UInt8(0), C.false, (*C.UInt8)(&buf[0]), maxBufLen, &usedBufLen) - return string(buf[:usedBufLen]) -} - -// ArrayToCFArray will return a CFArrayRef and if non-nil, must be released with -// Release(ref). -func ArrayToCFArray(a []C.CFTypeRef) C.CFArrayRef { - var values []C.uintptr_t - for _, value := range a { - values = append(values, C.uintptr_t(value)) - } - numValues := len(values) - var valuesPointer *C.uintptr_t - if numValues > 0 { - valuesPointer = &values[0] - } - return C.CFArrayCreateSafe2(C.kCFAllocatorDefault, valuesPointer, C.CFIndex(numValues), &C.kCFTypeArrayCallBacks) -} - -// CFArrayToArray converts a CFArrayRef to an array of CFTypes. -func CFArrayToArray(cfArray C.CFArrayRef) (a []C.CFTypeRef) { - count := C.CFArrayGetCount(cfArray) - if count > 0 { - a = make([]C.CFTypeRef, count) - C.CFArrayGetValues(cfArray, C.CFRange{0, count}, (*unsafe.Pointer)(unsafe.Pointer(&a[0]))) - } - return -} - -// Convertable knows how to convert an instance to a CFTypeRef. -type Convertable interface { - Convert() (C.CFTypeRef, error) -} - -// ConvertMapToCFDictionary converts a map to a CFDictionary and if non-nil, -// must be released with Release(ref). -func ConvertMapToCFDictionary(attr map[string]interface{}) (C.CFDictionaryRef, error) { - m := make(map[C.CFTypeRef]C.CFTypeRef) - for key, i := range attr { - var valueRef C.CFTypeRef - switch val := i.(type) { - default: - return 0, fmt.Errorf("Unsupported value type: %v", reflect.TypeOf(i)) - case C.CFTypeRef: - valueRef = val - case bool: - if val { - valueRef = C.CFTypeRef(C.kCFBooleanTrue) - } else { - valueRef = C.CFTypeRef(C.kCFBooleanFalse) - } - case []byte: - bytesRef, err := BytesToCFData(val) - if err != nil { - return 0, err - } - valueRef = C.CFTypeRef(bytesRef) - defer Release(valueRef) - case string: - stringRef, err := StringToCFString(val) - if err != nil { - return 0, err - } - valueRef = C.CFTypeRef(stringRef) - defer Release(valueRef) - case Convertable: - convertedRef, err := val.Convert() - if err != nil { - return 0, err - } - valueRef = C.CFTypeRef(convertedRef) - defer Release(valueRef) - } - keyRef, err := StringToCFString(key) - if err != nil { - return 0, err - } - m[C.CFTypeRef(keyRef)] = valueRef - defer Release(C.CFTypeRef(keyRef)) - } - - cfDict, err := MapToCFDictionary(m) - if err != nil { - return 0, err - } - return cfDict, nil -} - -// CFTypeDescription returns type string for CFTypeRef. -func CFTypeDescription(ref C.CFTypeRef) string { - typeID := C.CFGetTypeID(ref) - typeDesc := C.CFCopyTypeIDDescription(typeID) - defer Release(C.CFTypeRef(typeDesc)) - return CFStringToString(typeDesc) -} - -// Convert converts a CFTypeRef to a go instance. -func Convert(ref C.CFTypeRef) (interface{}, error) { - typeID := C.CFGetTypeID(ref) - if typeID == C.CFStringGetTypeID() { - return CFStringToString(C.CFStringRef(ref)), nil - } else if typeID == C.CFDictionaryGetTypeID() { - return ConvertCFDictionary(C.CFDictionaryRef(ref)) - } else if typeID == C.CFArrayGetTypeID() { - arr := CFArrayToArray(C.CFArrayRef(ref)) - results := make([]interface{}, 0, len(arr)) - for _, ref := range arr { - v, err := Convert(ref) - if err != nil { - return nil, err - } - results = append(results, v) - } - return results, nil - } else if typeID == C.CFDataGetTypeID() { - b, err := CFDataToBytes(C.CFDataRef(ref)) - if err != nil { - return nil, err - } - return b, nil - } else if typeID == C.CFNumberGetTypeID() { - return CFNumberToInterface(C.CFNumberRef(ref)), nil - } else if typeID == C.CFBooleanGetTypeID() { - if C.CFBooleanGetValue(C.CFBooleanRef(ref)) != 0 { - return true, nil - } - return false, nil - } - - return nil, fmt.Errorf("Invalid type: %s", CFTypeDescription(ref)) -} - -// ConvertCFDictionary converts a CFDictionary to map (deep). -func ConvertCFDictionary(d C.CFDictionaryRef) (map[interface{}]interface{}, error) { - m := CFDictionaryToMap(C.CFDictionaryRef(d)) - result := make(map[interface{}]interface{}) - - for k, v := range m { - gk, err := Convert(k) - if err != nil { - return nil, err - } - gv, err := Convert(v) - if err != nil { - return nil, err - } - result[gk] = gv - } - return result, nil -} - -// CFNumberToInterface converts the CFNumberRef to the most appropriate numeric -// type. -// This code is from github.com/kballard/go-osx-plist. -func CFNumberToInterface(cfNumber C.CFNumberRef) interface{} { - typ := C.CFNumberGetType(cfNumber) - switch typ { - case C.kCFNumberSInt8Type: - var sint C.SInt8 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int8(sint) - case C.kCFNumberSInt16Type: - var sint C.SInt16 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int16(sint) - case C.kCFNumberSInt32Type: - var sint C.SInt32 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int32(sint) - case C.kCFNumberSInt64Type: - var sint C.SInt64 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int64(sint) - case C.kCFNumberFloat32Type: - var float C.Float32 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) - return float32(float) - case C.kCFNumberFloat64Type: - var float C.Float64 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) - return float64(float) - case C.kCFNumberCharType: - var char C.char - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&char)) - return byte(char) - case C.kCFNumberShortType: - var short C.short - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&short)) - return int16(short) - case C.kCFNumberIntType: - var i C.int - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&i)) - return int32(i) - case C.kCFNumberLongType: - var long C.long - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&long)) - return int(long) - case C.kCFNumberLongLongType: - // This is the only type that may actually overflow us - var longlong C.longlong - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&longlong)) - return int64(longlong) - case C.kCFNumberFloatType: - var float C.float - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) - return float32(float) - case C.kCFNumberDoubleType: - var double C.double - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&double)) - return float64(double) - case C.kCFNumberCFIndexType: - // CFIndex is a long - var index C.CFIndex - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&index)) - return int(index) - case C.kCFNumberNSIntegerType: - // We don't have a definition of NSInteger, but we know it's either an int or a long - var nsInt C.long - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&nsInt)) - return int(nsInt) - } - panic("Unknown CFNumber type") -} diff --git a/vendor/github.com/keybase/go-keychain/corefoundation_pre1.10.go b/vendor/github.com/keybase/go-keychain/corefoundation_pre1.10.go deleted file mode 100644 index 8427419..0000000 --- a/vendor/github.com/keybase/go-keychain/corefoundation_pre1.10.go +++ /dev/null @@ -1,344 +0,0 @@ -// +build darwin ios -// +build !go1.10 - -// TODO: Remove this file once we've completely migrated to go 1.10.x. - -package keychain - -/* -#cgo LDFLAGS: -framework CoreFoundation - -#include -*/ -import "C" -import ( - "errors" - "fmt" - "math" - "reflect" - "unicode/utf8" - "unsafe" -) - -// Release releases memory pointed to by a CFTypeRef. -func Release(ref C.CFTypeRef) { - C.CFRelease(ref) -} - -// BytesToCFData will return a CFDataRef and if non-nil, must be released with -// Release(ref). -func BytesToCFData(b []byte) (C.CFDataRef, error) { - if uint64(len(b)) > math.MaxUint32 { - return nil, errors.New("Data is too large") - } - var p *C.UInt8 - if len(b) > 0 { - p = (*C.UInt8)(&b[0]) - } - cfData := C.CFDataCreate(nil, p, C.CFIndex(len(b))) - if cfData == nil { - return nil, fmt.Errorf("CFDataCreate failed") - } - return cfData, nil -} - -// CFDataToBytes converts CFData to bytes. -func CFDataToBytes(cfData C.CFDataRef) ([]byte, error) { - return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData))), nil -} - -// MapToCFDictionary will return a CFDictionaryRef and if non-nil, must be -// released with Release(ref). -func MapToCFDictionary(m map[C.CFTypeRef]C.CFTypeRef) (C.CFDictionaryRef, error) { - var keys, values []unsafe.Pointer - for key, value := range m { - keys = append(keys, unsafe.Pointer(key)) - values = append(values, unsafe.Pointer(value)) - } - numValues := len(values) - var keysPointer, valuesPointer *unsafe.Pointer - if numValues > 0 { - keysPointer = &keys[0] - valuesPointer = &values[0] - } - cfDict := C.CFDictionaryCreate(nil, keysPointer, valuesPointer, C.CFIndex(numValues), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) - if cfDict == nil { - return nil, fmt.Errorf("CFDictionaryCreate failed") - } - return cfDict, nil -} - -// CFDictionaryToMap converts CFDictionaryRef to a map. -func CFDictionaryToMap(cfDict C.CFDictionaryRef) (m map[C.CFTypeRef]uintptr) { - count := C.CFDictionaryGetCount(cfDict) - if count > 0 { - keys := make([]C.CFTypeRef, count) - values := make([]C.CFTypeRef, count) - C.CFDictionaryGetKeysAndValues(cfDict, (*unsafe.Pointer)(&keys[0]), (*unsafe.Pointer)(&values[0])) - m = make(map[C.CFTypeRef]uintptr, count) - for i := C.CFIndex(0); i < count; i++ { - k := keys[i] - v := values[i] - m[k] = uintptr(v) - } - } - return -} - -// StringToCFString will return a CFStringRef and if non-nil, must be released with -// Release(ref). -func StringToCFString(s string) (C.CFStringRef, error) { - if !utf8.ValidString(s) { - return nil, errors.New("Invalid UTF-8 string") - } - if uint64(len(s)) > math.MaxUint32 { - return nil, errors.New("String is too large") - } - - bytes := []byte(s) - var p *C.UInt8 - if len(bytes) > 0 { - p = (*C.UInt8)(&bytes[0]) - } - return C.CFStringCreateWithBytes(nil, p, C.CFIndex(len(s)), C.kCFStringEncodingUTF8, C.false), nil -} - -// CFStringToString converts a CFStringRef to a string. -func CFStringToString(s C.CFStringRef) string { - p := C.CFStringGetCStringPtr(s, C.kCFStringEncodingUTF8) - if p != nil { - return C.GoString(p) - } - length := C.CFStringGetLength(s) - if length == 0 { - return "" - } - maxBufLen := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8) - if maxBufLen == 0 { - return "" - } - buf := make([]byte, maxBufLen) - var usedBufLen C.CFIndex - _ = C.CFStringGetBytes(s, C.CFRange{0, length}, C.kCFStringEncodingUTF8, C.UInt8(0), C.false, (*C.UInt8)(&buf[0]), maxBufLen, &usedBufLen) - return string(buf[:usedBufLen]) -} - -// ArrayToCFArray will return a CFArrayRef and if non-nil, must be released with -// Release(ref). -func ArrayToCFArray(a []C.CFTypeRef) C.CFArrayRef { - var values []unsafe.Pointer - for _, value := range a { - values = append(values, unsafe.Pointer(value)) - } - numValues := len(values) - var valuesPointer *unsafe.Pointer - if numValues > 0 { - valuesPointer = &values[0] - } - return C.CFArrayCreate(nil, valuesPointer, C.CFIndex(numValues), &C.kCFTypeArrayCallBacks) -} - -// CFArrayToArray converts a CFArrayRef to an array of CFTypes. -func CFArrayToArray(cfArray C.CFArrayRef) (a []C.CFTypeRef) { - count := C.CFArrayGetCount(cfArray) - if count > 0 { - a = make([]C.CFTypeRef, count) - C.CFArrayGetValues(cfArray, C.CFRange{0, count}, (*unsafe.Pointer)(&a[0])) - } - return -} - -// Convertable knows how to convert an instance to a CFTypeRef. -type Convertable interface { - Convert() (C.CFTypeRef, error) -} - -// ConvertMapToCFDictionary converts a map to a CFDictionary and if non-nil, -// must be released with Release(ref). -func ConvertMapToCFDictionary(attr map[string]interface{}) (C.CFDictionaryRef, error) { - m := make(map[C.CFTypeRef]C.CFTypeRef) - for key, i := range attr { - var valueRef C.CFTypeRef - switch val := i.(type) { - default: - return nil, fmt.Errorf("Unsupported value type: %v", reflect.TypeOf(i)) - case C.CFTypeRef: - valueRef = val - case bool: - if val { - valueRef = C.CFTypeRef(C.kCFBooleanTrue) - } else { - valueRef = C.CFTypeRef(C.kCFBooleanFalse) - } - case []byte: - bytesRef, err := BytesToCFData(val) - if err != nil { - return nil, err - } - valueRef = C.CFTypeRef(bytesRef) - defer Release(valueRef) - case string: - stringRef, err := StringToCFString(val) - if err != nil { - return nil, err - } - valueRef = C.CFTypeRef(stringRef) - defer Release(valueRef) - case Convertable: - convertedRef, err := val.Convert() - if err != nil { - return nil, err - } - valueRef = C.CFTypeRef(convertedRef) - defer Release(valueRef) - } - keyRef, err := StringToCFString(key) - if err != nil { - return nil, err - } - m[C.CFTypeRef(keyRef)] = valueRef - defer Release(C.CFTypeRef(keyRef)) - } - - cfDict, err := MapToCFDictionary(m) - if err != nil { - return nil, err - } - return cfDict, nil -} - -// CFTypeDescription returns type string for CFTypeRef. -func CFTypeDescription(ref C.CFTypeRef) string { - typeID := C.CFGetTypeID(ref) - typeDesc := C.CFCopyTypeIDDescription(typeID) - defer Release(C.CFTypeRef(typeDesc)) - return CFStringToString(typeDesc) -} - -// Convert converts a CFTypeRef to a go instance. -func Convert(ref C.CFTypeRef) (interface{}, error) { - typeID := C.CFGetTypeID(ref) - if typeID == C.CFStringGetTypeID() { - return CFStringToString(C.CFStringRef(ref)), nil - } else if typeID == C.CFDictionaryGetTypeID() { - return ConvertCFDictionary(C.CFDictionaryRef(ref)) - } else if typeID == C.CFArrayGetTypeID() { - arr := CFArrayToArray(C.CFArrayRef(ref)) - results := make([]interface{}, 0, len(arr)) - for _, ref := range arr { - v, err := Convert(ref) - if err != nil { - return nil, err - } - results = append(results, v) - } - return results, nil - } else if typeID == C.CFDataGetTypeID() { - b, err := CFDataToBytes(C.CFDataRef(ref)) - if err != nil { - return nil, err - } - return b, nil - } else if typeID == C.CFNumberGetTypeID() { - return CFNumberToInterface(C.CFNumberRef(ref)), nil - } else if typeID == C.CFBooleanGetTypeID() { - if C.CFBooleanGetValue(C.CFBooleanRef(ref)) != 0 { - return true, nil - } - return false, nil - } - - return nil, fmt.Errorf("Invalid type: %s", CFTypeDescription(ref)) -} - -// ConvertCFDictionary converts a CFDictionary to map (deep). -func ConvertCFDictionary(d C.CFDictionaryRef) (map[interface{}]interface{}, error) { - m := CFDictionaryToMap(C.CFDictionaryRef(d)) - result := make(map[interface{}]interface{}) - - for k, v := range m { - gk, err := Convert(k) - if err != nil { - return nil, err - } - gv, err := Convert(C.CFTypeRef(v)) - if err != nil { - return nil, err - } - result[gk] = gv - } - return result, nil -} - -// CFNumberToInterface converts the CFNumberRef to the most appropriate numeric -// type. -// This code is from github.com/kballard/go-osx-plist. -func CFNumberToInterface(cfNumber C.CFNumberRef) interface{} { - typ := C.CFNumberGetType(cfNumber) - switch typ { - case C.kCFNumberSInt8Type: - var sint C.SInt8 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int8(sint) - case C.kCFNumberSInt16Type: - var sint C.SInt16 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int16(sint) - case C.kCFNumberSInt32Type: - var sint C.SInt32 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int32(sint) - case C.kCFNumberSInt64Type: - var sint C.SInt64 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) - return int64(sint) - case C.kCFNumberFloat32Type: - var float C.Float32 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) - return float32(float) - case C.kCFNumberFloat64Type: - var float C.Float64 - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) - return float64(float) - case C.kCFNumberCharType: - var char C.char - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&char)) - return byte(char) - case C.kCFNumberShortType: - var short C.short - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&short)) - return int16(short) - case C.kCFNumberIntType: - var i C.int - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&i)) - return int32(i) - case C.kCFNumberLongType: - var long C.long - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&long)) - return int(long) - case C.kCFNumberLongLongType: - // This is the only type that may actually overflow us - var longlong C.longlong - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&longlong)) - return int64(longlong) - case C.kCFNumberFloatType: - var float C.float - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) - return float32(float) - case C.kCFNumberDoubleType: - var double C.double - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&double)) - return float64(double) - case C.kCFNumberCFIndexType: - // CFIndex is a long - var index C.CFIndex - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&index)) - return int(index) - case C.kCFNumberNSIntegerType: - // We don't have a definition of NSInteger, but we know it's either an int or a long - var nsInt C.long - C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&nsInt)) - return int(nsInt) - } - panic("Unknown CFNumber type") -} diff --git a/vendor/github.com/keybase/go-keychain/datetime.go b/vendor/github.com/keybase/go-keychain/datetime.go deleted file mode 100644 index 3a783b4..0000000 --- a/vendor/github.com/keybase/go-keychain/datetime.go +++ /dev/null @@ -1,69 +0,0 @@ -// +build darwin ios - -package keychain - -/* -#cgo LDFLAGS: -framework CoreFoundation - -#include -*/ -import "C" -import ( - "math" - "time" -) - -const nsPerSec = 1000 * 1000 * 1000 - -// absoluteTimeIntervalSince1970() returns the number of seconds from -// the Unix epoch (1970-01-01T00:00:00+00:00) to the Core Foundation -// absolute reference date (2001-01-01T00:00:00+00:00). It should be -// exactly 978307200. -func absoluteTimeIntervalSince1970() int64 { - return int64(C.kCFAbsoluteTimeIntervalSince1970) -} - -func unixToAbsoluteTime(s int64, ns int64) C.CFAbsoluteTime { - // Subtract as int64s first before converting to floating - // point to minimize precision loss (assuming the given time - // isn't much earlier than the Core Foundation absolute - // reference date). - abs := s - absoluteTimeIntervalSince1970() - // Need to cast CFTimeInterval to CFAbsoluteTime for go 1.9.x. - return C.CFAbsoluteTime(abs) + C.CFAbsoluteTime(C.CFTimeInterval(ns))/nsPerSec -} - -func absoluteTimeToUnix(abs C.CFAbsoluteTime) (int64, int64) { - int, frac := math.Modf(float64(abs)) - return int64(int) + absoluteTimeIntervalSince1970(), int64(frac * nsPerSec) -} - -// TimeToCFDate will convert the given time.Time to a CFDateRef, which -// must be released with Release(ref). -func TimeToCFDate(t time.Time) C.CFDateRef { - s := t.Unix() - ns := int64(t.Nanosecond()) - abs := unixToAbsoluteTime(s, ns) - return C.CFDateCreate(C.kCFAllocatorDefault, abs) -} - -// CFDateToTime will convert the given CFDateRef to a time.Time. -func CFDateToTime(d C.CFDateRef) time.Time { - abs := C.CFDateGetAbsoluteTime(d) - s, ns := absoluteTimeToUnix(abs) - return time.Unix(s, ns) -} - -// Wrappers around C functions for testing. - -func cfDateToAbsoluteTime(d C.CFDateRef) C.CFAbsoluteTime { - return C.CFDateGetAbsoluteTime(d) -} - -func absoluteTimeToCFDate(abs C.CFAbsoluteTime) C.CFDateRef { - return C.CFDateCreate(C.kCFAllocatorDefault, abs) -} - -func releaseCFDate(d C.CFDateRef) { - Release(C.CFTypeRef(d)) -} diff --git a/vendor/github.com/keybase/go-keychain/ios.go b/vendor/github.com/keybase/go-keychain/ios.go deleted file mode 100644 index abbaf28..0000000 --- a/vendor/github.com/keybase/go-keychain/ios.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build darwin,ios - -package keychain - -/* -#cgo LDFLAGS: -framework CoreFoundation -framework Security - -#include -#include -*/ -import "C" - -var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible)) -var accessibleTypeRef = map[Accessible]C.CFTypeRef{ - AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked), - AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock), - AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways), - AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly), - AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly), - AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly), - AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly), -} diff --git a/vendor/github.com/keybase/go-keychain/keychain_1.10.go b/vendor/github.com/keybase/go-keychain/keychain_1.10.go deleted file mode 100644 index 347c3ef..0000000 --- a/vendor/github.com/keybase/go-keychain/keychain_1.10.go +++ /dev/null @@ -1,532 +0,0 @@ -// +build darwin -// +build go1.10 - -package keychain - -// See https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html for the APIs used below. - -// Also see https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html . - -/* -#cgo LDFLAGS: -framework CoreFoundation -framework Security - -#include -#include -*/ -import "C" -import ( - "fmt" - "time" -) - -// Error defines keychain errors -type Error int - -var ( - // ErrorUnimplemented corresponds to errSecUnimplemented result code - ErrorUnimplemented = Error(C.errSecUnimplemented) - // ErrorParam corresponds to errSecParam result code - ErrorParam = Error(C.errSecParam) - // ErrorAllocate corresponds to errSecAllocate result code - ErrorAllocate = Error(C.errSecAllocate) - // ErrorNotAvailable corresponds to errSecNotAvailable result code - ErrorNotAvailable = Error(C.errSecNotAvailable) - // ErrorAuthFailed corresponds to errSecAuthFailed result code - ErrorAuthFailed = Error(C.errSecAuthFailed) - // ErrorDuplicateItem corresponds to errSecDuplicateItem result code - ErrorDuplicateItem = Error(C.errSecDuplicateItem) - // ErrorItemNotFound corresponds to errSecItemNotFound result code - ErrorItemNotFound = Error(C.errSecItemNotFound) - // ErrorInteractionNotAllowed corresponds to errSecInteractionNotAllowed result code - ErrorInteractionNotAllowed = Error(C.errSecInteractionNotAllowed) - // ErrorDecode corresponds to errSecDecode result code - ErrorDecode = Error(C.errSecDecode) - // ErrorNoSuchKeychain corresponds to errSecNoSuchKeychain result code - ErrorNoSuchKeychain = Error(C.errSecNoSuchKeychain) - // ErrorNoAcccessForItem corresponds to errSecNoAccessForItem result code - ErrorNoAccessForItem = Error(C.errSecNoAccessForItem) -) - -func checkError(errCode C.OSStatus) error { - if errCode == C.errSecSuccess { - return nil - } - return Error(errCode) -} - -func (k Error) Error() (msg string) { - // SecCopyErrorMessageString is only available on OSX, so derive manually. - // Messages derived from `$ security error $errcode`. - switch k { - case ErrorUnimplemented: - msg = "Function or operation not implemented." - case ErrorParam: - msg = "One or more parameters passed to the function were not valid." - case ErrorAllocate: - msg = "Failed to allocate memory." - case ErrorNotAvailable: - msg = "No keychain is available. You may need to restart your computer." - case ErrorAuthFailed: - msg = "The user name or passphrase you entered is not correct." - case ErrorDuplicateItem: - msg = "The specified item already exists in the keychain." - case ErrorItemNotFound: - msg = "The specified item could not be found in the keychain." - case ErrorInteractionNotAllowed: - msg = "User interaction is not allowed." - case ErrorDecode: - msg = "Unable to decode the provided data." - case ErrorNoSuchKeychain: - msg = "The specified keychain could not be found." - case ErrorNoAccessForItem: - msg = "The specified item has no access control." - default: - msg = "Keychain Error." - } - return fmt.Sprintf("%s (%d)", msg, k) -} - -// SecClass is the items class code -type SecClass int - -// Keychain Item Classes -var ( - /* - kSecClassGenericPassword item attributes: - kSecAttrAccess (OS X only) - kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified) - kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified) - kSecAttrAccount - kSecAttrService - */ - SecClassGenericPassword SecClass = 1 - SecClassInternetPassword SecClass = 2 -) - -// SecClassKey is the key type for SecClass -var SecClassKey = attrKey(C.CFTypeRef(C.kSecClass)) -var secClassTypeRef = map[SecClass]C.CFTypeRef{ - SecClassGenericPassword: C.CFTypeRef(C.kSecClassGenericPassword), - SecClassInternetPassword: C.CFTypeRef(C.kSecClassInternetPassword), -} - -var ( - // ServiceKey is for kSecAttrService - ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService)) - // LabelKey is for kSecAttrLabel - LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel)) - // AccountKey is for kSecAttrAccount - AccountKey = attrKey(C.CFTypeRef(C.kSecAttrAccount)) - // AccessGroupKey is for kSecAttrAccessGroup - AccessGroupKey = attrKey(C.CFTypeRef(C.kSecAttrAccessGroup)) - // DataKey is for kSecValueData - DataKey = attrKey(C.CFTypeRef(C.kSecValueData)) - // DescriptionKey is for kSecAttrDescription - DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription)) - // CreationDateKey is for kSecAttrCreationDate - CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate)) - // ModificationDateKey is for kSecAttrModificationDate - ModificationDateKey = attrKey(C.CFTypeRef(C.kSecAttrModificationDate)) -) - -// Synchronizable is the items synchronizable status -type Synchronizable int - -const ( - // SynchronizableDefault is the default setting - SynchronizableDefault Synchronizable = 0 - // SynchronizableAny is for kSecAttrSynchronizableAny - SynchronizableAny = 1 - // SynchronizableYes enables synchronization - SynchronizableYes = 2 - // SynchronizableNo disables synchronization - SynchronizableNo = 3 -) - -// SynchronizableKey is the key type for Synchronizable -var SynchronizableKey = attrKey(C.CFTypeRef(C.kSecAttrSynchronizable)) -var syncTypeRef = map[Synchronizable]C.CFTypeRef{ - SynchronizableAny: C.CFTypeRef(C.kSecAttrSynchronizableAny), - SynchronizableYes: C.CFTypeRef(C.kCFBooleanTrue), - SynchronizableNo: C.CFTypeRef(C.kCFBooleanFalse), -} - -// Accessible is the items accessibility -type Accessible int - -const ( - // AccessibleDefault is the default - AccessibleDefault Accessible = 0 - // AccessibleWhenUnlocked is when unlocked - AccessibleWhenUnlocked = 1 - // AccessibleAfterFirstUnlock is after first unlock - AccessibleAfterFirstUnlock = 2 - // AccessibleAlways is always - AccessibleAlways = 3 - // AccessibleWhenPasscodeSetThisDeviceOnly is when passcode is set - AccessibleWhenPasscodeSetThisDeviceOnly = 4 - // AccessibleWhenUnlockedThisDeviceOnly is when unlocked for this device only - AccessibleWhenUnlockedThisDeviceOnly = 5 - // AccessibleAfterFirstUnlockThisDeviceOnly is after first unlock for this device only - AccessibleAfterFirstUnlockThisDeviceOnly = 6 - // AccessibleAccessibleAlwaysThisDeviceOnly is always for this device only - AccessibleAccessibleAlwaysThisDeviceOnly = 7 -) - -// MatchLimit is whether to limit results on query -type MatchLimit int - -const ( - // MatchLimitDefault is the default - MatchLimitDefault MatchLimit = 0 - // MatchLimitOne limits to one result - MatchLimitOne = 1 - // MatchLimitAll is no limit - MatchLimitAll = 2 -) - -// MatchLimitKey is key type for MatchLimit -var MatchLimitKey = attrKey(C.CFTypeRef(C.kSecMatchLimit)) -var matchTypeRef = map[MatchLimit]C.CFTypeRef{ - MatchLimitOne: C.CFTypeRef(C.kSecMatchLimitOne), - MatchLimitAll: C.CFTypeRef(C.kSecMatchLimitAll), -} - -// ReturnAttributesKey is key type for kSecReturnAttributes -var ReturnAttributesKey = attrKey(C.CFTypeRef(C.kSecReturnAttributes)) - -// ReturnDataKey is key type for kSecReturnData -var ReturnDataKey = attrKey(C.CFTypeRef(C.kSecReturnData)) - -// ReturnRefKey is key type for kSecReturnRef -var ReturnRefKey = attrKey(C.CFTypeRef(C.kSecReturnRef)) - -// Item for adding, querying or deleting. -type Item struct { - // Values can be string, []byte, Convertable or CFTypeRef (constant). - attr map[string]interface{} -} - -// SetSecClass sets the security class -func (k *Item) SetSecClass(sc SecClass) { - k.attr[SecClassKey] = secClassTypeRef[sc] -} - -// SetString sets a string attibute for a string key -func (k *Item) SetString(key string, s string) { - if s != "" { - k.attr[key] = s - } else { - delete(k.attr, key) - } -} - -// SetService sets the service attribute -func (k *Item) SetService(s string) { - k.SetString(ServiceKey, s) -} - -// SetAccount sets the account attribute -func (k *Item) SetAccount(a string) { - k.SetString(AccountKey, a) -} - -// SetLabel sets the label attribute -func (k *Item) SetLabel(l string) { - k.SetString(LabelKey, l) -} - -// SetDescription sets the description attribute -func (k *Item) SetDescription(s string) { - k.SetString(DescriptionKey, s) -} - -// SetData sets the data attribute -func (k *Item) SetData(b []byte) { - if b != nil { - k.attr[DataKey] = b - } else { - delete(k.attr, DataKey) - } -} - -// SetAccessGroup sets the access group attribute -func (k *Item) SetAccessGroup(ag string) { - k.SetString(AccessGroupKey, ag) -} - -// SetSynchronizable sets the synchronizable attribute -func (k *Item) SetSynchronizable(sync Synchronizable) { - if sync != SynchronizableDefault { - k.attr[SynchronizableKey] = syncTypeRef[sync] - } else { - delete(k.attr, SynchronizableKey) - } -} - -// SetAccessible sets the accessible attribute -func (k *Item) SetAccessible(accessible Accessible) { - if accessible != AccessibleDefault { - k.attr[AccessibleKey] = accessibleTypeRef[accessible] - } else { - delete(k.attr, AccessibleKey) - } -} - -// SetMatchLimit sets the match limit -func (k *Item) SetMatchLimit(matchLimit MatchLimit) { - if matchLimit != MatchLimitDefault { - k.attr[MatchLimitKey] = matchTypeRef[matchLimit] - } else { - delete(k.attr, MatchLimitKey) - } -} - -// SetReturnAttributes sets the return value type on query -func (k *Item) SetReturnAttributes(b bool) { - k.attr[ReturnAttributesKey] = b -} - -// SetReturnData enables returning data on query -func (k *Item) SetReturnData(b bool) { - k.attr[ReturnDataKey] = b -} - -// SetReturnRef enables returning references on query -func (k *Item) SetReturnRef(b bool) { - k.attr[ReturnRefKey] = b -} - -// NewItem is a new empty keychain item -func NewItem() Item { - return Item{make(map[string]interface{})} -} - -// NewGenericPassword creates a generic password item with the default keychain. This is a convenience method. -func NewGenericPassword(service string, account string, label string, data []byte, accessGroup string) Item { - item := NewItem() - item.SetSecClass(SecClassGenericPassword) - item.SetService(service) - item.SetAccount(account) - item.SetLabel(label) - item.SetData(data) - item.SetAccessGroup(accessGroup) - return item -} - -// AddItem adds a Item to a Keychain -func AddItem(item Item) error { - cfDict, err := ConvertMapToCFDictionary(item.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDict)) - - errCode := C.SecItemAdd(cfDict, nil) - err = checkError(errCode) - return err -} - -// UpdateItem updates the queryItem with the parameters from updateItem -func UpdateItem(queryItem Item, updateItem Item) error { - cfDict, err := ConvertMapToCFDictionary(queryItem.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDict)) - cfDictUpdate, err := ConvertMapToCFDictionary(updateItem.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDictUpdate)) - errCode := C.SecItemUpdate(cfDict, cfDictUpdate) - err = checkError(errCode) - return err -} - -// QueryResult stores all possible results from queries. -// Not all fields are applicable all the time. Results depend on query. -type QueryResult struct { - Service string - Account string - AccessGroup string - Label string - Description string - Data []byte - CreationDate time.Time - ModificationDate time.Time -} - -// QueryItemRef returns query result as CFTypeRef. You must release it when you are done. -func QueryItemRef(item Item) (C.CFTypeRef, error) { - cfDict, err := ConvertMapToCFDictionary(item.attr) - if err != nil { - return 0, err - } - defer Release(C.CFTypeRef(cfDict)) - - var resultsRef C.CFTypeRef - errCode := C.SecItemCopyMatching(cfDict, &resultsRef) - if Error(errCode) == ErrorItemNotFound { - return 0, nil - } - err = checkError(errCode) - if err != nil { - return 0, err - } - return resultsRef, nil -} - -// QueryItem returns a list of query results. -func QueryItem(item Item) ([]QueryResult, error) { - resultsRef, err := QueryItemRef(item) - if err != nil { - return nil, err - } - if resultsRef == 0 { - return nil, nil - } - defer Release(resultsRef) - - results := make([]QueryResult, 0, 1) - - typeID := C.CFGetTypeID(resultsRef) - if typeID == C.CFArrayGetTypeID() { - arr := CFArrayToArray(C.CFArrayRef(resultsRef)) - for _, ref := range arr { - elementTypeID := C.CFGetTypeID(ref) - if elementTypeID == C.CFDictionaryGetTypeID() { - item, err := convertResult(C.CFDictionaryRef(ref)) - if err != nil { - return nil, err - } - results = append(results, *item) - } else { - return nil, fmt.Errorf("invalid result type (If you SetReturnRef(true) you should use QueryItemRef directly)") - } - } - } else if typeID == C.CFDictionaryGetTypeID() { - item, err := convertResult(C.CFDictionaryRef(resultsRef)) - if err != nil { - return nil, err - } - results = append(results, *item) - } else if typeID == C.CFDataGetTypeID() { - b, err := CFDataToBytes(C.CFDataRef(resultsRef)) - if err != nil { - return nil, err - } - item := QueryResult{Data: b} - results = append(results, item) - } else { - return nil, fmt.Errorf("Invalid result type: %s", CFTypeDescription(resultsRef)) - } - - return results, nil -} - -func attrKey(ref C.CFTypeRef) string { - return CFStringToString(C.CFStringRef(ref)) -} - -func convertResult(d C.CFDictionaryRef) (*QueryResult, error) { - m := CFDictionaryToMap(C.CFDictionaryRef(d)) - result := QueryResult{} - for k, v := range m { - switch attrKey(k) { - case ServiceKey: - result.Service = CFStringToString(C.CFStringRef(v)) - case AccountKey: - result.Account = CFStringToString(C.CFStringRef(v)) - case AccessGroupKey: - result.AccessGroup = CFStringToString(C.CFStringRef(v)) - case LabelKey: - result.Label = CFStringToString(C.CFStringRef(v)) - case DescriptionKey: - result.Description = CFStringToString(C.CFStringRef(v)) - case DataKey: - b, err := CFDataToBytes(C.CFDataRef(v)) - if err != nil { - return nil, err - } - result.Data = b - case CreationDateKey: - result.CreationDate = CFDateToTime(C.CFDateRef(v)) - case ModificationDateKey: - result.ModificationDate = CFDateToTime(C.CFDateRef(v)) - // default: - // fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(v)) - } - } - return &result, nil -} - -// DeleteGenericPasswordItem removes a generic password item. -func DeleteGenericPasswordItem(service string, account string) error { - item := NewItem() - item.SetSecClass(SecClassGenericPassword) - item.SetService(service) - item.SetAccount(account) - return DeleteItem(item) -} - -// DeleteItem removes a Item -func DeleteItem(item Item) error { - cfDict, err := ConvertMapToCFDictionary(item.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDict)) - - errCode := C.SecItemDelete(cfDict) - return checkError(errCode) -} - -// GetAccountsForService is deprecated -func GetAccountsForService(service string) ([]string, error) { - return GetGenericPasswordAccounts(service) -} - -// GetGenericPasswordAccounts returns generic password accounts for service. This is a convenience method. -func GetGenericPasswordAccounts(service string) ([]string, error) { - query := NewItem() - query.SetSecClass(SecClassGenericPassword) - query.SetService(service) - query.SetMatchLimit(MatchLimitAll) - query.SetReturnAttributes(true) - results, err := QueryItem(query) - if err != nil { - return nil, err - } - - accounts := make([]string, 0, len(results)) - for _, r := range results { - accounts = append(accounts, r.Account) - } - - return accounts, nil -} - -// GetGenericPassword returns password data for service and account. This is a convenience method. -// If item is not found returns nil, nil. -func GetGenericPassword(service string, account string, label string, accessGroup string) ([]byte, error) { - query := NewItem() - query.SetSecClass(SecClassGenericPassword) - query.SetService(service) - query.SetAccount(account) - query.SetLabel(label) - query.SetAccessGroup(accessGroup) - query.SetMatchLimit(MatchLimitOne) - query.SetReturnData(true) - results, err := QueryItem(query) - if err != nil { - return nil, err - } - if len(results) > 1 { - return nil, fmt.Errorf("Too many results") - } - if len(results) == 1 { - return results[0].Data, nil - } - return nil, nil -} diff --git a/vendor/github.com/keybase/go-keychain/keychain_pre1.10.go b/vendor/github.com/keybase/go-keychain/keychain_pre1.10.go deleted file mode 100644 index 8e02359..0000000 --- a/vendor/github.com/keybase/go-keychain/keychain_pre1.10.go +++ /dev/null @@ -1,601 +0,0 @@ -// +build darwin -// +build !go1.10 - -// TODO: Remove this file once we've completely migrated to go 1.10.x. - -package keychain - -// See https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html for the APIs used below. - -// Also see https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html . - -/* -#cgo LDFLAGS: -framework CoreFoundation -framework Security - -#include -#include - -// Casting C.CFTypeRef to unsafe.Pointer is unsafe in Go, so have shim -// functions to do the casting in C (where it's safe). - -const UInt8 * CFDataGetBytePtrSafe(uintptr_t theData) { - return CFDataGetBytePtr((CFDataRef)theData); -} - -CFIndex CFDataGetLengthSafe(uintptr_t theData) { - return CFDataGetLength((CFDataRef)theData); -} - -const char * CFStringGetCStringPtrSafe(uintptr_t theString, CFStringEncoding encoding) { - return CFStringGetCStringPtr((CFStringRef)theString, encoding); -} - -CFIndex CFStringGetLengthSafe(uintptr_t theString) { - return CFStringGetLength((CFStringRef)theString); -} - -CFIndex CFStringGetBytesSafe(uintptr_t theString, CFRange range, CFStringEncoding encoding, UInt8 lossByte, Boolean isExternalRepresentation, UInt8 *buffer, CFIndex maxBufLen, CFIndex *usedBufLen) { - return CFStringGetBytes((CFStringRef)theString, range, encoding, lossByte, isExternalRepresentation, buffer, maxBufLen, usedBufLen); -} - -CFAbsoluteTime CFDateGetAbsoluteTimeSafe(uintptr_t theDate) { - return CFDateGetAbsoluteTime((CFDateRef)theDate); -} -*/ -import "C" -import ( - "fmt" - "time" - "unsafe" -) - -// Error defines keychain errors -type Error int - -var ( - // ErrorUnimplemented corresponds to errSecUnimplemented result code - ErrorUnimplemented = Error(C.errSecUnimplemented) - // ErrorParam corresponds to errSecParam result code - ErrorParam = Error(C.errSecParam) - // ErrorAllocate corresponds to errSecAllocate result code - ErrorAllocate = Error(C.errSecAllocate) - // ErrorNotAvailable corresponds to errSecNotAvailable result code - ErrorNotAvailable = Error(C.errSecNotAvailable) - // ErrorAuthFailed corresponds to errSecAuthFailed result code - ErrorAuthFailed = Error(C.errSecAuthFailed) - // ErrorDuplicateItem corresponds to errSecDuplicateItem result code - ErrorDuplicateItem = Error(C.errSecDuplicateItem) - // ErrorItemNotFound corresponds to errSecItemNotFound result code - ErrorItemNotFound = Error(C.errSecItemNotFound) - // ErrorInteractionNotAllowed corresponds to errSecInteractionNotAllowed result code - ErrorInteractionNotAllowed = Error(C.errSecInteractionNotAllowed) - // ErrorDecode corresponds to errSecDecode result code - ErrorDecode = Error(C.errSecDecode) - // ErrorNoSuchKeychain corresponds to errSecNoSuchKeychain result code - ErrorNoSuchKeychain = Error(C.errSecNoSuchKeychain) - // ErrorNoAcccessForItem corresponds to errSecNoAccessForItem result code - ErrorNoAccessForItem = Error(C.errSecNoAccessForItem) -) - -func checkError(errCode C.OSStatus) error { - if errCode == C.errSecSuccess { - return nil - } - return Error(errCode) -} - -func (k Error) Error() (msg string) { - // SecCopyErrorMessageString is only available on OSX, so derive manually. - // Messages derived from `$ security error $errcode`. - switch k { - case ErrorUnimplemented: - msg = "Function or operation not implemented." - case ErrorParam: - msg = "One or more parameters passed to the function were not valid." - case ErrorAllocate: - msg = "Failed to allocate memory." - case ErrorNotAvailable: - msg = "No keychain is available. You may need to restart your computer." - case ErrorAuthFailed: - msg = "The user name or passphrase you entered is not correct." - case ErrorDuplicateItem: - msg = "The specified item already exists in the keychain." - case ErrorItemNotFound: - msg = "The specified item could not be found in the keychain." - case ErrorInteractionNotAllowed: - msg = "User interaction is not allowed." - case ErrorDecode: - msg = "Unable to decode the provided data." - case ErrorNoSuchKeychain: - msg = "The specified keychain could not be found." - case ErrorNoAccessForItem: - msg = "The specified item has no access control." - default: - msg = "Keychain Error." - } - return fmt.Sprintf("%s (%d)", msg, k) -} - -// SecClass is the items class code -type SecClass int - -// Keychain Item Classes -var ( - /* - kSecClassGenericPassword item attributes: - kSecAttrAccess (OS X only) - kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified) - kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified) - kSecAttrAccount - kSecAttrService - */ - SecClassGenericPassword SecClass = 1 -) - -// SecClassKey is the key type for SecClass -var SecClassKey = attrKey(C.CFTypeRef(C.kSecClass)) -var secClassTypeRef = map[SecClass]C.CFTypeRef{ - SecClassGenericPassword: C.CFTypeRef(C.kSecClassGenericPassword), -} - -var ( - // ServiceKey is for kSecAttrService - ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService)) - // LabelKey is for kSecAttrLabel - LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel)) - // AccountKey is for kSecAttrAccount - AccountKey = attrKey(C.CFTypeRef(C.kSecAttrAccount)) - // AccessGroupKey is for kSecAttrAccessGroup - AccessGroupKey = attrKey(C.CFTypeRef(C.kSecAttrAccessGroup)) - // DataKey is for kSecValueData - DataKey = attrKey(C.CFTypeRef(C.kSecValueData)) - // DescriptionKey is for kSecAttrDescription - DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription)) - // CreationDateKey is for kSecAttrCreationDate - CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate)) - // ModificationDateKey is for kSecAttrModificationDate - ModificationDateKey = attrKey(C.CFTypeRef(C.kSecAttrModificationDate)) -) - -// Synchronizable is the items synchronizable status -type Synchronizable int - -const ( - // SynchronizableDefault is the default setting - SynchronizableDefault Synchronizable = 0 - // SynchronizableAny is for kSecAttrSynchronizableAny - SynchronizableAny = 1 - // SynchronizableYes enables synchronization - SynchronizableYes = 2 - // SynchronizableNo disables synchronization - SynchronizableNo = 3 -) - -// SynchronizableKey is the key type for Synchronizable -var SynchronizableKey = attrKey(C.CFTypeRef(C.kSecAttrSynchronizable)) -var syncTypeRef = map[Synchronizable]C.CFTypeRef{ - SynchronizableAny: C.CFTypeRef(C.kSecAttrSynchronizableAny), - SynchronizableYes: C.CFTypeRef(C.kCFBooleanTrue), - SynchronizableNo: C.CFTypeRef(C.kCFBooleanFalse), -} - -// Accessible is the items accessibility -type Accessible int - -const ( - // AccessibleDefault is the default - AccessibleDefault Accessible = 0 - // AccessibleWhenUnlocked is when unlocked - AccessibleWhenUnlocked = 1 - // AccessibleAfterFirstUnlock is after first unlock - AccessibleAfterFirstUnlock = 2 - // AccessibleAlways is always - AccessibleAlways = 3 - // AccessibleWhenPasscodeSetThisDeviceOnly is when passcode is set - AccessibleWhenPasscodeSetThisDeviceOnly = 4 - // AccessibleWhenUnlockedThisDeviceOnly is when unlocked for this device only - AccessibleWhenUnlockedThisDeviceOnly = 5 - // AccessibleAfterFirstUnlockThisDeviceOnly is after first unlock for this device only - AccessibleAfterFirstUnlockThisDeviceOnly = 6 - // AccessibleAccessibleAlwaysThisDeviceOnly is always for this device only - AccessibleAccessibleAlwaysThisDeviceOnly = 7 -) - -// MatchLimit is whether to limit results on query -type MatchLimit int - -const ( - // MatchLimitDefault is the default - MatchLimitDefault MatchLimit = 0 - // MatchLimitOne limits to one result - MatchLimitOne = 1 - // MatchLimitAll is no limit - MatchLimitAll = 2 -) - -// MatchLimitKey is key type for MatchLimit -var MatchLimitKey = attrKey(C.CFTypeRef(C.kSecMatchLimit)) -var matchTypeRef = map[MatchLimit]C.CFTypeRef{ - MatchLimitOne: C.CFTypeRef(C.kSecMatchLimitOne), - MatchLimitAll: C.CFTypeRef(C.kSecMatchLimitAll), -} - -// ReturnAttributesKey is key type for kSecReturnAttributes -var ReturnAttributesKey = attrKey(C.CFTypeRef(C.kSecReturnAttributes)) - -// ReturnDataKey is key type for kSecReturnData -var ReturnDataKey = attrKey(C.CFTypeRef(C.kSecReturnData)) - -// ReturnRefKey is key type for kSecReturnRef -var ReturnRefKey = attrKey(C.CFTypeRef(C.kSecReturnRef)) - -// Item for adding, querying or deleting. -type Item struct { - // Values can be string, []byte, Convertable or CFTypeRef (constant). - attr map[string]interface{} -} - -// SetSecClass sets the security class -func (k *Item) SetSecClass(sc SecClass) { - k.attr[SecClassKey] = secClassTypeRef[sc] -} - -// SetString sets a string attibute for a string key -func (k *Item) SetString(key string, s string) { - if s != "" { - k.attr[key] = s - } else { - delete(k.attr, key) - } -} - -// SetService sets the service attribute -func (k *Item) SetService(s string) { - k.SetString(ServiceKey, s) -} - -// SetAccount sets the account attribute -func (k *Item) SetAccount(a string) { - k.SetString(AccountKey, a) -} - -// SetLabel sets the label attribute -func (k *Item) SetLabel(l string) { - k.SetString(LabelKey, l) -} - -// SetDescription sets the description attribute -func (k *Item) SetDescription(s string) { - k.SetString(DescriptionKey, s) -} - -// SetData sets the data attribute -func (k *Item) SetData(b []byte) { - if b != nil { - k.attr[DataKey] = b - } else { - delete(k.attr, DataKey) - } -} - -// SetAccessGroup sets the access group attribute -func (k *Item) SetAccessGroup(ag string) { - k.SetString(AccessGroupKey, ag) -} - -// SetSynchronizable sets the synchronizable attribute -func (k *Item) SetSynchronizable(sync Synchronizable) { - if sync != SynchronizableDefault { - k.attr[SynchronizableKey] = syncTypeRef[sync] - } else { - delete(k.attr, SynchronizableKey) - } -} - -// SetAccessible sets the accessible attribute -func (k *Item) SetAccessible(accessible Accessible) { - if accessible != AccessibleDefault { - k.attr[AccessibleKey] = accessibleTypeRef[accessible] - } else { - delete(k.attr, AccessibleKey) - } -} - -// SetMatchLimit sets the match limit -func (k *Item) SetMatchLimit(matchLimit MatchLimit) { - if matchLimit != MatchLimitDefault { - k.attr[MatchLimitKey] = matchTypeRef[matchLimit] - } else { - delete(k.attr, MatchLimitKey) - } -} - -// SetReturnAttributes sets the return value type on query -func (k *Item) SetReturnAttributes(b bool) { - k.attr[ReturnAttributesKey] = b -} - -// SetReturnData enables returning data on query -func (k *Item) SetReturnData(b bool) { - k.attr[ReturnDataKey] = b -} - -// SetReturnRef enables returning references on query -func (k *Item) SetReturnRef(b bool) { - k.attr[ReturnRefKey] = b -} - -// NewItem is a new empty keychain item -func NewItem() Item { - return Item{make(map[string]interface{})} -} - -// NewGenericPassword creates a generic password item with the default keychain. This is a convenience method. -func NewGenericPassword(service string, account string, label string, data []byte, accessGroup string) Item { - item := NewItem() - item.SetSecClass(SecClassGenericPassword) - item.SetService(service) - item.SetAccount(account) - item.SetLabel(label) - item.SetData(data) - item.SetAccessGroup(accessGroup) - return item -} - -// AddItem adds a Item to a Keychain -func AddItem(item Item) error { - cfDict, err := ConvertMapToCFDictionary(item.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDict)) - - errCode := C.SecItemAdd(cfDict, nil) - err = checkError(errCode) - return err -} - -// UpdateItem updates the queryItem with the parameters from updateItem -func UpdateItem(queryItem Item, updateItem Item) error { - cfDict, err := ConvertMapToCFDictionary(queryItem.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDict)) - cfDictUpdate, err := ConvertMapToCFDictionary(updateItem.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDictUpdate)) - errCode := C.SecItemUpdate(cfDict, cfDictUpdate) - err = checkError(errCode) - return err -} - -// QueryResult stores all possible results from queries. -// Not all fields are applicable all the time. Results depend on query. -type QueryResult struct { - Service string - Account string - AccessGroup string - Label string - Description string - Data []byte - CreationDate time.Time - ModificationDate time.Time -} - -// QueryItemRef returns query result as CFTypeRef. You must release it when you are done. -func QueryItemRef(item Item) (C.CFTypeRef, error) { - cfDict, err := ConvertMapToCFDictionary(item.attr) - if err != nil { - return nil, err - } - defer Release(C.CFTypeRef(cfDict)) - - var resultsRef C.CFTypeRef - errCode := C.SecItemCopyMatching(cfDict, &resultsRef) - if Error(errCode) == ErrorItemNotFound { - return nil, nil - } - err = checkError(errCode) - if err != nil { - return nil, err - } - return resultsRef, nil -} - -// QueryItem returns a list of query results. -func QueryItem(item Item) ([]QueryResult, error) { - resultsRef, err := QueryItemRef(item) - if err != nil { - return nil, err - } - if resultsRef == nil { - return nil, nil - } - defer Release(resultsRef) - - results := make([]QueryResult, 0, 1) - - typeID := C.CFGetTypeID(resultsRef) - if typeID == C.CFArrayGetTypeID() { - arr := CFArrayToArray(C.CFArrayRef(resultsRef)) - for _, ref := range arr { - elementTypeID := C.CFGetTypeID(ref) - if elementTypeID == C.CFDictionaryGetTypeID() { - item, err := convertResult(C.CFDictionaryRef(ref)) - if err != nil { - return nil, err - } - results = append(results, *item) - } else { - return nil, fmt.Errorf("invalid result type (If you SetReturnRef(true) you should use QueryItemRef directly)") - } - } - } else if typeID == C.CFDictionaryGetTypeID() { - item, err := convertResult(C.CFDictionaryRef(resultsRef)) - if err != nil { - return nil, err - } - results = append(results, *item) - } else if typeID == C.CFDataGetTypeID() { - b, err := CFDataToBytes(C.CFDataRef(resultsRef)) - if err != nil { - return nil, err - } - item := QueryResult{Data: b} - results = append(results, item) - } else { - return nil, fmt.Errorf("Invalid result type: %s", CFTypeDescription(resultsRef)) - } - - return results, nil -} - -func attrKey(ref C.CFTypeRef) string { - return CFStringToString(C.CFStringRef(ref)) -} - -// uintptrCFDataToBytes converts a uintptr (assumed to have been -// converted from a CFDataRef) to bytes. -// -// This is an adaptation of CFDataToBytes. -func uintptrCFDataToBytes(cfData uintptr) ([]byte, error) { - return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtrSafe(C.uintptr_t(cfData))), C.int(C.CFDataGetLengthSafe(C.uintptr_t(cfData)))), nil -} - -// uintptrCFStringToString converts a uintptr (assumed to have been -// converted from a CFStringRef) to a string. -// -// This is an adaptation of CFStringToString. -func uintptrCFStringToString(s uintptr) string { - p := C.CFStringGetCStringPtrSafe(C.uintptr_t(s), C.kCFStringEncodingUTF8) - if p != nil { - return C.GoString(p) - } - length := C.CFStringGetLengthSafe(C.uintptr_t(s)) - if length == 0 { - return "" - } - maxBufLen := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8) - if maxBufLen == 0 { - return "" - } - buf := make([]byte, maxBufLen) - var usedBufLen C.CFIndex - _ = C.CFStringGetBytesSafe(C.uintptr_t(s), C.CFRange{0, length}, C.kCFStringEncodingUTF8, C.UInt8(0), C.false, (*C.UInt8)(&buf[0]), maxBufLen, &usedBufLen) - return string(buf[:usedBufLen]) -} - -// untptrCFDateToTime converts a uintptr (assumed to have been -// converted from a CFDateRef) to a time.Time. -// -// This is an adaptation of CFDateTimeToTime. -func uintptrCFDateToTime(p uintptr) time.Time { - abs := C.CFDateGetAbsoluteTimeSafe(C.uintptr_t(p)) - s, ns := absoluteTimeToUnix(abs) - return time.Unix(s, ns) -} - -func convertResult(d C.CFDictionaryRef) (*QueryResult, error) { - m := CFDictionaryToMap(C.CFDictionaryRef(d)) - result := QueryResult{} - for k, v := range m { - switch attrKey(k) { - case ServiceKey: - result.Service = uintptrCFStringToString(v) - case AccountKey: - result.Account = uintptrCFStringToString(v) - case AccessGroupKey: - result.AccessGroup = uintptrCFStringToString(v) - case LabelKey: - result.Label = uintptrCFStringToString(v) - case DescriptionKey: - result.Description = uintptrCFStringToString(v) - case DataKey: - b, err := uintptrCFDataToBytes(v) - if err != nil { - return nil, err - } - result.Data = b - case CreationDateKey: - result.CreationDate = uintptrCFDateToTime(v) - case ModificationDateKey: - result.ModificationDate = uintptrCFDateToTime(v) - // default: - // fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(p)) - } - } - return &result, nil -} - -// DeleteGenericPasswordItem removes a generic password item. -func DeleteGenericPasswordItem(service string, account string) error { - item := NewItem() - item.SetSecClass(SecClassGenericPassword) - item.SetService(service) - item.SetAccount(account) - return DeleteItem(item) -} - -// DeleteItem removes a Item -func DeleteItem(item Item) error { - cfDict, err := ConvertMapToCFDictionary(item.attr) - if err != nil { - return err - } - defer Release(C.CFTypeRef(cfDict)) - - errCode := C.SecItemDelete(cfDict) - return checkError(errCode) -} - -// GetAccountsForService is deprecated -func GetAccountsForService(service string) ([]string, error) { - return GetGenericPasswordAccounts(service) -} - -// GetGenericPasswordAccounts returns generic password accounts for service. This is a convenience method. -func GetGenericPasswordAccounts(service string) ([]string, error) { - query := NewItem() - query.SetSecClass(SecClassGenericPassword) - query.SetService(service) - query.SetMatchLimit(MatchLimitAll) - query.SetReturnAttributes(true) - results, err := QueryItem(query) - if err != nil { - return nil, err - } - - accounts := make([]string, 0, len(results)) - for _, r := range results { - accounts = append(accounts, r.Account) - } - - return accounts, nil -} - -// GetGenericPassword returns password data for service and account. This is a convenience method. -// If item is not found returns nil, nil. -func GetGenericPassword(service string, account string, label string, accessGroup string) ([]byte, error) { - query := NewItem() - query.SetSecClass(SecClassGenericPassword) - query.SetService(service) - query.SetAccount(account) - query.SetLabel(label) - query.SetAccessGroup(accessGroup) - query.SetMatchLimit(MatchLimitOne) - query.SetReturnData(true) - results, err := QueryItem(query) - if err != nil { - return nil, err - } - if len(results) > 1 { - return nil, fmt.Errorf("Too many results") - } - if len(results) == 1 { - return results[0].Data, nil - } - return nil, nil -} diff --git a/vendor/github.com/keybase/go-keychain/macos_1.10.go b/vendor/github.com/keybase/go-keychain/macos_1.10.go deleted file mode 100644 index 69711e1..0000000 --- a/vendor/github.com/keybase/go-keychain/macos_1.10.go +++ /dev/null @@ -1,272 +0,0 @@ -// +build darwin,!ios -// +build go1.10 - -package keychain - -/* -#cgo LDFLAGS: -framework CoreFoundation -framework Security - -#include -#include -*/ -import "C" -import ( - "os" - "unsafe" -) - -// AccessibleKey is key for kSecAttrAccessible -var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible)) -var accessibleTypeRef = map[Accessible]C.CFTypeRef{ - AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked), - AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock), - AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways), - AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly), - AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly), - AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly), - - // Only available in 10.10 - //AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly), -} - -var ( - // AccessKey is key for kSecAttrAccess - AccessKey = attrKey(C.CFTypeRef(C.kSecAttrAccess)) -) - -// createAccess creates a SecAccessRef as CFTypeRef. -// The returned SecAccessRef, if non-nil, must be released via CFRelease. -func createAccess(label string, trustedApplications []string) (C.CFTypeRef, error) { - var err error - var labelRef C.CFStringRef - if labelRef, err = StringToCFString(label); err != nil { - return 0, err - } - defer C.CFRelease(C.CFTypeRef(labelRef)) - - var trustedApplicationsArray C.CFArrayRef - if trustedApplications != nil { - if len(trustedApplications) > 0 { - // Always prepend with empty string which signifies that we - // include a NULL application, which means ourselves. - trustedApplications = append([]string{""}, trustedApplications...) - } - - var trustedApplicationsRefs []C.CFTypeRef - for _, trustedApplication := range trustedApplications { - trustedApplicationRef, createErr := createTrustedApplication(trustedApplication) - if createErr != nil { - return 0, createErr - } - defer C.CFRelease(C.CFTypeRef(trustedApplicationRef)) - trustedApplicationsRefs = append(trustedApplicationsRefs, trustedApplicationRef) - } - - trustedApplicationsArray = ArrayToCFArray(trustedApplicationsRefs) - defer C.CFRelease(C.CFTypeRef(trustedApplicationsArray)) - } - - var access C.SecAccessRef - errCode := C.SecAccessCreate(labelRef, trustedApplicationsArray, &access) - err = checkError(errCode) - if err != nil { - return 0, err - } - - return C.CFTypeRef(access), nil -} - -// createTrustedApplication creates a SecTrustedApplicationRef as a CFTypeRef. -// The returned SecTrustedApplicationRef, if non-nil, must be released via CFRelease. -func createTrustedApplication(trustedApplication string) (C.CFTypeRef, error) { - var trustedApplicationCStr *C.char - if trustedApplication != "" { - trustedApplicationCStr = C.CString(trustedApplication) - defer C.free(unsafe.Pointer(trustedApplicationCStr)) - } - - var trustedApplicationRef C.SecTrustedApplicationRef - errCode := C.SecTrustedApplicationCreateFromPath(trustedApplicationCStr, &trustedApplicationRef) - err := checkError(errCode) - if err != nil { - return 0, err - } - - return C.CFTypeRef(trustedApplicationRef), nil -} - -// Access defines whats applications can use the keychain item -type Access struct { - Label string - TrustedApplications []string -} - -// Convert converts Access to CFTypeRef. -// The returned CFTypeRef, if non-nil, must be released via CFRelease. -func (a Access) Convert() (C.CFTypeRef, error) { - return createAccess(a.Label, a.TrustedApplications) -} - -// SetAccess sets Access on Item -func (k *Item) SetAccess(a *Access) { - if a != nil { - k.attr[AccessKey] = a - } else { - delete(k.attr, AccessKey) - } -} - -// DeleteItemRef deletes a keychain item reference. -func DeleteItemRef(ref C.CFTypeRef) error { - errCode := C.SecKeychainItemDelete(C.SecKeychainItemRef(ref)) - return checkError(errCode) -} - -var ( - // KeychainKey is key for kSecUseKeychain - KeychainKey = attrKey(C.CFTypeRef(C.kSecUseKeychain)) - // MatchSearchListKey is key for kSecMatchSearchList - MatchSearchListKey = attrKey(C.CFTypeRef(C.kSecMatchSearchList)) -) - -// Keychain represents the path to a specific OSX keychain -type Keychain struct { - path string -} - -// NewKeychain creates a new keychain file with a password -func NewKeychain(path string, password string) (Keychain, error) { - return newKeychain(path, password, false) -} - -// NewKeychainWithPrompt creates a new Keychain and prompts user for password -func NewKeychainWithPrompt(path string) (Keychain, error) { - return newKeychain(path, "", true) -} - -func newKeychain(path, password string, promptUser bool) (Keychain, error) { - pathRef := C.CString(path) - defer C.free(unsafe.Pointer(pathRef)) - - var errCode C.OSStatus - var kref C.SecKeychainRef - - if promptUser { - errCode = C.SecKeychainCreate(pathRef, C.UInt32(0), nil, C.Boolean(1), 0, &kref) - } else { - passwordRef := C.CString(password) - defer C.free(unsafe.Pointer(passwordRef)) - errCode = C.SecKeychainCreate(pathRef, C.UInt32(len(password)), unsafe.Pointer(passwordRef), C.Boolean(0), 0, &kref) - } - - if err := checkError(errCode); err != nil { - return Keychain{}, err - } - - // TODO: Without passing in kref I get 'One or more parameters passed to the function were not valid (-50)' - defer Release(C.CFTypeRef(kref)) - - return Keychain{ - path: path, - }, nil -} - -// NewWithPath to use an existing keychain -func NewWithPath(path string) Keychain { - return Keychain{ - path: path, - } -} - -// Status returns the status of the keychain -func (kc Keychain) Status() error { - // returns no error even if it doesn't exist - kref, err := openKeychainRef(kc.path) - if err != nil { - return err - } - defer C.CFRelease(C.CFTypeRef(kref)) - - var status C.SecKeychainStatus - return checkError(C.SecKeychainGetStatus(kref, &status)) -} - -// The returned SecKeychainRef, if non-nil, must be released via CFRelease. -func openKeychainRef(path string) (C.SecKeychainRef, error) { - pathName := C.CString(path) - defer C.free(unsafe.Pointer(pathName)) - - var kref C.SecKeychainRef - if err := checkError(C.SecKeychainOpen(pathName, &kref)); err != nil { - return 0, err - } - - return kref, nil -} - -// UnlockAtPath unlocks keychain at path -func UnlockAtPath(path string, password string) error { - kref, err := openKeychainRef(path) - defer Release(C.CFTypeRef(kref)) - if err != nil { - return err - } - passwordRef := C.CString(password) - defer C.free(unsafe.Pointer(passwordRef)) - return checkError(C.SecKeychainUnlock(kref, C.UInt32(len(password)), unsafe.Pointer(passwordRef), C.Boolean(1))) -} - -// LockAtPath locks keychain at path -func LockAtPath(path string) error { - kref, err := openKeychainRef(path) - defer Release(C.CFTypeRef(kref)) - if err != nil { - return err - } - return checkError(C.SecKeychainLock(kref)) -} - -// Delete the Keychain -func (kc *Keychain) Delete() error { - return os.Remove(kc.path) -} - -// Convert Keychain to CFTypeRef. -// The returned CFTypeRef, if non-nil, must be released via CFRelease. -func (kc Keychain) Convert() (C.CFTypeRef, error) { - keyRef, err := openKeychainRef(kc.path) - return C.CFTypeRef(keyRef), err -} - -type keychainArray []Keychain - -// Convert the keychainArray to a CFTypeRef. -// The returned CFTypeRef, if non-nil, must be released via CFRelease. -func (ka keychainArray) Convert() (C.CFTypeRef, error) { - var refs = make([]C.CFTypeRef, len(ka)) - var err error - - for idx, kc := range ka { - if refs[idx], err = kc.Convert(); err != nil { - // If we error trying to convert lets release any we converted before - for _, ref := range refs { - if ref != 0 { - Release(ref) - } - } - return 0, err - } - } - - return C.CFTypeRef(ArrayToCFArray(refs)), nil -} - -// SetMatchSearchList sets match type on keychains -func (k *Item) SetMatchSearchList(karr ...Keychain) { - k.attr[MatchSearchListKey] = keychainArray(karr) -} - -// UseKeychain tells item to use the specified Keychain -func (k *Item) UseKeychain(kc Keychain) { - k.attr[KeychainKey] = kc -} diff --git a/vendor/github.com/keybase/go-keychain/macos_pre1.10.go b/vendor/github.com/keybase/go-keychain/macos_pre1.10.go deleted file mode 100644 index 7990af2..0000000 --- a/vendor/github.com/keybase/go-keychain/macos_pre1.10.go +++ /dev/null @@ -1,274 +0,0 @@ -// +build darwin,!ios -// +build !go1.10 - -// TODO: Remove this file once we've completely migrated to go 1.10.x. - -package keychain - -/* -#cgo LDFLAGS: -framework CoreFoundation -framework Security - -#include -#include -*/ -import "C" -import ( - "os" - "unsafe" -) - -// AccessibleKey is key for kSecAttrAccessible -var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible)) -var accessibleTypeRef = map[Accessible]C.CFTypeRef{ - AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked), - AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock), - AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways), - AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly), - AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly), - AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly), - - // Only available in 10.10 - //AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly), -} - -var ( - // AccessKey is key for kSecAttrAccess - AccessKey = attrKey(C.CFTypeRef(C.kSecAttrAccess)) -) - -// createAccess creates a SecAccessRef as CFTypeRef. -// The returned SecAccessRef, if non-nil, must be released via CFRelease. -func createAccess(label string, trustedApplications []string) (C.CFTypeRef, error) { - var err error - var labelRef C.CFStringRef - if labelRef, err = StringToCFString(label); err != nil { - return nil, err - } - defer C.CFRelease(C.CFTypeRef(labelRef)) - - var trustedApplicationsArray C.CFArrayRef - if trustedApplications != nil { - if len(trustedApplications) > 0 { - // Always prepend with empty string which signifies that we - // include a NULL application, which means ourselves. - trustedApplications = append([]string{""}, trustedApplications...) - } - - var trustedApplicationsRefs []C.CFTypeRef - for _, trustedApplication := range trustedApplications { - trustedApplicationRef, createErr := createTrustedApplication(trustedApplication) - if createErr != nil { - return nil, createErr - } - defer C.CFRelease(C.CFTypeRef(trustedApplicationRef)) - trustedApplicationsRefs = append(trustedApplicationsRefs, trustedApplicationRef) - } - - trustedApplicationsArray = ArrayToCFArray(trustedApplicationsRefs) - defer C.CFRelease(C.CFTypeRef(trustedApplicationsArray)) - } - - var access C.SecAccessRef - errCode := C.SecAccessCreate(labelRef, trustedApplicationsArray, &access) - err = checkError(errCode) - if err != nil { - return nil, err - } - - return C.CFTypeRef(access), nil -} - -// createTrustedApplication creates a SecTrustedApplicationRef as a CFTypeRef. -// The returned SecTrustedApplicationRef, if non-nil, must be released via CFRelease. -func createTrustedApplication(trustedApplication string) (C.CFTypeRef, error) { - var trustedApplicationCStr *C.char - if trustedApplication != "" { - trustedApplicationCStr = C.CString(trustedApplication) - defer C.free(unsafe.Pointer(trustedApplicationCStr)) - } - - var trustedApplicationRef C.SecTrustedApplicationRef - errCode := C.SecTrustedApplicationCreateFromPath(trustedApplicationCStr, &trustedApplicationRef) - err := checkError(errCode) - if err != nil { - return nil, err - } - - return C.CFTypeRef(trustedApplicationRef), nil -} - -// Access defines whats applications can use the keychain item -type Access struct { - Label string - TrustedApplications []string -} - -// Convert converts Access to CFTypeRef. -// The returned CFTypeRef, if non-nil, must be released via CFRelease. -func (a Access) Convert() (C.CFTypeRef, error) { - return createAccess(a.Label, a.TrustedApplications) -} - -// SetAccess sets Access on Item -func (k *Item) SetAccess(a *Access) { - if a != nil { - k.attr[AccessKey] = a - } else { - delete(k.attr, AccessKey) - } -} - -// DeleteItemRef deletes a keychain item reference. -func DeleteItemRef(ref C.CFTypeRef) error { - errCode := C.SecKeychainItemDelete(C.SecKeychainItemRef(ref)) - return checkError(errCode) -} - -var ( - // KeychainKey is key for kSecUseKeychain - KeychainKey = attrKey(C.CFTypeRef(C.kSecUseKeychain)) - // MatchSearchListKey is key for kSecMatchSearchList - MatchSearchListKey = attrKey(C.CFTypeRef(C.kSecMatchSearchList)) -) - -// Keychain represents the path to a specific OSX keychain -type Keychain struct { - path string -} - -// NewKeychain creates a new keychain file with a password -func NewKeychain(path string, password string) (Keychain, error) { - return newKeychain(path, password, false) -} - -// NewKeychainWithPrompt creates a new Keychain and prompts user for password -func NewKeychainWithPrompt(path string) (Keychain, error) { - return newKeychain(path, "", true) -} - -func newKeychain(path, password string, promptUser bool) (Keychain, error) { - pathRef := C.CString(path) - defer C.free(unsafe.Pointer(pathRef)) - - var errCode C.OSStatus - var kref C.SecKeychainRef - - if promptUser { - errCode = C.SecKeychainCreate(pathRef, C.UInt32(0), nil, C.Boolean(1), nil, &kref) - } else { - passwordRef := C.CString(password) - defer C.free(unsafe.Pointer(passwordRef)) - errCode = C.SecKeychainCreate(pathRef, C.UInt32(len(password)), unsafe.Pointer(passwordRef), C.Boolean(0), nil, &kref) - } - - if err := checkError(errCode); err != nil { - return Keychain{}, err - } - - // TODO: Without passing in kref I get 'One or more parameters passed to the function were not valid (-50)' - defer Release(C.CFTypeRef(kref)) - - return Keychain{ - path: path, - }, nil -} - -// NewWithPath to use an existing keychain -func NewWithPath(path string) Keychain { - return Keychain{ - path: path, - } -} - -// Status returns the status of the keychain -func (kc Keychain) Status() error { - // returns no error even if it doesn't exist - kref, err := openKeychainRef(kc.path) - if err != nil { - return err - } - defer C.CFRelease(C.CFTypeRef(kref)) - - var status C.SecKeychainStatus - return checkError(C.SecKeychainGetStatus(kref, &status)) -} - -// The returned SecKeychainRef, if non-nil, must be released via CFRelease. -func openKeychainRef(path string) (C.SecKeychainRef, error) { - pathName := C.CString(path) - defer C.free(unsafe.Pointer(pathName)) - - var kref C.SecKeychainRef - if err := checkError(C.SecKeychainOpen(pathName, &kref)); err != nil { - return nil, err - } - - return kref, nil -} - -// UnlockAtPath unlocks keychain at path -func UnlockAtPath(path string, password string) error { - kref, err := openKeychainRef(path) - defer Release(C.CFTypeRef(kref)) - if err != nil { - return err - } - passwordRef := C.CString(password) - defer C.free(unsafe.Pointer(passwordRef)) - return checkError(C.SecKeychainUnlock(kref, C.UInt32(len(password)), unsafe.Pointer(passwordRef), C.Boolean(1))) -} - -// LockAtPath locks keychain at path -func LockAtPath(path string) error { - kref, err := openKeychainRef(path) - defer Release(C.CFTypeRef(kref)) - if err != nil { - return err - } - return checkError(C.SecKeychainLock(kref)) -} - -// Delete the Keychain -func (kc *Keychain) Delete() error { - return os.Remove(kc.path) -} - -// Convert Keychain to CFTypeRef. -// The returned CFTypeRef, if non-nil, must be released via CFRelease. -func (kc Keychain) Convert() (C.CFTypeRef, error) { - keyRef, err := openKeychainRef(kc.path) - return C.CFTypeRef(keyRef), err -} - -type keychainArray []Keychain - -// Convert the keychainArray to a CFTypeRef. -// The returned CFTypeRef, if non-nil, must be released via CFRelease. -func (ka keychainArray) Convert() (C.CFTypeRef, error) { - var refs = make([]C.CFTypeRef, len(ka)) - var err error - - for idx, kc := range ka { - if refs[idx], err = kc.Convert(); err != nil { - // If we error trying to convert lets release any we converted before - for _, ref := range refs { - if ref != nil { - Release(ref) - } - } - return nil, err - } - } - - return C.CFTypeRef(ArrayToCFArray(refs)), nil -} - -// SetMatchSearchList sets match type on keychains -func (k *Item) SetMatchSearchList(karr ...Keychain) { - k.attr[MatchSearchListKey] = keychainArray(karr) -} - -// UseKeychain tells item to use the specified Keychain -func (k *Item) UseKeychain(kc Keychain) { - k.attr[KeychainKey] = kc -} diff --git a/vendor/github.com/keybase/go-keychain/staticcheck.conf b/vendor/github.com/keybase/go-keychain/staticcheck.conf deleted file mode 100644 index 2f4a540..0000000 --- a/vendor/github.com/keybase/go-keychain/staticcheck.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Skip the following: -# ST1005 Incorrectly formatted error string -# ST1000 Incorrect or missing package comment -# ST1003 Poorly chosen identifier -checks = ["all", "-ST1005", "-ST1000", "-ST1003"] diff --git a/vendor/github.com/keybase/go-keychain/util.go b/vendor/github.com/keybase/go-keychain/util.go deleted file mode 100644 index 29cbfc6..0000000 --- a/vendor/github.com/keybase/go-keychain/util.go +++ /dev/null @@ -1,31 +0,0 @@ -package keychain - -import ( - "crypto/rand" - "encoding/base32" - "strings" -) - -var randRead = rand.Read - -// RandomID returns random ID (base32) string with prefix, using 256 bits as -// recommended by tptacek: https://gist.github.com/tqbf/be58d2d39690c3b366ad -func RandomID(prefix string) (string, error) { - buf, err := RandBytes(32) - if err != nil { - return "", err - } - str := base32.StdEncoding.EncodeToString(buf) - str = strings.Replace(str, "=", "", -1) - str = prefix + str - return str, nil -} - -// RandBytes returns random bytes of length -func RandBytes(length int) ([]byte, error) { - buf := make([]byte, length) - if _, err := randRead(buf); err != nil { - return nil, err - } - return buf, nil -} diff --git a/vendor/github.com/magiconair/properties/.gitignore b/vendor/github.com/magiconair/properties/.gitignore new file mode 100644 index 0000000..e7081ff --- /dev/null +++ b/vendor/github.com/magiconair/properties/.gitignore @@ -0,0 +1,6 @@ +*.sublime-project +*.sublime-workspace +*.un~ +*.swp +.idea/ +*.iml diff --git a/vendor/github.com/magiconair/properties/.travis.yml b/vendor/github.com/magiconair/properties/.travis.yml new file mode 100644 index 0000000..3e7c3d2 --- /dev/null +++ b/vendor/github.com/magiconair/properties/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.4.x + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - "1.10.x" + - tip diff --git a/vendor/github.com/magiconair/properties/CHANGELOG.md b/vendor/github.com/magiconair/properties/CHANGELOG.md new file mode 100644 index 0000000..f83adc2 --- /dev/null +++ b/vendor/github.com/magiconair/properties/CHANGELOG.md @@ -0,0 +1,131 @@ +## Changelog + +### [1.8](https://github.com/magiconair/properties/tree/v1.8) - 15 May 2018 + + * [PR #26](https://github.com/magiconair/properties/pull/26): Disable expansion during loading + + This adds the option to disable property expansion during loading. + + Thanks to [@kmala](https://github.com/kmala) for the patch. + +### [1.7.6](https://github.com/magiconair/properties/tree/v1.7.6) - 14 Feb 2018 + + * [PR #29](https://github.com/magiconair/properties/pull/29): Reworked expansion logic to handle more complex cases. + + See PR for an example. + + Thanks to [@yobert](https://github.com/yobert) for the fix. + +### [1.7.5](https://github.com/magiconair/properties/tree/v1.7.5) - 13 Feb 2018 + + * [PR #28](https://github.com/magiconair/properties/pull/28): Support duplicate expansions in the same value + + Values which expand the same key multiple times (e.g. `key=${a} ${a}`) will no longer fail + with a `circular reference error`. + + Thanks to [@yobert](https://github.com/yobert) for the fix. + +### [1.7.4](https://github.com/magiconair/properties/tree/v1.7.4) - 31 Oct 2017 + + * [Issue #23](https://github.com/magiconair/properties/issues/23): Ignore blank lines with whitespaces + + * [PR #24](https://github.com/magiconair/properties/pull/24): Update keys when DisableExpansion is enabled + + Thanks to [@mgurov](https://github.com/mgurov) for the fix. + +### [1.7.3](https://github.com/magiconair/properties/tree/v1.7.3) - 10 Jul 2017 + + * [Issue #17](https://github.com/magiconair/properties/issues/17): Add [SetValue()](http://godoc.org/github.com/magiconair/properties#Properties.SetValue) method to set values generically + * [Issue #22](https://github.com/magiconair/properties/issues/22): Add [LoadMap()](http://godoc.org/github.com/magiconair/properties#LoadMap) function to load properties from a string map + +### [1.7.2](https://github.com/magiconair/properties/tree/v1.7.2) - 20 Mar 2017 + + * [Issue #15](https://github.com/magiconair/properties/issues/15): Drop gocheck dependency + * [PR #21](https://github.com/magiconair/properties/pull/21): Add [Map()](http://godoc.org/github.com/magiconair/properties#Properties.Map) and [FilterFunc()](http://godoc.org/github.com/magiconair/properties#Properties.FilterFunc) + +### [1.7.1](https://github.com/magiconair/properties/tree/v1.7.1) - 13 Jan 2017 + + * [Issue #14](https://github.com/magiconair/properties/issues/14): Decouple TestLoadExpandedFile from `$USER` + * [PR #12](https://github.com/magiconair/properties/pull/12): Load from files and URLs + * [PR #16](https://github.com/magiconair/properties/pull/16): Keep gofmt happy + * [PR #18](https://github.com/magiconair/properties/pull/18): Fix Delete() function + +### [1.7.0](https://github.com/magiconair/properties/tree/v1.7.0) - 20 Mar 2016 + + * [Issue #10](https://github.com/magiconair/properties/issues/10): Add [LoadURL,LoadURLs,MustLoadURL,MustLoadURLs](http://godoc.org/github.com/magiconair/properties#LoadURL) method to load properties from a URL. + * [Issue #11](https://github.com/magiconair/properties/issues/11): Add [LoadString,MustLoadString](http://godoc.org/github.com/magiconair/properties#LoadString) method to load properties from an UTF8 string. + * [PR #8](https://github.com/magiconair/properties/pull/8): Add [MustFlag](http://godoc.org/github.com/magiconair/properties#Properties.MustFlag) method to provide overrides via command line flags. (@pascaldekloe) + +### [1.6.0](https://github.com/magiconair/properties/tree/v1.6.0) - 11 Dec 2015 + + * Add [Decode](http://godoc.org/github.com/magiconair/properties#Properties.Decode) method to populate struct from properties via tags. + +### [1.5.6](https://github.com/magiconair/properties/tree/v1.5.6) - 18 Oct 2015 + + * Vendored in gopkg.in/check.v1 + +### [1.5.5](https://github.com/magiconair/properties/tree/v1.5.5) - 31 Jul 2015 + + * [PR #6](https://github.com/magiconair/properties/pull/6): Add [Delete](http://godoc.org/github.com/magiconair/properties#Properties.Delete) method to remove keys including comments. (@gerbenjacobs) + +### [1.5.4](https://github.com/magiconair/properties/tree/v1.5.4) - 23 Jun 2015 + + * [Issue #5](https://github.com/magiconair/properties/issues/5): Allow disabling of property expansion [DisableExpansion](http://godoc.org/github.com/magiconair/properties#Properties.DisableExpansion). When property expansion is disabled Properties become a simple key/value store and don't check for circular references. + +### [1.5.3](https://github.com/magiconair/properties/tree/v1.5.3) - 02 Jun 2015 + + * [Issue #4](https://github.com/magiconair/properties/issues/4): Maintain key order in [Filter()](http://godoc.org/github.com/magiconair/properties#Properties.Filter), [FilterPrefix()](http://godoc.org/github.com/magiconair/properties#Properties.FilterPrefix) and [FilterRegexp()](http://godoc.org/github.com/magiconair/properties#Properties.FilterRegexp) + +### [1.5.2](https://github.com/magiconair/properties/tree/v1.5.2) - 10 Apr 2015 + + * [Issue #3](https://github.com/magiconair/properties/issues/3): Don't print comments in [WriteComment()](http://godoc.org/github.com/magiconair/properties#Properties.WriteComment) if they are all empty + * Add clickable links to README + +### [1.5.1](https://github.com/magiconair/properties/tree/v1.5.1) - 08 Dec 2014 + + * Added [GetParsedDuration()](http://godoc.org/github.com/magiconair/properties#Properties.GetParsedDuration) and [MustGetParsedDuration()](http://godoc.org/github.com/magiconair/properties#Properties.MustGetParsedDuration) for values specified compatible with + [time.ParseDuration()](http://golang.org/pkg/time/#ParseDuration). + +### [1.5.0](https://github.com/magiconair/properties/tree/v1.5.0) - 18 Nov 2014 + + * Added support for single and multi-line comments (reading, writing and updating) + * The order of keys is now preserved + * Calling [Set()](http://godoc.org/github.com/magiconair/properties#Properties.Set) with an empty key now silently ignores the call and does not create a new entry + * Added a [MustSet()](http://godoc.org/github.com/magiconair/properties#Properties.MustSet) method + * Migrated test library from launchpad.net/gocheck to [gopkg.in/check.v1](http://gopkg.in/check.v1) + +### [1.4.2](https://github.com/magiconair/properties/tree/v1.4.2) - 15 Nov 2014 + + * [Issue #2](https://github.com/magiconair/properties/issues/2): Fixed goroutine leak in parser which created two lexers but cleaned up only one + +### [1.4.1](https://github.com/magiconair/properties/tree/v1.4.1) - 13 Nov 2014 + + * [Issue #1](https://github.com/magiconair/properties/issues/1): Fixed bug in Keys() method which returned an empty string + +### [1.4.0](https://github.com/magiconair/properties/tree/v1.4.0) - 23 Sep 2014 + + * Added [Keys()](http://godoc.org/github.com/magiconair/properties#Properties.Keys) to get the keys + * Added [Filter()](http://godoc.org/github.com/magiconair/properties#Properties.Filter), [FilterRegexp()](http://godoc.org/github.com/magiconair/properties#Properties.FilterRegexp) and [FilterPrefix()](http://godoc.org/github.com/magiconair/properties#Properties.FilterPrefix) to get a subset of the properties + +### [1.3.0](https://github.com/magiconair/properties/tree/v1.3.0) - 18 Mar 2014 + +* Added support for time.Duration +* Made MustXXX() failure beha[ior configurable (log.Fatal, panic](https://github.com/magiconair/properties/tree/vior configurable (log.Fatal, panic) - custom) +* Changed default of MustXXX() failure from panic to log.Fatal + +### [1.2.0](https://github.com/magiconair/properties/tree/v1.2.0) - 05 Mar 2014 + +* Added MustGet... functions +* Added support for int and uint with range checks on 32 bit platforms + +### [1.1.0](https://github.com/magiconair/properties/tree/v1.1.0) - 20 Jan 2014 + +* Renamed from goproperties to properties +* Added support for expansion of environment vars in + filenames and value expressions +* Fixed bug where value expressions were not at the + start of the string + +### [1.0.0](https://github.com/magiconair/properties/tree/v1.0.0) - 7 Jan 2014 + +* Initial release diff --git a/vendor/github.com/magiconair/properties/LICENSE b/vendor/github.com/magiconair/properties/LICENSE new file mode 100644 index 0000000..b387087 --- /dev/null +++ b/vendor/github.com/magiconair/properties/LICENSE @@ -0,0 +1,25 @@ +goproperties - properties file decoder for Go + +Copyright (c) 2013-2018 - Frank Schroeder + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. 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. + +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/magiconair/properties/README.md b/vendor/github.com/magiconair/properties/README.md new file mode 100644 index 0000000..2c05f29 --- /dev/null +++ b/vendor/github.com/magiconair/properties/README.md @@ -0,0 +1,129 @@ +[![](https://img.shields.io/github/tag/magiconair/properties.svg?style=flat-square&label=release)](https://github.com/magiconair/properties/releases) +[![Travis CI Status](https://img.shields.io/travis/magiconair/properties.svg?branch=master&style=flat-square&label=travis)](https://travis-ci.org/magiconair/properties) +[![Codeship CI Status](https://img.shields.io/codeship/16aaf660-f615-0135-b8f0-7e33b70920c0/master.svg?label=codeship&style=flat-square)](https://app.codeship.com/projects/274177") +[![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg?style=flat-square)](https://raw.githubusercontent.com/magiconair/properties/master/LICENSE) +[![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](http://godoc.org/github.com/magiconair/properties) + +# Overview + +#### Please run `git pull --tags` to update the tags. See [below](#updated-git-tags) why. + +properties is a Go library for reading and writing properties files. + +It supports reading from multiple files or URLs and Spring style recursive +property expansion of expressions like `${key}` to their corresponding value. +Value expressions can refer to other keys like in `${key}` or to environment +variables like in `${USER}`. Filenames can also contain environment variables +like in `/home/${USER}/myapp.properties`. + +Properties can be decoded into structs, maps, arrays and values through +struct tags. + +Comments and the order of keys are preserved. Comments can be modified +and can be written to the output. + +The properties library supports both ISO-8859-1 and UTF-8 encoded data. + +Starting from version 1.3.0 the behavior of the MustXXX() functions is +configurable by providing a custom `ErrorHandler` function. The default has +changed from `panic` to `log.Fatal` but this is configurable and custom +error handling functions can be provided. See the package documentation for +details. + +Read the full documentation on [GoDoc](https://godoc.org/github.com/magiconair/properties) [![GoDoc](https://godoc.org/github.com/magiconair/properties?status.png)](https://godoc.org/github.com/magiconair/properties) + +## Getting Started + +```go +import ( + "flag" + "github.com/magiconair/properties" +) + +func main() { + // init from a file + p := properties.MustLoadFile("${HOME}/config.properties", properties.UTF8) + + // or multiple files + p = properties.MustLoadFiles([]string{ + "${HOME}/config.properties", + "${HOME}/config-${USER}.properties", + }, properties.UTF8, true) + + // or from a map + p = properties.LoadMap(map[string]string{"key": "value", "abc": "def"}) + + // or from a string + p = properties.MustLoadString("key=value\nabc=def") + + // or from a URL + p = properties.MustLoadURL("http://host/path") + + // or from multiple URLs + p = properties.MustLoadURL([]string{ + "http://host/config", + "http://host/config-${USER}", + }, true) + + // or from flags + p.MustFlag(flag.CommandLine) + + // get values through getters + host := p.MustGetString("host") + port := p.GetInt("port", 8080) + + // or through Decode + type Config struct { + Host string `properties:"host"` + Port int `properties:"port,default=9000"` + Accept []string `properties:"accept,default=image/png;image;gif"` + Timeout time.Duration `properties:"timeout,default=5s"` + } + var cfg Config + if err := p.Decode(&cfg); err != nil { + log.Fatal(err) + } +} + +``` + +## Installation and Upgrade + +``` +$ go get -u github.com/magiconair/properties +``` + +## License + +2 clause BSD license. See [LICENSE](https://github.com/magiconair/properties/blob/master/LICENSE) file for details. + +## ToDo + +* Dump contents with passwords and secrets obscured + +## Updated Git tags + +#### 13 Feb 2018 + +I realized that all of the git tags I had pushed before v1.7.5 were lightweight tags +and I've only recently learned that this doesn't play well with `git describe` 😞 + +I have replaced all lightweight tags with signed tags using this script which should +retain the commit date, name and email address. Please run `git pull --tags` to update them. + +Worst case you have to reclone the repo. + +```shell +#!/bin/bash +tag=$1 +echo "Updating $tag" +date=$(git show ${tag}^0 --format=%aD | head -1) +email=$(git show ${tag}^0 --format=%aE | head -1) +name=$(git show ${tag}^0 --format=%aN | head -1) +GIT_COMMITTER_DATE="$date" GIT_COMMITTER_NAME="$name" GIT_COMMITTER_EMAIL="$email" git tag -s -f ${tag} ${tag}^0 -m ${tag} +``` + +I apologize for the inconvenience. + +Frank + diff --git a/vendor/github.com/magiconair/properties/decode.go b/vendor/github.com/magiconair/properties/decode.go new file mode 100644 index 0000000..3ebf804 --- /dev/null +++ b/vendor/github.com/magiconair/properties/decode.go @@ -0,0 +1,289 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" +) + +// Decode assigns property values to exported fields of a struct. +// +// Decode traverses v recursively and returns an error if a value cannot be +// converted to the field type or a required value is missing for a field. +// +// The following type dependent decodings are used: +// +// String, boolean, numeric fields have the value of the property key assigned. +// The property key name is the name of the field. A different key and a default +// value can be set in the field's tag. Fields without default value are +// required. If the value cannot be converted to the field type an error is +// returned. +// +// time.Duration fields have the result of time.ParseDuration() assigned. +// +// time.Time fields have the vaule of time.Parse() assigned. The default layout +// is time.RFC3339 but can be set in the field's tag. +// +// Arrays and slices of string, boolean, numeric, time.Duration and time.Time +// fields have the value interpreted as a comma separated list of values. The +// individual values are trimmed of whitespace and empty values are ignored. A +// default value can be provided as a semicolon separated list in the field's +// tag. +// +// Struct fields are decoded recursively using the field name plus "." as +// prefix. The prefix (without dot) can be overridden in the field's tag. +// Default values are not supported in the field's tag. Specify them on the +// fields of the inner struct instead. +// +// Map fields must have a key of type string and are decoded recursively by +// using the field's name plus ".' as prefix and the next element of the key +// name as map key. The prefix (without dot) can be overridden in the field's +// tag. Default values are not supported. +// +// Examples: +// +// // Field is ignored. +// Field int `properties:"-"` +// +// // Field is assigned value of 'Field'. +// Field int +// +// // Field is assigned value of 'myName'. +// Field int `properties:"myName"` +// +// // Field is assigned value of key 'myName' and has a default +// // value 15 if the key does not exist. +// Field int `properties:"myName,default=15"` +// +// // Field is assigned value of key 'Field' and has a default +// // value 15 if the key does not exist. +// Field int `properties:",default=15"` +// +// // Field is assigned value of key 'date' and the date +// // is in format 2006-01-02 +// Field time.Time `properties:"date,layout=2006-01-02"` +// +// // Field is assigned the non-empty and whitespace trimmed +// // values of key 'Field' split by commas. +// Field []string +// +// // Field is assigned the non-empty and whitespace trimmed +// // values of key 'Field' split by commas and has a default +// // value ["a", "b", "c"] if the key does not exist. +// Field []string `properties:",default=a;b;c"` +// +// // Field is decoded recursively with "Field." as key prefix. +// Field SomeStruct +// +// // Field is decoded recursively with "myName." as key prefix. +// Field SomeStruct `properties:"myName"` +// +// // Field is decoded recursively with "Field." as key prefix +// // and the next dotted element of the key as map key. +// Field map[string]string +// +// // Field is decoded recursively with "myName." as key prefix +// // and the next dotted element of the key as map key. +// Field map[string]string `properties:"myName"` +func (p *Properties) Decode(x interface{}) error { + t, v := reflect.TypeOf(x), reflect.ValueOf(x) + if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct { + return fmt.Errorf("not a pointer to struct: %s", t) + } + if err := dec(p, "", nil, nil, v); err != nil { + return err + } + return nil +} + +func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error { + t := v.Type() + + // value returns the property value for key or the default if provided. + value := func() (string, error) { + if val, ok := p.Get(key); ok { + return val, nil + } + if def != nil { + return *def, nil + } + return "", fmt.Errorf("missing required key %s", key) + } + + // conv converts a string to a value of the given type. + conv := func(s string, t reflect.Type) (val reflect.Value, err error) { + var v interface{} + + switch { + case isDuration(t): + v, err = time.ParseDuration(s) + + case isTime(t): + layout := opts["layout"] + if layout == "" { + layout = time.RFC3339 + } + v, err = time.Parse(layout, s) + + case isBool(t): + v, err = boolVal(s), nil + + case isString(t): + v, err = s, nil + + case isFloat(t): + v, err = strconv.ParseFloat(s, 64) + + case isInt(t): + v, err = strconv.ParseInt(s, 10, 64) + + case isUint(t): + v, err = strconv.ParseUint(s, 10, 64) + + default: + return reflect.Zero(t), fmt.Errorf("unsupported type %s", t) + } + if err != nil { + return reflect.Zero(t), err + } + return reflect.ValueOf(v).Convert(t), nil + } + + // keydef returns the property key and the default value based on the + // name of the struct field and the options in the tag. + keydef := func(f reflect.StructField) (string, *string, map[string]string) { + _key, _opts := parseTag(f.Tag.Get("properties")) + + var _def *string + if d, ok := _opts["default"]; ok { + _def = &d + } + if _key != "" { + return _key, _def, _opts + } + return f.Name, _def, _opts + } + + switch { + case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t): + s, err := value() + if err != nil { + return err + } + val, err := conv(s, t) + if err != nil { + return err + } + v.Set(val) + + case isPtr(t): + return dec(p, key, def, opts, v.Elem()) + + case isStruct(t): + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + fk, def, opts := keydef(t.Field(i)) + if !fv.CanSet() { + return fmt.Errorf("cannot set %s", t.Field(i).Name) + } + if fk == "-" { + continue + } + if key != "" { + fk = key + "." + fk + } + if err := dec(p, fk, def, opts, fv); err != nil { + return err + } + } + return nil + + case isArray(t): + val, err := value() + if err != nil { + return err + } + vals := split(val, ";") + a := reflect.MakeSlice(t, 0, len(vals)) + for _, s := range vals { + val, err := conv(s, t.Elem()) + if err != nil { + return err + } + a = reflect.Append(a, val) + } + v.Set(a) + + case isMap(t): + valT := t.Elem() + m := reflect.MakeMap(t) + for postfix := range p.FilterStripPrefix(key + ".").m { + pp := strings.SplitN(postfix, ".", 2) + mk, mv := pp[0], reflect.New(valT) + if err := dec(p, key+"."+mk, nil, nil, mv); err != nil { + return err + } + m.SetMapIndex(reflect.ValueOf(mk), mv.Elem()) + } + v.Set(m) + + default: + return fmt.Errorf("unsupported type %s", t) + } + return nil +} + +// split splits a string on sep, trims whitespace of elements +// and omits empty elements +func split(s string, sep string) []string { + var a []string + for _, v := range strings.Split(s, sep) { + if v = strings.TrimSpace(v); v != "" { + a = append(a, v) + } + } + return a +} + +// parseTag parses a "key,k=v,k=v,..." +func parseTag(tag string) (key string, opts map[string]string) { + opts = map[string]string{} + for i, s := range strings.Split(tag, ",") { + if i == 0 { + key = s + continue + } + + pp := strings.SplitN(s, "=", 2) + if len(pp) == 1 { + opts[pp[0]] = "" + } else { + opts[pp[0]] = pp[1] + } + } + return key, opts +} + +func isArray(t reflect.Type) bool { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice } +func isBool(t reflect.Type) bool { return t.Kind() == reflect.Bool } +func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) } +func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map } +func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr } +func isString(t reflect.Type) bool { return t.Kind() == reflect.String } +func isStruct(t reflect.Type) bool { return t.Kind() == reflect.Struct } +func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) } +func isFloat(t reflect.Type) bool { + return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 +} +func isInt(t reflect.Type) bool { + return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64 +} +func isUint(t reflect.Type) bool { + return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64 +} diff --git a/vendor/github.com/magiconair/properties/doc.go b/vendor/github.com/magiconair/properties/doc.go new file mode 100644 index 0000000..f8822da --- /dev/null +++ b/vendor/github.com/magiconair/properties/doc.go @@ -0,0 +1,156 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package properties provides functions for reading and writing +// ISO-8859-1 and UTF-8 encoded .properties files and has +// support for recursive property expansion. +// +// Java properties files are ISO-8859-1 encoded and use Unicode +// literals for characters outside the ISO character set. Unicode +// literals can be used in UTF-8 encoded properties files but +// aren't necessary. +// +// To load a single properties file use MustLoadFile(): +// +// p := properties.MustLoadFile(filename, properties.UTF8) +// +// To load multiple properties files use MustLoadFiles() +// which loads the files in the given order and merges the +// result. Missing properties files can be ignored if the +// 'ignoreMissing' flag is set to true. +// +// Filenames can contain environment variables which are expanded +// before loading. +// +// f1 := "/etc/myapp/myapp.conf" +// f2 := "/home/${USER}/myapp.conf" +// p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true) +// +// All of the different key/value delimiters ' ', ':' and '=' are +// supported as well as the comment characters '!' and '#' and +// multi-line values. +// +// ! this is a comment +// # and so is this +// +// # the following expressions are equal +// key value +// key=value +// key:value +// key = value +// key : value +// key = val\ +// ue +// +// Properties stores all comments preceding a key and provides +// GetComments() and SetComments() methods to retrieve and +// update them. The convenience functions GetComment() and +// SetComment() allow access to the last comment. The +// WriteComment() method writes properties files including +// the comments and with the keys in the original order. +// This can be used for sanitizing properties files. +// +// Property expansion is recursive and circular references +// and malformed expressions are not allowed and cause an +// error. Expansion of environment variables is supported. +// +// # standard property +// key = value +// +// # property expansion: key2 = value +// key2 = ${key} +// +// # recursive expansion: key3 = value +// key3 = ${key2} +// +// # circular reference (error) +// key = ${key} +// +// # malformed expression (error) +// key = ${ke +// +// # refers to the users' home dir +// home = ${HOME} +// +// # local key takes precedence over env var: u = foo +// USER = foo +// u = ${USER} +// +// The default property expansion format is ${key} but can be +// changed by setting different pre- and postfix values on the +// Properties object. +// +// p := properties.NewProperties() +// p.Prefix = "#[" +// p.Postfix = "]#" +// +// Properties provides convenience functions for getting typed +// values with default values if the key does not exist or the +// type conversion failed. +// +// # Returns true if the value is either "1", "on", "yes" or "true" +// # Returns false for every other value and the default value if +// # the key does not exist. +// v = p.GetBool("key", false) +// +// # Returns the value if the key exists and the format conversion +// # was successful. Otherwise, the default value is returned. +// v = p.GetInt64("key", 999) +// v = p.GetUint64("key", 999) +// v = p.GetFloat64("key", 123.0) +// v = p.GetString("key", "def") +// v = p.GetDuration("key", 999) +// +// As an alternative properties may be applied with the standard +// library's flag implementation at any time. +// +// # Standard configuration +// v = flag.Int("key", 999, "help message") +// flag.Parse() +// +// # Merge p into the flag set +// p.MustFlag(flag.CommandLine) +// +// Properties provides several MustXXX() convenience functions +// which will terminate the app if an error occurs. The behavior +// of the failure is configurable and the default is to call +// log.Fatal(err). To have the MustXXX() functions panic instead +// of logging the error set a different ErrorHandler before +// you use the Properties package. +// +// properties.ErrorHandler = properties.PanicHandler +// +// # Will panic instead of logging an error +// p := properties.MustLoadFile("config.properties") +// +// You can also provide your own ErrorHandler function. The only requirement +// is that the error handler function must exit after handling the error. +// +// properties.ErrorHandler = func(err error) { +// fmt.Println(err) +// os.Exit(1) +// } +// +// # Will write to stdout and then exit +// p := properties.MustLoadFile("config.properties") +// +// Properties can also be loaded into a struct via the `Decode` +// method, e.g. +// +// type S struct { +// A string `properties:"a,default=foo"` +// D time.Duration `properties:"timeout,default=5s"` +// E time.Time `properties:"expires,layout=2006-01-02,default=2015-01-01"` +// } +// +// See `Decode()` method for the full documentation. +// +// The following documents provide a description of the properties +// file format. +// +// http://en.wikipedia.org/wiki/.properties +// +// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29 +// +package properties diff --git a/vendor/github.com/magiconair/properties/integrate.go b/vendor/github.com/magiconair/properties/integrate.go new file mode 100644 index 0000000..74d38dc --- /dev/null +++ b/vendor/github.com/magiconair/properties/integrate.go @@ -0,0 +1,34 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import "flag" + +// MustFlag sets flags that are skipped by dst.Parse when p contains +// the respective key for flag.Flag.Name. +// +// It's use is recommended with command line arguments as in: +// flag.Parse() +// p.MustFlag(flag.CommandLine) +func (p *Properties) MustFlag(dst *flag.FlagSet) { + m := make(map[string]*flag.Flag) + dst.VisitAll(func(f *flag.Flag) { + m[f.Name] = f + }) + dst.Visit(func(f *flag.Flag) { + delete(m, f.Name) // overridden + }) + + for name, f := range m { + v, ok := p.Get(name) + if !ok { + continue + } + + if err := f.Value.Set(v); err != nil { + ErrorHandler(err) + } + } +} diff --git a/vendor/github.com/magiconair/properties/lex.go b/vendor/github.com/magiconair/properties/lex.go new file mode 100644 index 0000000..367166d --- /dev/null +++ b/vendor/github.com/magiconair/properties/lex.go @@ -0,0 +1,407 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Parts of the lexer are from the template/text/parser package +// For these parts the following applies: +// +// 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 of the go 1.2 +// distribution. + +package properties + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// item represents a token or text string returned from the scanner. +type item struct { + typ itemType // The type of this item. + pos int // The starting position, in bytes, of this item in the input string. + val string // The value of this item. +} + +func (i item) String() string { + switch { + case i.typ == itemEOF: + return "EOF" + case i.typ == itemError: + return i.val + case len(i.val) > 10: + return fmt.Sprintf("%.10q...", i.val) + } + return fmt.Sprintf("%q", i.val) +} + +// itemType identifies the type of lex items. +type itemType int + +const ( + itemError itemType = iota // error occurred; value is text of error + itemEOF + itemKey // a key + itemValue // a value + itemComment // a comment +) + +// defines a constant for EOF +const eof = -1 + +// permitted whitespace characters space, FF and TAB +const whitespace = " \f\t" + +// stateFn represents the state of the scanner as a function that returns the next state. +type stateFn func(*lexer) stateFn + +// lexer holds the state of the scanner. +type lexer struct { + input string // the string being scanned + state stateFn // the next lexing function to enter + pos int // current position in the input + start int // start position of this item + width int // width of last rune read from input + lastPos int // position of most recent item returned by nextItem + runes []rune // scanned runes for this item + items chan item // channel of scanned items +} + +// next returns the next rune in the input. +func (l *lexer) next() rune { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(l.input[l.pos:]) + l.width = w + l.pos += l.width + return r +} + +// peek returns but does not consume the next rune in the input. +func (l *lexer) peek() rune { + r := l.next() + l.backup() + return r +} + +// backup steps back one rune. Can only be called once per call of next. +func (l *lexer) backup() { + l.pos -= l.width +} + +// emit passes an item back to the client. +func (l *lexer) emit(t itemType) { + i := item{t, l.start, string(l.runes)} + l.items <- i + l.start = l.pos + l.runes = l.runes[:0] +} + +// ignore skips over the pending input before this point. +func (l *lexer) ignore() { + l.start = l.pos +} + +// appends the rune to the current value +func (l *lexer) appendRune(r rune) { + l.runes = append(l.runes, r) +} + +// accept consumes the next rune if it's from the valid set. +func (l *lexer) accept(valid string) bool { + if strings.ContainsRune(valid, l.next()) { + return true + } + l.backup() + return false +} + +// acceptRun consumes a run of runes from the valid set. +func (l *lexer) acceptRun(valid string) { + for strings.ContainsRune(valid, l.next()) { + } + l.backup() +} + +// acceptRunUntil consumes a run of runes up to a terminator. +func (l *lexer) acceptRunUntil(term rune) { + for term != l.next() { + } + l.backup() +} + +// hasText returns true if the current parsed text is not empty. +func (l *lexer) isNotEmpty() bool { + return l.pos > l.start +} + +// lineNumber reports which line we're on, based on the position of +// the previous item returned by nextItem. Doing it this way +// means we don't have to worry about peek double counting. +func (l *lexer) lineNumber() int { + return 1 + strings.Count(l.input[:l.lastPos], "\n") +} + +// errorf returns an error token and terminates the scan by passing +// back a nil pointer that will be the next state, terminating l.nextItem. +func (l *lexer) errorf(format string, args ...interface{}) stateFn { + l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)} + return nil +} + +// nextItem returns the next item from the input. +func (l *lexer) nextItem() item { + i := <-l.items + l.lastPos = i.pos + return i +} + +// lex creates a new scanner for the input string. +func lex(input string) *lexer { + l := &lexer{ + input: input, + items: make(chan item), + runes: make([]rune, 0, 32), + } + go l.run() + return l +} + +// run runs the state machine for the lexer. +func (l *lexer) run() { + for l.state = lexBeforeKey(l); l.state != nil; { + l.state = l.state(l) + } +} + +// state functions + +// lexBeforeKey scans until a key begins. +func lexBeforeKey(l *lexer) stateFn { + switch r := l.next(); { + case isEOF(r): + l.emit(itemEOF) + return nil + + case isEOL(r): + l.ignore() + return lexBeforeKey + + case isComment(r): + return lexComment + + case isWhitespace(r): + l.ignore() + return lexBeforeKey + + default: + l.backup() + return lexKey + } +} + +// lexComment scans a comment line. The comment character has already been scanned. +func lexComment(l *lexer) stateFn { + l.acceptRun(whitespace) + l.ignore() + for { + switch r := l.next(); { + case isEOF(r): + l.ignore() + l.emit(itemEOF) + return nil + case isEOL(r): + l.emit(itemComment) + return lexBeforeKey + default: + l.appendRune(r) + } + } +} + +// lexKey scans the key up to a delimiter +func lexKey(l *lexer) stateFn { + var r rune + +Loop: + for { + switch r = l.next(); { + + case isEscape(r): + err := l.scanEscapeSequence() + if err != nil { + return l.errorf(err.Error()) + } + + case isEndOfKey(r): + l.backup() + break Loop + + case isEOF(r): + break Loop + + default: + l.appendRune(r) + } + } + + if len(l.runes) > 0 { + l.emit(itemKey) + } + + if isEOF(r) { + l.emit(itemEOF) + return nil + } + + return lexBeforeValue +} + +// lexBeforeValue scans the delimiter between key and value. +// Leading and trailing whitespace is ignored. +// We expect to be just after the key. +func lexBeforeValue(l *lexer) stateFn { + l.acceptRun(whitespace) + l.accept(":=") + l.acceptRun(whitespace) + l.ignore() + return lexValue +} + +// lexValue scans text until the end of the line. We expect to be just after the delimiter. +func lexValue(l *lexer) stateFn { + for { + switch r := l.next(); { + case isEscape(r): + if isEOL(l.peek()) { + l.next() + l.acceptRun(whitespace) + } else { + err := l.scanEscapeSequence() + if err != nil { + return l.errorf(err.Error()) + } + } + + case isEOL(r): + l.emit(itemValue) + l.ignore() + return lexBeforeKey + + case isEOF(r): + l.emit(itemValue) + l.emit(itemEOF) + return nil + + default: + l.appendRune(r) + } + } +} + +// scanEscapeSequence scans either one of the escaped characters +// or a unicode literal. We expect to be after the escape character. +func (l *lexer) scanEscapeSequence() error { + switch r := l.next(); { + + case isEscapedCharacter(r): + l.appendRune(decodeEscapedCharacter(r)) + return nil + + case atUnicodeLiteral(r): + return l.scanUnicodeLiteral() + + case isEOF(r): + return fmt.Errorf("premature EOF") + + // silently drop the escape character and append the rune as is + default: + l.appendRune(r) + return nil + } +} + +// scans a unicode literal in the form \uXXXX. We expect to be after the \u. +func (l *lexer) scanUnicodeLiteral() error { + // scan the digits + d := make([]rune, 4) + for i := 0; i < 4; i++ { + d[i] = l.next() + if d[i] == eof || !strings.ContainsRune("0123456789abcdefABCDEF", d[i]) { + return fmt.Errorf("invalid unicode literal") + } + } + + // decode the digits into a rune + r, err := strconv.ParseInt(string(d), 16, 0) + if err != nil { + return err + } + + l.appendRune(rune(r)) + return nil +} + +// decodeEscapedCharacter returns the unescaped rune. We expect to be after the escape character. +func decodeEscapedCharacter(r rune) rune { + switch r { + case 'f': + return '\f' + case 'n': + return '\n' + case 'r': + return '\r' + case 't': + return '\t' + default: + return r + } +} + +// atUnicodeLiteral reports whether we are at a unicode literal. +// The escape character has already been consumed. +func atUnicodeLiteral(r rune) bool { + return r == 'u' +} + +// isComment reports whether we are at the start of a comment. +func isComment(r rune) bool { + return r == '#' || r == '!' +} + +// isEndOfKey reports whether the rune terminates the current key. +func isEndOfKey(r rune) bool { + return strings.ContainsRune(" \f\t\r\n:=", r) +} + +// isEOF reports whether we are at EOF. +func isEOF(r rune) bool { + return r == eof +} + +// isEOL reports whether we are at a new line character. +func isEOL(r rune) bool { + return r == '\n' || r == '\r' +} + +// isEscape reports whether the rune is the escape character which +// prefixes unicode literals and other escaped characters. +func isEscape(r rune) bool { + return r == '\\' +} + +// isEscapedCharacter reports whether we are at one of the characters that need escaping. +// The escape character has already been consumed. +func isEscapedCharacter(r rune) bool { + return strings.ContainsRune(" :=fnrt", r) +} + +// isWhitespace reports whether the rune is a whitespace character. +func isWhitespace(r rune) bool { + return strings.ContainsRune(whitespace, r) +} diff --git a/vendor/github.com/magiconair/properties/load.go b/vendor/github.com/magiconair/properties/load.go new file mode 100644 index 0000000..c8e1b58 --- /dev/null +++ b/vendor/github.com/magiconair/properties/load.go @@ -0,0 +1,292 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" +) + +// Encoding specifies encoding of the input data. +type Encoding uint + +const ( + // utf8Default is a private placeholder for the zero value of Encoding to + // ensure that it has the correct meaning. UTF8 is the default encoding but + // was assigned a non-zero value which cannot be changed without breaking + // existing code. Clients should continue to use the public constants. + utf8Default Encoding = iota + + // UTF8 interprets the input data as UTF-8. + UTF8 + + // ISO_8859_1 interprets the input data as ISO-8859-1. + ISO_8859_1 +) + +type Loader struct { + // Encoding determines how the data from files and byte buffers + // is interpreted. For URLs the Content-Type header is used + // to determine the encoding of the data. + Encoding Encoding + + // DisableExpansion configures the property expansion of the + // returned property object. When set to true, the property values + // will not be expanded and the Property object will not be checked + // for invalid expansion expressions. + DisableExpansion bool + + // IgnoreMissing configures whether missing files or URLs which return + // 404 are reported as errors. When set to true, missing files and 404 + // status codes are not reported as errors. + IgnoreMissing bool +} + +// Load reads a buffer into a Properties struct. +func (l *Loader) LoadBytes(buf []byte) (*Properties, error) { + return l.loadBytes(buf, l.Encoding) +} + +// LoadAll reads the content of multiple URLs or files in the given order into +// a Properties struct. If IgnoreMissing is true then a 404 status code or +// missing file will not be reported as error. Encoding sets the encoding for +// files. For the URLs see LoadURL for the Content-Type header and the +// encoding. +func (l *Loader) LoadAll(names []string) (*Properties, error) { + all := NewProperties() + for _, name := range names { + n, err := expandName(name) + if err != nil { + return nil, err + } + + var p *Properties + switch { + case strings.HasPrefix(n, "http://"): + p, err = l.LoadURL(n) + case strings.HasPrefix(n, "https://"): + p, err = l.LoadURL(n) + default: + p, err = l.LoadFile(n) + } + if err != nil { + return nil, err + } + all.Merge(p) + } + + all.DisableExpansion = l.DisableExpansion + if all.DisableExpansion { + return all, nil + } + return all, all.check() +} + +// LoadFile reads a file into a Properties struct. +// If IgnoreMissing is true then a missing file will not be +// reported as error. +func (l *Loader) LoadFile(filename string) (*Properties, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + if l.IgnoreMissing && os.IsNotExist(err) { + LogPrintf("properties: %s not found. skipping", filename) + return NewProperties(), nil + } + return nil, err + } + return l.loadBytes(data, l.Encoding) +} + +// LoadURL reads the content of the URL into a Properties struct. +// +// The encoding is determined via the Content-Type header which +// should be set to 'text/plain'. If the 'charset' parameter is +// missing, 'iso-8859-1' or 'latin1' the encoding is set to +// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the +// encoding is set to UTF-8. A missing content type header is +// interpreted as 'text/plain; charset=utf-8'. +func (l *Loader) LoadURL(url string) (*Properties, error) { + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("properties: error fetching %q. %s", url, err) + } + + if resp.StatusCode == 404 && l.IgnoreMissing { + LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode) + return NewProperties(), nil + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("properties: %s error reading response. %s", url, err) + } + defer resp.Body.Close() + + ct := resp.Header.Get("Content-Type") + var enc Encoding + switch strings.ToLower(ct) { + case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1": + enc = ISO_8859_1 + case "", "text/plain; charset=utf-8": + enc = UTF8 + default: + return nil, fmt.Errorf("properties: invalid content type %s", ct) + } + + return l.loadBytes(body, enc) +} + +func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) { + p, err := parse(convert(buf, enc)) + if err != nil { + return nil, err + } + p.DisableExpansion = l.DisableExpansion + if p.DisableExpansion { + return p, nil + } + return p, p.check() +} + +// Load reads a buffer into a Properties struct. +func Load(buf []byte, enc Encoding) (*Properties, error) { + l := &Loader{Encoding: enc} + return l.LoadBytes(buf) +} + +// LoadString reads an UTF8 string into a properties struct. +func LoadString(s string) (*Properties, error) { + l := &Loader{Encoding: UTF8} + return l.LoadBytes([]byte(s)) +} + +// LoadMap creates a new Properties struct from a string map. +func LoadMap(m map[string]string) *Properties { + p := NewProperties() + for k, v := range m { + p.Set(k, v) + } + return p +} + +// LoadFile reads a file into a Properties struct. +func LoadFile(filename string, enc Encoding) (*Properties, error) { + l := &Loader{Encoding: enc} + return l.LoadAll([]string{filename}) +} + +// LoadFiles reads multiple files in the given order into +// a Properties struct. If 'ignoreMissing' is true then +// non-existent files will not be reported as error. +func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) { + l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing} + return l.LoadAll(filenames) +} + +// LoadURL reads the content of the URL into a Properties struct. +// See Loader#LoadURL for details. +func LoadURL(url string) (*Properties, error) { + l := &Loader{Encoding: UTF8} + return l.LoadAll([]string{url}) +} + +// LoadURLs reads the content of multiple URLs in the given order into a +// Properties struct. If IgnoreMissing is true then a 404 status code will +// not be reported as error. See Loader#LoadURL for the Content-Type header +// and the encoding. +func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) { + l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing} + return l.LoadAll(urls) +} + +// LoadAll reads the content of multiple URLs or files in the given order into a +// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will +// not be reported as error. Encoding sets the encoding for files. For the URLs please see +// LoadURL for the Content-Type header and the encoding. +func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) { + l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing} + return l.LoadAll(names) +} + +// MustLoadString reads an UTF8 string into a Properties struct and +// panics on error. +func MustLoadString(s string) *Properties { + return must(LoadString(s)) +} + +// MustLoadFile reads a file into a Properties struct and +// panics on error. +func MustLoadFile(filename string, enc Encoding) *Properties { + return must(LoadFile(filename, enc)) +} + +// MustLoadFiles reads multiple files in the given order into +// a Properties struct and panics on error. If 'ignoreMissing' +// is true then non-existent files will not be reported as error. +func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties { + return must(LoadFiles(filenames, enc, ignoreMissing)) +} + +// MustLoadURL reads the content of a URL into a Properties struct and +// panics on error. +func MustLoadURL(url string) *Properties { + return must(LoadURL(url)) +} + +// MustLoadURLs reads the content of multiple URLs in the given order into a +// Properties struct and panics on error. If 'ignoreMissing' is true then a 404 +// status code will not be reported as error. +func MustLoadURLs(urls []string, ignoreMissing bool) *Properties { + return must(LoadURLs(urls, ignoreMissing)) +} + +// MustLoadAll reads the content of multiple URLs or files in the given order into a +// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will +// not be reported as error. Encoding sets the encoding for files. For the URLs please see +// LoadURL for the Content-Type header and the encoding. It panics on error. +func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties { + return must(LoadAll(names, enc, ignoreMissing)) +} + +func must(p *Properties, err error) *Properties { + if err != nil { + ErrorHandler(err) + } + return p +} + +// expandName expands ${ENV_VAR} expressions in a name. +// If the environment variable does not exist then it will be replaced +// with an empty string. Malformed expressions like "${ENV_VAR" will +// be reported as error. +func expandName(name string) (string, error) { + return expand(name, []string{}, "${", "}", make(map[string]string)) +} + +// Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string. +// For ISO-8859-1 we can convert each byte straight into a rune since the +// first 256 unicode code points cover ISO-8859-1. +func convert(buf []byte, enc Encoding) string { + switch enc { + case utf8Default, UTF8: + return string(buf) + case ISO_8859_1: + runes := make([]rune, len(buf)) + for i, b := range buf { + runes[i] = rune(b) + } + return string(runes) + default: + ErrorHandler(fmt.Errorf("unsupported encoding %v", enc)) + } + panic("ErrorHandler should exit") +} diff --git a/vendor/github.com/magiconair/properties/parser.go b/vendor/github.com/magiconair/properties/parser.go new file mode 100644 index 0000000..cdc4a80 --- /dev/null +++ b/vendor/github.com/magiconair/properties/parser.go @@ -0,0 +1,95 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "runtime" +) + +type parser struct { + lex *lexer +} + +func parse(input string) (properties *Properties, err error) { + p := &parser{lex: lex(input)} + defer p.recover(&err) + + properties = NewProperties() + key := "" + comments := []string{} + + for { + token := p.expectOneOf(itemComment, itemKey, itemEOF) + switch token.typ { + case itemEOF: + goto done + case itemComment: + comments = append(comments, token.val) + continue + case itemKey: + key = token.val + if _, ok := properties.m[key]; !ok { + properties.k = append(properties.k, key) + } + } + + token = p.expectOneOf(itemValue, itemEOF) + if len(comments) > 0 { + properties.c[key] = comments + comments = []string{} + } + switch token.typ { + case itemEOF: + properties.m[key] = "" + goto done + case itemValue: + properties.m[key] = token.val + } + } + +done: + return properties, nil +} + +func (p *parser) errorf(format string, args ...interface{}) { + format = fmt.Sprintf("properties: Line %d: %s", p.lex.lineNumber(), format) + panic(fmt.Errorf(format, args...)) +} + +func (p *parser) expect(expected itemType) (token item) { + token = p.lex.nextItem() + if token.typ != expected { + p.unexpected(token) + } + return token +} + +func (p *parser) expectOneOf(expected ...itemType) (token item) { + token = p.lex.nextItem() + for _, v := range expected { + if token.typ == v { + return token + } + } + p.unexpected(token) + panic("unexpected token") +} + +func (p *parser) unexpected(token item) { + p.errorf(token.String()) +} + +// recover is the handler that turns panics into returns from the top level of Parse. +func (p *parser) recover(errp *error) { + e := recover() + if e != nil { + if _, ok := e.(runtime.Error); ok { + panic(e) + } + *errp = e.(error) + } + return +} diff --git a/vendor/github.com/magiconair/properties/properties.go b/vendor/github.com/magiconair/properties/properties.go new file mode 100644 index 0000000..cb3d1a3 --- /dev/null +++ b/vendor/github.com/magiconair/properties/properties.go @@ -0,0 +1,833 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +// BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer. +// BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used. + +import ( + "fmt" + "io" + "log" + "os" + "regexp" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +const maxExpansionDepth = 64 + +// ErrorHandlerFunc defines the type of function which handles failures +// of the MustXXX() functions. An error handler function must exit +// the application after handling the error. +type ErrorHandlerFunc func(error) + +// ErrorHandler is the function which handles failures of the MustXXX() +// functions. The default is LogFatalHandler. +var ErrorHandler ErrorHandlerFunc = LogFatalHandler + +// LogHandlerFunc defines the function prototype for logging errors. +type LogHandlerFunc func(fmt string, args ...interface{}) + +// LogPrintf defines a log handler which uses log.Printf. +var LogPrintf LogHandlerFunc = log.Printf + +// LogFatalHandler handles the error by logging a fatal error and exiting. +func LogFatalHandler(err error) { + log.Fatal(err) +} + +// PanicHandler handles the error by panicking. +func PanicHandler(err error) { + panic(err) +} + +// ----------------------------------------------------------------------------- + +// A Properties contains the key/value pairs from the properties input. +// All values are stored in unexpanded form and are expanded at runtime +type Properties struct { + // Pre-/Postfix for property expansion. + Prefix string + Postfix string + + // DisableExpansion controls the expansion of properties on Get() + // and the check for circular references on Set(). When set to + // true Properties behaves like a simple key/value store and does + // not check for circular references on Get() or on Set(). + DisableExpansion bool + + // Stores the key/value pairs + m map[string]string + + // Stores the comments per key. + c map[string][]string + + // Stores the keys in order of appearance. + k []string +} + +// NewProperties creates a new Properties struct with the default +// configuration for "${key}" expressions. +func NewProperties() *Properties { + return &Properties{ + Prefix: "${", + Postfix: "}", + m: map[string]string{}, + c: map[string][]string{}, + k: []string{}, + } +} + +// Load reads a buffer into the given Properties struct. +func (p *Properties) Load(buf []byte, enc Encoding) error { + l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion} + newProperties, err := l.LoadBytes(buf) + if err != nil { + return err + } + p.Merge(newProperties) + return nil +} + +// Get returns the expanded value for the given key if exists. +// Otherwise, ok is false. +func (p *Properties) Get(key string) (value string, ok bool) { + v, ok := p.m[key] + if p.DisableExpansion { + return v, ok + } + if !ok { + return "", false + } + + expanded, err := p.expand(key, v) + + // we guarantee that the expanded value is free of + // circular references and malformed expressions + // so we panic if we still get an error here. + if err != nil { + ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v)) + } + + return expanded, true +} + +// MustGet returns the expanded value for the given key if exists. +// Otherwise, it panics. +func (p *Properties) MustGet(key string) string { + if v, ok := p.Get(key); ok { + return v + } + ErrorHandler(invalidKeyError(key)) + panic("ErrorHandler should exit") +} + +// ---------------------------------------------------------------------------- + +// ClearComments removes the comments for all keys. +func (p *Properties) ClearComments() { + p.c = map[string][]string{} +} + +// ---------------------------------------------------------------------------- + +// GetComment returns the last comment before the given key or an empty string. +func (p *Properties) GetComment(key string) string { + comments, ok := p.c[key] + if !ok || len(comments) == 0 { + return "" + } + return comments[len(comments)-1] +} + +// ---------------------------------------------------------------------------- + +// GetComments returns all comments that appeared before the given key or nil. +func (p *Properties) GetComments(key string) []string { + if comments, ok := p.c[key]; ok { + return comments + } + return nil +} + +// ---------------------------------------------------------------------------- + +// SetComment sets the comment for the key. +func (p *Properties) SetComment(key, comment string) { + p.c[key] = []string{comment} +} + +// ---------------------------------------------------------------------------- + +// SetComments sets the comments for the key. If the comments are nil then +// all comments for this key are deleted. +func (p *Properties) SetComments(key string, comments []string) { + if comments == nil { + delete(p.c, key) + return + } + p.c[key] = comments +} + +// ---------------------------------------------------------------------------- + +// GetBool checks if the expanded value is one of '1', 'yes', +// 'true' or 'on' if the key exists. The comparison is case-insensitive. +// If the key does not exist the default value is returned. +func (p *Properties) GetBool(key string, def bool) bool { + v, err := p.getBool(key) + if err != nil { + return def + } + return v +} + +// MustGetBool checks if the expanded value is one of '1', 'yes', +// 'true' or 'on' if the key exists. The comparison is case-insensitive. +// If the key does not exist the function panics. +func (p *Properties) MustGetBool(key string) bool { + v, err := p.getBool(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getBool(key string) (value bool, err error) { + if v, ok := p.Get(key); ok { + return boolVal(v), nil + } + return false, invalidKeyError(key) +} + +func boolVal(v string) bool { + v = strings.ToLower(v) + return v == "1" || v == "true" || v == "yes" || v == "on" +} + +// ---------------------------------------------------------------------------- + +// GetDuration parses the expanded value as an time.Duration (in ns) if the +// key exists. If key does not exist or the value cannot be parsed the default +// value is returned. In almost all cases you want to use GetParsedDuration(). +func (p *Properties) GetDuration(key string, def time.Duration) time.Duration { + v, err := p.getInt64(key) + if err != nil { + return def + } + return time.Duration(v) +} + +// MustGetDuration parses the expanded value as an time.Duration (in ns) if +// the key exists. If key does not exist or the value cannot be parsed the +// function panics. In almost all cases you want to use MustGetParsedDuration(). +func (p *Properties) MustGetDuration(key string) time.Duration { + v, err := p.getInt64(key) + if err != nil { + ErrorHandler(err) + } + return time.Duration(v) +} + +// ---------------------------------------------------------------------------- + +// GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration { + s, ok := p.Get(key) + if !ok { + return def + } + v, err := time.ParseDuration(s) + if err != nil { + return def + } + return v +} + +// MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetParsedDuration(key string) time.Duration { + s, ok := p.Get(key) + if !ok { + ErrorHandler(invalidKeyError(key)) + } + v, err := time.ParseDuration(s) + if err != nil { + ErrorHandler(err) + } + return v +} + +// ---------------------------------------------------------------------------- + +// GetFloat64 parses the expanded value as a float64 if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetFloat64(key string, def float64) float64 { + v, err := p.getFloat64(key) + if err != nil { + return def + } + return v +} + +// MustGetFloat64 parses the expanded value as a float64 if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetFloat64(key string) float64 { + v, err := p.getFloat64(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getFloat64(key string) (value float64, err error) { + if v, ok := p.Get(key); ok { + value, err = strconv.ParseFloat(v, 64) + if err != nil { + return 0, err + } + return value, nil + } + return 0, invalidKeyError(key) +} + +// ---------------------------------------------------------------------------- + +// GetInt parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. If the value does not fit into an int the +// function panics with an out of range error. +func (p *Properties) GetInt(key string, def int) int { + v, err := p.getInt64(key) + if err != nil { + return def + } + return intRangeCheck(key, v) +} + +// MustGetInt parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +// If the value does not fit into an int the function panics with +// an out of range error. +func (p *Properties) MustGetInt(key string) int { + v, err := p.getInt64(key) + if err != nil { + ErrorHandler(err) + } + return intRangeCheck(key, v) +} + +// ---------------------------------------------------------------------------- + +// GetInt64 parses the expanded value as an int64 if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetInt64(key string, def int64) int64 { + v, err := p.getInt64(key) + if err != nil { + return def + } + return v +} + +// MustGetInt64 parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetInt64(key string) int64 { + v, err := p.getInt64(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getInt64(key string) (value int64, err error) { + if v, ok := p.Get(key); ok { + value, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, err + } + return value, nil + } + return 0, invalidKeyError(key) +} + +// ---------------------------------------------------------------------------- + +// GetUint parses the expanded value as an uint if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. If the value does not fit into an int the +// function panics with an out of range error. +func (p *Properties) GetUint(key string, def uint) uint { + v, err := p.getUint64(key) + if err != nil { + return def + } + return uintRangeCheck(key, v) +} + +// MustGetUint parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +// If the value does not fit into an int the function panics with +// an out of range error. +func (p *Properties) MustGetUint(key string) uint { + v, err := p.getUint64(key) + if err != nil { + ErrorHandler(err) + } + return uintRangeCheck(key, v) +} + +// ---------------------------------------------------------------------------- + +// GetUint64 parses the expanded value as an uint64 if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetUint64(key string, def uint64) uint64 { + v, err := p.getUint64(key) + if err != nil { + return def + } + return v +} + +// MustGetUint64 parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetUint64(key string) uint64 { + v, err := p.getUint64(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getUint64(key string) (value uint64, err error) { + if v, ok := p.Get(key); ok { + value, err = strconv.ParseUint(v, 10, 64) + if err != nil { + return 0, err + } + return value, nil + } + return 0, invalidKeyError(key) +} + +// ---------------------------------------------------------------------------- + +// GetString returns the expanded value for the given key if exists or +// the default value otherwise. +func (p *Properties) GetString(key, def string) string { + if v, ok := p.Get(key); ok { + return v + } + return def +} + +// MustGetString returns the expanded value for the given key if exists or +// panics otherwise. +func (p *Properties) MustGetString(key string) string { + if v, ok := p.Get(key); ok { + return v + } + ErrorHandler(invalidKeyError(key)) + panic("ErrorHandler should exit") +} + +// ---------------------------------------------------------------------------- + +// Filter returns a new properties object which contains all properties +// for which the key matches the pattern. +func (p *Properties) Filter(pattern string) (*Properties, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + + return p.FilterRegexp(re), nil +} + +// FilterRegexp returns a new properties object which contains all properties +// for which the key matches the regular expression. +func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties { + pp := NewProperties() + for _, k := range p.k { + if re.MatchString(k) { + // TODO(fs): we are ignoring the error which flags a circular reference. + // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) + pp.Set(k, p.m[k]) + } + } + return pp +} + +// FilterPrefix returns a new properties object with a subset of all keys +// with the given prefix. +func (p *Properties) FilterPrefix(prefix string) *Properties { + pp := NewProperties() + for _, k := range p.k { + if strings.HasPrefix(k, prefix) { + // TODO(fs): we are ignoring the error which flags a circular reference. + // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) + pp.Set(k, p.m[k]) + } + } + return pp +} + +// FilterStripPrefix returns a new properties object with a subset of all keys +// with the given prefix and the prefix removed from the keys. +func (p *Properties) FilterStripPrefix(prefix string) *Properties { + pp := NewProperties() + n := len(prefix) + for _, k := range p.k { + if len(k) > len(prefix) && strings.HasPrefix(k, prefix) { + // TODO(fs): we are ignoring the error which flags a circular reference. + // TODO(fs): since we are modifying keys I am not entirely sure whether we can create a circular reference + // TODO(fs): this function should probably return an error but the signature is fixed + pp.Set(k[n:], p.m[k]) + } + } + return pp +} + +// Len returns the number of keys. +func (p *Properties) Len() int { + return len(p.m) +} + +// Keys returns all keys in the same order as in the input. +func (p *Properties) Keys() []string { + keys := make([]string, len(p.k)) + copy(keys, p.k) + return keys +} + +// Set sets the property key to the corresponding value. +// If a value for key existed before then ok is true and prev +// contains the previous value. If the value contains a +// circular reference or a malformed expression then +// an error is returned. +// An empty key is silently ignored. +func (p *Properties) Set(key, value string) (prev string, ok bool, err error) { + if key == "" { + return "", false, nil + } + + // if expansion is disabled we allow circular references + if p.DisableExpansion { + prev, ok = p.Get(key) + p.m[key] = value + if !ok { + p.k = append(p.k, key) + } + return prev, ok, nil + } + + // to check for a circular reference we temporarily need + // to set the new value. If there is an error then revert + // to the previous state. Only if all tests are successful + // then we add the key to the p.k list. + prev, ok = p.Get(key) + p.m[key] = value + + // now check for a circular reference + _, err = p.expand(key, value) + if err != nil { + + // revert to the previous state + if ok { + p.m[key] = prev + } else { + delete(p.m, key) + } + + return "", false, err + } + + if !ok { + p.k = append(p.k, key) + } + + return prev, ok, nil +} + +// SetValue sets property key to the default string value +// as defined by fmt.Sprintf("%v"). +func (p *Properties) SetValue(key string, value interface{}) error { + _, _, err := p.Set(key, fmt.Sprintf("%v", value)) + return err +} + +// MustSet sets the property key to the corresponding value. +// If a value for key existed before then ok is true and prev +// contains the previous value. An empty key is silently ignored. +func (p *Properties) MustSet(key, value string) (prev string, ok bool) { + prev, ok, err := p.Set(key, value) + if err != nil { + ErrorHandler(err) + } + return prev, ok +} + +// String returns a string of all expanded 'key = value' pairs. +func (p *Properties) String() string { + var s string + for _, key := range p.k { + value, _ := p.Get(key) + s = fmt.Sprintf("%s%s = %s\n", s, key, value) + } + return s +} + +// Write writes all unexpanded 'key = value' pairs to the given writer. +// Write returns the number of bytes written and any write error encountered. +func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) { + return p.WriteComment(w, "", enc) +} + +// WriteComment writes all unexpanced 'key = value' pairs to the given writer. +// If prefix is not empty then comments are written with a blank line and the +// given prefix. The prefix should be either "# " or "! " to be compatible with +// the properties file format. Otherwise, the properties parser will not be +// able to read the file back in. It returns the number of bytes written and +// any write error encountered. +func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) { + var x int + + for _, key := range p.k { + value := p.m[key] + + if prefix != "" { + if comments, ok := p.c[key]; ok { + // don't print comments if they are all empty + allEmpty := true + for _, c := range comments { + if c != "" { + allEmpty = false + break + } + } + + if !allEmpty { + // add a blank line between entries but not at the top + if len(comments) > 0 && n > 0 { + x, err = fmt.Fprintln(w) + if err != nil { + return + } + n += x + } + + for _, c := range comments { + x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc)) + if err != nil { + return + } + n += x + } + } + } + } + + x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) + if err != nil { + return + } + n += x + } + return +} + +// Map returns a copy of the properties as a map. +func (p *Properties) Map() map[string]string { + m := make(map[string]string) + for k, v := range p.m { + m[k] = v + } + return m +} + +// FilterFunc returns a copy of the properties which includes the values which passed all filters. +func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties { + pp := NewProperties() +outer: + for k, v := range p.m { + for _, f := range filters { + if !f(k, v) { + continue outer + } + pp.Set(k, v) + } + } + return pp +} + +// ---------------------------------------------------------------------------- + +// Delete removes the key and its comments. +func (p *Properties) Delete(key string) { + delete(p.m, key) + delete(p.c, key) + newKeys := []string{} + for _, k := range p.k { + if k != key { + newKeys = append(newKeys, k) + } + } + p.k = newKeys +} + +// Merge merges properties, comments and keys from other *Properties into p +func (p *Properties) Merge(other *Properties) { + for k, v := range other.m { + p.m[k] = v + } + for k, v := range other.c { + p.c[k] = v + } + +outer: + for _, otherKey := range other.k { + for _, key := range p.k { + if otherKey == key { + continue outer + } + } + p.k = append(p.k, otherKey) + } +} + +// ---------------------------------------------------------------------------- + +// check expands all values and returns an error if a circular reference or +// a malformed expression was found. +func (p *Properties) check() error { + for key, value := range p.m { + if _, err := p.expand(key, value); err != nil { + return err + } + } + return nil +} + +func (p *Properties) expand(key, input string) (string, error) { + // no pre/postfix -> nothing to expand + if p.Prefix == "" && p.Postfix == "" { + return input, nil + } + + return expand(input, []string{key}, p.Prefix, p.Postfix, p.m) +} + +// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values. +// The function keeps track of the keys that were already expanded and stops if it +// detects a circular reference or a malformed expression of the form '(prefix)key'. +func expand(s string, keys []string, prefix, postfix string, values map[string]string) (string, error) { + if len(keys) > maxExpansionDepth { + return "", fmt.Errorf("expansion too deep") + } + + for { + start := strings.Index(s, prefix) + if start == -1 { + return s, nil + } + + keyStart := start + len(prefix) + keyLen := strings.Index(s[keyStart:], postfix) + if keyLen == -1 { + return "", fmt.Errorf("malformed expression") + } + + end := keyStart + keyLen + len(postfix) - 1 + key := s[keyStart : keyStart+keyLen] + + // fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key) + + for _, k := range keys { + if key == k { + return "", fmt.Errorf("circular reference") + } + } + + val, ok := values[key] + if !ok { + val = os.Getenv(key) + } + new_val, err := expand(val, append(keys, key), prefix, postfix, values) + if err != nil { + return "", err + } + s = s[:start] + new_val + s[end+1:] + } + return s, nil +} + +// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters. +func encode(s string, special string, enc Encoding) string { + switch enc { + case UTF8: + return encodeUtf8(s, special) + case ISO_8859_1: + return encodeIso(s, special) + default: + panic(fmt.Sprintf("unsupported encoding %v", enc)) + } +} + +func encodeUtf8(s string, special string) string { + v := "" + for pos := 0; pos < len(s); { + r, w := utf8.DecodeRuneInString(s[pos:]) + pos += w + v += escape(r, special) + } + return v +} + +func encodeIso(s string, special string) string { + var r rune + var w int + var v string + for pos := 0; pos < len(s); { + switch r, w = utf8.DecodeRuneInString(s[pos:]); { + case r < 1<<8: // single byte rune -> escape special chars only + v += escape(r, special) + case r < 1<<16: // two byte rune -> unicode literal + v += fmt.Sprintf("\\u%04x", r) + default: // more than two bytes per rune -> can't encode + v += "?" + } + pos += w + } + return v +} + +func escape(r rune, special string) string { + switch r { + case '\f': + return "\\f" + case '\n': + return "\\n" + case '\r': + return "\\r" + case '\t': + return "\\t" + default: + if strings.ContainsRune(special, r) { + return "\\" + string(r) + } + return string(r) + } +} + +func invalidKeyError(key string) error { + return fmt.Errorf("unknown property: %s", key) +} diff --git a/vendor/github.com/magiconair/properties/rangecheck.go b/vendor/github.com/magiconair/properties/rangecheck.go new file mode 100644 index 0000000..b013a2e --- /dev/null +++ b/vendor/github.com/magiconair/properties/rangecheck.go @@ -0,0 +1,31 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "math" +) + +// make this a var to overwrite it in a test +var is32Bit = ^uint(0) == math.MaxUint32 + +// intRangeCheck checks if the value fits into the int type and +// panics if it does not. +func intRangeCheck(key string, v int64) int { + if is32Bit && (v < math.MinInt32 || v > math.MaxInt32) { + panic(fmt.Sprintf("Value %d for key %s out of range", v, key)) + } + return int(v) +} + +// uintRangeCheck checks if the value fits into the uint type and +// panics if it does not. +func uintRangeCheck(key string, v uint64) uint { + if is32Bit && v > math.MaxUint32 { + panic(fmt.Sprintf("Value %d for key %s out of range", v, key)) + } + return uint(v) +} diff --git a/vendor/github.com/mitchellh/mapstructure/.travis.yml b/vendor/github.com/mitchellh/mapstructure/.travis.yml new file mode 100644 index 0000000..1689c7d --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/.travis.yml @@ -0,0 +1,8 @@ +language: go + +go: + - "1.11.x" + - tip + +script: + - go test diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md new file mode 100644 index 0000000..3b3cb72 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md @@ -0,0 +1,21 @@ +## 1.1.2 + +* Fix error when decode hook decodes interface implementation into interface + type. [GH-140] + +## 1.1.1 + +* Fix panic that can happen in `decodePtr` + +## 1.1.0 + +* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133] +* Support struct to struct decoding [GH-137] +* If source map value is nil, then destination map value is nil (instead of empty) +* If source slice value is nil, then destination slice value is nil (instead of empty) +* If source pointer is nil, then destination pointer is set to nil (instead of + allocated zero value of type) + +## 1.0.0 + +* Initial tagged stable release. diff --git a/vendor/github.com/otiai10/copy/LICENSE b/vendor/github.com/mitchellh/mapstructure/LICENSE similarity index 96% rename from vendor/github.com/otiai10/copy/LICENSE rename to vendor/github.com/mitchellh/mapstructure/LICENSE index 1f0cc5d..f9c841a 100644 --- a/vendor/github.com/otiai10/copy/LICENSE +++ b/vendor/github.com/mitchellh/mapstructure/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 otiai10 +Copyright (c) 2013 Mitchell Hashimoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/mitchellh/mapstructure/README.md b/vendor/github.com/mitchellh/mapstructure/README.md new file mode 100644 index 0000000..0018dc7 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/README.md @@ -0,0 +1,46 @@ +# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](https://godoc.org/github.com/mitchellh/mapstructure) + +mapstructure is a Go library for decoding generic map values to structures +and vice versa, while providing helpful error handling. + +This library is most useful when decoding values from some data stream (JSON, +Gob, etc.) where you don't _quite_ know the structure of the underlying data +until you read a part of it. You can therefore read a `map[string]interface{}` +and use this library to decode it into the proper underlying native Go +structure. + +## Installation + +Standard `go get`: + +``` +$ go get github.com/mitchellh/mapstructure +``` + +## Usage & Example + +For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure). + +The `Decode` function has examples associated with it there. + +## But Why?! + +Go offers fantastic standard libraries for decoding formats such as JSON. +The standard method is to have a struct pre-created, and populate that struct +from the bytes of the encoded format. This is great, but the problem is if +you have configuration or an encoding that changes slightly depending on +specific fields. For example, consider this JSON: + +```json +{ + "type": "person", + "name": "Mitchell" +} +``` + +Perhaps we can't populate a specific structure without first reading +the "type" field from the JSON. We could always do two passes over the +decoding of the JSON (reading the "type" first, and the rest later). +However, it is much simpler to just decode this into a `map[string]interface{}` +structure, read the "type" key, then use something like this library +to decode it into the proper structure. diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go new file mode 100644 index 0000000..1f0abc6 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go @@ -0,0 +1,217 @@ +package mapstructure + +import ( + "errors" + "fmt" + "net" + "reflect" + "strconv" + "strings" + "time" +) + +// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns +// it into the proper DecodeHookFunc type, such as DecodeHookFuncType. +func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { + // Create variables here so we can reference them with the reflect pkg + var f1 DecodeHookFuncType + var f2 DecodeHookFuncKind + + // Fill in the variables into this interface and the rest is done + // automatically using the reflect package. + potential := []interface{}{f1, f2} + + v := reflect.ValueOf(h) + vt := v.Type() + for _, raw := range potential { + pt := reflect.ValueOf(raw).Type() + if vt.ConvertibleTo(pt) { + return v.Convert(pt).Interface() + } + } + + return nil +} + +// DecodeHookExec executes the given decode hook. This should be used +// since it'll naturally degrade to the older backwards compatible DecodeHookFunc +// that took reflect.Kind instead of reflect.Type. +func DecodeHookExec( + raw DecodeHookFunc, + from reflect.Type, to reflect.Type, + data interface{}) (interface{}, error) { + switch f := typedDecodeHook(raw).(type) { + case DecodeHookFuncType: + return f(from, to, data) + case DecodeHookFuncKind: + return f(from.Kind(), to.Kind(), data) + default: + return nil, errors.New("invalid decode hook signature") + } +} + +// ComposeDecodeHookFunc creates a single DecodeHookFunc that +// automatically composes multiple DecodeHookFuncs. +// +// The composed funcs are called in order, with the result of the +// previous transformation. +func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + var err error + for _, f1 := range fs { + data, err = DecodeHookExec(f1, f, t, data) + if err != nil { + return nil, err + } + + // Modify the from kind to be correct with the new data + f = nil + if val := reflect.ValueOf(data); val.IsValid() { + f = val.Type() + } + } + + return data, nil + } +} + +// StringToSliceHookFunc returns a DecodeHookFunc that converts +// string to []string by splitting on the given sep. +func StringToSliceHookFunc(sep string) DecodeHookFunc { + return func( + f reflect.Kind, + t reflect.Kind, + data interface{}) (interface{}, error) { + if f != reflect.String || t != reflect.Slice { + return data, nil + } + + raw := data.(string) + if raw == "" { + return []string{}, nil + } + + return strings.Split(raw, sep), nil + } +} + +// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts +// strings to time.Duration. +func StringToTimeDurationHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Duration(5)) { + return data, nil + } + + // Convert it by parsing + return time.ParseDuration(data.(string)) + } +} + +// StringToIPHookFunc returns a DecodeHookFunc that converts +// strings to net.IP +func StringToIPHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IP{}) { + return data, nil + } + + // Convert it by parsing + ip := net.ParseIP(data.(string)) + if ip == nil { + return net.IP{}, fmt.Errorf("failed parsing ip %v", data) + } + + return ip, nil + } +} + +// StringToIPNetHookFunc returns a DecodeHookFunc that converts +// strings to net.IPNet +func StringToIPNetHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IPNet{}) { + return data, nil + } + + // Convert it by parsing + _, net, err := net.ParseCIDR(data.(string)) + return net, err + } +} + +// StringToTimeHookFunc returns a DecodeHookFunc that converts +// strings to time.Time. +func StringToTimeHookFunc(layout string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Time{}) { + return data, nil + } + + // Convert it by parsing + return time.Parse(layout, data.(string)) + } +} + +// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to +// the decoder. +// +// Note that this is significantly different from the WeaklyTypedInput option +// of the DecoderConfig. +func WeaklyTypedHook( + f reflect.Kind, + t reflect.Kind, + data interface{}) (interface{}, error) { + dataVal := reflect.ValueOf(data) + switch t { + case reflect.String: + switch f { + case reflect.Bool: + if dataVal.Bool() { + return "1", nil + } + return "0", nil + case reflect.Float32: + return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil + case reflect.Int: + return strconv.FormatInt(dataVal.Int(), 10), nil + case reflect.Slice: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + if elemKind == reflect.Uint8 { + return string(dataVal.Interface().([]uint8)), nil + } + case reflect.Uint: + return strconv.FormatUint(dataVal.Uint(), 10), nil + } + } + + return data, nil +} diff --git a/vendor/github.com/mitchellh/mapstructure/error.go b/vendor/github.com/mitchellh/mapstructure/error.go new file mode 100644 index 0000000..47a99e5 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/error.go @@ -0,0 +1,50 @@ +package mapstructure + +import ( + "errors" + "fmt" + "sort" + "strings" +) + +// Error implements the error interface and can represents multiple +// errors that occur in the course of a single decode. +type Error struct { + Errors []string +} + +func (e *Error) Error() string { + points := make([]string, len(e.Errors)) + for i, err := range e.Errors { + points[i] = fmt.Sprintf("* %s", err) + } + + sort.Strings(points) + return fmt.Sprintf( + "%d error(s) decoding:\n\n%s", + len(e.Errors), strings.Join(points, "\n")) +} + +// WrappedErrors implements the errwrap.Wrapper interface to make this +// return value more useful with the errwrap and go-multierror libraries. +func (e *Error) WrappedErrors() []error { + if e == nil { + return nil + } + + result := make([]error, len(e.Errors)) + for i, e := range e.Errors { + result[i] = errors.New(e) + } + + return result +} + +func appendErrors(errors []string, err error) []string { + switch e := err.(type) { + case *Error: + return append(errors, e.Errors...) + default: + return append(errors, e.Error()) + } +} diff --git a/vendor/github.com/mitchellh/mapstructure/go.mod b/vendor/github.com/mitchellh/mapstructure/go.mod new file mode 100644 index 0000000..d2a7125 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/mapstructure diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go new file mode 100644 index 0000000..256ee63 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -0,0 +1,1149 @@ +// Package mapstructure exposes functionality to convert an arbitrary +// map[string]interface{} into a native Go structure. +// +// The Go structure can be arbitrarily complex, containing slices, +// other structs, etc. and the decoder will properly decode nested +// maps and so on into the proper structures in the native Go struct. +// See the examples to see what the decoder is capable of. +package mapstructure + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" +) + +// DecodeHookFunc is the callback function that can be used for +// data transformations. See "DecodeHook" in the DecoderConfig +// struct. +// +// The type should be DecodeHookFuncType or DecodeHookFuncKind. +// Either is accepted. Types are a superset of Kinds (Types can return +// Kinds) and are generally a richer thing to use, but Kinds are simpler +// if you only need those. +// +// The reason DecodeHookFunc is multi-typed is for backwards compatibility: +// we started with Kinds and then realized Types were the better solution, +// but have a promise to not break backwards compat so we now support +// both. +type DecodeHookFunc interface{} + +// DecodeHookFuncType is a DecodeHookFunc which has complete information about +// the source and target types. +type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error) + +// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the +// source and target types. +type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) + +// DecoderConfig is the configuration that is used to create a new decoder +// and allows customization of various aspects of decoding. +type DecoderConfig struct { + // DecodeHook, if set, will be called before any decoding and any + // type conversion (if WeaklyTypedInput is on). This lets you modify + // the values before they're set down onto the resulting struct. + // + // If an error is returned, the entire decode will fail with that + // error. + DecodeHook DecodeHookFunc + + // If ErrorUnused is true, then it is an error for there to exist + // keys in the original map that were unused in the decoding process + // (extra keys). + ErrorUnused bool + + // ZeroFields, if set to true, will zero fields before writing them. + // For example, a map will be emptied before decoded values are put in + // it. If this is false, a map will be merged. + ZeroFields bool + + // If WeaklyTypedInput is true, the decoder will make the following + // "weak" conversions: + // + // - bools to string (true = "1", false = "0") + // - numbers to string (base 10) + // - bools to int/uint (true = 1, false = 0) + // - strings to int/uint (base implied by prefix) + // - int to bool (true if value != 0) + // - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F, + // FALSE, false, False. Anything else is an error) + // - empty array = empty map and vice versa + // - negative numbers to overflowed uint values (base 10) + // - slice of maps to a merged map + // - single values are converted to slices if required. Each + // element is weakly decoded. For example: "4" can become []int{4} + // if the target type is an int slice. + // + WeaklyTypedInput bool + + // Metadata is the struct that will contain extra metadata about + // the decoding. If this is nil, then no metadata will be tracked. + Metadata *Metadata + + // Result is a pointer to the struct that will contain the decoded + // value. + Result interface{} + + // The tag name that mapstructure reads for field names. This + // defaults to "mapstructure" + TagName string +} + +// A Decoder takes a raw interface value and turns it into structured +// data, keeping track of rich error information along the way in case +// anything goes wrong. Unlike the basic top-level Decode method, you can +// more finely control how the Decoder behaves using the DecoderConfig +// structure. The top-level Decode method is just a convenience that sets +// up the most basic Decoder. +type Decoder struct { + config *DecoderConfig +} + +// Metadata contains information about decoding a structure that +// is tedious or difficult to get otherwise. +type Metadata struct { + // Keys are the keys of the structure which were successfully decoded + Keys []string + + // Unused is a slice of keys that were found in the raw value but + // weren't decoded since there was no matching field in the result interface + Unused []string +} + +// Decode takes an input structure and uses reflection to translate it to +// the output structure. output must be a pointer to a map or struct. +func Decode(input interface{}, output interface{}) error { + config := &DecoderConfig{ + Metadata: nil, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecode is the same as Decode but is shorthand to enable +// WeaklyTypedInput. See DecoderConfig for more info. +func WeakDecode(input, output interface{}) error { + config := &DecoderConfig{ + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// DecodeMetadata is the same as Decode, but is shorthand to +// enable metadata collection. See DecoderConfig for more info. +func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecodeMetadata is the same as Decode, but is shorthand to +// enable both WeaklyTypedInput and metadata collection. See +// DecoderConfig for more info. +func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// NewDecoder returns a new decoder for the given configuration. Once +// a decoder has been returned, the same configuration must not be used +// again. +func NewDecoder(config *DecoderConfig) (*Decoder, error) { + val := reflect.ValueOf(config.Result) + if val.Kind() != reflect.Ptr { + return nil, errors.New("result must be a pointer") + } + + val = val.Elem() + if !val.CanAddr() { + return nil, errors.New("result must be addressable (a pointer)") + } + + if config.Metadata != nil { + if config.Metadata.Keys == nil { + config.Metadata.Keys = make([]string, 0) + } + + if config.Metadata.Unused == nil { + config.Metadata.Unused = make([]string, 0) + } + } + + if config.TagName == "" { + config.TagName = "mapstructure" + } + + result := &Decoder{ + config: config, + } + + return result, nil +} + +// Decode decodes the given raw interface to the target pointer specified +// by the configuration. +func (d *Decoder) Decode(input interface{}) error { + return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) +} + +// Decodes an unknown data type into a specific reflection value. +func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + + if input == nil { + // If the data is nil, then we don't set anything, unless ZeroFields is set + // to true. + if d.config.ZeroFields { + outVal.Set(reflect.Zero(outVal.Type())) + + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + } + return nil + } + + if !inputVal.IsValid() { + // If the input value is invalid, then we just set the value + // to be the zero value. + outVal.Set(reflect.Zero(outVal.Type())) + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + return nil + } + + if d.config.DecodeHook != nil { + // We have a DecodeHook, so let's pre-process the input. + var err error + input, err = DecodeHookExec( + d.config.DecodeHook, + inputVal.Type(), outVal.Type(), input) + if err != nil { + return fmt.Errorf("error decoding '%s': %s", name, err) + } + } + + var err error + outputKind := getKind(outVal) + switch outputKind { + case reflect.Bool: + err = d.decodeBool(name, input, outVal) + case reflect.Interface: + err = d.decodeBasic(name, input, outVal) + case reflect.String: + err = d.decodeString(name, input, outVal) + case reflect.Int: + err = d.decodeInt(name, input, outVal) + case reflect.Uint: + err = d.decodeUint(name, input, outVal) + case reflect.Float32: + err = d.decodeFloat(name, input, outVal) + case reflect.Struct: + err = d.decodeStruct(name, input, outVal) + case reflect.Map: + err = d.decodeMap(name, input, outVal) + case reflect.Ptr: + err = d.decodePtr(name, input, outVal) + case reflect.Slice: + err = d.decodeSlice(name, input, outVal) + case reflect.Array: + err = d.decodeArray(name, input, outVal) + case reflect.Func: + err = d.decodeFunc(name, input, outVal) + default: + // If we reached this point then we weren't able to decode it + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) + } + + // If we reached here, then we successfully decoded SOMETHING, so + // mark the key as used if we're tracking metainput. + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + + return err +} + +// This decodes a basic type (bool, int, string, etc.) and sets the +// value to "data" of that type. +func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { + if val.IsValid() && val.Elem().IsValid() { + return d.decode(name, data, val.Elem()) + } + + dataVal := reflect.ValueOf(data) + + // If the input data is a pointer, and the assigned type is the dereference + // of that exact pointer, then indirect it so that we can assign it. + // Example: *string to string + if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() { + dataVal = reflect.Indirect(dataVal) + } + + if !dataVal.IsValid() { + dataVal = reflect.Zero(val.Type()) + } + + dataValType := dataVal.Type() + if !dataValType.AssignableTo(val.Type()) { + return fmt.Errorf( + "'%s' expected type '%s', got '%s'", + name, val.Type(), dataValType) + } + + val.Set(dataVal) + return nil +} + +func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + converted := true + switch { + case dataKind == reflect.String: + val.SetString(dataVal.String()) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetString("1") + } else { + val.SetString("0") + } + case dataKind == reflect.Int && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatInt(dataVal.Int(), 10)) + case dataKind == reflect.Uint && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) + case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: + val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) + case dataKind == reflect.Slice && d.config.WeaklyTypedInput, + dataKind == reflect.Array && d.config.WeaklyTypedInput: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + switch elemKind { + case reflect.Uint8: + var uints []uint8 + if dataKind == reflect.Array { + uints = make([]uint8, dataVal.Len(), dataVal.Len()) + for i := range uints { + uints[i] = dataVal.Index(i).Interface().(uint8) + } + } else { + uints = dataVal.Interface().([]uint8) + } + val.SetString(string(uints)) + default: + converted = false + } + default: + converted = false + } + + if !converted { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetInt(dataVal.Int()) + case dataKind == reflect.Uint: + val.SetInt(int64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetInt(int64(dataVal.Float())) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetInt(1) + } else { + val.SetInt(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetInt(i) + } else { + return fmt.Errorf("cannot parse '%s' as int: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetInt(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + switch { + case dataKind == reflect.Int: + i := dataVal.Int() + if i < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + case dataKind == reflect.Uint: + val.SetUint(dataVal.Uint()) + case dataKind == reflect.Float32: + f := dataVal.Float() + if f < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %f overflows uint", + name, f) + } + val.SetUint(uint64(f)) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetUint(1) + } else { + val.SetUint(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetUint(i) + } else { + return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) + } + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + switch { + case dataKind == reflect.Bool: + val.SetBool(dataVal.Bool()) + case dataKind == reflect.Int && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Int() != 0) + case dataKind == reflect.Uint && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Uint() != 0) + case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: + val.SetBool(dataVal.Float() != 0) + case dataKind == reflect.String && d.config.WeaklyTypedInput: + b, err := strconv.ParseBool(dataVal.String()) + if err == nil { + val.SetBool(b) + } else if dataVal.String() == "" { + val.SetBool(false) + } else { + return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) + } + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetFloat(float64(dataVal.Int())) + case dataKind == reflect.Uint: + val.SetFloat(float64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetFloat(dataVal.Float()) + case dataKind == reflect.Bool && d.config.WeaklyTypedInput: + if dataVal.Bool() { + val.SetFloat(1) + } else { + val.SetFloat(0) + } + case dataKind == reflect.String && d.config.WeaklyTypedInput: + f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits()) + if err == nil { + val.SetFloat(f) + } else { + return fmt.Errorf("cannot parse '%s' as float: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Float64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetFloat(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + // By default we overwrite keys in the current map + valMap := val + + // If the map is nil or we're purposely zeroing fields, make a new map + if valMap.IsNil() || d.config.ZeroFields { + // Make a new map to hold our result + mapType := reflect.MapOf(valKeyType, valElemType) + valMap = reflect.MakeMap(mapType) + } + + // Check input type and based on the input type jump to the proper func + dataVal := reflect.Indirect(reflect.ValueOf(data)) + switch dataVal.Kind() { + case reflect.Map: + return d.decodeMapFromMap(name, dataVal, val, valMap) + + case reflect.Struct: + return d.decodeMapFromStruct(name, dataVal, val, valMap) + + case reflect.Array, reflect.Slice: + if d.config.WeaklyTypedInput { + return d.decodeMapFromSlice(name, dataVal, val, valMap) + } + + fallthrough + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + // Special case for BC reasons (covered by tests) + if dataVal.Len() == 0 { + val.Set(valMap) + return nil + } + + for i := 0; i < dataVal.Len(); i++ { + err := d.decode( + fmt.Sprintf("%s[%d]", name, i), + dataVal.Index(i).Interface(), val) + if err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + // Accumulate errors + errors := make([]string, 0) + + // If the input data is empty, then we just match what the input data is. + if dataVal.Len() == 0 { + if dataVal.IsNil() { + if !val.IsNil() { + val.Set(dataVal) + } + } else { + // Set to empty allocated value + val.Set(valMap) + } + + return nil + } + + for _, k := range dataVal.MapKeys() { + fieldName := fmt.Sprintf("%s[%s]", name, k) + + // First decode the key into the proper type + currentKey := reflect.Indirect(reflect.New(valKeyType)) + if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { + errors = appendErrors(errors, err) + continue + } + + // Next decode the data into the proper type + v := dataVal.MapIndex(k).Interface() + currentVal := reflect.Indirect(reflect.New(valElemType)) + if err := d.decode(fieldName, v, currentVal); err != nil { + errors = appendErrors(errors, err) + continue + } + + valMap.SetMapIndex(currentKey, currentVal) + } + + // Set the built up map to the value + val.Set(valMap) + + // If we had errors, return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + typ := dataVal.Type() + for i := 0; i < typ.NumField(); i++ { + // Get the StructField first since this is a cheap operation. If the + // field is unexported, then ignore it. + f := typ.Field(i) + if f.PkgPath != "" { + continue + } + + // Next get the actual value of this field and verify it is assignable + // to the map value. + v := dataVal.Field(i) + if !v.Type().AssignableTo(valMap.Type().Elem()) { + return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) + } + + tagValue := f.Tag.Get(d.config.TagName) + tagParts := strings.Split(tagValue, ",") + + // Determine the name of the key in the map + keyName := f.Name + if tagParts[0] != "" { + if tagParts[0] == "-" { + continue + } + keyName = tagParts[0] + } + + // If "squash" is specified in the tag, we squash the field down. + squash := false + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + if squash && v.Kind() != reflect.Struct { + return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) + } + + switch v.Kind() { + // this is an embedded struct, so handle it differently + case reflect.Struct: + x := reflect.New(v.Type()) + x.Elem().Set(v) + + vType := valMap.Type() + vKeyType := vType.Key() + vElemType := vType.Elem() + mType := reflect.MapOf(vKeyType, vElemType) + vMap := reflect.MakeMap(mType) + + err := d.decode(keyName, x.Interface(), vMap) + if err != nil { + return err + } + + if squash { + for _, k := range vMap.MapKeys() { + valMap.SetMapIndex(k, vMap.MapIndex(k)) + } + } else { + valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) + } + + default: + valMap.SetMapIndex(reflect.ValueOf(keyName), v) + } + } + + if val.CanAddr() { + val.Set(valMap) + } + + return nil +} + +func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return nil + } + + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + valType := val.Type() + valElemType := valType.Elem() + if val.CanSet() { + realVal := val + if realVal.IsNil() || d.config.ZeroFields { + realVal = reflect.New(valElemType) + } + + if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { + return err + } + + val.Set(realVal) + } else { + if err := d.decode(name, data, reflect.Indirect(val)); err != nil { + return err + } + } + return nil +} + +func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error { + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + dataVal := reflect.Indirect(reflect.ValueOf(data)) + if val.Type() != dataVal.Type() { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + val.Set(dataVal) + return nil +} + +func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataValKind := dataVal.Kind() + valType := val.Type() + valElemType := valType.Elem() + sliceType := reflect.SliceOf(valElemType) + + valSlice := val + if valSlice.IsNil() || d.config.ZeroFields { + if d.config.WeaklyTypedInput { + switch { + // Slice and array we use the normal logic + case dataValKind == reflect.Slice, dataValKind == reflect.Array: + break + + // Empty maps turn into empty slices + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.MakeSlice(sliceType, 0, 0)) + return nil + } + // Create slice of maps of other sizes + return d.decodeSlice(name, []interface{}{data}, val) + + case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: + return d.decodeSlice(name, []byte(dataVal.String()), val) + + // All other types we try to convert to the slice type + // and "lift" it into it. i.e. a string becomes a string slice. + default: + // Just re-try this function with data as a slice. + return d.decodeSlice(name, []interface{}{data}, val) + } + } + + // Check input type + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + + } + + // If the input value is empty, then don't allocate since non-nil != nil + if dataVal.Len() == 0 { + return nil + } + + // Make a new slice to hold our result, same size as the original data. + valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) + } + + // Accumulate any errors + errors := make([]string, 0) + + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + for valSlice.Len() <= i { + valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) + } + currentField := valSlice.Index(i) + + fieldName := fmt.Sprintf("%s[%d]", name, i) + if err := d.decode(fieldName, currentData, currentField); err != nil { + errors = appendErrors(errors, err) + } + } + + // Finally, set the value to the slice we built up + val.Set(valSlice) + + // If there were errors, we return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataValKind := dataVal.Kind() + valType := val.Type() + valElemType := valType.Elem() + arrayType := reflect.ArrayOf(valType.Len(), valElemType) + + valArray := val + + if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields { + // Check input type + if dataValKind != reflect.Array && dataValKind != reflect.Slice { + if d.config.WeaklyTypedInput { + switch { + // Empty maps turn into empty arrays + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.Zero(arrayType)) + return nil + } + + // All other types we try to convert to the array type + // and "lift" it into it. i.e. a string becomes a string array. + default: + // Just re-try this function with data as a slice. + return d.decodeArray(name, []interface{}{data}, val) + } + } + + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + + } + if dataVal.Len() > arrayType.Len() { + return fmt.Errorf( + "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len()) + + } + + // Make a new array to hold our result, same size as the original data. + valArray = reflect.New(arrayType).Elem() + } + + // Accumulate any errors + errors := make([]string, 0) + + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + currentField := valArray.Index(i) + + fieldName := fmt.Sprintf("%s[%d]", name, i) + if err := d.decode(fieldName, currentData, currentField); err != nil { + errors = appendErrors(errors, err) + } + } + + // Finally, set the value to the array we built up + val.Set(valArray) + + // If there were errors, we return those + if len(errors) > 0 { + return &Error{errors} + } + + return nil +} + +func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + + // If the type of the value to write to and the data match directly, + // then we just set it directly instead of recursing into the structure. + if dataVal.Type() == val.Type() { + val.Set(dataVal) + return nil + } + + dataValKind := dataVal.Kind() + switch dataValKind { + case reflect.Map: + return d.decodeStructFromMap(name, dataVal, val) + + case reflect.Struct: + // Not the most efficient way to do this but we can optimize later if + // we want to. To convert from struct to struct we go to map first + // as an intermediary. + m := make(map[string]interface{}) + mval := reflect.Indirect(reflect.ValueOf(&m)) + if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil { + return err + } + + result := d.decodeStructFromMap(name, mval, val) + return result + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { + dataValType := dataVal.Type() + if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { + return fmt.Errorf( + "'%s' needs a map with string keys, has '%s' keys", + name, dataValType.Key().Kind()) + } + + dataValKeys := make(map[reflect.Value]struct{}) + dataValKeysUnused := make(map[interface{}]struct{}) + for _, dataValKey := range dataVal.MapKeys() { + dataValKeys[dataValKey] = struct{}{} + dataValKeysUnused[dataValKey.Interface()] = struct{}{} + } + + errors := make([]string, 0) + + // This slice will keep track of all the structs we'll be decoding. + // There can be more than one struct if there are embedded structs + // that are squashed. + structs := make([]reflect.Value, 1, 5) + structs[0] = val + + // Compile the list of all the fields that we're going to be decoding + // from all the structs. + type field struct { + field reflect.StructField + val reflect.Value + } + fields := []field{} + for len(structs) > 0 { + structVal := structs[0] + structs = structs[1:] + + structType := structVal.Type() + + for i := 0; i < structType.NumField(); i++ { + fieldType := structType.Field(i) + fieldKind := fieldType.Type.Kind() + + // If "squash" is specified in the tag, we squash the field down. + squash := false + tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + + if squash { + if fieldKind != reflect.Struct { + errors = appendErrors(errors, + fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind)) + } else { + structs = append(structs, structVal.FieldByName(fieldType.Name)) + } + continue + } + + // Normal struct field, store it away + fields = append(fields, field{fieldType, structVal.Field(i)}) + } + } + + // for fieldType, field := range fields { + for _, f := range fields { + field, fieldValue := f.field, f.val + fieldName := field.Name + + tagValue := field.Tag.Get(d.config.TagName) + tagValue = strings.SplitN(tagValue, ",", 2)[0] + if tagValue != "" { + fieldName = tagValue + } + + rawMapKey := reflect.ValueOf(fieldName) + rawMapVal := dataVal.MapIndex(rawMapKey) + if !rawMapVal.IsValid() { + // Do a slower search by iterating over each key and + // doing case-insensitive search. + for dataValKey := range dataValKeys { + mK, ok := dataValKey.Interface().(string) + if !ok { + // Not a string key + continue + } + + if strings.EqualFold(mK, fieldName) { + rawMapKey = dataValKey + rawMapVal = dataVal.MapIndex(dataValKey) + break + } + } + + if !rawMapVal.IsValid() { + // There was no matching key in the map for the value in + // the struct. Just ignore. + continue + } + } + + // Delete the key we're using from the unused map so we stop tracking + delete(dataValKeysUnused, rawMapKey.Interface()) + + if !fieldValue.IsValid() { + // This should never happen + panic("field is not valid") + } + + // If we can't set the field, then it is unexported or something, + // and we just continue onwards. + if !fieldValue.CanSet() { + continue + } + + // If the name is empty string, then we're at the root, and we + // don't dot-join the fields. + if name != "" { + fieldName = fmt.Sprintf("%s.%s", name, fieldName) + } + + if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { + errors = appendErrors(errors, err) + } + } + + if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { + keys := make([]string, 0, len(dataValKeysUnused)) + for rawKey := range dataValKeysUnused { + keys = append(keys, rawKey.(string)) + } + sort.Strings(keys) + + err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) + errors = appendErrors(errors, err) + } + + if len(errors) > 0 { + return &Error{errors} + } + + // Add the unused keys to the list of unused keys if we're tracking metadata + if d.config.Metadata != nil { + for rawKey := range dataValKeysUnused { + key := rawKey.(string) + if name != "" { + key = fmt.Sprintf("%s.%s", name, key) + } + + d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) + } + } + + return nil +} + +func getKind(val reflect.Value) reflect.Kind { + kind := val.Kind() + + switch { + case kind >= reflect.Int && kind <= reflect.Int64: + return reflect.Int + case kind >= reflect.Uint && kind <= reflect.Uint64: + return reflect.Uint + case kind >= reflect.Float32 && kind <= reflect.Float64: + return reflect.Float32 + default: + return kind + } +} diff --git a/vendor/github.com/mtibben/percent/LICENSE b/vendor/github.com/mtibben/percent/LICENSE deleted file mode 100644 index 4bad1f9..0000000 --- a/vendor/github.com/mtibben/percent/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Michael Tibben - -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/mtibben/percent/README.md b/vendor/github.com/mtibben/percent/README.md deleted file mode 100644 index 4e85b56..0000000 --- a/vendor/github.com/mtibben/percent/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Percent - -Package percent escapes strings using [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding) - -[Docs](https://pkg.go.dev/github.com/mtibben/percent?tab=doc) diff --git a/vendor/github.com/mtibben/percent/go.mod b/vendor/github.com/mtibben/percent/go.mod deleted file mode 100644 index f0bb33e..0000000 --- a/vendor/github.com/mtibben/percent/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/mtibben/percent - -go 1.14 diff --git a/vendor/github.com/mtibben/percent/percent.go b/vendor/github.com/mtibben/percent/percent.go deleted file mode 100644 index 565222e..0000000 --- a/vendor/github.com/mtibben/percent/percent.go +++ /dev/null @@ -1,64 +0,0 @@ -// Package percent escapes strings using percent-encoding -package percent - -import ( - "strings" -) - -const upperhex = "0123456789ABCDEF" - -func unhex(c byte) byte { - switch { - case '0' <= c && c <= '9': - return c - '0' - case 'a' <= c && c <= 'f': - return c - 'a' + 10 - case 'A' <= c && c <= 'F': - return c - 'A' + 10 - } - return 0 -} - -func ishex(c byte) bool { - switch { - case '0' <= c && c <= '9': - return true - case 'a' <= c && c <= 'f': - return true - case 'A' <= c && c <= 'F': - return true - } - return false -} - -// Encode escapes the string using percent-encoding, converting the runes found in charsToEncode with hex-encoded %AB sequences -func Encode(s string, charsToEncode string) string { - var t strings.Builder - for _, c := range s { - if strings.IndexRune(charsToEncode, c) != -1 || c == '%' { - for _, b := range []byte(string(c)) { - t.WriteByte('%') - t.WriteByte(upperhex[b>>4]) - t.WriteByte(upperhex[b&15]) - } - } else { - t.WriteRune(c) - } - } - return t.String() -} - -// Decode does the inverse transformation of Encode, converting each 3-byte encoded substring of the form "%AB" into the hex-decoded byte 0xAB -func Decode(s string) string { - var t []byte - for i := 0; i < len(s); i++ { - // check next 2 chars are valid - if s[i] == '%' && i+2 < len(s) && ishex(s[i+1]) && ishex(s[i+2]) { - t = append(t, (unhex(s[i+1])<<4 | unhex(s[i+2]))) - i += 2 - } else { - t = append(t, s[i]) - } - } - return string(t) -} diff --git a/vendor/github.com/otiai10/copy/.gitignore b/vendor/github.com/otiai10/copy/.gitignore deleted file mode 100644 index 5b5ae0a..0000000 --- a/vendor/github.com/otiai10/copy/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -testdata.copy -coverage.txt -vendor diff --git a/vendor/github.com/otiai10/copy/README.md b/vendor/github.com/otiai10/copy/README.md deleted file mode 100644 index 5ad7c57..0000000 --- a/vendor/github.com/otiai10/copy/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# copy - -[![Actions Status](https://github.com/otiai10/copy/workflows/Go/badge.svg)](https://github.com/otiai10/copy/actions) -[![codecov](https://codecov.io/gh/otiai10/copy/branch/master/graph/badge.svg)](https://codecov.io/gh/otiai10/copy) -[![GoDoc](https://godoc.org/github.com/otiai10/copy?status.svg)](https://godoc.org/github.com/otiai10/copy) -[![Go Report Card](https://goreportcard.com/badge/github.com/otiai10/copy)](https://goreportcard.com/report/github.com/otiai10/copy) - -`copy` copies directories recursively. - -Example: - -```go -err := Copy("your/directory", "your/directory.copy") -``` diff --git a/vendor/github.com/otiai10/copy/copy.go b/vendor/github.com/otiai10/copy/copy.go deleted file mode 100644 index 0ff0e5b..0000000 --- a/vendor/github.com/otiai10/copy/copy.go +++ /dev/null @@ -1,184 +0,0 @@ -package copy - -import ( - "io" - "io/ioutil" - "os" - "path/filepath" -) - -const ( - // tmpPermissionForDirectory makes the destination directory writable, - // so that stuff can be copied recursively even if any original directory is NOT writable. - // See https://github.com/otiai10/copy/pull/9 for more information. - tmpPermissionForDirectory = os.FileMode(0755) -) - -// Copy copies src to dest, doesn't matter if src is a directory or a file. -func Copy(src, dest string, opt ...Options) error { - info, err := os.Lstat(src) - if err != nil { - return err - } - return switchboard(src, dest, info, assure(opt...)) -} - -// switchboard switches proper copy functions regarding file type, etc... -// If there would be anything else here, add a case to this switchboard. -func switchboard(src, dest string, info os.FileInfo, opt Options) error { - switch { - case info.Mode()&os.ModeSymlink != 0: - return onsymlink(src, dest, info, opt) - case info.IsDir(): - return dcopy(src, dest, info, opt) - default: - return fcopy(src, dest, info, opt) - } -} - -// copy decide if this src should be copied or not. -// Because this "copy" could be called recursively, -// "info" MUST be given here, NOT nil. -func copy(src, dest string, info os.FileInfo, opt Options) error { - skip, err := opt.Skip(src) - if err != nil { - return err - } - if skip { - return nil - } - return switchboard(src, dest, info, opt) -} - -// fcopy is for just a file, -// with considering existence of parent directory -// and file permission. -func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { - - if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { - return - } - - f, err := os.Create(dest) - if err != nil { - return - } - defer fclose(f, &err) - - if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil { - return - } - - s, err := os.Open(src) - if err != nil { - return - } - defer fclose(s, &err) - - if _, err = io.Copy(f, s); err != nil { - return - } - - if opt.Sync { - err = f.Sync() - } - - return -} - -// dcopy is for a directory, -// with scanning contents inside the directory -// and pass everything to "copy" recursively. -func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { - - originalMode := info.Mode() - - // Make dest dir with 0755 so that everything writable. - if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil { - return - } - // Recover dir mode with original one. - defer chmod(destdir, originalMode|opt.AddPermission, &err) - - contents, err := ioutil.ReadDir(srcdir) - if err != nil { - return - } - - for _, content := range contents { - cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) - - if err = copy(cs, cd, content, opt); err != nil { - // If any error, exit immediately - return - } - } - - return -} - -func onsymlink(src, dest string, info os.FileInfo, opt Options) error { - - switch opt.OnSymlink(src) { - case Shallow: - return lcopy(src, dest) - case Deep: - orig, err := os.Readlink(src) - if err != nil { - return err - } - info, err = os.Lstat(orig) - if err != nil { - return err - } - return copy(orig, dest, info, opt) - case Skip: - fallthrough - default: - return nil // do nothing - } -} - -// lcopy is for a symlink, -// with just creating a new symlink by replicating src symlink. -func lcopy(src, dest string) error { - src, err := os.Readlink(src) - if err != nil { - return err - } - return os.Symlink(src, dest) -} - -// fclose ANYHOW closes file, -// with asiging error raised during Close, -// BUT respecting the error already reported. -func fclose(f *os.File, reported *error) { - if err := f.Close(); *reported == nil { - *reported = err - } -} - -// chmod ANYHOW changes file mode, -// with asiging error raised during Chmod, -// BUT respecting the error already reported. -func chmod(dir string, mode os.FileMode, reported *error) { - if err := os.Chmod(dir, mode); *reported == nil { - *reported = err - } -} - -// assure Options struct, should be called only once. -// All optional values MUST NOT BE nil/zero after assured. -func assure(opts ...Options) Options { - if len(opts) == 0 { - return getDefaultOptions() - } - defopt := getDefaultOptions() - if opts[0].OnSymlink == nil { - opts[0].OnSymlink = defopt.OnSymlink - } - if opts[0].Skip == nil { - opts[0].Skip = defopt.Skip - } - return opts[0] -} diff --git a/vendor/github.com/otiai10/copy/go.mod b/vendor/github.com/otiai10/copy/go.mod deleted file mode 100644 index eecb26d..0000000 --- a/vendor/github.com/otiai10/copy/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/otiai10/copy - -go 1.12 - -require github.com/otiai10/mint v1.3.1 diff --git a/vendor/github.com/otiai10/copy/go.sum b/vendor/github.com/otiai10/copy/go.sum deleted file mode 100644 index 952304a..0000000 --- a/vendor/github.com/otiai10/copy/go.sum +++ /dev/null @@ -1,5 +0,0 @@ -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= diff --git a/vendor/github.com/otiai10/copy/options.go b/vendor/github.com/otiai10/copy/options.go deleted file mode 100644 index 21504cd..0000000 --- a/vendor/github.com/otiai10/copy/options.go +++ /dev/null @@ -1,46 +0,0 @@ -package copy - -import "os" - -// Options specifies optional actions on copying. -type Options struct { - // OnSymlink can specify what to do on symlink - OnSymlink func(src string) SymlinkAction - // Skip can specify which files should be skipped - Skip func(src string) (bool, error) - // AddPermission to every entities, - // NO MORE THAN 0777 - AddPermission os.FileMode - // Sync file after copy. - // Useful in case when file must be on the disk - // (in case crash happens, for example), - // at the expense of some performance penalty - Sync bool -} - -// SymlinkAction represents what to do on symlink. -type SymlinkAction int - -const ( - // Deep creates hard-copy of contents. - Deep SymlinkAction = iota - // Shallow creates new symlink to the dest of symlink. - Shallow - // Skip does nothing with symlink. - Skip -) - -// getDefaultOptions provides default options, -// which would be modified by usage-side. -func getDefaultOptions() Options { - return Options{ - OnSymlink: func(string) SymlinkAction { - return Shallow // Do shallow copy - }, - Skip: func(string) (bool, error) { - return false, nil // Don't skip - }, - AddPermission: 0, // Add nothing - Sync: false, // Do not sync - } -} diff --git a/vendor/github.com/pelletier/go-toml/.gitignore b/vendor/github.com/pelletier/go-toml/.gitignore new file mode 100644 index 0000000..99e38bb --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/.gitignore @@ -0,0 +1,2 @@ +test_program/test_program_bin +fuzz/ diff --git a/vendor/github.com/pelletier/go-toml/.travis.yml b/vendor/github.com/pelletier/go-toml/.travis.yml new file mode 100644 index 0000000..c9fbf30 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/.travis.yml @@ -0,0 +1,23 @@ +sudo: false +language: go +go: + - 1.8.x + - 1.9.x + - 1.10.x + - tip +matrix: + allow_failures: + - go: tip + fast_finish: true +script: + - if [ -n "$(go fmt ./...)" ]; then exit 1; fi + - ./test.sh + - ./benchmark.sh $TRAVIS_BRANCH https://github.com/$TRAVIS_REPO_SLUG.git +before_install: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi +branches: + only: [master] +after_success: + - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=coverage.out -repotoken $COVERALLS_TOKEN diff --git a/vendor/github.com/gsterjov/go-libsecret/LICENSE b/vendor/github.com/pelletier/go-toml/LICENSE similarity index 94% rename from vendor/github.com/gsterjov/go-libsecret/LICENSE rename to vendor/github.com/pelletier/go-toml/LICENSE index 51f2292..583bdae 100644 --- a/vendor/github.com/gsterjov/go-libsecret/LICENSE +++ b/vendor/github.com/pelletier/go-toml/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Goran Sterjov +Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +19,3 @@ 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/pelletier/go-toml/README.md b/vendor/github.com/pelletier/go-toml/README.md new file mode 100644 index 0000000..0d357ac --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/README.md @@ -0,0 +1,131 @@ +# go-toml + +Go library for the [TOML](https://github.com/mojombo/toml) format. + +This library supports TOML version +[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) + +[![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/github.com/pelletier/go-toml) +[![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE) +[![Build Status](https://travis-ci.org/pelletier/go-toml.svg?branch=master)](https://travis-ci.org/pelletier/go-toml) +[![Coverage Status](https://coveralls.io/repos/github/pelletier/go-toml/badge.svg?branch=master)](https://coveralls.io/github/pelletier/go-toml?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml) + +## Features + +Go-toml provides the following features for using data parsed from TOML documents: + +* Load TOML documents from files and string data +* Easily navigate TOML structure using Tree +* Mashaling and unmarshaling to and from data structures +* Line & column position data for all parsed elements +* [Query support similar to JSON-Path](query/) +* Syntax errors contain line and column numbers + +## Import + +```go +import "github.com/pelletier/go-toml" +``` + +## Usage example + +Read a TOML document: + +```go +config, _ := toml.Load(` +[postgres] +user = "pelletier" +password = "mypassword"`) +// retrieve data directly +user := config.Get("postgres.user").(string) + +// or using an intermediate object +postgresConfig := config.Get("postgres").(*toml.Tree) +password := postgresConfig.Get("password").(string) +``` + +Or use Unmarshal: + +```go +type Postgres struct { + User string + Password string +} +type Config struct { + Postgres Postgres +} + +doc := []byte(` +[Postgres] +User = "pelletier" +Password = "mypassword"`) + +config := Config{} +toml.Unmarshal(doc, &config) +fmt.Println("user=", config.Postgres.User) +``` + +Or use a query: + +```go +// use a query to gather elements without walking the tree +q, _ := query.Compile("$..[user,password]") +results := q.Execute(config) +for ii, item := range results.Values() { + fmt.Println("Query result %d: %v", ii, item) +} +``` + +## Documentation + +The documentation and additional examples are available at +[godoc.org](http://godoc.org/github.com/pelletier/go-toml). + +## Tools + +Go-toml provides two handy command line tools: + +* `tomll`: Reads TOML files and lint them. + + ``` + go install github.com/pelletier/go-toml/cmd/tomll + tomll --help + ``` +* `tomljson`: Reads a TOML file and outputs its JSON representation. + + ``` + go install github.com/pelletier/go-toml/cmd/tomljson + tomljson --help + ``` + +## Contribute + +Feel free to report bugs and patches using GitHub's pull requests system on +[pelletier/go-toml](https://github.com/pelletier/go-toml). Any feedback would be +much appreciated! + +### Run tests + +You have to make sure two kind of tests run: + +1. The Go unit tests +2. The TOML examples base + +You can run both of them using `./test.sh`. + +### Fuzzing + +The script `./fuzz.sh` is available to +run [go-fuzz](https://github.com/dvyukov/go-fuzz) on go-toml. + +## Versioning + +Go-toml follows [Semantic Versioning](http://semver.org/). The supported version +of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of +this document. The last two major versions of Go are supported +(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)). + +## License + +The MIT License (MIT). Read [LICENSE](LICENSE). diff --git a/vendor/github.com/pelletier/go-toml/benchmark.json b/vendor/github.com/pelletier/go-toml/benchmark.json new file mode 100644 index 0000000..86f99c6 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/benchmark.json @@ -0,0 +1,164 @@ +{ + "array": { + "key1": [ + 1, + 2, + 3 + ], + "key2": [ + "red", + "yellow", + "green" + ], + "key3": [ + [ + 1, + 2 + ], + [ + 3, + 4, + 5 + ] + ], + "key4": [ + [ + 1, + 2 + ], + [ + "a", + "b", + "c" + ] + ], + "key5": [ + 1, + 2, + 3 + ], + "key6": [ + 1, + 2 + ] + }, + "boolean": { + "False": false, + "True": true + }, + "datetime": { + "key1": "1979-05-27T07:32:00Z", + "key2": "1979-05-27T00:32:00-07:00", + "key3": "1979-05-27T00:32:00.999999-07:00" + }, + "float": { + "both": { + "key": 6.626e-34 + }, + "exponent": { + "key1": 5e+22, + "key2": 1000000, + "key3": -0.02 + }, + "fractional": { + "key1": 1, + "key2": 3.1415, + "key3": -0.01 + }, + "underscores": { + "key1": 9224617.445991227, + "key2": 1e+100 + } + }, + "fruit": [{ + "name": "apple", + "physical": { + "color": "red", + "shape": "round" + }, + "variety": [{ + "name": "red delicious" + }, + { + "name": "granny smith" + } + ] + }, + { + "name": "banana", + "variety": [{ + "name": "plantain" + }] + } + ], + "integer": { + "key1": 99, + "key2": 42, + "key3": 0, + "key4": -17, + "underscores": { + "key1": 1000, + "key2": 5349221, + "key3": 12345 + } + }, + "products": [{ + "name": "Hammer", + "sku": 738594937 + }, + {}, + { + "color": "gray", + "name": "Nail", + "sku": 284758393 + } + ], + "string": { + "basic": { + "basic": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." + }, + "literal": { + "multiline": { + "lines": "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", + "regex2": "I [dw]on't need \\d{2} apples" + }, + "quoted": "Tom \"Dubs\" Preston-Werner", + "regex": "\u003c\\i\\c*\\s*\u003e", + "winpath": "C:\\Users\\nodejs\\templates", + "winpath2": "\\\\ServerX\\admin$\\system32\\" + }, + "multiline": { + "continued": { + "key1": "The quick brown fox jumps over the lazy dog.", + "key2": "The quick brown fox jumps over the lazy dog.", + "key3": "The quick brown fox jumps over the lazy dog." + }, + "key1": "One\nTwo", + "key2": "One\nTwo", + "key3": "One\nTwo" + } + }, + "table": { + "inline": { + "name": { + "first": "Tom", + "last": "Preston-Werner" + }, + "point": { + "x": 1, + "y": 2 + } + }, + "key": "value", + "subtable": { + "key": "another value" + } + }, + "x": { + "y": { + "z": { + "w": {} + } + } + } +} diff --git a/vendor/github.com/pelletier/go-toml/benchmark.sh b/vendor/github.com/pelletier/go-toml/benchmark.sh new file mode 100644 index 0000000..8b8bb52 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/benchmark.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +reference_ref=${1:-master} +reference_git=${2:-.} + +if ! `hash benchstat 2>/dev/null`; then + echo "Installing benchstat" + go get golang.org/x/perf/cmd/benchstat + go install golang.org/x/perf/cmd/benchstat +fi + +tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX` +ref_tempdir="${tempdir}/ref" +ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt" +local_benchmark="`pwd`/benchmark-local.txt" + +echo "=== ${reference_ref} (${ref_tempdir})" +git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null +pushd ${ref_tempdir} >/dev/null +git checkout ${reference_ref} >/dev/null 2>/dev/null +go test -bench=. -benchmem | tee ${ref_benchmark} +popd >/dev/null + +echo "" +echo "=== local" +go test -bench=. -benchmem | tee ${local_benchmark} + +echo "" +echo "=== diff" +benchstat -delta-test=none ${ref_benchmark} ${local_benchmark} \ No newline at end of file diff --git a/vendor/github.com/pelletier/go-toml/benchmark.toml b/vendor/github.com/pelletier/go-toml/benchmark.toml new file mode 100644 index 0000000..dfd77e0 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/benchmark.toml @@ -0,0 +1,244 @@ +################################################################################ +## Comment + +# Speak your mind with the hash symbol. They go from the symbol to the end of +# the line. + + +################################################################################ +## Table + +# Tables (also known as hash tables or dictionaries) are collections of +# key/value pairs. They appear in square brackets on a line by themselves. + +[table] + +key = "value" # Yeah, you can do this. + +# Nested tables are denoted by table names with dots in them. Name your tables +# whatever crap you please, just don't use #, ., [ or ]. + +[table.subtable] + +key = "another value" + +# You don't need to specify all the super-tables if you don't want to. TOML +# knows how to do it for you. + +# [x] you +# [x.y] don't +# [x.y.z] need these +[x.y.z.w] # for this to work + + +################################################################################ +## Inline Table + +# Inline tables provide a more compact syntax for expressing tables. They are +# especially useful for grouped data that can otherwise quickly become verbose. +# Inline tables are enclosed in curly braces `{` and `}`. No newlines are +# allowed between the curly braces unless they are valid within a value. + +[table.inline] + +name = { first = "Tom", last = "Preston-Werner" } +point = { x = 1, y = 2 } + + +################################################################################ +## String + +# There are four ways to express strings: basic, multi-line basic, literal, and +# multi-line literal. All strings must contain only valid UTF-8 characters. + +[string.basic] + +basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." + +[string.multiline] + +# The following strings are byte-for-byte equivalent: +key1 = "One\nTwo" +key2 = """One\nTwo""" +key3 = """ +One +Two""" + +[string.multiline.continued] + +# The following strings are byte-for-byte equivalent: +key1 = "The quick brown fox jumps over the lazy dog." + +key2 = """ +The quick brown \ + + + fox jumps over \ + the lazy dog.""" + +key3 = """\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + """ + +[string.literal] + +# What you see is what you get. +winpath = 'C:\Users\nodejs\templates' +winpath2 = '\\ServerX\admin$\system32\' +quoted = 'Tom "Dubs" Preston-Werner' +regex = '<\i\c*\s*>' + + +[string.literal.multiline] + +regex2 = '''I [dw]on't need \d{2} apples''' +lines = ''' +The first newline is +trimmed in raw strings. + All other whitespace + is preserved. +''' + + +################################################################################ +## Integer + +# Integers are whole numbers. Positive numbers may be prefixed with a plus sign. +# Negative numbers are prefixed with a minus sign. + +[integer] + +key1 = +99 +key2 = 42 +key3 = 0 +key4 = -17 + +[integer.underscores] + +# For large numbers, you may use underscores to enhance readability. Each +# underscore must be surrounded by at least one digit. +key1 = 1_000 +key2 = 5_349_221 +key3 = 1_2_3_4_5 # valid but inadvisable + + +################################################################################ +## Float + +# A float consists of an integer part (which may be prefixed with a plus or +# minus sign) followed by a fractional part and/or an exponent part. + +[float.fractional] + +key1 = +1.0 +key2 = 3.1415 +key3 = -0.01 + +[float.exponent] + +key1 = 5e+22 +key2 = 1e6 +key3 = -2E-2 + +[float.both] + +key = 6.626e-34 + +[float.underscores] + +key1 = 9_224_617.445_991_228_313 +key2 = 1e1_00 + + +################################################################################ +## Boolean + +# Booleans are just the tokens you're used to. Always lowercase. + +[boolean] + +True = true +False = false + + +################################################################################ +## Datetime + +# Datetimes are RFC 3339 dates. + +[datetime] + +key1 = 1979-05-27T07:32:00Z +key2 = 1979-05-27T00:32:00-07:00 +key3 = 1979-05-27T00:32:00.999999-07:00 + + +################################################################################ +## Array + +# Arrays are square brackets with other primitives inside. Whitespace is +# ignored. Elements are separated by commas. Data types may not be mixed. + +[array] + +key1 = [ 1, 2, 3 ] +key2 = [ "red", "yellow", "green" ] +key3 = [ [ 1, 2 ], [3, 4, 5] ] +#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok + +# Arrays can also be multiline. So in addition to ignoring whitespace, arrays +# also ignore newlines between the brackets. Terminating commas are ok before +# the closing bracket. + +key5 = [ + 1, 2, 3 +] +key6 = [ + 1, + 2, # this is ok +] + + +################################################################################ +## Array of Tables + +# These can be expressed by using a table name in double brackets. Each table +# with the same double bracketed name will be an element in the array. The +# tables are inserted in the order encountered. + +[[products]] + +name = "Hammer" +sku = 738594937 + +[[products]] + +[[products]] + +name = "Nail" +sku = 284758393 +color = "gray" + + +# You can create nested arrays of tables as well. + +[[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + +[[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" diff --git a/vendor/github.com/pelletier/go-toml/benchmark.yml b/vendor/github.com/pelletier/go-toml/benchmark.yml new file mode 100644 index 0000000..0bd19f0 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/benchmark.yml @@ -0,0 +1,121 @@ +--- +array: + key1: + - 1 + - 2 + - 3 + key2: + - red + - yellow + - green + key3: + - - 1 + - 2 + - - 3 + - 4 + - 5 + key4: + - - 1 + - 2 + - - a + - b + - c + key5: + - 1 + - 2 + - 3 + key6: + - 1 + - 2 +boolean: + 'False': false + 'True': true +datetime: + key1: '1979-05-27T07:32:00Z' + key2: '1979-05-27T00:32:00-07:00' + key3: '1979-05-27T00:32:00.999999-07:00' +float: + both: + key: 6.626e-34 + exponent: + key1: 5.0e+22 + key2: 1000000 + key3: -0.02 + fractional: + key1: 1 + key2: 3.1415 + key3: -0.01 + underscores: + key1: 9224617.445991227 + key2: 1.0e+100 +fruit: +- name: apple + physical: + color: red + shape: round + variety: + - name: red delicious + - name: granny smith +- name: banana + variety: + - name: plantain +integer: + key1: 99 + key2: 42 + key3: 0 + key4: -17 + underscores: + key1: 1000 + key2: 5349221 + key3: 12345 +products: +- name: Hammer + sku: 738594937 +- {} +- color: gray + name: Nail + sku: 284758393 +string: + basic: + basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." + literal: + multiline: + lines: | + The first newline is + trimmed in raw strings. + All other whitespace + is preserved. + regex2: I [dw]on't need \d{2} apples + quoted: Tom "Dubs" Preston-Werner + regex: "<\\i\\c*\\s*>" + winpath: C:\Users\nodejs\templates + winpath2: "\\\\ServerX\\admin$\\system32\\" + multiline: + continued: + key1: The quick brown fox jumps over the lazy dog. + key2: The quick brown fox jumps over the lazy dog. + key3: The quick brown fox jumps over the lazy dog. + key1: |- + One + Two + key2: |- + One + Two + key3: |- + One + Two +table: + inline: + name: + first: Tom + last: Preston-Werner + point: + x: 1 + y: 2 + key: value + subtable: + key: another value +x: + y: + z: + w: {} diff --git a/vendor/github.com/pelletier/go-toml/doc.go b/vendor/github.com/pelletier/go-toml/doc.go new file mode 100644 index 0000000..d5fd98c --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/doc.go @@ -0,0 +1,23 @@ +// Package toml is a TOML parser and manipulation library. +// +// This version supports the specification as described in +// https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md +// +// Marshaling +// +// Go-toml can marshal and unmarshal TOML documents from and to data +// structures. +// +// TOML document as a tree +// +// Go-toml can operate on a TOML document as a tree. Use one of the Load* +// functions to parse TOML data and obtain a Tree instance, then one of its +// methods to manipulate the tree. +// +// JSONPath-like queries +// +// The package github.com/pelletier/go-toml/query implements a system +// similar to JSONPath to quickly retrieve elements of a TOML document using a +// single expression. See the package documentation for more information. +// +package toml diff --git a/vendor/github.com/pelletier/go-toml/example-crlf.toml b/vendor/github.com/pelletier/go-toml/example-crlf.toml new file mode 100644 index 0000000..3d902f2 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/example-crlf.toml @@ -0,0 +1,29 @@ +# This is a TOML document. Boom. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # First class dates? Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it diff --git a/vendor/github.com/pelletier/go-toml/example.toml b/vendor/github.com/pelletier/go-toml/example.toml new file mode 100644 index 0000000..3d902f2 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/example.toml @@ -0,0 +1,29 @@ +# This is a TOML document. Boom. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # First class dates? Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it diff --git a/vendor/github.com/pelletier/go-toml/fuzz.go b/vendor/github.com/pelletier/go-toml/fuzz.go new file mode 100644 index 0000000..14570c8 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/fuzz.go @@ -0,0 +1,31 @@ +// +build gofuzz + +package toml + +func Fuzz(data []byte) int { + tree, err := LoadBytes(data) + if err != nil { + if tree != nil { + panic("tree must be nil if there is an error") + } + return 0 + } + + str, err := tree.ToTomlString() + if err != nil { + if str != "" { + panic(`str must be "" if there is an error`) + } + panic(err) + } + + tree, err = Load(str) + if err != nil { + if tree != nil { + panic("tree must be nil if there is an error") + } + return 0 + } + + return 1 +} diff --git a/vendor/github.com/pelletier/go-toml/fuzz.sh b/vendor/github.com/pelletier/go-toml/fuzz.sh new file mode 100644 index 0000000..3204b4c --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/fuzz.sh @@ -0,0 +1,15 @@ +#! /bin/sh +set -eu + +go get github.com/dvyukov/go-fuzz/go-fuzz +go get github.com/dvyukov/go-fuzz/go-fuzz-build + +if [ ! -e toml-fuzz.zip ]; then + go-fuzz-build github.com/pelletier/go-toml +fi + +rm -fr fuzz +mkdir -p fuzz/corpus +cp *.toml fuzz/corpus + +go-fuzz -bin=toml-fuzz.zip -workdir=fuzz diff --git a/vendor/github.com/pelletier/go-toml/keysparsing.go b/vendor/github.com/pelletier/go-toml/keysparsing.go new file mode 100644 index 0000000..284db64 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/keysparsing.go @@ -0,0 +1,85 @@ +// Parsing keys handling both bare and quoted keys. + +package toml + +import ( + "bytes" + "errors" + "fmt" + "unicode" +) + +// Convert the bare key group string to an array. +// The input supports double quotation to allow "." inside the key name, +// but escape sequences are not supported. Lexers must unescape them beforehand. +func parseKey(key string) ([]string, error) { + groups := []string{} + var buffer bytes.Buffer + inQuotes := false + wasInQuotes := false + ignoreSpace := true + expectDot := false + + for _, char := range key { + if ignoreSpace { + if char == ' ' { + continue + } + ignoreSpace = false + } + switch char { + case '"': + if inQuotes { + groups = append(groups, buffer.String()) + buffer.Reset() + wasInQuotes = true + } + inQuotes = !inQuotes + expectDot = false + case '.': + if inQuotes { + buffer.WriteRune(char) + } else { + if !wasInQuotes { + if buffer.Len() == 0 { + return nil, errors.New("empty table key") + } + groups = append(groups, buffer.String()) + buffer.Reset() + } + ignoreSpace = true + expectDot = false + wasInQuotes = false + } + case ' ': + if inQuotes { + buffer.WriteRune(char) + } else { + expectDot = true + } + default: + if !inQuotes && !isValidBareChar(char) { + return nil, fmt.Errorf("invalid bare character: %c", char) + } + if !inQuotes && expectDot { + return nil, errors.New("what?") + } + buffer.WriteRune(char) + expectDot = false + } + } + if inQuotes { + return nil, errors.New("mismatched quotes") + } + if buffer.Len() > 0 { + groups = append(groups, buffer.String()) + } + if len(groups) == 0 { + return nil, errors.New("empty key") + } + return groups, nil +} + +func isValidBareChar(r rune) bool { + return isAlphanumeric(r) || r == '-' || unicode.IsNumber(r) +} diff --git a/vendor/github.com/pelletier/go-toml/lexer.go b/vendor/github.com/pelletier/go-toml/lexer.go new file mode 100644 index 0000000..d11de42 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/lexer.go @@ -0,0 +1,750 @@ +// TOML lexer. +// +// Written using the principles developed by Rob Pike in +// http://www.youtube.com/watch?v=HxaD_trXwRE + +package toml + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +var dateRegexp *regexp.Regexp + +// Define state functions +type tomlLexStateFn func() tomlLexStateFn + +// Define lexer +type tomlLexer struct { + inputIdx int + input []rune // Textual source + currentTokenStart int + currentTokenStop int + tokens []token + depth int + line int + col int + endbufferLine int + endbufferCol int +} + +// Basic read operations on input + +func (l *tomlLexer) read() rune { + r := l.peek() + if r == '\n' { + l.endbufferLine++ + l.endbufferCol = 1 + } else { + l.endbufferCol++ + } + l.inputIdx++ + return r +} + +func (l *tomlLexer) next() rune { + r := l.read() + + if r != eof { + l.currentTokenStop++ + } + return r +} + +func (l *tomlLexer) ignore() { + l.currentTokenStart = l.currentTokenStop + l.line = l.endbufferLine + l.col = l.endbufferCol +} + +func (l *tomlLexer) skip() { + l.next() + l.ignore() +} + +func (l *tomlLexer) fastForward(n int) { + for i := 0; i < n; i++ { + l.next() + } +} + +func (l *tomlLexer) emitWithValue(t tokenType, value string) { + l.tokens = append(l.tokens, token{ + Position: Position{l.line, l.col}, + typ: t, + val: value, + }) + l.ignore() +} + +func (l *tomlLexer) emit(t tokenType) { + l.emitWithValue(t, string(l.input[l.currentTokenStart:l.currentTokenStop])) +} + +func (l *tomlLexer) peek() rune { + if l.inputIdx >= len(l.input) { + return eof + } + return l.input[l.inputIdx] +} + +func (l *tomlLexer) peekString(size int) string { + maxIdx := len(l.input) + upperIdx := l.inputIdx + size // FIXME: potential overflow + if upperIdx > maxIdx { + upperIdx = maxIdx + } + return string(l.input[l.inputIdx:upperIdx]) +} + +func (l *tomlLexer) follow(next string) bool { + return next == l.peekString(len(next)) +} + +// Error management + +func (l *tomlLexer) errorf(format string, args ...interface{}) tomlLexStateFn { + l.tokens = append(l.tokens, token{ + Position: Position{l.line, l.col}, + typ: tokenError, + val: fmt.Sprintf(format, args...), + }) + return nil +} + +// State functions + +func (l *tomlLexer) lexVoid() tomlLexStateFn { + for { + next := l.peek() + switch next { + case '[': + return l.lexTableKey + case '#': + return l.lexComment(l.lexVoid) + case '=': + return l.lexEqual + case '\r': + fallthrough + case '\n': + l.skip() + continue + } + + if isSpace(next) { + l.skip() + } + + if l.depth > 0 { + return l.lexRvalue + } + + if isKeyStartChar(next) { + return l.lexKey + } + + if next == eof { + l.next() + break + } + } + + l.emit(tokenEOF) + return nil +} + +func (l *tomlLexer) lexRvalue() tomlLexStateFn { + for { + next := l.peek() + switch next { + case '.': + return l.errorf("cannot start float with a dot") + case '=': + return l.lexEqual + case '[': + l.depth++ + return l.lexLeftBracket + case ']': + l.depth-- + return l.lexRightBracket + case '{': + return l.lexLeftCurlyBrace + case '}': + return l.lexRightCurlyBrace + case '#': + return l.lexComment(l.lexRvalue) + case '"': + return l.lexString + case '\'': + return l.lexLiteralString + case ',': + return l.lexComma + case '\r': + fallthrough + case '\n': + l.skip() + if l.depth == 0 { + return l.lexVoid + } + return l.lexRvalue + case '_': + return l.errorf("cannot start number with underscore") + } + + if l.follow("true") { + return l.lexTrue + } + + if l.follow("false") { + return l.lexFalse + } + + if l.follow("inf") { + return l.lexInf + } + + if l.follow("nan") { + return l.lexNan + } + + if isSpace(next) { + l.skip() + continue + } + + if next == eof { + l.next() + break + } + + possibleDate := l.peekString(35) + dateMatch := dateRegexp.FindString(possibleDate) + if dateMatch != "" { + l.fastForward(len(dateMatch)) + return l.lexDate + } + + if next == '+' || next == '-' || isDigit(next) { + return l.lexNumber + } + + if isAlphanumeric(next) { + return l.lexKey + } + + return l.errorf("no value can start with %c", next) + } + + l.emit(tokenEOF) + return nil +} + +func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn { + l.next() + l.emit(tokenLeftCurlyBrace) + return l.lexRvalue +} + +func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn { + l.next() + l.emit(tokenRightCurlyBrace) + return l.lexRvalue +} + +func (l *tomlLexer) lexDate() tomlLexStateFn { + l.emit(tokenDate) + return l.lexRvalue +} + +func (l *tomlLexer) lexTrue() tomlLexStateFn { + l.fastForward(4) + l.emit(tokenTrue) + return l.lexRvalue +} + +func (l *tomlLexer) lexFalse() tomlLexStateFn { + l.fastForward(5) + l.emit(tokenFalse) + return l.lexRvalue +} + +func (l *tomlLexer) lexInf() tomlLexStateFn { + l.fastForward(3) + l.emit(tokenInf) + return l.lexRvalue +} + +func (l *tomlLexer) lexNan() tomlLexStateFn { + l.fastForward(3) + l.emit(tokenNan) + return l.lexRvalue +} + +func (l *tomlLexer) lexEqual() tomlLexStateFn { + l.next() + l.emit(tokenEqual) + return l.lexRvalue +} + +func (l *tomlLexer) lexComma() tomlLexStateFn { + l.next() + l.emit(tokenComma) + return l.lexRvalue +} + +// Parse the key and emits its value without escape sequences. +// bare keys, basic string keys and literal string keys are supported. +func (l *tomlLexer) lexKey() tomlLexStateFn { + growingString := "" + + for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() { + if r == '"' { + l.next() + str, err := l.lexStringAsString(`"`, false, true) + if err != nil { + return l.errorf(err.Error()) + } + growingString += str + l.next() + continue + } else if r == '\'' { + l.next() + str, err := l.lexLiteralStringAsString(`'`, false) + if err != nil { + return l.errorf(err.Error()) + } + growingString += str + l.next() + continue + } else if r == '\n' { + return l.errorf("keys cannot contain new lines") + } else if isSpace(r) { + break + } else if !isValidBareChar(r) { + return l.errorf("keys cannot contain %c character", r) + } + growingString += string(r) + l.next() + } + l.emitWithValue(tokenKey, growingString) + return l.lexVoid +} + +func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn { + return func() tomlLexStateFn { + for next := l.peek(); next != '\n' && next != eof; next = l.peek() { + if next == '\r' && l.follow("\r\n") { + break + } + l.next() + } + l.ignore() + return previousState + } +} + +func (l *tomlLexer) lexLeftBracket() tomlLexStateFn { + l.next() + l.emit(tokenLeftBracket) + return l.lexRvalue +} + +func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) { + growingString := "" + + if discardLeadingNewLine { + if l.follow("\r\n") { + l.skip() + l.skip() + } else if l.peek() == '\n' { + l.skip() + } + } + + // find end of string + for { + if l.follow(terminator) { + return growingString, nil + } + + next := l.peek() + if next == eof { + break + } + growingString += string(l.next()) + } + + return "", errors.New("unclosed string") +} + +func (l *tomlLexer) lexLiteralString() tomlLexStateFn { + l.skip() + + // handle special case for triple-quote + terminator := "'" + discardLeadingNewLine := false + if l.follow("''") { + l.skip() + l.skip() + terminator = "'''" + discardLeadingNewLine = true + } + + str, err := l.lexLiteralStringAsString(terminator, discardLeadingNewLine) + if err != nil { + return l.errorf(err.Error()) + } + + l.emitWithValue(tokenString, str) + l.fastForward(len(terminator)) + l.ignore() + return l.lexRvalue +} + +// Lex a string and return the results as a string. +// Terminator is the substring indicating the end of the token. +// The resulting string does not include the terminator. +func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) { + growingString := "" + + if discardLeadingNewLine { + if l.follow("\r\n") { + l.skip() + l.skip() + } else if l.peek() == '\n' { + l.skip() + } + } + + for { + if l.follow(terminator) { + return growingString, nil + } + + if l.follow("\\") { + l.next() + switch l.peek() { + case '\r': + fallthrough + case '\n': + fallthrough + case '\t': + fallthrough + case ' ': + // skip all whitespace chars following backslash + for strings.ContainsRune("\r\n\t ", l.peek()) { + l.next() + } + case '"': + growingString += "\"" + l.next() + case 'n': + growingString += "\n" + l.next() + case 'b': + growingString += "\b" + l.next() + case 'f': + growingString += "\f" + l.next() + case '/': + growingString += "/" + l.next() + case 't': + growingString += "\t" + l.next() + case 'r': + growingString += "\r" + l.next() + case '\\': + growingString += "\\" + l.next() + case 'u': + l.next() + code := "" + for i := 0; i < 4; i++ { + c := l.peek() + if !isHexDigit(c) { + return "", errors.New("unfinished unicode escape") + } + l.next() + code = code + string(c) + } + intcode, err := strconv.ParseInt(code, 16, 32) + if err != nil { + return "", errors.New("invalid unicode escape: \\u" + code) + } + growingString += string(rune(intcode)) + case 'U': + l.next() + code := "" + for i := 0; i < 8; i++ { + c := l.peek() + if !isHexDigit(c) { + return "", errors.New("unfinished unicode escape") + } + l.next() + code = code + string(c) + } + intcode, err := strconv.ParseInt(code, 16, 64) + if err != nil { + return "", errors.New("invalid unicode escape: \\U" + code) + } + growingString += string(rune(intcode)) + default: + return "", errors.New("invalid escape sequence: \\" + string(l.peek())) + } + } else { + r := l.peek() + + if 0x00 <= r && r <= 0x1F && !(acceptNewLines && (r == '\n' || r == '\r')) { + return "", fmt.Errorf("unescaped control character %U", r) + } + l.next() + growingString += string(r) + } + + if l.peek() == eof { + break + } + } + + return "", errors.New("unclosed string") +} + +func (l *tomlLexer) lexString() tomlLexStateFn { + l.skip() + + // handle special case for triple-quote + terminator := `"` + discardLeadingNewLine := false + acceptNewLines := false + if l.follow(`""`) { + l.skip() + l.skip() + terminator = `"""` + discardLeadingNewLine = true + acceptNewLines = true + } + + str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines) + + if err != nil { + return l.errorf(err.Error()) + } + + l.emitWithValue(tokenString, str) + l.fastForward(len(terminator)) + l.ignore() + return l.lexRvalue +} + +func (l *tomlLexer) lexTableKey() tomlLexStateFn { + l.next() + + if l.peek() == '[' { + // token '[[' signifies an array of tables + l.next() + l.emit(tokenDoubleLeftBracket) + return l.lexInsideTableArrayKey + } + // vanilla table key + l.emit(tokenLeftBracket) + return l.lexInsideTableKey +} + +// Parse the key till "]]", but only bare keys are supported +func (l *tomlLexer) lexInsideTableArrayKey() tomlLexStateFn { + for r := l.peek(); r != eof; r = l.peek() { + switch r { + case ']': + if l.currentTokenStop > l.currentTokenStart { + l.emit(tokenKeyGroupArray) + } + l.next() + if l.peek() != ']' { + break + } + l.next() + l.emit(tokenDoubleRightBracket) + return l.lexVoid + case '[': + return l.errorf("table array key cannot contain ']'") + default: + l.next() + } + } + return l.errorf("unclosed table array key") +} + +// Parse the key till "]" but only bare keys are supported +func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn { + for r := l.peek(); r != eof; r = l.peek() { + switch r { + case ']': + if l.currentTokenStop > l.currentTokenStart { + l.emit(tokenKeyGroup) + } + l.next() + l.emit(tokenRightBracket) + return l.lexVoid + case '[': + return l.errorf("table key cannot contain ']'") + default: + l.next() + } + } + return l.errorf("unclosed table key") +} + +func (l *tomlLexer) lexRightBracket() tomlLexStateFn { + l.next() + l.emit(tokenRightBracket) + return l.lexRvalue +} + +type validRuneFn func(r rune) bool + +func isValidHexRune(r rune) bool { + return r >= 'a' && r <= 'f' || + r >= 'A' && r <= 'F' || + r >= '0' && r <= '9' || + r == '_' +} + +func isValidOctalRune(r rune) bool { + return r >= '0' && r <= '7' || r == '_' +} + +func isValidBinaryRune(r rune) bool { + return r == '0' || r == '1' || r == '_' +} + +func (l *tomlLexer) lexNumber() tomlLexStateFn { + r := l.peek() + + if r == '0' { + follow := l.peekString(2) + if len(follow) == 2 { + var isValidRune validRuneFn + switch follow[1] { + case 'x': + isValidRune = isValidHexRune + case 'o': + isValidRune = isValidOctalRune + case 'b': + isValidRune = isValidBinaryRune + default: + if follow[1] >= 'a' && follow[1] <= 'z' || follow[1] >= 'A' && follow[1] <= 'Z' { + return l.errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(follow[1])) + } + } + + if isValidRune != nil { + l.next() + l.next() + digitSeen := false + for { + next := l.peek() + if !isValidRune(next) { + break + } + digitSeen = true + l.next() + } + + if !digitSeen { + return l.errorf("number needs at least one digit") + } + + l.emit(tokenInteger) + + return l.lexRvalue + } + } + } + + if r == '+' || r == '-' { + l.next() + if l.follow("inf") { + return l.lexInf + } + if l.follow("nan") { + return l.lexNan + } + } + + pointSeen := false + expSeen := false + digitSeen := false + for { + next := l.peek() + if next == '.' { + if pointSeen { + return l.errorf("cannot have two dots in one float") + } + l.next() + if !isDigit(l.peek()) { + return l.errorf("float cannot end with a dot") + } + pointSeen = true + } else if next == 'e' || next == 'E' { + expSeen = true + l.next() + r := l.peek() + if r == '+' || r == '-' { + l.next() + } + } else if isDigit(next) { + digitSeen = true + l.next() + } else if next == '_' { + l.next() + } else { + break + } + if pointSeen && !digitSeen { + return l.errorf("cannot start float with a dot") + } + } + + if !digitSeen { + return l.errorf("no digit in that number") + } + if pointSeen || expSeen { + l.emit(tokenFloat) + } else { + l.emit(tokenInteger) + } + return l.lexRvalue +} + +func (l *tomlLexer) run() { + for state := l.lexVoid; state != nil; { + state = state() + } +} + +func init() { + dateRegexp = regexp.MustCompile(`^\d{1,4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})`) +} + +// Entry point +func lexToml(inputBytes []byte) []token { + runes := bytes.Runes(inputBytes) + l := &tomlLexer{ + input: runes, + tokens: make([]token, 0, 256), + line: 1, + col: 1, + endbufferLine: 1, + endbufferCol: 1, + } + l.run() + return l.tokens +} diff --git a/vendor/github.com/pelletier/go-toml/marshal.go b/vendor/github.com/pelletier/go-toml/marshal.go new file mode 100644 index 0000000..671da55 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/marshal.go @@ -0,0 +1,609 @@ +package toml + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" +) + +const tagKeyMultiline = "multiline" + +type tomlOpts struct { + name string + comment string + commented bool + multiline bool + include bool + omitempty bool +} + +type encOpts struct { + quoteMapKeys bool + arraysOneElementPerLine bool +} + +var encOptsDefaults = encOpts{ + quoteMapKeys: false, +} + +var timeType = reflect.TypeOf(time.Time{}) +var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() + +// Check if the given marshall type maps to a Tree primitive +func isPrimitive(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Ptr: + return isPrimitive(mtype.Elem()) + case reflect.Bool: + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Struct: + return mtype == timeType || isCustomMarshaler(mtype) + default: + return false + } +} + +// Check if the given marshall type maps to a Tree slice +func isTreeSlice(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Slice: + return !isOtherSlice(mtype) + default: + return false + } +} + +// Check if the given marshall type maps to a non-Tree slice +func isOtherSlice(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Ptr: + return isOtherSlice(mtype.Elem()) + case reflect.Slice: + return isPrimitive(mtype.Elem()) || isOtherSlice(mtype.Elem()) + default: + return false + } +} + +// Check if the given marshall type maps to a Tree +func isTree(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Map: + return true + case reflect.Struct: + return !isPrimitive(mtype) + default: + return false + } +} + +func isCustomMarshaler(mtype reflect.Type) bool { + return mtype.Implements(marshalerType) +} + +func callCustomMarshaler(mval reflect.Value) ([]byte, error) { + return mval.Interface().(Marshaler).MarshalTOML() +} + +// Marshaler is the interface implemented by types that +// can marshal themselves into valid TOML. +type Marshaler interface { + MarshalTOML() ([]byte, error) +} + +/* +Marshal returns the TOML encoding of v. Behavior is similar to the Go json +encoder, except that there is no concept of a Marshaler interface or MarshalTOML +function for sub-structs, and currently only definite types can be marshaled +(i.e. no `interface{}`). + +The following struct annotations are supported: + + toml:"Field" Overrides the field's name to output. + omitempty When set, empty values and groups are not emitted. + comment:"comment" Emits a # comment on the same line. This supports new lines. + commented:"true" Emits the value as commented. + +Note that pointers are automatically assigned the "omitempty" option, as TOML +explicitly does not handle null values (saying instead the label should be +dropped). + +Tree structural types and corresponding marshal types: + + *Tree (*)struct, (*)map[string]interface{} + []*Tree (*)[](*)struct, (*)[](*)map[string]interface{} + []interface{} (as interface{}) (*)[]primitive, (*)[]([]interface{}) + interface{} (*)primitive + +Tree primitive types and corresponding marshal types: + + uint64 uint, uint8-uint64, pointers to same + int64 int, int8-uint64, pointers to same + float64 float32, float64, pointers to same + string string, pointers to same + bool bool, pointers to same + time.Time time.Time{}, pointers to same +*/ +func Marshal(v interface{}) ([]byte, error) { + return NewEncoder(nil).marshal(v) +} + +// Encoder writes TOML values to an output stream. +type Encoder struct { + w io.Writer + encOpts +} + +// NewEncoder returns a new encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: w, + encOpts: encOptsDefaults, + } +} + +// Encode writes the TOML encoding of v to the stream. +// +// See the documentation for Marshal for details. +func (e *Encoder) Encode(v interface{}) error { + b, err := e.marshal(v) + if err != nil { + return err + } + if _, err := e.w.Write(b); err != nil { + return err + } + return nil +} + +// QuoteMapKeys sets up the encoder to encode +// maps with string type keys with quoted TOML keys. +// +// This relieves the character limitations on map keys. +func (e *Encoder) QuoteMapKeys(v bool) *Encoder { + e.quoteMapKeys = v + return e +} + +// ArraysWithOneElementPerLine sets up the encoder to encode arrays +// with more than one element on multiple lines instead of one. +// +// For example: +// +// A = [1,2,3] +// +// Becomes +// +// A = [ +// 1, +// 2, +// 3, +// ] +func (e *Encoder) ArraysWithOneElementPerLine(v bool) *Encoder { + e.arraysOneElementPerLine = v + return e +} + +func (e *Encoder) marshal(v interface{}) ([]byte, error) { + mtype := reflect.TypeOf(v) + if mtype.Kind() != reflect.Struct { + return []byte{}, errors.New("Only a struct can be marshaled to TOML") + } + sval := reflect.ValueOf(v) + if isCustomMarshaler(mtype) { + return callCustomMarshaler(sval) + } + t, err := e.valueToTree(mtype, sval) + if err != nil { + return []byte{}, err + } + + var buf bytes.Buffer + _, err = t.writeTo(&buf, "", "", 0, e.arraysOneElementPerLine) + + return buf.Bytes(), err +} + +// Convert given marshal struct or map value to toml tree +func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) { + if mtype.Kind() == reflect.Ptr { + return e.valueToTree(mtype.Elem(), mval.Elem()) + } + tval := newTree() + switch mtype.Kind() { + case reflect.Struct: + for i := 0; i < mtype.NumField(); i++ { + mtypef, mvalf := mtype.Field(i), mval.Field(i) + opts := tomlOptions(mtypef) + if opts.include && (!opts.omitempty || !isZero(mvalf)) { + val, err := e.valueToToml(mtypef.Type, mvalf) + if err != nil { + return nil, err + } + + tval.SetWithOptions(opts.name, SetOptions{ + Comment: opts.comment, + Commented: opts.commented, + Multiline: opts.multiline, + }, val) + } + } + case reflect.Map: + for _, key := range mval.MapKeys() { + mvalf := mval.MapIndex(key) + val, err := e.valueToToml(mtype.Elem(), mvalf) + if err != nil { + return nil, err + } + if e.quoteMapKeys { + keyStr, err := tomlValueStringRepresentation(key.String(), "", e.arraysOneElementPerLine) + if err != nil { + return nil, err + } + tval.SetPath([]string{keyStr}, val) + } else { + tval.Set(key.String(), val) + } + } + } + return tval, nil +} + +// Convert given marshal slice to slice of Toml trees +func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*Tree, error) { + tval := make([]*Tree, mval.Len(), mval.Len()) + for i := 0; i < mval.Len(); i++ { + val, err := e.valueToTree(mtype.Elem(), mval.Index(i)) + if err != nil { + return nil, err + } + tval[i] = val + } + return tval, nil +} + +// Convert given marshal slice to slice of toml values +func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { + tval := make([]interface{}, mval.Len(), mval.Len()) + for i := 0; i < mval.Len(); i++ { + val, err := e.valueToToml(mtype.Elem(), mval.Index(i)) + if err != nil { + return nil, err + } + tval[i] = val + } + return tval, nil +} + +// Convert given marshal value to toml value +func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { + if mtype.Kind() == reflect.Ptr { + return e.valueToToml(mtype.Elem(), mval.Elem()) + } + switch { + case isCustomMarshaler(mtype): + return callCustomMarshaler(mval) + case isTree(mtype): + return e.valueToTree(mtype, mval) + case isTreeSlice(mtype): + return e.valueToTreeSlice(mtype, mval) + case isOtherSlice(mtype): + return e.valueToOtherSlice(mtype, mval) + default: + switch mtype.Kind() { + case reflect.Bool: + return mval.Bool(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return mval.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return mval.Uint(), nil + case reflect.Float32, reflect.Float64: + return mval.Float(), nil + case reflect.String: + return mval.String(), nil + case reflect.Struct: + return mval.Interface().(time.Time), nil + default: + return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) + } + } +} + +// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v. +// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for +// sub-structs, and only definite types can be unmarshaled. +func (t *Tree) Unmarshal(v interface{}) error { + d := Decoder{tval: t} + return d.unmarshal(v) +} + +// Marshal returns the TOML encoding of Tree. +// See Marshal() documentation for types mapping table. +func (t *Tree) Marshal() ([]byte, error) { + var buf bytes.Buffer + err := NewEncoder(&buf).Encode(t) + return buf.Bytes(), err +} + +// Unmarshal parses the TOML-encoded data and stores the result in the value +// pointed to by v. Behavior is similar to the Go json encoder, except that there +// is no concept of an Unmarshaler interface or UnmarshalTOML function for +// sub-structs, and currently only definite types can be unmarshaled to (i.e. no +// `interface{}`). +// +// The following struct annotations are supported: +// +// toml:"Field" Overrides the field's name to map to. +// +// See Marshal() documentation for types mapping table. +func Unmarshal(data []byte, v interface{}) error { + t, err := LoadReader(bytes.NewReader(data)) + if err != nil { + return err + } + return t.Unmarshal(v) +} + +// Decoder reads and decodes TOML values from an input stream. +type Decoder struct { + r io.Reader + tval *Tree + encOpts +} + +// NewDecoder returns a new decoder that reads from r. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + r: r, + encOpts: encOptsDefaults, + } +} + +// Decode reads a TOML-encoded value from it's input +// and unmarshals it in the value pointed at by v. +// +// See the documentation for Marshal for details. +func (d *Decoder) Decode(v interface{}) error { + var err error + d.tval, err = LoadReader(d.r) + if err != nil { + return err + } + return d.unmarshal(v) +} + +func (d *Decoder) unmarshal(v interface{}) error { + mtype := reflect.TypeOf(v) + if mtype.Kind() != reflect.Ptr || mtype.Elem().Kind() != reflect.Struct { + return errors.New("Only a pointer to struct can be unmarshaled from TOML") + } + + sval, err := d.valueFromTree(mtype.Elem(), d.tval) + if err != nil { + return err + } + reflect.ValueOf(v).Elem().Set(sval) + return nil +} + +// Convert toml tree to marshal struct or map, using marshal type +func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree) (reflect.Value, error) { + if mtype.Kind() == reflect.Ptr { + return d.unwrapPointer(mtype, tval) + } + var mval reflect.Value + switch mtype.Kind() { + case reflect.Struct: + mval = reflect.New(mtype).Elem() + for i := 0; i < mtype.NumField(); i++ { + mtypef := mtype.Field(i) + opts := tomlOptions(mtypef) + if opts.include { + baseKey := opts.name + keysToTry := []string{baseKey, strings.ToLower(baseKey), strings.ToTitle(baseKey)} + for _, key := range keysToTry { + exists := tval.Has(key) + if !exists { + continue + } + val := tval.Get(key) + mvalf, err := d.valueFromToml(mtypef.Type, val) + if err != nil { + return mval, formatError(err, tval.GetPosition(key)) + } + mval.Field(i).Set(mvalf) + break + } + } + } + case reflect.Map: + mval = reflect.MakeMap(mtype) + for _, key := range tval.Keys() { + // TODO: path splits key + val := tval.GetPath([]string{key}) + mvalf, err := d.valueFromToml(mtype.Elem(), val) + if err != nil { + return mval, formatError(err, tval.GetPosition(key)) + } + mval.SetMapIndex(reflect.ValueOf(key), mvalf) + } + } + return mval, nil +} + +// Convert toml value to marshal struct/map slice, using marshal type +func (d *Decoder) valueFromTreeSlice(mtype reflect.Type, tval []*Tree) (reflect.Value, error) { + mval := reflect.MakeSlice(mtype, len(tval), len(tval)) + for i := 0; i < len(tval); i++ { + val, err := d.valueFromTree(mtype.Elem(), tval[i]) + if err != nil { + return mval, err + } + mval.Index(i).Set(val) + } + return mval, nil +} + +// Convert toml value to marshal primitive slice, using marshal type +func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { + mval := reflect.MakeSlice(mtype, len(tval), len(tval)) + for i := 0; i < len(tval); i++ { + val, err := d.valueFromToml(mtype.Elem(), tval[i]) + if err != nil { + return mval, err + } + mval.Index(i).Set(val) + } + return mval, nil +} + +// Convert toml value to marshal value, using marshal type +func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}) (reflect.Value, error) { + if mtype.Kind() == reflect.Ptr { + return d.unwrapPointer(mtype, tval) + } + + switch tval.(type) { + case *Tree: + if isTree(mtype) { + return d.valueFromTree(mtype, tval.(*Tree)) + } + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval) + case []*Tree: + if isTreeSlice(mtype) { + return d.valueFromTreeSlice(mtype, tval.([]*Tree)) + } + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval) + case []interface{}: + if isOtherSlice(mtype) { + return d.valueFromOtherSlice(mtype, tval.([]interface{})) + } + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) + default: + switch mtype.Kind() { + case reflect.Bool, reflect.Struct: + val := reflect.ValueOf(tval) + // if this passes for when mtype is reflect.Struct, tval is a time.Time + if !val.Type().ConvertibleTo(mtype) { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) + } + + return val.Convert(mtype), nil + case reflect.String: + val := reflect.ValueOf(tval) + // stupidly, int64 is convertible to string. So special case this. + if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) + } + + return val.Convert(mtype), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + val := reflect.ValueOf(tval) + if !val.Type().ConvertibleTo(mtype) { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) + } + if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Int()) { + return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) + } + + return val.Convert(mtype), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + val := reflect.ValueOf(tval) + if !val.Type().ConvertibleTo(mtype) { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) + } + if val.Int() < 0 { + return reflect.ValueOf(nil), fmt.Errorf("%v(%T) is negative so does not fit in %v", tval, tval, mtype.String()) + } + if reflect.Indirect(reflect.New(mtype)).OverflowUint(uint64(val.Int())) { + return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) + } + + return val.Convert(mtype), nil + case reflect.Float32, reflect.Float64: + val := reflect.ValueOf(tval) + if !val.Type().ConvertibleTo(mtype) { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) + } + if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Float()) { + return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) + } + + return val.Convert(mtype), nil + default: + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) + } + } +} + +func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}) (reflect.Value, error) { + val, err := d.valueFromToml(mtype.Elem(), tval) + if err != nil { + return reflect.ValueOf(nil), err + } + mval := reflect.New(mtype.Elem()) + mval.Elem().Set(val) + return mval, nil +} + +func tomlOptions(vf reflect.StructField) tomlOpts { + tag := vf.Tag.Get("toml") + parse := strings.Split(tag, ",") + var comment string + if c := vf.Tag.Get("comment"); c != "" { + comment = c + } + commented, _ := strconv.ParseBool(vf.Tag.Get("commented")) + multiline, _ := strconv.ParseBool(vf.Tag.Get(tagKeyMultiline)) + result := tomlOpts{name: vf.Name, comment: comment, commented: commented, multiline: multiline, include: true, omitempty: false} + if parse[0] != "" { + if parse[0] == "-" && len(parse) == 1 { + result.include = false + } else { + result.name = strings.Trim(parse[0], " ") + } + } + if vf.PkgPath != "" { + result.include = false + } + if len(parse) > 1 && strings.Trim(parse[1], " ") == "omitempty" { + result.omitempty = true + } + if vf.Type.Kind() == reflect.Ptr { + result.omitempty = true + } + return result +} + +func isZero(val reflect.Value) bool { + switch val.Type().Kind() { + case reflect.Map: + fallthrough + case reflect.Array: + fallthrough + case reflect.Slice: + return val.Len() == 0 + default: + return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) + } +} + +func formatError(err error, pos Position) error { + if err.Error()[0] == '(' { // Error already contains position information + return err + } + return fmt.Errorf("%s: %s", pos, err) +} diff --git a/vendor/github.com/pelletier/go-toml/marshal_test.toml b/vendor/github.com/pelletier/go-toml/marshal_test.toml new file mode 100644 index 0000000..1c5f98e --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/marshal_test.toml @@ -0,0 +1,38 @@ +title = "TOML Marshal Testing" + +[basic] + bool = true + date = 1979-05-27T07:32:00Z + float = 123.4 + int = 5000 + string = "Bite me" + uint = 5001 + +[basic_lists] + bools = [true,false,true] + dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] + floats = [12.3,45.6,78.9] + ints = [8001,8001,8002] + strings = ["One","Two","Three"] + uints = [5002,5003] + +[basic_map] + one = "one" + two = "two" + +[subdoc] + + [subdoc.first] + name = "First" + + [subdoc.second] + name = "Second" + +[[subdoclist]] + name = "List.First" + +[[subdoclist]] + name = "List.Second" + +[[subdocptrs]] + name = "Second" diff --git a/vendor/github.com/pelletier/go-toml/parser.go b/vendor/github.com/pelletier/go-toml/parser.go new file mode 100644 index 0000000..2d27599 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/parser.go @@ -0,0 +1,430 @@ +// TOML Parser. + +package toml + +import ( + "errors" + "fmt" + "math" + "reflect" + "regexp" + "strconv" + "strings" + "time" +) + +type tomlParser struct { + flowIdx int + flow []token + tree *Tree + currentTable []string + seenTableKeys []string +} + +type tomlParserStateFn func() tomlParserStateFn + +// Formats and panics an error message based on a token +func (p *tomlParser) raiseError(tok *token, msg string, args ...interface{}) { + panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) +} + +func (p *tomlParser) run() { + for state := p.parseStart; state != nil; { + state = state() + } +} + +func (p *tomlParser) peek() *token { + if p.flowIdx >= len(p.flow) { + return nil + } + return &p.flow[p.flowIdx] +} + +func (p *tomlParser) assume(typ tokenType) { + tok := p.getToken() + if tok == nil { + p.raiseError(tok, "was expecting token %s, but token stream is empty", tok) + } + if tok.typ != typ { + p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok) + } +} + +func (p *tomlParser) getToken() *token { + tok := p.peek() + if tok == nil { + return nil + } + p.flowIdx++ + return tok +} + +func (p *tomlParser) parseStart() tomlParserStateFn { + tok := p.peek() + + // end of stream, parsing is finished + if tok == nil { + return nil + } + + switch tok.typ { + case tokenDoubleLeftBracket: + return p.parseGroupArray + case tokenLeftBracket: + return p.parseGroup + case tokenKey: + return p.parseAssign + case tokenEOF: + return nil + default: + p.raiseError(tok, "unexpected token") + } + return nil +} + +func (p *tomlParser) parseGroupArray() tomlParserStateFn { + startToken := p.getToken() // discard the [[ + key := p.getToken() + if key.typ != tokenKeyGroupArray { + p.raiseError(key, "unexpected token %s, was expecting a table array key", key) + } + + // get or create table array element at the indicated part in the path + keys, err := parseKey(key.val) + if err != nil { + p.raiseError(key, "invalid table array key: %s", err) + } + p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries + destTree := p.tree.GetPath(keys) + var array []*Tree + if destTree == nil { + array = make([]*Tree, 0) + } else if target, ok := destTree.([]*Tree); ok && target != nil { + array = destTree.([]*Tree) + } else { + p.raiseError(key, "key %s is already assigned and not of type table array", key) + } + p.currentTable = keys + + // add a new tree to the end of the table array + newTree := newTree() + newTree.position = startToken.Position + array = append(array, newTree) + p.tree.SetPath(p.currentTable, array) + + // remove all keys that were children of this table array + prefix := key.val + "." + found := false + for ii := 0; ii < len(p.seenTableKeys); { + tableKey := p.seenTableKeys[ii] + if strings.HasPrefix(tableKey, prefix) { + p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...) + } else { + found = (tableKey == key.val) + ii++ + } + } + + // keep this key name from use by other kinds of assignments + if !found { + p.seenTableKeys = append(p.seenTableKeys, key.val) + } + + // move to next parser state + p.assume(tokenDoubleRightBracket) + return p.parseStart +} + +func (p *tomlParser) parseGroup() tomlParserStateFn { + startToken := p.getToken() // discard the [ + key := p.getToken() + if key.typ != tokenKeyGroup { + p.raiseError(key, "unexpected token %s, was expecting a table key", key) + } + for _, item := range p.seenTableKeys { + if item == key.val { + p.raiseError(key, "duplicated tables") + } + } + + p.seenTableKeys = append(p.seenTableKeys, key.val) + keys, err := parseKey(key.val) + if err != nil { + p.raiseError(key, "invalid table array key: %s", err) + } + if err := p.tree.createSubTree(keys, startToken.Position); err != nil { + p.raiseError(key, "%s", err) + } + p.assume(tokenRightBracket) + p.currentTable = keys + return p.parseStart +} + +func (p *tomlParser) parseAssign() tomlParserStateFn { + key := p.getToken() + p.assume(tokenEqual) + + value := p.parseRvalue() + var tableKey []string + if len(p.currentTable) > 0 { + tableKey = p.currentTable + } else { + tableKey = []string{} + } + + // find the table to assign, looking out for arrays of tables + var targetNode *Tree + switch node := p.tree.GetPath(tableKey).(type) { + case []*Tree: + targetNode = node[len(node)-1] + case *Tree: + targetNode = node + default: + p.raiseError(key, "Unknown table type for path: %s", + strings.Join(tableKey, ".")) + } + + // assign value to the found table + keyVals := []string{key.val} + if len(keyVals) != 1 { + p.raiseError(key, "Invalid key") + } + keyVal := keyVals[0] + localKey := []string{keyVal} + finalKey := append(tableKey, keyVal) + if targetNode.GetPath(localKey) != nil { + p.raiseError(key, "The following key was defined twice: %s", + strings.Join(finalKey, ".")) + } + var toInsert interface{} + + switch value.(type) { + case *Tree, []*Tree: + toInsert = value + default: + toInsert = &tomlValue{value: value, position: key.Position} + } + targetNode.values[keyVal] = toInsert + return p.parseStart +} + +var numberUnderscoreInvalidRegexp *regexp.Regexp +var hexNumberUnderscoreInvalidRegexp *regexp.Regexp + +func numberContainsInvalidUnderscore(value string) error { + if numberUnderscoreInvalidRegexp.MatchString(value) { + return errors.New("invalid use of _ in number") + } + return nil +} + +func hexNumberContainsInvalidUnderscore(value string) error { + if hexNumberUnderscoreInvalidRegexp.MatchString(value) { + return errors.New("invalid use of _ in hex number") + } + return nil +} + +func cleanupNumberToken(value string) string { + cleanedVal := strings.Replace(value, "_", "", -1) + return cleanedVal +} + +func (p *tomlParser) parseRvalue() interface{} { + tok := p.getToken() + if tok == nil || tok.typ == tokenEOF { + p.raiseError(tok, "expecting a value") + } + + switch tok.typ { + case tokenString: + return tok.val + case tokenTrue: + return true + case tokenFalse: + return false + case tokenInf: + if tok.val[0] == '-' { + return math.Inf(-1) + } + return math.Inf(1) + case tokenNan: + return math.NaN() + case tokenInteger: + cleanedVal := cleanupNumberToken(tok.val) + var err error + var val int64 + if len(cleanedVal) >= 3 && cleanedVal[0] == '0' { + switch cleanedVal[1] { + case 'x': + err = hexNumberContainsInvalidUnderscore(tok.val) + if err != nil { + p.raiseError(tok, "%s", err) + } + val, err = strconv.ParseInt(cleanedVal[2:], 16, 64) + case 'o': + err = numberContainsInvalidUnderscore(tok.val) + if err != nil { + p.raiseError(tok, "%s", err) + } + val, err = strconv.ParseInt(cleanedVal[2:], 8, 64) + case 'b': + err = numberContainsInvalidUnderscore(tok.val) + if err != nil { + p.raiseError(tok, "%s", err) + } + val, err = strconv.ParseInt(cleanedVal[2:], 2, 64) + default: + panic("invalid base") // the lexer should catch this first + } + } else { + err = numberContainsInvalidUnderscore(tok.val) + if err != nil { + p.raiseError(tok, "%s", err) + } + val, err = strconv.ParseInt(cleanedVal, 10, 64) + } + if err != nil { + p.raiseError(tok, "%s", err) + } + return val + case tokenFloat: + err := numberContainsInvalidUnderscore(tok.val) + if err != nil { + p.raiseError(tok, "%s", err) + } + cleanedVal := cleanupNumberToken(tok.val) + val, err := strconv.ParseFloat(cleanedVal, 64) + if err != nil { + p.raiseError(tok, "%s", err) + } + return val + case tokenDate: + val, err := time.ParseInLocation(time.RFC3339Nano, tok.val, time.UTC) + if err != nil { + p.raiseError(tok, "%s", err) + } + return val + case tokenLeftBracket: + return p.parseArray() + case tokenLeftCurlyBrace: + return p.parseInlineTable() + case tokenEqual: + p.raiseError(tok, "cannot have multiple equals for the same key") + case tokenError: + p.raiseError(tok, "%s", tok) + } + + p.raiseError(tok, "never reached") + + return nil +} + +func tokenIsComma(t *token) bool { + return t != nil && t.typ == tokenComma +} + +func (p *tomlParser) parseInlineTable() *Tree { + tree := newTree() + var previous *token +Loop: + for { + follow := p.peek() + if follow == nil || follow.typ == tokenEOF { + p.raiseError(follow, "unterminated inline table") + } + switch follow.typ { + case tokenRightCurlyBrace: + p.getToken() + break Loop + case tokenKey: + if !tokenIsComma(previous) && previous != nil { + p.raiseError(follow, "comma expected between fields in inline table") + } + key := p.getToken() + p.assume(tokenEqual) + value := p.parseRvalue() + tree.Set(key.val, value) + case tokenComma: + if previous == nil { + p.raiseError(follow, "inline table cannot start with a comma") + } + if tokenIsComma(previous) { + p.raiseError(follow, "need field between two commas in inline table") + } + p.getToken() + default: + p.raiseError(follow, "unexpected token type in inline table: %s", follow.String()) + } + previous = follow + } + if tokenIsComma(previous) { + p.raiseError(previous, "trailing comma at the end of inline table") + } + return tree +} + +func (p *tomlParser) parseArray() interface{} { + var array []interface{} + arrayType := reflect.TypeOf(nil) + for { + follow := p.peek() + if follow == nil || follow.typ == tokenEOF { + p.raiseError(follow, "unterminated array") + } + if follow.typ == tokenRightBracket { + p.getToken() + break + } + val := p.parseRvalue() + if arrayType == nil { + arrayType = reflect.TypeOf(val) + } + if reflect.TypeOf(val) != arrayType { + p.raiseError(follow, "mixed types in array") + } + array = append(array, val) + follow = p.peek() + if follow == nil || follow.typ == tokenEOF { + p.raiseError(follow, "unterminated array") + } + if follow.typ != tokenRightBracket && follow.typ != tokenComma { + p.raiseError(follow, "missing comma") + } + if follow.typ == tokenComma { + p.getToken() + } + } + // An array of Trees is actually an array of inline + // tables, which is a shorthand for a table array. If the + // array was not converted from []interface{} to []*Tree, + // the two notations would not be equivalent. + if arrayType == reflect.TypeOf(newTree()) { + tomlArray := make([]*Tree, len(array)) + for i, v := range array { + tomlArray[i] = v.(*Tree) + } + return tomlArray + } + return array +} + +func parseToml(flow []token) *Tree { + result := newTree() + result.position = Position{1, 1} + parser := &tomlParser{ + flowIdx: 0, + flow: flow, + tree: result, + currentTable: make([]string, 0), + seenTableKeys: make([]string, 0), + } + parser.run() + return result +} + +func init() { + numberUnderscoreInvalidRegexp = regexp.MustCompile(`([^\d]_|_[^\d])|_$|^_`) + hexNumberUnderscoreInvalidRegexp = regexp.MustCompile(`(^0x_)|([^\da-f]_|_[^\da-f])|_$|^_`) +} diff --git a/vendor/github.com/pelletier/go-toml/position.go b/vendor/github.com/pelletier/go-toml/position.go new file mode 100644 index 0000000..c17bff8 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/position.go @@ -0,0 +1,29 @@ +// Position support for go-toml + +package toml + +import ( + "fmt" +) + +// Position of a document element within a TOML document. +// +// Line and Col are both 1-indexed positions for the element's line number and +// column number, respectively. Values of zero or less will cause Invalid(), +// to return true. +type Position struct { + Line int // line within the document + Col int // column within the line +} + +// String representation of the position. +// Displays 1-indexed line and column numbers. +func (p Position) String() string { + return fmt.Sprintf("(%d, %d)", p.Line, p.Col) +} + +// Invalid returns whether or not the position is valid (i.e. with negative or +// null values) +func (p Position) Invalid() bool { + return p.Line <= 0 || p.Col <= 0 +} diff --git a/vendor/github.com/pelletier/go-toml/test.sh b/vendor/github.com/pelletier/go-toml/test.sh new file mode 100644 index 0000000..ba6adf3 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/test.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# fail out of the script if anything here fails +set -e +set -o pipefail + +# set the path to the present working directory +export GOPATH=`pwd` + +function git_clone() { + path=$1 + branch=$2 + version=$3 + if [ ! -d "src/$path" ]; then + mkdir -p src/$path + git clone https://$path.git src/$path + fi + pushd src/$path + git checkout "$branch" + git reset --hard "$version" + popd +} + +# Remove potential previous runs +rm -rf src test_program_bin toml-test + +go get github.com/pelletier/go-buffruneio +go get github.com/davecgh/go-spew/spew +go get gopkg.in/yaml.v2 +go get github.com/BurntSushi/toml + +# get code for BurntSushi TOML validation +# pinning all to 'HEAD' for version 0.3.x work (TODO: pin to commit hash when tests stabilize) +git_clone github.com/BurntSushi/toml master HEAD +git_clone github.com/BurntSushi/toml-test master HEAD #was: 0.2.0 HEAD + +# build the BurntSushi test application +go build -o toml-test github.com/BurntSushi/toml-test + +# vendorize the current lib for testing +# NOTE: this basically mocks an install without having to go back out to github for code +mkdir -p src/github.com/pelletier/go-toml/cmd +mkdir -p src/github.com/pelletier/go-toml/query +cp *.go *.toml src/github.com/pelletier/go-toml +cp -R cmd/* src/github.com/pelletier/go-toml/cmd +cp -R query/* src/github.com/pelletier/go-toml/query +go build -o test_program_bin src/github.com/pelletier/go-toml/cmd/test_program.go + +# Run basic unit tests +go test github.com/pelletier/go-toml -covermode=count -coverprofile=coverage.out +go test github.com/pelletier/go-toml/cmd/tomljson +go test github.com/pelletier/go-toml/query + +# run the entire BurntSushi test suite +if [[ $# -eq 0 ]] ; then + echo "Running all BurntSushi tests" + ./toml-test ./test_program_bin | tee test_out +else + # run a specific test + test=$1 + test_path='src/github.com/BurntSushi/toml-test/tests' + valid_test="$test_path/valid/$test" + invalid_test="$test_path/invalid/$test" + + if [ -e "$valid_test.toml" ]; then + echo "Valid Test TOML for $test:" + echo "====" + cat "$valid_test.toml" + + echo "Valid Test JSON for $test:" + echo "====" + cat "$valid_test.json" + + echo "Go-TOML Output for $test:" + echo "====" + cat "$valid_test.toml" | ./test_program_bin + fi + + if [ -e "$invalid_test.toml" ]; then + echo "Invalid Test TOML for $test:" + echo "====" + cat "$invalid_test.toml" + + echo "Go-TOML Output for $test:" + echo "====" + echo "go-toml Output:" + cat "$invalid_test.toml" | ./test_program_bin + fi +fi diff --git a/vendor/github.com/pelletier/go-toml/token.go b/vendor/github.com/pelletier/go-toml/token.go new file mode 100644 index 0000000..1a90813 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/token.go @@ -0,0 +1,144 @@ +package toml + +import ( + "fmt" + "strconv" + "unicode" +) + +// Define tokens +type tokenType int + +const ( + eof = -(iota + 1) +) + +const ( + tokenError tokenType = iota + tokenEOF + tokenComment + tokenKey + tokenString + tokenInteger + tokenTrue + tokenFalse + tokenFloat + tokenInf + tokenNan + tokenEqual + tokenLeftBracket + tokenRightBracket + tokenLeftCurlyBrace + tokenRightCurlyBrace + tokenLeftParen + tokenRightParen + tokenDoubleLeftBracket + tokenDoubleRightBracket + tokenDate + tokenKeyGroup + tokenKeyGroupArray + tokenComma + tokenColon + tokenDollar + tokenStar + tokenQuestion + tokenDot + tokenDotDot + tokenEOL +) + +var tokenTypeNames = []string{ + "Error", + "EOF", + "Comment", + "Key", + "String", + "Integer", + "True", + "False", + "Float", + "Inf", + "NaN", + "=", + "[", + "]", + "{", + "}", + "(", + ")", + "]]", + "[[", + "Date", + "KeyGroup", + "KeyGroupArray", + ",", + ":", + "$", + "*", + "?", + ".", + "..", + "EOL", +} + +type token struct { + Position + typ tokenType + val string +} + +func (tt tokenType) String() string { + idx := int(tt) + if idx < len(tokenTypeNames) { + return tokenTypeNames[idx] + } + return "Unknown" +} + +func (t token) Int() int { + if result, err := strconv.Atoi(t.val); err != nil { + panic(err) + } else { + return result + } +} + +func (t token) String() string { + switch t.typ { + case tokenEOF: + return "EOF" + case tokenError: + return t.val + } + + return fmt.Sprintf("%q", t.val) +} + +func isSpace(r rune) bool { + return r == ' ' || r == '\t' +} + +func isAlphanumeric(r rune) bool { + return unicode.IsLetter(r) || r == '_' +} + +func isKeyChar(r rune) bool { + // Keys start with the first character that isn't whitespace or [ and end + // with the last non-whitespace character before the equals sign. Keys + // cannot contain a # character." + return !(r == '\r' || r == '\n' || r == eof || r == '=') +} + +func isKeyStartChar(r rune) bool { + return !(isSpace(r) || r == '\r' || r == '\n' || r == eof || r == '[') +} + +func isDigit(r rune) bool { + return unicode.IsNumber(r) +} + +func isHexDigit(r rune) bool { + return isDigit(r) || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} diff --git a/vendor/github.com/pelletier/go-toml/toml.go b/vendor/github.com/pelletier/go-toml/toml.go new file mode 100644 index 0000000..98c185a --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/toml.go @@ -0,0 +1,367 @@ +package toml + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "runtime" + "strings" +) + +type tomlValue struct { + value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list + comment string + commented bool + multiline bool + position Position +} + +// Tree is the result of the parsing of a TOML file. +type Tree struct { + values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree + comment string + commented bool + position Position +} + +func newTree() *Tree { + return &Tree{ + values: make(map[string]interface{}), + position: Position{}, + } +} + +// TreeFromMap initializes a new Tree object using the given map. +func TreeFromMap(m map[string]interface{}) (*Tree, error) { + result, err := toTree(m) + if err != nil { + return nil, err + } + return result.(*Tree), nil +} + +// Position returns the position of the tree. +func (t *Tree) Position() Position { + return t.position +} + +// Has returns a boolean indicating if the given key exists. +func (t *Tree) Has(key string) bool { + if key == "" { + return false + } + return t.HasPath(strings.Split(key, ".")) +} + +// HasPath returns true if the given path of keys exists, false otherwise. +func (t *Tree) HasPath(keys []string) bool { + return t.GetPath(keys) != nil +} + +// Keys returns the keys of the toplevel tree (does not recurse). +func (t *Tree) Keys() []string { + keys := make([]string, len(t.values)) + i := 0 + for k := range t.values { + keys[i] = k + i++ + } + return keys +} + +// Get the value at key in the Tree. +// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings. +// If you need to retrieve non-bare keys, use GetPath. +// Returns nil if the path does not exist in the tree. +// If keys is of length zero, the current tree is returned. +func (t *Tree) Get(key string) interface{} { + if key == "" { + return t + } + return t.GetPath(strings.Split(key, ".")) +} + +// GetPath returns the element in the tree indicated by 'keys'. +// If keys is of length zero, the current tree is returned. +func (t *Tree) GetPath(keys []string) interface{} { + if len(keys) == 0 { + return t + } + subtree := t + for _, intermediateKey := range keys[:len(keys)-1] { + value, exists := subtree.values[intermediateKey] + if !exists { + return nil + } + switch node := value.(type) { + case *Tree: + subtree = node + case []*Tree: + // go to most recent element + if len(node) == 0 { + return nil + } + subtree = node[len(node)-1] + default: + return nil // cannot navigate through other node types + } + } + // branch based on final node type + switch node := subtree.values[keys[len(keys)-1]].(type) { + case *tomlValue: + return node.value + default: + return node + } +} + +// GetPosition returns the position of the given key. +func (t *Tree) GetPosition(key string) Position { + if key == "" { + return t.position + } + return t.GetPositionPath(strings.Split(key, ".")) +} + +// GetPositionPath returns the element in the tree indicated by 'keys'. +// If keys is of length zero, the current tree is returned. +func (t *Tree) GetPositionPath(keys []string) Position { + if len(keys) == 0 { + return t.position + } + subtree := t + for _, intermediateKey := range keys[:len(keys)-1] { + value, exists := subtree.values[intermediateKey] + if !exists { + return Position{0, 0} + } + switch node := value.(type) { + case *Tree: + subtree = node + case []*Tree: + // go to most recent element + if len(node) == 0 { + return Position{0, 0} + } + subtree = node[len(node)-1] + default: + return Position{0, 0} + } + } + // branch based on final node type + switch node := subtree.values[keys[len(keys)-1]].(type) { + case *tomlValue: + return node.position + case *Tree: + return node.position + case []*Tree: + // go to most recent element + if len(node) == 0 { + return Position{0, 0} + } + return node[len(node)-1].position + default: + return Position{0, 0} + } +} + +// GetDefault works like Get but with a default value +func (t *Tree) GetDefault(key string, def interface{}) interface{} { + val := t.Get(key) + if val == nil { + return def + } + return val +} + +// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. +// The default values within the struct are valid default options. +type SetOptions struct { + Comment string + Commented bool + Multiline bool +} + +// SetWithOptions is the same as Set, but allows you to provide formatting +// instructions to the key, that will be used by Marshal(). +func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { + t.SetPathWithOptions(strings.Split(key, "."), opts, value) +} + +// SetPathWithOptions is the same as SetPath, but allows you to provide +// formatting instructions to the key, that will be reused by Marshal(). +func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { + subtree := t + for _, intermediateKey := range keys[:len(keys)-1] { + nextTree, exists := subtree.values[intermediateKey] + if !exists { + nextTree = newTree() + subtree.values[intermediateKey] = nextTree // add new element here + } + switch node := nextTree.(type) { + case *Tree: + subtree = node + case []*Tree: + // go to most recent element + if len(node) == 0 { + // create element if it does not exist + subtree.values[intermediateKey] = append(node, newTree()) + } + subtree = node[len(node)-1] + } + } + + var toInsert interface{} + + switch value.(type) { + case *Tree: + tt := value.(*Tree) + tt.comment = opts.Comment + toInsert = value + case []*Tree: + toInsert = value + case *tomlValue: + tt := value.(*tomlValue) + tt.comment = opts.Comment + toInsert = tt + default: + toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline} + } + + subtree.values[keys[len(keys)-1]] = toInsert +} + +// Set an element in the tree. +// Key is a dot-separated path (e.g. a.b.c). +// Creates all necessary intermediate trees, if needed. +func (t *Tree) Set(key string, value interface{}) { + t.SetWithComment(key, "", false, value) +} + +// SetWithComment is the same as Set, but allows you to provide comment +// information to the key, that will be reused by Marshal(). +func (t *Tree) SetWithComment(key string, comment string, commented bool, value interface{}) { + t.SetPathWithComment(strings.Split(key, "."), comment, commented, value) +} + +// SetPath sets an element in the tree. +// Keys is an array of path elements (e.g. {"a","b","c"}). +// Creates all necessary intermediate trees, if needed. +func (t *Tree) SetPath(keys []string, value interface{}) { + t.SetPathWithComment(keys, "", false, value) +} + +// SetPathWithComment is the same as SetPath, but allows you to provide comment +// information to the key, that will be reused by Marshal(). +func (t *Tree) SetPathWithComment(keys []string, comment string, commented bool, value interface{}) { + subtree := t + for _, intermediateKey := range keys[:len(keys)-1] { + nextTree, exists := subtree.values[intermediateKey] + if !exists { + nextTree = newTree() + subtree.values[intermediateKey] = nextTree // add new element here + } + switch node := nextTree.(type) { + case *Tree: + subtree = node + case []*Tree: + // go to most recent element + if len(node) == 0 { + // create element if it does not exist + subtree.values[intermediateKey] = append(node, newTree()) + } + subtree = node[len(node)-1] + } + } + + var toInsert interface{} + + switch value.(type) { + case *Tree: + tt := value.(*Tree) + tt.comment = comment + toInsert = value + case []*Tree: + toInsert = value + case *tomlValue: + tt := value.(*tomlValue) + tt.comment = comment + toInsert = tt + default: + toInsert = &tomlValue{value: value, comment: comment, commented: commented} + } + + subtree.values[keys[len(keys)-1]] = toInsert +} + +// createSubTree takes a tree and a key and create the necessary intermediate +// subtrees to create a subtree at that point. In-place. +// +// e.g. passing a.b.c will create (assuming tree is empty) tree[a], tree[a][b] +// and tree[a][b][c] +// +// Returns nil on success, error object on failure +func (t *Tree) createSubTree(keys []string, pos Position) error { + subtree := t + for _, intermediateKey := range keys { + nextTree, exists := subtree.values[intermediateKey] + if !exists { + tree := newTree() + tree.position = pos + subtree.values[intermediateKey] = tree + nextTree = tree + } + + switch node := nextTree.(type) { + case []*Tree: + subtree = node[len(node)-1] + case *Tree: + subtree = node + default: + return fmt.Errorf("unknown type for path %s (%s): %T (%#v)", + strings.Join(keys, "."), intermediateKey, nextTree, nextTree) + } + } + return nil +} + +// LoadBytes creates a Tree from a []byte. +func LoadBytes(b []byte) (tree *Tree, err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = errors.New(r.(string)) + } + }() + tree = parseToml(lexToml(b)) + return +} + +// LoadReader creates a Tree from any io.Reader. +func LoadReader(reader io.Reader) (tree *Tree, err error) { + inputBytes, err := ioutil.ReadAll(reader) + if err != nil { + return + } + tree, err = LoadBytes(inputBytes) + return +} + +// Load creates a Tree from a string. +func Load(content string) (tree *Tree, err error) { + return LoadBytes([]byte(content)) +} + +// LoadFile creates a Tree from a file. +func LoadFile(path string) (tree *Tree, err error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + return LoadReader(file) +} diff --git a/vendor/github.com/pelletier/go-toml/tomltree_create.go b/vendor/github.com/pelletier/go-toml/tomltree_create.go new file mode 100644 index 0000000..79610e9 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/tomltree_create.go @@ -0,0 +1,142 @@ +package toml + +import ( + "fmt" + "reflect" + "time" +) + +var kindToType = [reflect.String + 1]reflect.Type{ + reflect.Bool: reflect.TypeOf(true), + reflect.String: reflect.TypeOf(""), + reflect.Float32: reflect.TypeOf(float64(1)), + reflect.Float64: reflect.TypeOf(float64(1)), + reflect.Int: reflect.TypeOf(int64(1)), + reflect.Int8: reflect.TypeOf(int64(1)), + reflect.Int16: reflect.TypeOf(int64(1)), + reflect.Int32: reflect.TypeOf(int64(1)), + reflect.Int64: reflect.TypeOf(int64(1)), + reflect.Uint: reflect.TypeOf(uint64(1)), + reflect.Uint8: reflect.TypeOf(uint64(1)), + reflect.Uint16: reflect.TypeOf(uint64(1)), + reflect.Uint32: reflect.TypeOf(uint64(1)), + reflect.Uint64: reflect.TypeOf(uint64(1)), +} + +// typeFor returns a reflect.Type for a reflect.Kind, or nil if none is found. +// supported values: +// string, bool, int64, uint64, float64, time.Time, int, int8, int16, int32, uint, uint8, uint16, uint32, float32 +func typeFor(k reflect.Kind) reflect.Type { + if k > 0 && int(k) < len(kindToType) { + return kindToType[k] + } + return nil +} + +func simpleValueCoercion(object interface{}) (interface{}, error) { + switch original := object.(type) { + case string, bool, int64, uint64, float64, time.Time: + return original, nil + case int: + return int64(original), nil + case int8: + return int64(original), nil + case int16: + return int64(original), nil + case int32: + return int64(original), nil + case uint: + return uint64(original), nil + case uint8: + return uint64(original), nil + case uint16: + return uint64(original), nil + case uint32: + return uint64(original), nil + case float32: + return float64(original), nil + case fmt.Stringer: + return original.String(), nil + default: + return nil, fmt.Errorf("cannot convert type %T to Tree", object) + } +} + +func sliceToTree(object interface{}) (interface{}, error) { + // arrays are a bit tricky, since they can represent either a + // collection of simple values, which is represented by one + // *tomlValue, or an array of tables, which is represented by an + // array of *Tree. + + // holding the assumption that this function is called from toTree only when value.Kind() is Array or Slice + value := reflect.ValueOf(object) + insideType := value.Type().Elem() + length := value.Len() + if length > 0 { + insideType = reflect.ValueOf(value.Index(0).Interface()).Type() + } + if insideType.Kind() == reflect.Map { + // this is considered as an array of tables + tablesArray := make([]*Tree, 0, length) + for i := 0; i < length; i++ { + table := value.Index(i) + tree, err := toTree(table.Interface()) + if err != nil { + return nil, err + } + tablesArray = append(tablesArray, tree.(*Tree)) + } + return tablesArray, nil + } + + sliceType := typeFor(insideType.Kind()) + if sliceType == nil { + sliceType = insideType + } + + arrayValue := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, length) + + for i := 0; i < length; i++ { + val := value.Index(i).Interface() + simpleValue, err := simpleValueCoercion(val) + if err != nil { + return nil, err + } + arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue)) + } + return &tomlValue{value: arrayValue.Interface(), position: Position{}}, nil +} + +func toTree(object interface{}) (interface{}, error) { + value := reflect.ValueOf(object) + + if value.Kind() == reflect.Map { + values := map[string]interface{}{} + keys := value.MapKeys() + for _, key := range keys { + if key.Kind() != reflect.String { + if _, ok := key.Interface().(string); !ok { + return nil, fmt.Errorf("map key needs to be a string, not %T (%v)", key.Interface(), key.Kind()) + } + } + + v := value.MapIndex(key) + newValue, err := toTree(v.Interface()) + if err != nil { + return nil, err + } + values[key.String()] = newValue + } + return &Tree{values: values, position: Position{}}, nil + } + + if value.Kind() == reflect.Array || value.Kind() == reflect.Slice { + return sliceToTree(object) + } + + simpleValue, err := simpleValueCoercion(object) + if err != nil { + return nil, err + } + return &tomlValue{value: simpleValue, position: Position{}}, nil +} diff --git a/vendor/github.com/pelletier/go-toml/tomltree_write.go b/vendor/github.com/pelletier/go-toml/tomltree_write.go new file mode 100644 index 0000000..e4049e2 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/tomltree_write.go @@ -0,0 +1,333 @@ +package toml + +import ( + "bytes" + "fmt" + "io" + "math" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +// Encodes a string to a TOML-compliant multi-line string value +// This function is a clone of the existing encodeTomlString function, except that whitespace characters +// are preserved. Quotation marks and backslashes are also not escaped. +func encodeMultilineTomlString(value string) string { + var b bytes.Buffer + + for _, rr := range value { + switch rr { + case '\b': + b.WriteString(`\b`) + case '\t': + b.WriteString("\t") + case '\n': + b.WriteString("\n") + case '\f': + b.WriteString(`\f`) + case '\r': + b.WriteString("\r") + case '"': + b.WriteString(`"`) + case '\\': + b.WriteString(`\`) + default: + intRr := uint16(rr) + if intRr < 0x001F { + b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) + } else { + b.WriteRune(rr) + } + } + } + return b.String() +} + +// Encodes a string to a TOML-compliant string value +func encodeTomlString(value string) string { + var b bytes.Buffer + + for _, rr := range value { + switch rr { + case '\b': + b.WriteString(`\b`) + case '\t': + b.WriteString(`\t`) + case '\n': + b.WriteString(`\n`) + case '\f': + b.WriteString(`\f`) + case '\r': + b.WriteString(`\r`) + case '"': + b.WriteString(`\"`) + case '\\': + b.WriteString(`\\`) + default: + intRr := uint16(rr) + if intRr < 0x001F { + b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) + } else { + b.WriteRune(rr) + } + } + } + return b.String() +} + +func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElementPerLine bool) (string, error) { + // this interface check is added to dereference the change made in the writeTo function. + // That change was made to allow this function to see formatting options. + tv, ok := v.(*tomlValue) + if ok { + v = tv.value + } else { + tv = &tomlValue{} + } + + switch value := v.(type) { + case uint64: + return strconv.FormatUint(value, 10), nil + case int64: + return strconv.FormatInt(value, 10), nil + case float64: + // Ensure a round float does contain a decimal point. Otherwise feeding + // the output back to the parser would convert to an integer. + if math.Trunc(value) == value { + return strings.ToLower(strconv.FormatFloat(value, 'f', 1, 32)), nil + } + return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil + case string: + if tv.multiline { + return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil + } + return "\"" + encodeTomlString(value) + "\"", nil + case []byte: + b, _ := v.([]byte) + return tomlValueStringRepresentation(string(b), indent, arraysOneElementPerLine) + case bool: + if value { + return "true", nil + } + return "false", nil + case time.Time: + return value.Format(time.RFC3339), nil + case nil: + return "", nil + } + + rv := reflect.ValueOf(v) + + if rv.Kind() == reflect.Slice { + var values []string + for i := 0; i < rv.Len(); i++ { + item := rv.Index(i).Interface() + itemRepr, err := tomlValueStringRepresentation(item, indent, arraysOneElementPerLine) + if err != nil { + return "", err + } + values = append(values, itemRepr) + } + if arraysOneElementPerLine && len(values) > 1 { + stringBuffer := bytes.Buffer{} + valueIndent := indent + ` ` // TODO: move that to a shared encoder state + + stringBuffer.WriteString("[\n") + + for _, value := range values { + stringBuffer.WriteString(valueIndent) + stringBuffer.WriteString(value) + stringBuffer.WriteString(`,`) + stringBuffer.WriteString("\n") + } + + stringBuffer.WriteString(indent + "]") + + return stringBuffer.String(), nil + } + return "[" + strings.Join(values, ",") + "]", nil + } + return "", fmt.Errorf("unsupported value type %T: %v", v, v) +} + +func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) { + simpleValuesKeys := make([]string, 0) + complexValuesKeys := make([]string, 0) + + for k := range t.values { + v := t.values[k] + switch v.(type) { + case *Tree, []*Tree: + complexValuesKeys = append(complexValuesKeys, k) + default: + simpleValuesKeys = append(simpleValuesKeys, k) + } + } + + sort.Strings(simpleValuesKeys) + sort.Strings(complexValuesKeys) + + for _, k := range simpleValuesKeys { + v, ok := t.values[k].(*tomlValue) + if !ok { + return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) + } + + repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine) + if err != nil { + return bytesCount, err + } + + if v.comment != "" { + comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1) + start := "# " + if strings.HasPrefix(comment, "#") { + start = "" + } + writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n") + bytesCount += int64(writtenBytesCountComment) + if errc != nil { + return bytesCount, errc + } + } + + var commented string + if v.commented { + commented = "# " + } + writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n") + bytesCount += int64(writtenBytesCount) + if err != nil { + return bytesCount, err + } + } + + for _, k := range complexValuesKeys { + v := t.values[k] + + combinedKey := k + if keyspace != "" { + combinedKey = keyspace + "." + combinedKey + } + var commented string + if t.commented { + commented = "# " + } + + switch node := v.(type) { + // node has to be of those two types given how keys are sorted above + case *Tree: + tv, ok := t.values[k].(*Tree) + if !ok { + return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) + } + if tv.comment != "" { + comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1) + start := "# " + if strings.HasPrefix(comment, "#") { + start = "" + } + writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment) + bytesCount += int64(writtenBytesCountComment) + if errc != nil { + return bytesCount, errc + } + } + writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n") + bytesCount += int64(writtenBytesCount) + if err != nil { + return bytesCount, err + } + bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine) + if err != nil { + return bytesCount, err + } + case []*Tree: + for _, subTree := range node { + writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n") + bytesCount += int64(writtenBytesCount) + if err != nil { + return bytesCount, err + } + + bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine) + if err != nil { + return bytesCount, err + } + } + } + } + + return bytesCount, nil +} + +func writeStrings(w io.Writer, s ...string) (int, error) { + var n int + for i := range s { + b, err := io.WriteString(w, s[i]) + n += b + if err != nil { + return n, err + } + } + return n, nil +} + +// WriteTo encode the Tree as Toml and writes it to the writer w. +// Returns the number of bytes written in case of success, or an error if anything happened. +func (t *Tree) WriteTo(w io.Writer) (int64, error) { + return t.writeTo(w, "", "", 0, false) +} + +// ToTomlString generates a human-readable representation of the current tree. +// Output spans multiple lines, and is suitable for ingest by a TOML parser. +// If the conversion cannot be performed, ToString returns a non-nil error. +func (t *Tree) ToTomlString() (string, error) { + var buf bytes.Buffer + _, err := t.WriteTo(&buf) + if err != nil { + return "", err + } + return buf.String(), nil +} + +// String generates a human-readable representation of the current tree. +// Alias of ToString. Present to implement the fmt.Stringer interface. +func (t *Tree) String() string { + result, _ := t.ToTomlString() + return result +} + +// ToMap recursively generates a representation of the tree using Go built-in structures. +// The following types are used: +// +// * bool +// * float64 +// * int64 +// * string +// * uint64 +// * time.Time +// * map[string]interface{} (where interface{} is any of this list) +// * []interface{} (where interface{} is any of this list) +func (t *Tree) ToMap() map[string]interface{} { + result := map[string]interface{}{} + + for k, v := range t.values { + switch node := v.(type) { + case []*Tree: + var array []interface{} + for _, item := range node { + array = append(array, item.ToMap()) + } + result[k] = array + case *Tree: + result[k] = node.ToMap() + case *tomlValue: + result[k] = node.value + } + } + return result +} diff --git a/vendor/github.com/spf13/afero/.travis.yml b/vendor/github.com/spf13/afero/.travis.yml new file mode 100644 index 0000000..8fc1261 --- /dev/null +++ b/vendor/github.com/spf13/afero/.travis.yml @@ -0,0 +1,21 @@ +sudo: false +language: go + +go: + - 1.9 + - "1.10" + - tip + +os: + - linux + - osx + +matrix: + allow_failures: + - go: tip + fast_finish: true + +script: + - go build + - go test -race -v ./... + diff --git a/vendor/github.com/spf13/afero/LICENSE.txt b/vendor/github.com/spf13/afero/LICENSE.txt new file mode 100644 index 0000000..298f0e2 --- /dev/null +++ b/vendor/github.com/spf13/afero/LICENSE.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/spf13/afero/README.md b/vendor/github.com/spf13/afero/README.md new file mode 100644 index 0000000..0c9b04b --- /dev/null +++ b/vendor/github.com/spf13/afero/README.md @@ -0,0 +1,452 @@ +![afero logo-sm](https://cloud.githubusercontent.com/assets/173412/11490338/d50e16dc-97a5-11e5-8b12-019a300d0fcb.png) + +A FileSystem Abstraction System for Go + +[![Build Status](https://travis-ci.org/spf13/afero.svg)](https://travis-ci.org/spf13/afero) [![Build status](https://ci.appveyor.com/api/projects/status/github/spf13/afero?branch=master&svg=true)](https://ci.appveyor.com/project/spf13/afero) [![GoDoc](https://godoc.org/github.com/spf13/afero?status.svg)](https://godoc.org/github.com/spf13/afero) [![Join the chat at https://gitter.im/spf13/afero](https://badges.gitter.im/Dev%20Chat.svg)](https://gitter.im/spf13/afero?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +# Overview + +Afero is an filesystem framework providing a simple, uniform and universal API +interacting with any filesystem, as an abstraction layer providing interfaces, +types and methods. Afero has an exceptionally clean interface and simple design +without needless constructors or initialization methods. + +Afero is also a library providing a base set of interoperable backend +filesystems that make it easy to work with afero while retaining all the power +and benefit of the os and ioutil packages. + +Afero provides significant improvements over using the os package alone, most +notably the ability to create mock and testing filesystems without relying on the disk. + +It is suitable for use in a any situation where you would consider using the OS +package as it provides an additional abstraction that makes it easy to use a +memory backed file system during testing. It also adds support for the http +filesystem for full interoperability. + + +## Afero Features + +* A single consistent API for accessing a variety of filesystems +* Interoperation between a variety of file system types +* A set of interfaces to encourage and enforce interoperability between backends +* An atomic cross platform memory backed file system +* Support for compositional (union) file systems by combining multiple file systems acting as one +* Specialized backends which modify existing filesystems (Read Only, Regexp filtered) +* A set of utility functions ported from io, ioutil & hugo to be afero aware + + +# Using Afero + +Afero is easy to use and easier to adopt. + +A few different ways you could use Afero: + +* Use the interfaces alone to define you own file system. +* Wrap for the OS packages. +* Define different filesystems for different parts of your application. +* Use Afero for mock filesystems while testing + +## Step 1: Install Afero + +First use go get to install the latest version of the library. + + $ go get github.com/spf13/afero + +Next include Afero in your application. +```go +import "github.com/spf13/afero" +``` + +## Step 2: Declare a backend + +First define a package variable and set it to a pointer to a filesystem. +```go +var AppFs = afero.NewMemMapFs() + +or + +var AppFs = afero.NewOsFs() +``` +It is important to note that if you repeat the composite literal you +will be using a completely new and isolated filesystem. In the case of +OsFs it will still use the same underlying filesystem but will reduce +the ability to drop in other filesystems as desired. + +## Step 3: Use it like you would the OS package + +Throughout your application use any function and method like you normally +would. + +So if my application before had: +```go +os.Open('/tmp/foo') +``` +We would replace it with: +```go +AppFs.Open('/tmp/foo') +``` + +`AppFs` being the variable we defined above. + + +## List of all available functions + +File System Methods Available: +```go +Chmod(name string, mode os.FileMode) : error +Chtimes(name string, atime time.Time, mtime time.Time) : error +Create(name string) : File, error +Mkdir(name string, perm os.FileMode) : error +MkdirAll(path string, perm os.FileMode) : error +Name() : string +Open(name string) : File, error +OpenFile(name string, flag int, perm os.FileMode) : File, error +Remove(name string) : error +RemoveAll(path string) : error +Rename(oldname, newname string) : error +Stat(name string) : os.FileInfo, error +``` +File Interfaces and Methods Available: +```go +io.Closer +io.Reader +io.ReaderAt +io.Seeker +io.Writer +io.WriterAt + +Name() : string +Readdir(count int) : []os.FileInfo, error +Readdirnames(n int) : []string, error +Stat() : os.FileInfo, error +Sync() : error +Truncate(size int64) : error +WriteString(s string) : ret int, err error +``` +In some applications it may make sense to define a new package that +simply exports the file system variable for easy access from anywhere. + +## Using Afero's utility functions + +Afero provides a set of functions to make it easier to use the underlying file systems. +These functions have been primarily ported from io & ioutil with some developed for Hugo. + +The afero utilities support all afero compatible backends. + +The list of utilities includes: + +```go +DirExists(path string) (bool, error) +Exists(path string) (bool, error) +FileContainsBytes(filename string, subslice []byte) (bool, error) +GetTempDir(subPath string) string +IsDir(path string) (bool, error) +IsEmpty(path string) (bool, error) +ReadDir(dirname string) ([]os.FileInfo, error) +ReadFile(filename string) ([]byte, error) +SafeWriteReader(path string, r io.Reader) (err error) +TempDir(dir, prefix string) (name string, err error) +TempFile(dir, prefix string) (f File, err error) +Walk(root string, walkFn filepath.WalkFunc) error +WriteFile(filename string, data []byte, perm os.FileMode) error +WriteReader(path string, r io.Reader) (err error) +``` +For a complete list see [Afero's GoDoc](https://godoc.org/github.com/spf13/afero) + +They are available under two different approaches to use. You can either call +them directly where the first parameter of each function will be the file +system, or you can declare a new `Afero`, a custom type used to bind these +functions as methods to a given filesystem. + +### Calling utilities directly + +```go +fs := new(afero.MemMapFs) +f, err := afero.TempFile(fs,"", "ioutil-test") + +``` + +### Calling via Afero + +```go +fs := afero.NewMemMapFs() +afs := &afero.Afero{Fs: fs} +f, err := afs.TempFile("", "ioutil-test") +``` + +## Using Afero for Testing + +There is a large benefit to using a mock filesystem for testing. It has a +completely blank state every time it is initialized and can be easily +reproducible regardless of OS. You could create files to your heart’s content +and the file access would be fast while also saving you from all the annoying +issues with deleting temporary files, Windows file locking, etc. The MemMapFs +backend is perfect for testing. + +* Much faster than performing I/O operations on disk +* Avoid security issues and permissions +* Far more control. 'rm -rf /' with confidence +* Test setup is far more easier to do +* No test cleanup needed + +One way to accomplish this is to define a variable as mentioned above. +In your application this will be set to afero.NewOsFs() during testing you +can set it to afero.NewMemMapFs(). + +It wouldn't be uncommon to have each test initialize a blank slate memory +backend. To do this I would define my `appFS = afero.NewOsFs()` somewhere +appropriate in my application code. This approach ensures that Tests are order +independent, with no test relying on the state left by an earlier test. + +Then in my tests I would initialize a new MemMapFs for each test: +```go +func TestExist(t *testing.T) { + appFS := afero.NewMemMapFs() + // create test files and directories + appFS.MkdirAll("src/a", 0755) + afero.WriteFile(appFS, "src/a/b", []byte("file b"), 0644) + afero.WriteFile(appFS, "src/c", []byte("file c"), 0644) + name := "src/c" + _, err := appFS.Stat(name) + if os.IsNotExist(err) { + t.Errorf("file \"%s\" does not exist.\n", name) + } +} +``` + +# Available Backends + +## Operating System Native + +### OsFs + +The first is simply a wrapper around the native OS calls. This makes it +very easy to use as all of the calls are the same as the existing OS +calls. It also makes it trivial to have your code use the OS during +operation and a mock filesystem during testing or as needed. + +```go +appfs := afero.NewOsFs() +appfs.MkdirAll("src/a", 0755)) +``` + +## Memory Backed Storage + +### MemMapFs + +Afero also provides a fully atomic memory backed filesystem perfect for use in +mocking and to speed up unnecessary disk io when persistence isn’t +necessary. It is fully concurrent and will work within go routines +safely. + +```go +mm := afero.NewMemMapFs() +mm.MkdirAll("src/a", 0755)) +``` + +#### InMemoryFile + +As part of MemMapFs, Afero also provides an atomic, fully concurrent memory +backed file implementation. This can be used in other memory backed file +systems with ease. Plans are to add a radix tree memory stored file +system using InMemoryFile. + +## Network Interfaces + +### SftpFs + +Afero has experimental support for secure file transfer protocol (sftp). Which can +be used to perform file operations over a encrypted channel. + +## Filtering Backends + +### BasePathFs + +The BasePathFs restricts all operations to a given path within an Fs. +The given file name to the operations on this Fs will be prepended with +the base path before calling the source Fs. + +```go +bp := afero.NewBasePathFs(afero.NewOsFs(), "/base/path") +``` + +### ReadOnlyFs + +A thin wrapper around the source Fs providing a read only view. + +```go +fs := afero.NewReadOnlyFs(afero.NewOsFs()) +_, err := fs.Create("/file.txt") +// err = syscall.EPERM +``` + +# RegexpFs + +A filtered view on file names, any file NOT matching +the passed regexp will be treated as non-existing. +Files not matching the regexp provided will not be created. +Directories are not filtered. + +```go +fs := afero.NewRegexpFs(afero.NewMemMapFs(), regexp.MustCompile(`\.txt$`)) +_, err := fs.Create("/file.html") +// err = syscall.ENOENT +``` + +### HttpFs + +Afero provides an http compatible backend which can wrap any of the existing +backends. + +The Http package requires a slightly specific version of Open which +returns an http.File type. + +Afero provides an httpFs file system which satisfies this requirement. +Any Afero FileSystem can be used as an httpFs. + +```go +httpFs := afero.NewHttpFs() +fileserver := http.FileServer(httpFs.Dir())) +http.Handle("/", fileserver) +``` + +## Composite Backends + +Afero provides the ability have two filesystems (or more) act as a single +file system. + +### CacheOnReadFs + +The CacheOnReadFs will lazily make copies of any accessed files from the base +layer into the overlay. Subsequent reads will be pulled from the overlay +directly permitting the request is within the cache duration of when it was +created in the overlay. + +If the base filesystem is writeable, any changes to files will be +done first to the base, then to the overlay layer. Write calls to open file +handles like `Write()` or `Truncate()` to the overlay first. + +To writing files to the overlay only, you can use the overlay Fs directly (not +via the union Fs). + +Cache files in the layer for the given time.Duration, a cache duration of 0 +means "forever" meaning the file will not be re-requested from the base ever. + +A read-only base will make the overlay also read-only but still copy files +from the base to the overlay when they're not present (or outdated) in the +caching layer. + +```go +base := afero.NewOsFs() +layer := afero.NewMemMapFs() +ufs := afero.NewCacheOnReadFs(base, layer, 100 * time.Second) +``` + +### CopyOnWriteFs() + +The CopyOnWriteFs is a read only base file system with a potentially +writeable layer on top. + +Read operations will first look in the overlay and if not found there, will +serve the file from the base. + +Changes to the file system will only be made in the overlay. + +Any attempt to modify a file found only in the base will copy the file to the +overlay layer before modification (including opening a file with a writable +handle). + +Removing and Renaming files present only in the base layer is not currently +permitted. If a file is present in the base layer and the overlay, only the +overlay will be removed/renamed. + +```go + base := afero.NewOsFs() + roBase := afero.NewReadOnlyFs(base) + ufs := afero.NewCopyOnWriteFs(roBase, afero.NewMemMapFs()) + + fh, _ = ufs.Create("/home/test/file2.txt") + fh.WriteString("This is a test") + fh.Close() +``` + +In this example all write operations will only occur in memory (MemMapFs) +leaving the base filesystem (OsFs) untouched. + + +## Desired/possible backends + +The following is a short list of possible backends we hope someone will +implement: + +* SSH +* ZIP +* TAR +* S3 + +# About the project + +## What's in the name + +Afero comes from the latin roots Ad-Facere. + +**"Ad"** is a prefix meaning "to". + +**"Facere"** is a form of the root "faciō" making "make or do". + +The literal meaning of afero is "to make" or "to do" which seems very fitting +for a library that allows one to make files and directories and do things with them. + +The English word that shares the same roots as Afero is "affair". Affair shares +the same concept but as a noun it means "something that is made or done" or "an +object of a particular type". + +It's also nice that unlike some of my other libraries (hugo, cobra, viper) it +Googles very well. + +## Release Notes + +* **0.10.0** 2015.12.10 + * Full compatibility with Windows + * Introduction of afero utilities + * Test suite rewritten to work cross platform + * Normalize paths for MemMapFs + * Adding Sync to the file interface + * **Breaking Change** Walk and ReadDir have changed parameter order + * Moving types used by MemMapFs to a subpackage + * General bugfixes and improvements +* **0.9.0** 2015.11.05 + * New Walk function similar to filepath.Walk + * MemMapFs.OpenFile handles O_CREATE, O_APPEND, O_TRUNC + * MemMapFs.Remove now really deletes the file + * InMemoryFile.Readdir and Readdirnames work correctly + * InMemoryFile functions lock it for concurrent access + * Test suite improvements +* **0.8.0** 2014.10.28 + * First public version + * Interfaces feel ready for people to build using + * Interfaces satisfy all known uses + * MemMapFs passes the majority of the OS test suite + * OsFs passes the majority of the OS test suite + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## Contributors + +Names in no particular order: + +* [spf13](https://github.com/spf13) +* [jaqx0r](https://github.com/jaqx0r) +* [mbertschler](https://github.com/mbertschler) +* [xor-gate](https://github.com/xor-gate) + +## License + +Afero is released under the Apache 2.0 license. See +[LICENSE.txt](https://github.com/spf13/afero/blob/master/LICENSE.txt) diff --git a/vendor/github.com/spf13/afero/afero.go b/vendor/github.com/spf13/afero/afero.go new file mode 100644 index 0000000..f5b5e12 --- /dev/null +++ b/vendor/github.com/spf13/afero/afero.go @@ -0,0 +1,108 @@ +// Copyright © 2014 Steve Francia . +// Copyright 2013 tsuru authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package afero provides types and methods for interacting with the filesystem, +// as an abstraction layer. + +// Afero also provides a few implementations that are mostly interoperable. One that +// uses the operating system filesystem, one that uses memory to store files +// (cross platform) and an interface that should be implemented if you want to +// provide your own filesystem. + +package afero + +import ( + "errors" + "io" + "os" + "time" +) + +type Afero struct { + Fs +} + +// File represents a file in the filesystem. +type File interface { + io.Closer + io.Reader + io.ReaderAt + io.Seeker + io.Writer + io.WriterAt + + Name() string + Readdir(count int) ([]os.FileInfo, error) + Readdirnames(n int) ([]string, error) + Stat() (os.FileInfo, error) + Sync() error + Truncate(size int64) error + WriteString(s string) (ret int, err error) +} + +// Fs is the filesystem interface. +// +// Any simulated or real filesystem should implement this interface. +type Fs interface { + // Create creates a file in the filesystem, returning the file and an + // error, if any happens. + Create(name string) (File, error) + + // Mkdir creates a directory in the filesystem, return an error if any + // happens. + Mkdir(name string, perm os.FileMode) error + + // MkdirAll creates a directory path and all parents that does not exist + // yet. + MkdirAll(path string, perm os.FileMode) error + + // Open opens a file, returning it or an error, if any happens. + Open(name string) (File, error) + + // OpenFile opens a file using the given flags and the given mode. + OpenFile(name string, flag int, perm os.FileMode) (File, error) + + // Remove removes a file identified by name, returning an error, if any + // happens. + Remove(name string) error + + // RemoveAll removes a directory path and any children it contains. It + // does not fail if the path does not exist (return nil). + RemoveAll(path string) error + + // Rename renames a file. + Rename(oldname, newname string) error + + // Stat returns a FileInfo describing the named file, or an error, if any + // happens. + Stat(name string) (os.FileInfo, error) + + // The name of this FileSystem + Name() string + + //Chmod changes the mode of the named file to mode. + Chmod(name string, mode os.FileMode) error + + //Chtimes changes the access and modification times of the named file + Chtimes(name string, atime time.Time, mtime time.Time) error +} + +var ( + ErrFileClosed = errors.New("File is closed") + ErrOutOfRange = errors.New("Out of range") + ErrTooLarge = errors.New("Too large") + ErrFileNotFound = os.ErrNotExist + ErrFileExists = os.ErrExist + ErrDestinationExists = os.ErrExist +) diff --git a/vendor/github.com/spf13/afero/appveyor.yml b/vendor/github.com/spf13/afero/appveyor.yml new file mode 100644 index 0000000..a633ad5 --- /dev/null +++ b/vendor/github.com/spf13/afero/appveyor.yml @@ -0,0 +1,15 @@ +version: '{build}' +clone_folder: C:\gopath\src\github.com\spf13\afero +environment: + GOPATH: C:\gopath +build_script: +- cmd: >- + go version + + go env + + go get -v github.com/spf13/afero/... + + go build github.com/spf13/afero +test_script: +- cmd: go test -race -v github.com/spf13/afero/... diff --git a/vendor/github.com/spf13/afero/basepath.go b/vendor/github.com/spf13/afero/basepath.go new file mode 100644 index 0000000..616ff8f --- /dev/null +++ b/vendor/github.com/spf13/afero/basepath.go @@ -0,0 +1,180 @@ +package afero + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +var _ Lstater = (*BasePathFs)(nil) + +// The BasePathFs restricts all operations to a given path within an Fs. +// The given file name to the operations on this Fs will be prepended with +// the base path before calling the base Fs. +// Any file name (after filepath.Clean()) outside this base path will be +// treated as non existing file. +// +// Note that it does not clean the error messages on return, so you may +// reveal the real path on errors. +type BasePathFs struct { + source Fs + path string +} + +type BasePathFile struct { + File + path string +} + +func (f *BasePathFile) Name() string { + sourcename := f.File.Name() + return strings.TrimPrefix(sourcename, filepath.Clean(f.path)) +} + +func NewBasePathFs(source Fs, path string) Fs { + return &BasePathFs{source: source, path: path} +} + +// on a file outside the base path it returns the given file name and an error, +// else the given file with the base path prepended +func (b *BasePathFs) RealPath(name string) (path string, err error) { + if err := validateBasePathName(name); err != nil { + return name, err + } + + bpath := filepath.Clean(b.path) + path = filepath.Clean(filepath.Join(bpath, name)) + if !strings.HasPrefix(path, bpath) { + return name, os.ErrNotExist + } + + return path, nil +} + +func validateBasePathName(name string) error { + if runtime.GOOS != "windows" { + // Not much to do here; + // the virtual file paths all look absolute on *nix. + return nil + } + + // On Windows a common mistake would be to provide an absolute OS path + // We could strip out the base part, but that would not be very portable. + if filepath.IsAbs(name) { + return os.ErrNotExist + } + + return nil +} + +func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{Op: "chtimes", Path: name, Err: err} + } + return b.source.Chtimes(name, atime, mtime) +} + +func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{Op: "chmod", Path: name, Err: err} + } + return b.source.Chmod(name, mode) +} + +func (b *BasePathFs) Name() string { + return "BasePathFs" +} + +func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{Op: "stat", Path: name, Err: err} + } + return b.source.Stat(name) +} + +func (b *BasePathFs) Rename(oldname, newname string) (err error) { + if oldname, err = b.RealPath(oldname); err != nil { + return &os.PathError{Op: "rename", Path: oldname, Err: err} + } + if newname, err = b.RealPath(newname); err != nil { + return &os.PathError{Op: "rename", Path: newname, Err: err} + } + return b.source.Rename(oldname, newname) +} + +func (b *BasePathFs) RemoveAll(name string) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{Op: "remove_all", Path: name, Err: err} + } + return b.source.RemoveAll(name) +} + +func (b *BasePathFs) Remove(name string) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{Op: "remove", Path: name, Err: err} + } + return b.source.Remove(name) +} + +func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{Op: "openfile", Path: name, Err: err} + } + sourcef, err := b.source.OpenFile(name, flag, mode) + if err != nil { + return nil, err + } + return &BasePathFile{sourcef, b.path}, nil +} + +func (b *BasePathFs) Open(name string) (f File, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{Op: "open", Path: name, Err: err} + } + sourcef, err := b.source.Open(name) + if err != nil { + return nil, err + } + return &BasePathFile{File: sourcef, path: b.path}, nil +} + +func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{Op: "mkdir", Path: name, Err: err} + } + return b.source.Mkdir(name, mode) +} + +func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) { + if name, err = b.RealPath(name); err != nil { + return &os.PathError{Op: "mkdir", Path: name, Err: err} + } + return b.source.MkdirAll(name, mode) +} + +func (b *BasePathFs) Create(name string) (f File, err error) { + if name, err = b.RealPath(name); err != nil { + return nil, &os.PathError{Op: "create", Path: name, Err: err} + } + sourcef, err := b.source.Create(name) + if err != nil { + return nil, err + } + return &BasePathFile{File: sourcef, path: b.path}, nil +} + +func (b *BasePathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + name, err := b.RealPath(name) + if err != nil { + return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err} + } + if lstater, ok := b.source.(Lstater); ok { + return lstater.LstatIfPossible(name) + } + fi, err := b.source.Stat(name) + return fi, false, err +} + +// vim: ts=4 sw=4 noexpandtab nolist syn=go diff --git a/vendor/github.com/spf13/afero/cacheOnReadFs.go b/vendor/github.com/spf13/afero/cacheOnReadFs.go new file mode 100644 index 0000000..29a26c6 --- /dev/null +++ b/vendor/github.com/spf13/afero/cacheOnReadFs.go @@ -0,0 +1,290 @@ +package afero + +import ( + "os" + "syscall" + "time" +) + +// If the cache duration is 0, cache time will be unlimited, i.e. once +// a file is in the layer, the base will never be read again for this file. +// +// For cache times greater than 0, the modification time of a file is +// checked. Note that a lot of file system implementations only allow a +// resolution of a second for timestamps... or as the godoc for os.Chtimes() +// states: "The underlying filesystem may truncate or round the values to a +// less precise time unit." +// +// This caching union will forward all write calls also to the base file +// system first. To prevent writing to the base Fs, wrap it in a read-only +// filter - Note: this will also make the overlay read-only, for writing files +// in the overlay, use the overlay Fs directly, not via the union Fs. +type CacheOnReadFs struct { + base Fs + layer Fs + cacheTime time.Duration +} + +func NewCacheOnReadFs(base Fs, layer Fs, cacheTime time.Duration) Fs { + return &CacheOnReadFs{base: base, layer: layer, cacheTime: cacheTime} +} + +type cacheState int + +const ( + // not present in the overlay, unknown if it exists in the base: + cacheMiss cacheState = iota + // present in the overlay and in base, base file is newer: + cacheStale + // present in the overlay - with cache time == 0 it may exist in the base, + // with cacheTime > 0 it exists in the base and is same age or newer in the + // overlay + cacheHit + // happens if someone writes directly to the overlay without + // going through this union + cacheLocal +) + +func (u *CacheOnReadFs) cacheStatus(name string) (state cacheState, fi os.FileInfo, err error) { + var lfi, bfi os.FileInfo + lfi, err = u.layer.Stat(name) + if err == nil { + if u.cacheTime == 0 { + return cacheHit, lfi, nil + } + if lfi.ModTime().Add(u.cacheTime).Before(time.Now()) { + bfi, err = u.base.Stat(name) + if err != nil { + return cacheLocal, lfi, nil + } + if bfi.ModTime().After(lfi.ModTime()) { + return cacheStale, bfi, nil + } + } + return cacheHit, lfi, nil + } + + if err == syscall.ENOENT || os.IsNotExist(err) { + return cacheMiss, nil, nil + } + + return cacheMiss, nil, err +} + +func (u *CacheOnReadFs) copyToLayer(name string) error { + return copyToLayer(u.base, u.layer, name) +} + +func (u *CacheOnReadFs) Chtimes(name string, atime, mtime time.Time) error { + st, _, err := u.cacheStatus(name) + if err != nil { + return err + } + switch st { + case cacheLocal: + case cacheHit: + err = u.base.Chtimes(name, atime, mtime) + case cacheStale, cacheMiss: + if err := u.copyToLayer(name); err != nil { + return err + } + err = u.base.Chtimes(name, atime, mtime) + } + if err != nil { + return err + } + return u.layer.Chtimes(name, atime, mtime) +} + +func (u *CacheOnReadFs) Chmod(name string, mode os.FileMode) error { + st, _, err := u.cacheStatus(name) + if err != nil { + return err + } + switch st { + case cacheLocal: + case cacheHit: + err = u.base.Chmod(name, mode) + case cacheStale, cacheMiss: + if err := u.copyToLayer(name); err != nil { + return err + } + err = u.base.Chmod(name, mode) + } + if err != nil { + return err + } + return u.layer.Chmod(name, mode) +} + +func (u *CacheOnReadFs) Stat(name string) (os.FileInfo, error) { + st, fi, err := u.cacheStatus(name) + if err != nil { + return nil, err + } + switch st { + case cacheMiss: + return u.base.Stat(name) + default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo + return fi, nil + } +} + +func (u *CacheOnReadFs) Rename(oldname, newname string) error { + st, _, err := u.cacheStatus(oldname) + if err != nil { + return err + } + switch st { + case cacheLocal: + case cacheHit: + err = u.base.Rename(oldname, newname) + case cacheStale, cacheMiss: + if err := u.copyToLayer(oldname); err != nil { + return err + } + err = u.base.Rename(oldname, newname) + } + if err != nil { + return err + } + return u.layer.Rename(oldname, newname) +} + +func (u *CacheOnReadFs) Remove(name string) error { + st, _, err := u.cacheStatus(name) + if err != nil { + return err + } + switch st { + case cacheLocal: + case cacheHit, cacheStale, cacheMiss: + err = u.base.Remove(name) + } + if err != nil { + return err + } + return u.layer.Remove(name) +} + +func (u *CacheOnReadFs) RemoveAll(name string) error { + st, _, err := u.cacheStatus(name) + if err != nil { + return err + } + switch st { + case cacheLocal: + case cacheHit, cacheStale, cacheMiss: + err = u.base.RemoveAll(name) + } + if err != nil { + return err + } + return u.layer.RemoveAll(name) +} + +func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + st, _, err := u.cacheStatus(name) + if err != nil { + return nil, err + } + switch st { + case cacheLocal, cacheHit: + default: + if err := u.copyToLayer(name); err != nil { + return nil, err + } + } + if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { + bfi, err := u.base.OpenFile(name, flag, perm) + if err != nil { + return nil, err + } + lfi, err := u.layer.OpenFile(name, flag, perm) + if err != nil { + bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...? + return nil, err + } + return &UnionFile{Base: bfi, Layer: lfi}, nil + } + return u.layer.OpenFile(name, flag, perm) +} + +func (u *CacheOnReadFs) Open(name string) (File, error) { + st, fi, err := u.cacheStatus(name) + if err != nil { + return nil, err + } + + switch st { + case cacheLocal: + return u.layer.Open(name) + + case cacheMiss: + bfi, err := u.base.Stat(name) + if err != nil { + return nil, err + } + if bfi.IsDir() { + return u.base.Open(name) + } + if err := u.copyToLayer(name); err != nil { + return nil, err + } + return u.layer.Open(name) + + case cacheStale: + if !fi.IsDir() { + if err := u.copyToLayer(name); err != nil { + return nil, err + } + return u.layer.Open(name) + } + case cacheHit: + if !fi.IsDir() { + return u.layer.Open(name) + } + } + // the dirs from cacheHit, cacheStale fall down here: + bfile, _ := u.base.Open(name) + lfile, err := u.layer.Open(name) + if err != nil && bfile == nil { + return nil, err + } + return &UnionFile{Base: bfile, Layer: lfile}, nil +} + +func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error { + err := u.base.Mkdir(name, perm) + if err != nil { + return err + } + return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache +} + +func (u *CacheOnReadFs) Name() string { + return "CacheOnReadFs" +} + +func (u *CacheOnReadFs) MkdirAll(name string, perm os.FileMode) error { + err := u.base.MkdirAll(name, perm) + if err != nil { + return err + } + return u.layer.MkdirAll(name, perm) +} + +func (u *CacheOnReadFs) Create(name string) (File, error) { + bfh, err := u.base.Create(name) + if err != nil { + return nil, err + } + lfh, err := u.layer.Create(name) + if err != nil { + // oops, see comment about OS_TRUNC above, should we remove? then we have to + // remember if the file did not exist before + bfh.Close() + return nil, err + } + return &UnionFile{Base: bfh, Layer: lfh}, nil +} diff --git a/vendor/github.com/spf13/afero/const_bsds.go b/vendor/github.com/spf13/afero/const_bsds.go new file mode 100644 index 0000000..5728243 --- /dev/null +++ b/vendor/github.com/spf13/afero/const_bsds.go @@ -0,0 +1,22 @@ +// Copyright © 2016 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build darwin openbsd freebsd netbsd dragonfly + +package afero + +import ( + "syscall" +) + +const BADFD = syscall.EBADF diff --git a/vendor/github.com/spf13/afero/const_win_unix.go b/vendor/github.com/spf13/afero/const_win_unix.go new file mode 100644 index 0000000..968fc27 --- /dev/null +++ b/vendor/github.com/spf13/afero/const_win_unix.go @@ -0,0 +1,25 @@ +// Copyright © 2016 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build !darwin +// +build !openbsd +// +build !freebsd +// +build !dragonfly +// +build !netbsd + +package afero + +import ( + "syscall" +) + +const BADFD = syscall.EBADFD diff --git a/vendor/github.com/spf13/afero/copyOnWriteFs.go b/vendor/github.com/spf13/afero/copyOnWriteFs.go new file mode 100644 index 0000000..9aef397 --- /dev/null +++ b/vendor/github.com/spf13/afero/copyOnWriteFs.go @@ -0,0 +1,292 @@ +package afero + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + "time" +) + +var _ Lstater = (*CopyOnWriteFs)(nil) + +// The CopyOnWriteFs is a union filesystem: a read only base file system with +// a possibly writeable layer on top. Changes to the file system will only +// be made in the overlay: Changing an existing file in the base layer which +// is not present in the overlay will copy the file to the overlay ("changing" +// includes also calls to e.g. Chtimes() and Chmod()). +// +// Reading directories is currently only supported via Open(), not OpenFile(). +type CopyOnWriteFs struct { + base Fs + layer Fs +} + +func NewCopyOnWriteFs(base Fs, layer Fs) Fs { + return &CopyOnWriteFs{base: base, layer: layer} +} + +// Returns true if the file is not in the overlay +func (u *CopyOnWriteFs) isBaseFile(name string) (bool, error) { + if _, err := u.layer.Stat(name); err == nil { + return false, nil + } + _, err := u.base.Stat(name) + if err != nil { + if oerr, ok := err.(*os.PathError); ok { + if oerr.Err == os.ErrNotExist || oerr.Err == syscall.ENOENT || oerr.Err == syscall.ENOTDIR { + return false, nil + } + } + if err == syscall.ENOENT { + return false, nil + } + } + return true, err +} + +func (u *CopyOnWriteFs) copyToLayer(name string) error { + return copyToLayer(u.base, u.layer, name) +} + +func (u *CopyOnWriteFs) Chtimes(name string, atime, mtime time.Time) error { + b, err := u.isBaseFile(name) + if err != nil { + return err + } + if b { + if err := u.copyToLayer(name); err != nil { + return err + } + } + return u.layer.Chtimes(name, atime, mtime) +} + +func (u *CopyOnWriteFs) Chmod(name string, mode os.FileMode) error { + b, err := u.isBaseFile(name) + if err != nil { + return err + } + if b { + if err := u.copyToLayer(name); err != nil { + return err + } + } + return u.layer.Chmod(name, mode) +} + +func (u *CopyOnWriteFs) Stat(name string) (os.FileInfo, error) { + fi, err := u.layer.Stat(name) + if err != nil { + isNotExist := u.isNotExist(err) + if isNotExist { + return u.base.Stat(name) + } + return nil, err + } + return fi, nil +} + +func (u *CopyOnWriteFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + llayer, ok1 := u.layer.(Lstater) + lbase, ok2 := u.base.(Lstater) + + if ok1 { + fi, b, err := llayer.LstatIfPossible(name) + if err == nil { + return fi, b, nil + } + + if !u.isNotExist(err) { + return nil, b, err + } + } + + if ok2 { + fi, b, err := lbase.LstatIfPossible(name) + if err == nil { + return fi, b, nil + } + if !u.isNotExist(err) { + return nil, b, err + } + } + + fi, err := u.Stat(name) + + return fi, false, err +} + +func (u *CopyOnWriteFs) isNotExist(err error) bool { + if e, ok := err.(*os.PathError); ok { + err = e.Err + } + if err == os.ErrNotExist || err == syscall.ENOENT || err == syscall.ENOTDIR { + return true + } + return false +} + +// Renaming files present only in the base layer is not permitted +func (u *CopyOnWriteFs) Rename(oldname, newname string) error { + b, err := u.isBaseFile(oldname) + if err != nil { + return err + } + if b { + return syscall.EPERM + } + return u.layer.Rename(oldname, newname) +} + +// Removing files present only in the base layer is not permitted. If +// a file is present in the base layer and the overlay, only the overlay +// will be removed. +func (u *CopyOnWriteFs) Remove(name string) error { + err := u.layer.Remove(name) + switch err { + case syscall.ENOENT: + _, err = u.base.Stat(name) + if err == nil { + return syscall.EPERM + } + return syscall.ENOENT + default: + return err + } +} + +func (u *CopyOnWriteFs) RemoveAll(name string) error { + err := u.layer.RemoveAll(name) + switch err { + case syscall.ENOENT: + _, err = u.base.Stat(name) + if err == nil { + return syscall.EPERM + } + return syscall.ENOENT + default: + return err + } +} + +func (u *CopyOnWriteFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + b, err := u.isBaseFile(name) + if err != nil { + return nil, err + } + + if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { + if b { + if err = u.copyToLayer(name); err != nil { + return nil, err + } + return u.layer.OpenFile(name, flag, perm) + } + + dir := filepath.Dir(name) + isaDir, err := IsDir(u.base, dir) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if isaDir { + if err = u.layer.MkdirAll(dir, 0777); err != nil { + return nil, err + } + return u.layer.OpenFile(name, flag, perm) + } + + isaDir, err = IsDir(u.layer, dir) + if err != nil { + return nil, err + } + if isaDir { + return u.layer.OpenFile(name, flag, perm) + } + + return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOTDIR} // ...or os.ErrNotExist? + } + if b { + return u.base.OpenFile(name, flag, perm) + } + return u.layer.OpenFile(name, flag, perm) +} + +// This function handles the 9 different possibilities caused +// by the union which are the intersection of the following... +// layer: doesn't exist, exists as a file, and exists as a directory +// base: doesn't exist, exists as a file, and exists as a directory +func (u *CopyOnWriteFs) Open(name string) (File, error) { + // Since the overlay overrides the base we check that first + b, err := u.isBaseFile(name) + if err != nil { + return nil, err + } + + // If overlay doesn't exist, return the base (base state irrelevant) + if b { + return u.base.Open(name) + } + + // If overlay is a file, return it (base state irrelevant) + dir, err := IsDir(u.layer, name) + if err != nil { + return nil, err + } + if !dir { + return u.layer.Open(name) + } + + // Overlay is a directory, base state now matters. + // Base state has 3 states to check but 2 outcomes: + // A. It's a file or non-readable in the base (return just the overlay) + // B. It's an accessible directory in the base (return a UnionFile) + + // If base is file or nonreadable, return overlay + dir, err = IsDir(u.base, name) + if !dir || err != nil { + return u.layer.Open(name) + } + + // Both base & layer are directories + // Return union file (if opens are without error) + bfile, bErr := u.base.Open(name) + lfile, lErr := u.layer.Open(name) + + // If either have errors at this point something is very wrong. Return nil and the errors + if bErr != nil || lErr != nil { + return nil, fmt.Errorf("BaseErr: %v\nOverlayErr: %v", bErr, lErr) + } + + return &UnionFile{Base: bfile, Layer: lfile}, nil +} + +func (u *CopyOnWriteFs) Mkdir(name string, perm os.FileMode) error { + dir, err := IsDir(u.base, name) + if err != nil { + return u.layer.MkdirAll(name, perm) + } + if dir { + return syscall.EEXIST + } + return u.layer.MkdirAll(name, perm) +} + +func (u *CopyOnWriteFs) Name() string { + return "CopyOnWriteFs" +} + +func (u *CopyOnWriteFs) MkdirAll(name string, perm os.FileMode) error { + dir, err := IsDir(u.base, name) + if err != nil { + return u.layer.MkdirAll(name, perm) + } + if dir { + return syscall.EEXIST + } + return u.layer.MkdirAll(name, perm) +} + +func (u *CopyOnWriteFs) Create(name string) (File, error) { + return u.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666) +} diff --git a/vendor/github.com/spf13/afero/go.mod b/vendor/github.com/spf13/afero/go.mod new file mode 100644 index 0000000..9eff4fe --- /dev/null +++ b/vendor/github.com/spf13/afero/go.mod @@ -0,0 +1 @@ +module github.com/spf13/afero diff --git a/vendor/github.com/spf13/afero/httpFs.go b/vendor/github.com/spf13/afero/httpFs.go new file mode 100644 index 0000000..c421936 --- /dev/null +++ b/vendor/github.com/spf13/afero/httpFs.go @@ -0,0 +1,110 @@ +// Copyright © 2014 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "errors" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +type httpDir struct { + basePath string + fs HttpFs +} + +func (d httpDir) Open(name string) (http.File, error) { + if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || + strings.Contains(name, "\x00") { + return nil, errors.New("http: invalid character in file path") + } + dir := string(d.basePath) + if dir == "" { + dir = "." + } + + f, err := d.fs.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))) + if err != nil { + return nil, err + } + return f, nil +} + +type HttpFs struct { + source Fs +} + +func NewHttpFs(source Fs) *HttpFs { + return &HttpFs{source: source} +} + +func (h HttpFs) Dir(s string) *httpDir { + return &httpDir{basePath: s, fs: h} +} + +func (h HttpFs) Name() string { return "h HttpFs" } + +func (h HttpFs) Create(name string) (File, error) { + return h.source.Create(name) +} + +func (h HttpFs) Chmod(name string, mode os.FileMode) error { + return h.source.Chmod(name, mode) +} + +func (h HttpFs) Chtimes(name string, atime time.Time, mtime time.Time) error { + return h.source.Chtimes(name, atime, mtime) +} + +func (h HttpFs) Mkdir(name string, perm os.FileMode) error { + return h.source.Mkdir(name, perm) +} + +func (h HttpFs) MkdirAll(path string, perm os.FileMode) error { + return h.source.MkdirAll(path, perm) +} + +func (h HttpFs) Open(name string) (http.File, error) { + f, err := h.source.Open(name) + if err == nil { + if httpfile, ok := f.(http.File); ok { + return httpfile, nil + } + } + return nil, err +} + +func (h HttpFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + return h.source.OpenFile(name, flag, perm) +} + +func (h HttpFs) Remove(name string) error { + return h.source.Remove(name) +} + +func (h HttpFs) RemoveAll(path string) error { + return h.source.RemoveAll(path) +} + +func (h HttpFs) Rename(oldname, newname string) error { + return h.source.Rename(oldname, newname) +} + +func (h HttpFs) Stat(name string) (os.FileInfo, error) { + return h.source.Stat(name) +} diff --git a/vendor/github.com/spf13/afero/ioutil.go b/vendor/github.com/spf13/afero/ioutil.go new file mode 100644 index 0000000..5c3a3d8 --- /dev/null +++ b/vendor/github.com/spf13/afero/ioutil.go @@ -0,0 +1,230 @@ +// Copyright ©2015 The Go Authors +// Copyright ©2015 Steve Francia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "bytes" + "io" + "os" + "path/filepath" + "sort" + "strconv" + "sync" + "time" +) + +// byName implements sort.Interface. +type byName []os.FileInfo + +func (f byName) Len() int { return len(f) } +func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } +func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } + +// ReadDir reads the directory named by dirname and returns +// a list of sorted directory entries. +func (a Afero) ReadDir(dirname string) ([]os.FileInfo, error) { + return ReadDir(a.Fs, dirname) +} + +func ReadDir(fs Fs, dirname string) ([]os.FileInfo, error) { + f, err := fs.Open(dirname) + if err != nil { + return nil, err + } + list, err := f.Readdir(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Sort(byName(list)) + return list, nil +} + +// ReadFile reads the file named by filename and returns the contents. +// A successful call returns err == nil, not err == EOF. Because ReadFile +// reads the whole file, it does not treat an EOF from Read as an error +// to be reported. +func (a Afero) ReadFile(filename string) ([]byte, error) { + return ReadFile(a.Fs, filename) +} + +func ReadFile(fs Fs, filename string) ([]byte, error) { + f, err := fs.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + // It's a good but not certain bet that FileInfo will tell us exactly how much to + // read, so let's try it but be prepared for the answer to be wrong. + var n int64 + + if fi, err := f.Stat(); err == nil { + // Don't preallocate a huge buffer, just in case. + if size := fi.Size(); size < 1e9 { + n = size + } + } + // As initial capacity for readAll, use n + a little extra in case Size is zero, + // and to avoid another allocation after Read has filled the buffer. The readAll + // call will read into its allocated internal buffer cheaply. If the size was + // wrong, we'll either waste some space off the end or reallocate as needed, but + // in the overwhelmingly common case we'll get it just right. + return readAll(f, n+bytes.MinRead) +} + +// readAll reads from r until an error or EOF and returns the data it read +// from the internal buffer allocated with a specified capacity. +func readAll(r io.Reader, capacity int64) (b []byte, err error) { + buf := bytes.NewBuffer(make([]byte, 0, capacity)) + // If the buffer overflows, we will get bytes.ErrTooLarge. + // Return that as an error. Any other panic remains. + defer func() { + e := recover() + if e == nil { + return + } + if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { + err = panicErr + } else { + panic(e) + } + }() + _, err = buf.ReadFrom(r) + return buf.Bytes(), err +} + +// ReadAll reads from r until an error or EOF and returns the data it read. +// A successful call returns err == nil, not err == EOF. Because ReadAll is +// defined to read from src until EOF, it does not treat an EOF from Read +// as an error to be reported. +func ReadAll(r io.Reader) ([]byte, error) { + return readAll(r, bytes.MinRead) +} + +// WriteFile writes data to a file named by filename. +// If the file does not exist, WriteFile creates it with permissions perm; +// otherwise WriteFile truncates it before writing. +func (a Afero) WriteFile(filename string, data []byte, perm os.FileMode) error { + return WriteFile(a.Fs, filename, data, perm) +} + +func WriteFile(fs Fs, filename string, data []byte, perm os.FileMode) error { + f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + n, err := f.Write(data) + if err == nil && n < len(data) { + err = io.ErrShortWrite + } + if err1 := f.Close(); err == nil { + err = err1 + } + return err +} + +// Random number state. +// We generate random temporary file names so that there's a good +// chance the file doesn't exist yet - keeps the number of tries in +// TempFile to a minimum. +var rand uint32 +var randmu sync.Mutex + +func reseed() uint32 { + return uint32(time.Now().UnixNano() + int64(os.Getpid())) +} + +func nextSuffix() string { + randmu.Lock() + r := rand + if r == 0 { + r = reseed() + } + r = r*1664525 + 1013904223 // constants from Numerical Recipes + rand = r + randmu.Unlock() + return strconv.Itoa(int(1e9 + r%1e9))[1:] +} + +// TempFile creates a new temporary file in the directory dir +// with a name beginning with prefix, opens the file for reading +// and writing, and returns the resulting *File. +// If dir is the empty string, TempFile uses the default directory +// for temporary files (see os.TempDir). +// Multiple programs calling TempFile simultaneously +// will not choose the same file. The caller can use f.Name() +// to find the pathname of the file. It is the caller's responsibility +// to remove the file when no longer needed. +func (a Afero) TempFile(dir, prefix string) (f File, err error) { + return TempFile(a.Fs, dir, prefix) +} + +func TempFile(fs Fs, dir, prefix string) (f File, err error) { + if dir == "" { + dir = os.TempDir() + } + + nconflict := 0 + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, prefix+nextSuffix()) + f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if os.IsExist(err) { + if nconflict++; nconflict > 10 { + randmu.Lock() + rand = reseed() + randmu.Unlock() + } + continue + } + break + } + return +} + +// TempDir creates a new temporary directory in the directory dir +// with a name beginning with prefix and returns the path of the +// new directory. If dir is the empty string, TempDir uses the +// default directory for temporary files (see os.TempDir). +// Multiple programs calling TempDir simultaneously +// will not choose the same directory. It is the caller's responsibility +// to remove the directory when no longer needed. +func (a Afero) TempDir(dir, prefix string) (name string, err error) { + return TempDir(a.Fs, dir, prefix) +} +func TempDir(fs Fs, dir, prefix string) (name string, err error) { + if dir == "" { + dir = os.TempDir() + } + + nconflict := 0 + for i := 0; i < 10000; i++ { + try := filepath.Join(dir, prefix+nextSuffix()) + err = fs.Mkdir(try, 0700) + if os.IsExist(err) { + if nconflict++; nconflict > 10 { + randmu.Lock() + rand = reseed() + randmu.Unlock() + } + continue + } + if err == nil { + name = try + } + break + } + return +} diff --git a/vendor/github.com/spf13/afero/lstater.go b/vendor/github.com/spf13/afero/lstater.go new file mode 100644 index 0000000..89c1bfc --- /dev/null +++ b/vendor/github.com/spf13/afero/lstater.go @@ -0,0 +1,27 @@ +// Copyright © 2018 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "os" +) + +// Lstater is an optional interface in Afero. It is only implemented by the +// filesystems saying so. +// It will call Lstat if the filesystem iself is, or it delegates to, the os filesystem. +// Else it will call Stat. +// In addtion to the FileInfo, it will return a boolean telling whether Lstat was called or not. +type Lstater interface { + LstatIfPossible(name string) (os.FileInfo, bool, error) +} diff --git a/vendor/github.com/spf13/afero/match.go b/vendor/github.com/spf13/afero/match.go new file mode 100644 index 0000000..c18a87f --- /dev/null +++ b/vendor/github.com/spf13/afero/match.go @@ -0,0 +1,110 @@ +// Copyright © 2014 Steve Francia . +// Copyright 2009 The Go Authors. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "path/filepath" + "sort" + "strings" +) + +// Glob returns the names of all files matching pattern or nil +// if there is no matching file. The syntax of patterns is the same +// as in Match. The pattern may describe hierarchical names such as +// /usr/*/bin/ed (assuming the Separator is '/'). +// +// Glob ignores file system errors such as I/O errors reading directories. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +// +// This was adapted from (http://golang.org/pkg/path/filepath) and uses several +// built-ins from that package. +func Glob(fs Fs, pattern string) (matches []string, err error) { + if !hasMeta(pattern) { + // Lstat not supported by a ll filesystems. + if _, err = lstatIfPossible(fs, pattern); err != nil { + return nil, nil + } + return []string{pattern}, nil + } + + dir, file := filepath.Split(pattern) + switch dir { + case "": + dir = "." + case string(filepath.Separator): + // nothing + default: + dir = dir[0 : len(dir)-1] // chop off trailing separator + } + + if !hasMeta(dir) { + return glob(fs, dir, file, nil) + } + + var m []string + m, err = Glob(fs, dir) + if err != nil { + return + } + for _, d := range m { + matches, err = glob(fs, d, file, matches) + if err != nil { + return + } + } + return +} + +// glob searches for files matching pattern in the directory dir +// and appends them to matches. If the directory cannot be +// opened, it returns the existing matches. New matches are +// added in lexicographical order. +func glob(fs Fs, dir, pattern string, matches []string) (m []string, e error) { + m = matches + fi, err := fs.Stat(dir) + if err != nil { + return + } + if !fi.IsDir() { + return + } + d, err := fs.Open(dir) + if err != nil { + return + } + defer d.Close() + + names, _ := d.Readdirnames(-1) + sort.Strings(names) + + for _, n := range names { + matched, err := filepath.Match(pattern, n) + if err != nil { + return m, err + } + if matched { + m = append(m, filepath.Join(dir, n)) + } + } + return +} + +// hasMeta reports whether path contains any of the magic characters +// recognized by Match. +func hasMeta(path string) bool { + // TODO(niemeyer): Should other magic characters be added here? + return strings.IndexAny(path, "*?[") >= 0 +} diff --git a/vendor/github.com/spf13/afero/mem/dir.go b/vendor/github.com/spf13/afero/mem/dir.go new file mode 100644 index 0000000..e104013 --- /dev/null +++ b/vendor/github.com/spf13/afero/mem/dir.go @@ -0,0 +1,37 @@ +// Copyright © 2014 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mem + +type Dir interface { + Len() int + Names() []string + Files() []*FileData + Add(*FileData) + Remove(*FileData) +} + +func RemoveFromMemDir(dir *FileData, f *FileData) { + dir.memDir.Remove(f) +} + +func AddToMemDir(dir *FileData, f *FileData) { + dir.memDir.Add(f) +} + +func InitializeDir(d *FileData) { + if d.memDir == nil { + d.dir = true + d.memDir = &DirMap{} + } +} diff --git a/vendor/github.com/spf13/afero/mem/dirmap.go b/vendor/github.com/spf13/afero/mem/dirmap.go new file mode 100644 index 0000000..03a57ee --- /dev/null +++ b/vendor/github.com/spf13/afero/mem/dirmap.go @@ -0,0 +1,43 @@ +// Copyright © 2015 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mem + +import "sort" + +type DirMap map[string]*FileData + +func (m DirMap) Len() int { return len(m) } +func (m DirMap) Add(f *FileData) { m[f.name] = f } +func (m DirMap) Remove(f *FileData) { delete(m, f.name) } +func (m DirMap) Files() (files []*FileData) { + for _, f := range m { + files = append(files, f) + } + sort.Sort(filesSorter(files)) + return files +} + +// implement sort.Interface for []*FileData +type filesSorter []*FileData + +func (s filesSorter) Len() int { return len(s) } +func (s filesSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s filesSorter) Less(i, j int) bool { return s[i].name < s[j].name } + +func (m DirMap) Names() (names []string) { + for x := range m { + names = append(names, x) + } + return names +} diff --git a/vendor/github.com/spf13/afero/mem/file.go b/vendor/github.com/spf13/afero/mem/file.go new file mode 100644 index 0000000..7af2fb5 --- /dev/null +++ b/vendor/github.com/spf13/afero/mem/file.go @@ -0,0 +1,317 @@ +// Copyright © 2015 Steve Francia . +// Copyright 2013 tsuru authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mem + +import ( + "bytes" + "errors" + "io" + "os" + "path/filepath" + "sync" + "sync/atomic" +) + +import "time" + +const FilePathSeparator = string(filepath.Separator) + +type File struct { + // atomic requires 64-bit alignment for struct field access + at int64 + readDirCount int64 + closed bool + readOnly bool + fileData *FileData +} + +func NewFileHandle(data *FileData) *File { + return &File{fileData: data} +} + +func NewReadOnlyFileHandle(data *FileData) *File { + return &File{fileData: data, readOnly: true} +} + +func (f File) Data() *FileData { + return f.fileData +} + +type FileData struct { + sync.Mutex + name string + data []byte + memDir Dir + dir bool + mode os.FileMode + modtime time.Time +} + +func (d *FileData) Name() string { + d.Lock() + defer d.Unlock() + return d.name +} + +func CreateFile(name string) *FileData { + return &FileData{name: name, mode: os.ModeTemporary, modtime: time.Now()} +} + +func CreateDir(name string) *FileData { + return &FileData{name: name, memDir: &DirMap{}, dir: true} +} + +func ChangeFileName(f *FileData, newname string) { + f.Lock() + f.name = newname + f.Unlock() +} + +func SetMode(f *FileData, mode os.FileMode) { + f.Lock() + f.mode = mode + f.Unlock() +} + +func SetModTime(f *FileData, mtime time.Time) { + f.Lock() + setModTime(f, mtime) + f.Unlock() +} + +func setModTime(f *FileData, mtime time.Time) { + f.modtime = mtime +} + +func GetFileInfo(f *FileData) *FileInfo { + return &FileInfo{f} +} + +func (f *File) Open() error { + atomic.StoreInt64(&f.at, 0) + atomic.StoreInt64(&f.readDirCount, 0) + f.fileData.Lock() + f.closed = false + f.fileData.Unlock() + return nil +} + +func (f *File) Close() error { + f.fileData.Lock() + f.closed = true + if !f.readOnly { + setModTime(f.fileData, time.Now()) + } + f.fileData.Unlock() + return nil +} + +func (f *File) Name() string { + return f.fileData.Name() +} + +func (f *File) Stat() (os.FileInfo, error) { + return &FileInfo{f.fileData}, nil +} + +func (f *File) Sync() error { + return nil +} + +func (f *File) Readdir(count int) (res []os.FileInfo, err error) { + if !f.fileData.dir { + return nil, &os.PathError{Op: "readdir", Path: f.fileData.name, Err: errors.New("not a dir")} + } + var outLength int64 + + f.fileData.Lock() + files := f.fileData.memDir.Files()[f.readDirCount:] + if count > 0 { + if len(files) < count { + outLength = int64(len(files)) + } else { + outLength = int64(count) + } + if len(files) == 0 { + err = io.EOF + } + } else { + outLength = int64(len(files)) + } + f.readDirCount += outLength + f.fileData.Unlock() + + res = make([]os.FileInfo, outLength) + for i := range res { + res[i] = &FileInfo{files[i]} + } + + return res, err +} + +func (f *File) Readdirnames(n int) (names []string, err error) { + fi, err := f.Readdir(n) + names = make([]string, len(fi)) + for i, f := range fi { + _, names[i] = filepath.Split(f.Name()) + } + return names, err +} + +func (f *File) Read(b []byte) (n int, err error) { + f.fileData.Lock() + defer f.fileData.Unlock() + if f.closed == true { + return 0, ErrFileClosed + } + if len(b) > 0 && int(f.at) == len(f.fileData.data) { + return 0, io.EOF + } + if int(f.at) > len(f.fileData.data) { + return 0, io.ErrUnexpectedEOF + } + if len(f.fileData.data)-int(f.at) >= len(b) { + n = len(b) + } else { + n = len(f.fileData.data) - int(f.at) + } + copy(b, f.fileData.data[f.at:f.at+int64(n)]) + atomic.AddInt64(&f.at, int64(n)) + return +} + +func (f *File) ReadAt(b []byte, off int64) (n int, err error) { + atomic.StoreInt64(&f.at, off) + return f.Read(b) +} + +func (f *File) Truncate(size int64) error { + if f.closed == true { + return ErrFileClosed + } + if f.readOnly { + return &os.PathError{Op: "truncate", Path: f.fileData.name, Err: errors.New("file handle is read only")} + } + if size < 0 { + return ErrOutOfRange + } + if size > int64(len(f.fileData.data)) { + diff := size - int64(len(f.fileData.data)) + f.fileData.data = append(f.fileData.data, bytes.Repeat([]byte{00}, int(diff))...) + } else { + f.fileData.data = f.fileData.data[0:size] + } + setModTime(f.fileData, time.Now()) + return nil +} + +func (f *File) Seek(offset int64, whence int) (int64, error) { + if f.closed == true { + return 0, ErrFileClosed + } + switch whence { + case 0: + atomic.StoreInt64(&f.at, offset) + case 1: + atomic.AddInt64(&f.at, int64(offset)) + case 2: + atomic.StoreInt64(&f.at, int64(len(f.fileData.data))+offset) + } + return f.at, nil +} + +func (f *File) Write(b []byte) (n int, err error) { + if f.readOnly { + return 0, &os.PathError{Op: "write", Path: f.fileData.name, Err: errors.New("file handle is read only")} + } + n = len(b) + cur := atomic.LoadInt64(&f.at) + f.fileData.Lock() + defer f.fileData.Unlock() + diff := cur - int64(len(f.fileData.data)) + var tail []byte + if n+int(cur) < len(f.fileData.data) { + tail = f.fileData.data[n+int(cur):] + } + if diff > 0 { + f.fileData.data = append(bytes.Repeat([]byte{00}, int(diff)), b...) + f.fileData.data = append(f.fileData.data, tail...) + } else { + f.fileData.data = append(f.fileData.data[:cur], b...) + f.fileData.data = append(f.fileData.data, tail...) + } + setModTime(f.fileData, time.Now()) + + atomic.StoreInt64(&f.at, int64(len(f.fileData.data))) + return +} + +func (f *File) WriteAt(b []byte, off int64) (n int, err error) { + atomic.StoreInt64(&f.at, off) + return f.Write(b) +} + +func (f *File) WriteString(s string) (ret int, err error) { + return f.Write([]byte(s)) +} + +func (f *File) Info() *FileInfo { + return &FileInfo{f.fileData} +} + +type FileInfo struct { + *FileData +} + +// Implements os.FileInfo +func (s *FileInfo) Name() string { + s.Lock() + _, name := filepath.Split(s.name) + s.Unlock() + return name +} +func (s *FileInfo) Mode() os.FileMode { + s.Lock() + defer s.Unlock() + return s.mode +} +func (s *FileInfo) ModTime() time.Time { + s.Lock() + defer s.Unlock() + return s.modtime +} +func (s *FileInfo) IsDir() bool { + s.Lock() + defer s.Unlock() + return s.dir +} +func (s *FileInfo) Sys() interface{} { return nil } +func (s *FileInfo) Size() int64 { + if s.IsDir() { + return int64(42) + } + s.Lock() + defer s.Unlock() + return int64(len(s.data)) +} + +var ( + ErrFileClosed = errors.New("File is closed") + ErrOutOfRange = errors.New("Out of range") + ErrTooLarge = errors.New("Too large") + ErrFileNotFound = os.ErrNotExist + ErrFileExists = os.ErrExist + ErrDestinationExists = os.ErrExist +) diff --git a/vendor/github.com/spf13/afero/memmap.go b/vendor/github.com/spf13/afero/memmap.go new file mode 100644 index 0000000..09498e7 --- /dev/null +++ b/vendor/github.com/spf13/afero/memmap.go @@ -0,0 +1,365 @@ +// Copyright © 2014 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/spf13/afero/mem" +) + +type MemMapFs struct { + mu sync.RWMutex + data map[string]*mem.FileData + init sync.Once +} + +func NewMemMapFs() Fs { + return &MemMapFs{} +} + +func (m *MemMapFs) getData() map[string]*mem.FileData { + m.init.Do(func() { + m.data = make(map[string]*mem.FileData) + // Root should always exist, right? + // TODO: what about windows? + m.data[FilePathSeparator] = mem.CreateDir(FilePathSeparator) + }) + return m.data +} + +func (*MemMapFs) Name() string { return "MemMapFS" } + +func (m *MemMapFs) Create(name string) (File, error) { + name = normalizePath(name) + m.mu.Lock() + file := mem.CreateFile(name) + m.getData()[name] = file + m.registerWithParent(file) + m.mu.Unlock() + return mem.NewFileHandle(file), nil +} + +func (m *MemMapFs) unRegisterWithParent(fileName string) error { + f, err := m.lockfreeOpen(fileName) + if err != nil { + return err + } + parent := m.findParent(f) + if parent == nil { + log.Panic("parent of ", f.Name(), " is nil") + } + + parent.Lock() + mem.RemoveFromMemDir(parent, f) + parent.Unlock() + return nil +} + +func (m *MemMapFs) findParent(f *mem.FileData) *mem.FileData { + pdir, _ := filepath.Split(f.Name()) + pdir = filepath.Clean(pdir) + pfile, err := m.lockfreeOpen(pdir) + if err != nil { + return nil + } + return pfile +} + +func (m *MemMapFs) registerWithParent(f *mem.FileData) { + if f == nil { + return + } + parent := m.findParent(f) + if parent == nil { + pdir := filepath.Dir(filepath.Clean(f.Name())) + err := m.lockfreeMkdir(pdir, 0777) + if err != nil { + //log.Println("Mkdir error:", err) + return + } + parent, err = m.lockfreeOpen(pdir) + if err != nil { + //log.Println("Open after Mkdir error:", err) + return + } + } + + parent.Lock() + mem.InitializeDir(parent) + mem.AddToMemDir(parent, f) + parent.Unlock() +} + +func (m *MemMapFs) lockfreeMkdir(name string, perm os.FileMode) error { + name = normalizePath(name) + x, ok := m.getData()[name] + if ok { + // Only return ErrFileExists if it's a file, not a directory. + i := mem.FileInfo{FileData: x} + if !i.IsDir() { + return ErrFileExists + } + } else { + item := mem.CreateDir(name) + m.getData()[name] = item + m.registerWithParent(item) + } + return nil +} + +func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error { + name = normalizePath(name) + + m.mu.RLock() + _, ok := m.getData()[name] + m.mu.RUnlock() + if ok { + return &os.PathError{Op: "mkdir", Path: name, Err: ErrFileExists} + } + + m.mu.Lock() + item := mem.CreateDir(name) + m.getData()[name] = item + m.registerWithParent(item) + m.mu.Unlock() + + m.Chmod(name, perm|os.ModeDir) + + return nil +} + +func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error { + err := m.Mkdir(path, perm) + if err != nil { + if err.(*os.PathError).Err == ErrFileExists { + return nil + } + return err + } + return nil +} + +// Handle some relative paths +func normalizePath(path string) string { + path = filepath.Clean(path) + + switch path { + case ".": + return FilePathSeparator + case "..": + return FilePathSeparator + default: + return path + } +} + +func (m *MemMapFs) Open(name string) (File, error) { + f, err := m.open(name) + if f != nil { + return mem.NewReadOnlyFileHandle(f), err + } + return nil, err +} + +func (m *MemMapFs) openWrite(name string) (File, error) { + f, err := m.open(name) + if f != nil { + return mem.NewFileHandle(f), err + } + return nil, err +} + +func (m *MemMapFs) open(name string) (*mem.FileData, error) { + name = normalizePath(name) + + m.mu.RLock() + f, ok := m.getData()[name] + m.mu.RUnlock() + if !ok { + return nil, &os.PathError{Op: "open", Path: name, Err: ErrFileNotFound} + } + return f, nil +} + +func (m *MemMapFs) lockfreeOpen(name string) (*mem.FileData, error) { + name = normalizePath(name) + f, ok := m.getData()[name] + if ok { + return f, nil + } else { + return nil, ErrFileNotFound + } +} + +func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + chmod := false + file, err := m.openWrite(name) + if os.IsNotExist(err) && (flag&os.O_CREATE > 0) { + file, err = m.Create(name) + chmod = true + } + if err != nil { + return nil, err + } + if flag == os.O_RDONLY { + file = mem.NewReadOnlyFileHandle(file.(*mem.File).Data()) + } + if flag&os.O_APPEND > 0 { + _, err = file.Seek(0, os.SEEK_END) + if err != nil { + file.Close() + return nil, err + } + } + if flag&os.O_TRUNC > 0 && flag&(os.O_RDWR|os.O_WRONLY) > 0 { + err = file.Truncate(0) + if err != nil { + file.Close() + return nil, err + } + } + if chmod { + m.Chmod(name, perm) + } + return file, nil +} + +func (m *MemMapFs) Remove(name string) error { + name = normalizePath(name) + + m.mu.Lock() + defer m.mu.Unlock() + + if _, ok := m.getData()[name]; ok { + err := m.unRegisterWithParent(name) + if err != nil { + return &os.PathError{Op: "remove", Path: name, Err: err} + } + delete(m.getData(), name) + } else { + return &os.PathError{Op: "remove", Path: name, Err: os.ErrNotExist} + } + return nil +} + +func (m *MemMapFs) RemoveAll(path string) error { + path = normalizePath(path) + m.mu.Lock() + m.unRegisterWithParent(path) + m.mu.Unlock() + + m.mu.RLock() + defer m.mu.RUnlock() + + for p, _ := range m.getData() { + if strings.HasPrefix(p, path) { + m.mu.RUnlock() + m.mu.Lock() + delete(m.getData(), p) + m.mu.Unlock() + m.mu.RLock() + } + } + return nil +} + +func (m *MemMapFs) Rename(oldname, newname string) error { + oldname = normalizePath(oldname) + newname = normalizePath(newname) + + if oldname == newname { + return nil + } + + m.mu.RLock() + defer m.mu.RUnlock() + if _, ok := m.getData()[oldname]; ok { + m.mu.RUnlock() + m.mu.Lock() + m.unRegisterWithParent(oldname) + fileData := m.getData()[oldname] + delete(m.getData(), oldname) + mem.ChangeFileName(fileData, newname) + m.getData()[newname] = fileData + m.registerWithParent(fileData) + m.mu.Unlock() + m.mu.RLock() + } else { + return &os.PathError{Op: "rename", Path: oldname, Err: ErrFileNotFound} + } + return nil +} + +func (m *MemMapFs) Stat(name string) (os.FileInfo, error) { + f, err := m.Open(name) + if err != nil { + return nil, err + } + fi := mem.GetFileInfo(f.(*mem.File).Data()) + return fi, nil +} + +func (m *MemMapFs) Chmod(name string, mode os.FileMode) error { + name = normalizePath(name) + + m.mu.RLock() + f, ok := m.getData()[name] + m.mu.RUnlock() + if !ok { + return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound} + } + + m.mu.Lock() + mem.SetMode(f, mode) + m.mu.Unlock() + + return nil +} + +func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error { + name = normalizePath(name) + + m.mu.RLock() + f, ok := m.getData()[name] + m.mu.RUnlock() + if !ok { + return &os.PathError{Op: "chtimes", Path: name, Err: ErrFileNotFound} + } + + m.mu.Lock() + mem.SetModTime(f, mtime) + m.mu.Unlock() + + return nil +} + +func (m *MemMapFs) List() { + for _, x := range m.data { + y := mem.FileInfo{FileData: x} + fmt.Println(x.Name(), y.Size()) + } +} + +// func debugMemMapList(fs Fs) { +// if x, ok := fs.(*MemMapFs); ok { +// x.List() +// } +// } diff --git a/vendor/github.com/spf13/afero/os.go b/vendor/github.com/spf13/afero/os.go new file mode 100644 index 0000000..13cc1b8 --- /dev/null +++ b/vendor/github.com/spf13/afero/os.go @@ -0,0 +1,101 @@ +// Copyright © 2014 Steve Francia . +// Copyright 2013 tsuru authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "os" + "time" +) + +var _ Lstater = (*OsFs)(nil) + +// OsFs is a Fs implementation that uses functions provided by the os package. +// +// For details in any method, check the documentation of the os package +// (http://golang.org/pkg/os/). +type OsFs struct{} + +func NewOsFs() Fs { + return &OsFs{} +} + +func (OsFs) Name() string { return "OsFs" } + +func (OsFs) Create(name string) (File, error) { + f, e := os.Create(name) + if f == nil { + // while this looks strange, we need to return a bare nil (of type nil) not + // a nil value of type *os.File or nil won't be nil + return nil, e + } + return f, e +} + +func (OsFs) Mkdir(name string, perm os.FileMode) error { + return os.Mkdir(name, perm) +} + +func (OsFs) MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} + +func (OsFs) Open(name string) (File, error) { + f, e := os.Open(name) + if f == nil { + // while this looks strange, we need to return a bare nil (of type nil) not + // a nil value of type *os.File or nil won't be nil + return nil, e + } + return f, e +} + +func (OsFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + f, e := os.OpenFile(name, flag, perm) + if f == nil { + // while this looks strange, we need to return a bare nil (of type nil) not + // a nil value of type *os.File or nil won't be nil + return nil, e + } + return f, e +} + +func (OsFs) Remove(name string) error { + return os.Remove(name) +} + +func (OsFs) RemoveAll(path string) error { + return os.RemoveAll(path) +} + +func (OsFs) Rename(oldname, newname string) error { + return os.Rename(oldname, newname) +} + +func (OsFs) Stat(name string) (os.FileInfo, error) { + return os.Stat(name) +} + +func (OsFs) Chmod(name string, mode os.FileMode) error { + return os.Chmod(name, mode) +} + +func (OsFs) Chtimes(name string, atime time.Time, mtime time.Time) error { + return os.Chtimes(name, atime, mtime) +} + +func (OsFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + fi, err := os.Lstat(name) + return fi, true, err +} diff --git a/vendor/github.com/spf13/afero/path.go b/vendor/github.com/spf13/afero/path.go new file mode 100644 index 0000000..18f60a0 --- /dev/null +++ b/vendor/github.com/spf13/afero/path.go @@ -0,0 +1,106 @@ +// Copyright ©2015 The Go Authors +// Copyright ©2015 Steve Francia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "os" + "path/filepath" + "sort" +) + +// readDirNames reads the directory named by dirname and returns +// a sorted list of directory entries. +// adapted from https://golang.org/src/path/filepath/path.go +func readDirNames(fs Fs, dirname string) ([]string, error) { + f, err := fs.Open(dirname) + if err != nil { + return nil, err + } + names, err := f.Readdirnames(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +// walk recursively descends path, calling walkFn +// adapted from https://golang.org/src/path/filepath/path.go +func walk(fs Fs, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + err := walkFn(path, info, nil) + if err != nil { + if info.IsDir() && err == filepath.SkipDir { + return nil + } + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(fs, path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := lstatIfPossible(fs, filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = walk(fs, filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// if the filesystem supports it, use Lstat, else use fs.Stat +func lstatIfPossible(fs Fs, path string) (os.FileInfo, error) { + if lfs, ok := fs.(Lstater); ok { + fi, _, err := lfs.LstatIfPossible(path) + return fi, err + } + return fs.Stat(path) +} + +// Walk walks the file tree rooted at root, calling walkFn for each file or +// directory in the tree, including root. All errors that arise visiting files +// and directories are filtered by walkFn. The files are walked in lexical +// order, which makes the output deterministic but means that for very +// large directories Walk can be inefficient. +// Walk does not follow symbolic links. + +func (a Afero) Walk(root string, walkFn filepath.WalkFunc) error { + return Walk(a.Fs, root, walkFn) +} + +func Walk(fs Fs, root string, walkFn filepath.WalkFunc) error { + info, err := lstatIfPossible(fs, root) + if err != nil { + return walkFn(root, nil, err) + } + return walk(fs, root, info, walkFn) +} diff --git a/vendor/github.com/spf13/afero/readonlyfs.go b/vendor/github.com/spf13/afero/readonlyfs.go new file mode 100644 index 0000000..c6376ec --- /dev/null +++ b/vendor/github.com/spf13/afero/readonlyfs.go @@ -0,0 +1,80 @@ +package afero + +import ( + "os" + "syscall" + "time" +) + +var _ Lstater = (*ReadOnlyFs)(nil) + +type ReadOnlyFs struct { + source Fs +} + +func NewReadOnlyFs(source Fs) Fs { + return &ReadOnlyFs{source: source} +} + +func (r *ReadOnlyFs) ReadDir(name string) ([]os.FileInfo, error) { + return ReadDir(r.source, name) +} + +func (r *ReadOnlyFs) Chtimes(n string, a, m time.Time) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) Chmod(n string, m os.FileMode) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) Name() string { + return "ReadOnlyFilter" +} + +func (r *ReadOnlyFs) Stat(name string) (os.FileInfo, error) { + return r.source.Stat(name) +} + +func (r *ReadOnlyFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + if lsf, ok := r.source.(Lstater); ok { + return lsf.LstatIfPossible(name) + } + fi, err := r.Stat(name) + return fi, false, err +} + +func (r *ReadOnlyFs) Rename(o, n string) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) RemoveAll(p string) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) Remove(n string) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { + return nil, syscall.EPERM + } + return r.source.OpenFile(name, flag, perm) +} + +func (r *ReadOnlyFs) Open(n string) (File, error) { + return r.source.Open(n) +} + +func (r *ReadOnlyFs) Mkdir(n string, p os.FileMode) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) MkdirAll(n string, p os.FileMode) error { + return syscall.EPERM +} + +func (r *ReadOnlyFs) Create(n string) (File, error) { + return nil, syscall.EPERM +} diff --git a/vendor/github.com/spf13/afero/regexpfs.go b/vendor/github.com/spf13/afero/regexpfs.go new file mode 100644 index 0000000..9d92dbc --- /dev/null +++ b/vendor/github.com/spf13/afero/regexpfs.go @@ -0,0 +1,214 @@ +package afero + +import ( + "os" + "regexp" + "syscall" + "time" +) + +// The RegexpFs filters files (not directories) by regular expression. Only +// files matching the given regexp will be allowed, all others get a ENOENT error ( +// "No such file or directory"). +// +type RegexpFs struct { + re *regexp.Regexp + source Fs +} + +func NewRegexpFs(source Fs, re *regexp.Regexp) Fs { + return &RegexpFs{source: source, re: re} +} + +type RegexpFile struct { + f File + re *regexp.Regexp +} + +func (r *RegexpFs) matchesName(name string) error { + if r.re == nil { + return nil + } + if r.re.MatchString(name) { + return nil + } + return syscall.ENOENT +} + +func (r *RegexpFs) dirOrMatches(name string) error { + dir, err := IsDir(r.source, name) + if err != nil { + return err + } + if dir { + return nil + } + return r.matchesName(name) +} + +func (r *RegexpFs) Chtimes(name string, a, m time.Time) error { + if err := r.dirOrMatches(name); err != nil { + return err + } + return r.source.Chtimes(name, a, m) +} + +func (r *RegexpFs) Chmod(name string, mode os.FileMode) error { + if err := r.dirOrMatches(name); err != nil { + return err + } + return r.source.Chmod(name, mode) +} + +func (r *RegexpFs) Name() string { + return "RegexpFs" +} + +func (r *RegexpFs) Stat(name string) (os.FileInfo, error) { + if err := r.dirOrMatches(name); err != nil { + return nil, err + } + return r.source.Stat(name) +} + +func (r *RegexpFs) Rename(oldname, newname string) error { + dir, err := IsDir(r.source, oldname) + if err != nil { + return err + } + if dir { + return nil + } + if err := r.matchesName(oldname); err != nil { + return err + } + if err := r.matchesName(newname); err != nil { + return err + } + return r.source.Rename(oldname, newname) +} + +func (r *RegexpFs) RemoveAll(p string) error { + dir, err := IsDir(r.source, p) + if err != nil { + return err + } + if !dir { + if err := r.matchesName(p); err != nil { + return err + } + } + return r.source.RemoveAll(p) +} + +func (r *RegexpFs) Remove(name string) error { + if err := r.dirOrMatches(name); err != nil { + return err + } + return r.source.Remove(name) +} + +func (r *RegexpFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { + if err := r.dirOrMatches(name); err != nil { + return nil, err + } + return r.source.OpenFile(name, flag, perm) +} + +func (r *RegexpFs) Open(name string) (File, error) { + dir, err := IsDir(r.source, name) + if err != nil { + return nil, err + } + if !dir { + if err := r.matchesName(name); err != nil { + return nil, err + } + } + f, err := r.source.Open(name) + return &RegexpFile{f: f, re: r.re}, nil +} + +func (r *RegexpFs) Mkdir(n string, p os.FileMode) error { + return r.source.Mkdir(n, p) +} + +func (r *RegexpFs) MkdirAll(n string, p os.FileMode) error { + return r.source.MkdirAll(n, p) +} + +func (r *RegexpFs) Create(name string) (File, error) { + if err := r.matchesName(name); err != nil { + return nil, err + } + return r.source.Create(name) +} + +func (f *RegexpFile) Close() error { + return f.f.Close() +} + +func (f *RegexpFile) Read(s []byte) (int, error) { + return f.f.Read(s) +} + +func (f *RegexpFile) ReadAt(s []byte, o int64) (int, error) { + return f.f.ReadAt(s, o) +} + +func (f *RegexpFile) Seek(o int64, w int) (int64, error) { + return f.f.Seek(o, w) +} + +func (f *RegexpFile) Write(s []byte) (int, error) { + return f.f.Write(s) +} + +func (f *RegexpFile) WriteAt(s []byte, o int64) (int, error) { + return f.f.WriteAt(s, o) +} + +func (f *RegexpFile) Name() string { + return f.f.Name() +} + +func (f *RegexpFile) Readdir(c int) (fi []os.FileInfo, err error) { + var rfi []os.FileInfo + rfi, err = f.f.Readdir(c) + if err != nil { + return nil, err + } + for _, i := range rfi { + if i.IsDir() || f.re.MatchString(i.Name()) { + fi = append(fi, i) + } + } + return fi, nil +} + +func (f *RegexpFile) Readdirnames(c int) (n []string, err error) { + fi, err := f.Readdir(c) + if err != nil { + return nil, err + } + for _, s := range fi { + n = append(n, s.Name()) + } + return n, nil +} + +func (f *RegexpFile) Stat() (os.FileInfo, error) { + return f.f.Stat() +} + +func (f *RegexpFile) Sync() error { + return f.f.Sync() +} + +func (f *RegexpFile) Truncate(s int64) error { + return f.f.Truncate(s) +} + +func (f *RegexpFile) WriteString(s string) (int, error) { + return f.f.WriteString(s) +} diff --git a/vendor/github.com/spf13/afero/unionFile.go b/vendor/github.com/spf13/afero/unionFile.go new file mode 100644 index 0000000..1e78f7d --- /dev/null +++ b/vendor/github.com/spf13/afero/unionFile.go @@ -0,0 +1,305 @@ +package afero + +import ( + "io" + "os" + "path/filepath" + "syscall" +) + +// The UnionFile implements the afero.File interface and will be returned +// when reading a directory present at least in the overlay or opening a file +// for writing. +// +// The calls to +// Readdir() and Readdirnames() merge the file os.FileInfo / names from the +// base and the overlay - for files present in both layers, only those +// from the overlay will be used. +// +// When opening files for writing (Create() / OpenFile() with the right flags) +// the operations will be done in both layers, starting with the overlay. A +// successful read in the overlay will move the cursor position in the base layer +// by the number of bytes read. +type UnionFile struct { + Base File + Layer File + Merger DirsMerger + off int + files []os.FileInfo +} + +func (f *UnionFile) Close() error { + // first close base, so we have a newer timestamp in the overlay. If we'd close + // the overlay first, we'd get a cacheStale the next time we access this file + // -> cache would be useless ;-) + if f.Base != nil { + f.Base.Close() + } + if f.Layer != nil { + return f.Layer.Close() + } + return BADFD +} + +func (f *UnionFile) Read(s []byte) (int, error) { + if f.Layer != nil { + n, err := f.Layer.Read(s) + if (err == nil || err == io.EOF) && f.Base != nil { + // advance the file position also in the base file, the next + // call may be a write at this position (or a seek with SEEK_CUR) + if _, seekErr := f.Base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { + // only overwrite err in case the seek fails: we need to + // report an eventual io.EOF to the caller + err = seekErr + } + } + return n, err + } + if f.Base != nil { + return f.Base.Read(s) + } + return 0, BADFD +} + +func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) { + if f.Layer != nil { + n, err := f.Layer.ReadAt(s, o) + if (err == nil || err == io.EOF) && f.Base != nil { + _, err = f.Base.Seek(o+int64(n), os.SEEK_SET) + } + return n, err + } + if f.Base != nil { + return f.Base.ReadAt(s, o) + } + return 0, BADFD +} + +func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) { + if f.Layer != nil { + pos, err = f.Layer.Seek(o, w) + if (err == nil || err == io.EOF) && f.Base != nil { + _, err = f.Base.Seek(o, w) + } + return pos, err + } + if f.Base != nil { + return f.Base.Seek(o, w) + } + return 0, BADFD +} + +func (f *UnionFile) Write(s []byte) (n int, err error) { + if f.Layer != nil { + n, err = f.Layer.Write(s) + if err == nil && f.Base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark? + _, err = f.Base.Write(s) + } + return n, err + } + if f.Base != nil { + return f.Base.Write(s) + } + return 0, BADFD +} + +func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) { + if f.Layer != nil { + n, err = f.Layer.WriteAt(s, o) + if err == nil && f.Base != nil { + _, err = f.Base.WriteAt(s, o) + } + return n, err + } + if f.Base != nil { + return f.Base.WriteAt(s, o) + } + return 0, BADFD +} + +func (f *UnionFile) Name() string { + if f.Layer != nil { + return f.Layer.Name() + } + return f.Base.Name() +} + +// DirsMerger is how UnionFile weaves two directories together. +// It takes the FileInfo slices from the layer and the base and returns a +// single view. +type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) + +var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) { + var files = make(map[string]os.FileInfo) + + for _, fi := range lofi { + files[fi.Name()] = fi + } + + for _, fi := range bofi { + if _, exists := files[fi.Name()]; !exists { + files[fi.Name()] = fi + } + } + + rfi := make([]os.FileInfo, len(files)) + + i := 0 + for _, fi := range files { + rfi[i] = fi + i++ + } + + return rfi, nil + +} + +// Readdir will weave the two directories together and +// return a single view of the overlayed directories +func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) { + var merge DirsMerger = f.Merger + if merge == nil { + merge = defaultUnionMergeDirsFn + } + + if f.off == 0 { + var lfi []os.FileInfo + if f.Layer != nil { + lfi, err = f.Layer.Readdir(-1) + if err != nil { + return nil, err + } + } + + var bfi []os.FileInfo + if f.Base != nil { + bfi, err = f.Base.Readdir(-1) + if err != nil { + return nil, err + } + + } + merged, err := merge(lfi, bfi) + if err != nil { + return nil, err + } + f.files = append(f.files, merged...) + } + if c == -1 { + return f.files[f.off:], nil + } + defer func() { f.off += c }() + return f.files[f.off:c], nil +} + +func (f *UnionFile) Readdirnames(c int) ([]string, error) { + rfi, err := f.Readdir(c) + if err != nil { + return nil, err + } + var names []string + for _, fi := range rfi { + names = append(names, fi.Name()) + } + return names, nil +} + +func (f *UnionFile) Stat() (os.FileInfo, error) { + if f.Layer != nil { + return f.Layer.Stat() + } + if f.Base != nil { + return f.Base.Stat() + } + return nil, BADFD +} + +func (f *UnionFile) Sync() (err error) { + if f.Layer != nil { + err = f.Layer.Sync() + if err == nil && f.Base != nil { + err = f.Base.Sync() + } + return err + } + if f.Base != nil { + return f.Base.Sync() + } + return BADFD +} + +func (f *UnionFile) Truncate(s int64) (err error) { + if f.Layer != nil { + err = f.Layer.Truncate(s) + if err == nil && f.Base != nil { + err = f.Base.Truncate(s) + } + return err + } + if f.Base != nil { + return f.Base.Truncate(s) + } + return BADFD +} + +func (f *UnionFile) WriteString(s string) (n int, err error) { + if f.Layer != nil { + n, err = f.Layer.WriteString(s) + if err == nil && f.Base != nil { + _, err = f.Base.WriteString(s) + } + return n, err + } + if f.Base != nil { + return f.Base.WriteString(s) + } + return 0, BADFD +} + +func copyToLayer(base Fs, layer Fs, name string) error { + bfh, err := base.Open(name) + if err != nil { + return err + } + defer bfh.Close() + + // First make sure the directory exists + exists, err := Exists(layer, filepath.Dir(name)) + if err != nil { + return err + } + if !exists { + err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME? + if err != nil { + return err + } + } + + // Create the file on the overlay + lfh, err := layer.Create(name) + if err != nil { + return err + } + n, err := io.Copy(lfh, bfh) + if err != nil { + // If anything fails, clean up the file + layer.Remove(name) + lfh.Close() + return err + } + + bfi, err := bfh.Stat() + if err != nil || bfi.Size() != n { + layer.Remove(name) + lfh.Close() + return syscall.EIO + } + + err = lfh.Close() + if err != nil { + layer.Remove(name) + lfh.Close() + return err + } + return layer.Chtimes(name, bfi.ModTime(), bfi.ModTime()) +} diff --git a/vendor/github.com/spf13/afero/util.go b/vendor/github.com/spf13/afero/util.go new file mode 100644 index 0000000..4f253f4 --- /dev/null +++ b/vendor/github.com/spf13/afero/util.go @@ -0,0 +1,330 @@ +// Copyright ©2015 Steve Francia +// Portions Copyright ©2015 The Hugo Authors +// Portions Copyright 2016-present Bjørn Erik Pedersen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package afero + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "unicode" + + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" +) + +// Filepath separator defined by os.Separator. +const FilePathSeparator = string(filepath.Separator) + +// Takes a reader and a path and writes the content +func (a Afero) WriteReader(path string, r io.Reader) (err error) { + return WriteReader(a.Fs, path, r) +} + +func WriteReader(fs Fs, path string, r io.Reader) (err error) { + dir, _ := filepath.Split(path) + ospath := filepath.FromSlash(dir) + + if ospath != "" { + err = fs.MkdirAll(ospath, 0777) // rwx, rw, r + if err != nil { + if err != os.ErrExist { + return err + } + } + } + + file, err := fs.Create(path) + if err != nil { + return + } + defer file.Close() + + _, err = io.Copy(file, r) + return +} + +// Same as WriteReader but checks to see if file/directory already exists. +func (a Afero) SafeWriteReader(path string, r io.Reader) (err error) { + return SafeWriteReader(a.Fs, path, r) +} + +func SafeWriteReader(fs Fs, path string, r io.Reader) (err error) { + dir, _ := filepath.Split(path) + ospath := filepath.FromSlash(dir) + + if ospath != "" { + err = fs.MkdirAll(ospath, 0777) // rwx, rw, r + if err != nil { + return + } + } + + exists, err := Exists(fs, path) + if err != nil { + return + } + if exists { + return fmt.Errorf("%v already exists", path) + } + + file, err := fs.Create(path) + if err != nil { + return + } + defer file.Close() + + _, err = io.Copy(file, r) + return +} + +func (a Afero) GetTempDir(subPath string) string { + return GetTempDir(a.Fs, subPath) +} + +// GetTempDir returns the default temp directory with trailing slash +// if subPath is not empty then it will be created recursively with mode 777 rwx rwx rwx +func GetTempDir(fs Fs, subPath string) string { + addSlash := func(p string) string { + if FilePathSeparator != p[len(p)-1:] { + p = p + FilePathSeparator + } + return p + } + dir := addSlash(os.TempDir()) + + if subPath != "" { + // preserve windows backslash :-( + if FilePathSeparator == "\\" { + subPath = strings.Replace(subPath, "\\", "____", -1) + } + dir = dir + UnicodeSanitize((subPath)) + if FilePathSeparator == "\\" { + dir = strings.Replace(dir, "____", "\\", -1) + } + + if exists, _ := Exists(fs, dir); exists { + return addSlash(dir) + } + + err := fs.MkdirAll(dir, 0777) + if err != nil { + panic(err) + } + dir = addSlash(dir) + } + return dir +} + +// Rewrite string to remove non-standard path characters +func UnicodeSanitize(s string) string { + source := []rune(s) + target := make([]rune, 0, len(source)) + + for _, r := range source { + if unicode.IsLetter(r) || + unicode.IsDigit(r) || + unicode.IsMark(r) || + r == '.' || + r == '/' || + r == '\\' || + r == '_' || + r == '-' || + r == '%' || + r == ' ' || + r == '#' { + target = append(target, r) + } + } + + return string(target) +} + +// Transform characters with accents into plain forms. +func NeuterAccents(s string) string { + t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) + result, _, _ := transform.String(t, string(s)) + + return result +} + +func isMn(r rune) bool { + return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks +} + +func (a Afero) FileContainsBytes(filename string, subslice []byte) (bool, error) { + return FileContainsBytes(a.Fs, filename, subslice) +} + +// Check if a file contains a specified byte slice. +func FileContainsBytes(fs Fs, filename string, subslice []byte) (bool, error) { + f, err := fs.Open(filename) + if err != nil { + return false, err + } + defer f.Close() + + return readerContainsAny(f, subslice), nil +} + +func (a Afero) FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error) { + return FileContainsAnyBytes(a.Fs, filename, subslices) +} + +// Check if a file contains any of the specified byte slices. +func FileContainsAnyBytes(fs Fs, filename string, subslices [][]byte) (bool, error) { + f, err := fs.Open(filename) + if err != nil { + return false, err + } + defer f.Close() + + return readerContainsAny(f, subslices...), nil +} + +// readerContains reports whether any of the subslices is within r. +func readerContainsAny(r io.Reader, subslices ...[]byte) bool { + + if r == nil || len(subslices) == 0 { + return false + } + + largestSlice := 0 + + for _, sl := range subslices { + if len(sl) > largestSlice { + largestSlice = len(sl) + } + } + + if largestSlice == 0 { + return false + } + + bufflen := largestSlice * 4 + halflen := bufflen / 2 + buff := make([]byte, bufflen) + var err error + var n, i int + + for { + i++ + if i == 1 { + n, err = io.ReadAtLeast(r, buff[:halflen], halflen) + } else { + if i != 2 { + // shift left to catch overlapping matches + copy(buff[:], buff[halflen:]) + } + n, err = io.ReadAtLeast(r, buff[halflen:], halflen) + } + + if n > 0 { + for _, sl := range subslices { + if bytes.Contains(buff, sl) { + return true + } + } + } + + if err != nil { + break + } + } + return false +} + +func (a Afero) DirExists(path string) (bool, error) { + return DirExists(a.Fs, path) +} + +// DirExists checks if a path exists and is a directory. +func DirExists(fs Fs, path string) (bool, error) { + fi, err := fs.Stat(path) + if err == nil && fi.IsDir() { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func (a Afero) IsDir(path string) (bool, error) { + return IsDir(a.Fs, path) +} + +// IsDir checks if a given path is a directory. +func IsDir(fs Fs, path string) (bool, error) { + fi, err := fs.Stat(path) + if err != nil { + return false, err + } + return fi.IsDir(), nil +} + +func (a Afero) IsEmpty(path string) (bool, error) { + return IsEmpty(a.Fs, path) +} + +// IsEmpty checks if a given file or directory is empty. +func IsEmpty(fs Fs, path string) (bool, error) { + if b, _ := Exists(fs, path); !b { + return false, fmt.Errorf("%q path does not exist", path) + } + fi, err := fs.Stat(path) + if err != nil { + return false, err + } + if fi.IsDir() { + f, err := fs.Open(path) + if err != nil { + return false, err + } + defer f.Close() + list, err := f.Readdir(-1) + return len(list) == 0, nil + } + return fi.Size() == 0, nil +} + +func (a Afero) Exists(path string) (bool, error) { + return Exists(a.Fs, path) +} + +// Check if a file or directory exists. +func Exists(fs Fs, path string) (bool, error) { + _, err := fs.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string { + combinedPath := filepath.Join(basePathFs.path, relativePath) + if parent, ok := basePathFs.source.(*BasePathFs); ok { + return FullBaseFsPath(parent, combinedPath) + } + + return combinedPath +} diff --git a/vendor/github.com/dvsekhvalnov/jose2go/.gitignore b/vendor/github.com/spf13/jwalterweatherman/.gitignore similarity index 97% rename from vendor/github.com/dvsekhvalnov/jose2go/.gitignore rename to vendor/github.com/spf13/jwalterweatherman/.gitignore index 8365624..0026861 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/.gitignore +++ b/vendor/github.com/spf13/jwalterweatherman/.gitignore @@ -20,4 +20,3 @@ _cgo_export.* _testmain.go *.exe -*.test diff --git a/vendor/github.com/keybase/go-keychain/LICENSE b/vendor/github.com/spf13/jwalterweatherman/LICENSE similarity index 96% rename from vendor/github.com/keybase/go-keychain/LICENSE rename to vendor/github.com/spf13/jwalterweatherman/LICENSE index 2d54c65..4527efb 100644 --- a/vendor/github.com/keybase/go-keychain/LICENSE +++ b/vendor/github.com/spf13/jwalterweatherman/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Keybase +Copyright (c) 2014 Steve Francia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,5 +18,4 @@ 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. - +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/spf13/jwalterweatherman/README.md b/vendor/github.com/spf13/jwalterweatherman/README.md new file mode 100644 index 0000000..932a23f --- /dev/null +++ b/vendor/github.com/spf13/jwalterweatherman/README.md @@ -0,0 +1,148 @@ +jWalterWeatherman +================= + +Seamless printing to the terminal (stdout) and logging to a io.Writer +(file) that’s as easy to use as fmt.Println. + +![and_that__s_why_you_always_leave_a_note_by_jonnyetc-d57q7um](https://cloud.githubusercontent.com/assets/173412/11002937/ccd01654-847d-11e5-828e-12ebaf582eaf.jpg) +Graphic by [JonnyEtc](http://jonnyetc.deviantart.com/art/And-That-s-Why-You-Always-Leave-a-Note-315311422) + +JWW is primarily a wrapper around the excellent standard log library. It +provides a few advantages over using the standard log library alone. + +1. Ready to go out of the box. +2. One library for both printing to the terminal and logging (to files). +3. Really easy to log to either a temp file or a file you specify. + + +I really wanted a very straightforward library that could seamlessly do +the following things. + +1. Replace all the println, printf, etc statements thoughout my code with + something more useful +2. Allow the user to easily control what levels are printed to stdout +3. Allow the user to easily control what levels are logged +4. Provide an easy mechanism (like fmt.Println) to print info to the user + which can be easily logged as well +5. Due to 2 & 3 provide easy verbose mode for output and logs +6. Not have any unnecessary initialization cruft. Just use it. + +# Usage + +## Step 1. Use it +Put calls throughout your source based on type of feedback. +No initialization or setup needs to happen. Just start calling things. + +Available Loggers are: + + * TRACE + * DEBUG + * INFO + * WARN + * ERROR + * CRITICAL + * FATAL + +These each are loggers based on the log standard library and follow the +standard usage. Eg. + +```go + import ( + jww "github.com/spf13/jwalterweatherman" + ) + + ... + + if err != nil { + + // This is a pretty serious error and the user should know about + // it. It will be printed to the terminal as well as logged under the + // default thresholds. + + jww.ERROR.Println(err) + } + + if err2 != nil { + // This error isn’t going to materially change the behavior of the + // application, but it’s something that may not be what the user + // expects. Under the default thresholds, Warn will be logged, but + // not printed to the terminal. + + jww.WARN.Println(err2) + } + + // Information that’s relevant to what’s happening, but not very + // important for the user. Under the default thresholds this will be + // discarded. + + jww.INFO.Printf("information %q", response) + +``` + +NOTE: You can also use the library in a non-global setting by creating an instance of a Notebook: + +```go +notepad = jww.NewNotepad(jww.LevelInfo, jww.LevelTrace, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) +notepad.WARN.Println("Some warning"") +``` + +_Why 7 levels?_ + +Maybe you think that 7 levels are too much for any application... and you +are probably correct. Just because there are seven levels doesn’t mean +that you should be using all 7 levels. Pick the right set for your needs. +Remember they only have to mean something to your project. + +## Step 2. Optionally configure JWW + +Under the default thresholds : + + * Debug, Trace & Info goto /dev/null + * Warn and above is logged (when a log file/io.Writer is provided) + * Error and above is printed to the terminal (stdout) + +### Changing the thresholds + +The threshold can be changed at any time, but will only affect calls that +execute after the change was made. + +This is very useful if your application has a verbose mode. Of course you +can decide what verbose means to you or even have multiple levels of +verbosity. + + +```go + import ( + jww "github.com/spf13/jwalterweatherman" + ) + + if Verbose { + jww.SetLogThreshold(jww.LevelTrace) + jww.SetStdoutThreshold(jww.LevelInfo) + } +``` + +Note that JWW's own internal output uses log levels as well, so set the log +level before making any other calls if you want to see what it's up to. + + +### Setting a log file + +JWW can log to any `io.Writer`: + + +```go + + jww.SetLogOutput(customWriter) + +``` + + +# More information + +This is an early release. I’ve been using it for a while and this is the +third interface I’ve tried. I like this one pretty well, but no guarantees +that it won’t change a bit. + +I wrote this for use in [hugo](https://gohugo.io). If you are looking +for a static website engine that’s super fast please checkout Hugo. diff --git a/vendor/github.com/spf13/jwalterweatherman/default_notepad.go b/vendor/github.com/spf13/jwalterweatherman/default_notepad.go new file mode 100644 index 0000000..bcb7634 --- /dev/null +++ b/vendor/github.com/spf13/jwalterweatherman/default_notepad.go @@ -0,0 +1,113 @@ +// Copyright © 2016 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package jwalterweatherman + +import ( + "io" + "io/ioutil" + "log" + "os" +) + +var ( + TRACE *log.Logger + DEBUG *log.Logger + INFO *log.Logger + WARN *log.Logger + ERROR *log.Logger + CRITICAL *log.Logger + FATAL *log.Logger + + LOG *log.Logger + FEEDBACK *Feedback + + defaultNotepad *Notepad +) + +func reloadDefaultNotepad() { + TRACE = defaultNotepad.TRACE + DEBUG = defaultNotepad.DEBUG + INFO = defaultNotepad.INFO + WARN = defaultNotepad.WARN + ERROR = defaultNotepad.ERROR + CRITICAL = defaultNotepad.CRITICAL + FATAL = defaultNotepad.FATAL + + LOG = defaultNotepad.LOG + FEEDBACK = defaultNotepad.FEEDBACK +} + +func init() { + defaultNotepad = NewNotepad(LevelError, LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) + reloadDefaultNotepad() +} + +// SetLogThreshold set the log threshold for the default notepad. Trace by default. +func SetLogThreshold(threshold Threshold) { + defaultNotepad.SetLogThreshold(threshold) + reloadDefaultNotepad() +} + +// SetLogOutput set the log output for the default notepad. Discarded by default. +func SetLogOutput(handle io.Writer) { + defaultNotepad.SetLogOutput(handle) + reloadDefaultNotepad() +} + +// SetStdoutThreshold set the standard output threshold for the default notepad. +// Info by default. +func SetStdoutThreshold(threshold Threshold) { + defaultNotepad.SetStdoutThreshold(threshold) + reloadDefaultNotepad() +} + +// SetPrefix set the prefix for the default logger. Empty by default. +func SetPrefix(prefix string) { + defaultNotepad.SetPrefix(prefix) + reloadDefaultNotepad() +} + +// SetFlags set the flags for the default logger. "log.Ldate | log.Ltime" by default. +func SetFlags(flags int) { + defaultNotepad.SetFlags(flags) + reloadDefaultNotepad() +} + +// Level returns the current global log threshold. +func LogThreshold() Threshold { + return defaultNotepad.logThreshold +} + +// Level returns the current global output threshold. +func StdoutThreshold() Threshold { + return defaultNotepad.stdoutThreshold +} + +// GetStdoutThreshold returns the defined Treshold for the log logger. +func GetLogThreshold() Threshold { + return defaultNotepad.GetLogThreshold() +} + +// GetStdoutThreshold returns the Treshold for the stdout logger. +func GetStdoutThreshold() Threshold { + return defaultNotepad.GetStdoutThreshold() +} + +// LogCountForLevel returns the number of log invocations for a given threshold. +func LogCountForLevel(l Threshold) uint64 { + return defaultNotepad.LogCountForLevel(l) +} + +// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations +// greater than or equal to a given threshold. +func LogCountForLevelsGreaterThanorEqualTo(threshold Threshold) uint64 { + return defaultNotepad.LogCountForLevelsGreaterThanorEqualTo(threshold) +} + +// ResetLogCounters resets the invocation counters for all levels. +func ResetLogCounters() { + defaultNotepad.ResetLogCounters() +} diff --git a/vendor/github.com/spf13/jwalterweatherman/go.mod b/vendor/github.com/spf13/jwalterweatherman/go.mod new file mode 100644 index 0000000..bce549c --- /dev/null +++ b/vendor/github.com/spf13/jwalterweatherman/go.mod @@ -0,0 +1 @@ +module github.com/spf13/jwalterweatherman diff --git a/vendor/github.com/spf13/jwalterweatherman/log_counter.go b/vendor/github.com/spf13/jwalterweatherman/log_counter.go new file mode 100644 index 0000000..11423ac --- /dev/null +++ b/vendor/github.com/spf13/jwalterweatherman/log_counter.go @@ -0,0 +1,55 @@ +// Copyright © 2016 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package jwalterweatherman + +import ( + "sync/atomic" +) + +type logCounter struct { + counter uint64 +} + +func (c *logCounter) incr() { + atomic.AddUint64(&c.counter, 1) +} + +func (c *logCounter) resetCounter() { + atomic.StoreUint64(&c.counter, 0) +} + +func (c *logCounter) getCount() uint64 { + return atomic.LoadUint64(&c.counter) +} + +func (c *logCounter) Write(p []byte) (n int, err error) { + c.incr() + return len(p), nil +} + +// LogCountForLevel returns the number of log invocations for a given threshold. +func (n *Notepad) LogCountForLevel(l Threshold) uint64 { + return n.logCounters[l].getCount() +} + +// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations +// greater than or equal to a given threshold. +func (n *Notepad) LogCountForLevelsGreaterThanorEqualTo(threshold Threshold) uint64 { + var cnt uint64 + + for i := int(threshold); i < len(n.logCounters); i++ { + cnt += n.LogCountForLevel(Threshold(i)) + } + + return cnt +} + +// ResetLogCounters resets the invocation counters for all levels. +func (n *Notepad) ResetLogCounters() { + for _, np := range n.logCounters { + np.resetCounter() + } +} diff --git a/vendor/github.com/spf13/jwalterweatherman/notepad.go b/vendor/github.com/spf13/jwalterweatherman/notepad.go new file mode 100644 index 0000000..ae5aaf7 --- /dev/null +++ b/vendor/github.com/spf13/jwalterweatherman/notepad.go @@ -0,0 +1,194 @@ +// Copyright © 2016 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package jwalterweatherman + +import ( + "fmt" + "io" + "log" +) + +type Threshold int + +func (t Threshold) String() string { + return prefixes[t] +} + +const ( + LevelTrace Threshold = iota + LevelDebug + LevelInfo + LevelWarn + LevelError + LevelCritical + LevelFatal +) + +var prefixes map[Threshold]string = map[Threshold]string{ + LevelTrace: "TRACE", + LevelDebug: "DEBUG", + LevelInfo: "INFO", + LevelWarn: "WARN", + LevelError: "ERROR", + LevelCritical: "CRITICAL", + LevelFatal: "FATAL", +} + +// Notepad is where you leave a note! +type Notepad struct { + TRACE *log.Logger + DEBUG *log.Logger + INFO *log.Logger + WARN *log.Logger + ERROR *log.Logger + CRITICAL *log.Logger + FATAL *log.Logger + + LOG *log.Logger + FEEDBACK *Feedback + + loggers [7]**log.Logger + logHandle io.Writer + outHandle io.Writer + logThreshold Threshold + stdoutThreshold Threshold + prefix string + flags int + + // One per Threshold + logCounters [7]*logCounter +} + +// NewNotepad create a new notepad. +func NewNotepad(outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int) *Notepad { + n := &Notepad{} + + n.loggers = [7]**log.Logger{&n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL} + n.outHandle = outHandle + n.logHandle = logHandle + n.stdoutThreshold = outThreshold + n.logThreshold = logThreshold + + if len(prefix) != 0 { + n.prefix = "[" + prefix + "] " + } else { + n.prefix = "" + } + + n.flags = flags + + n.LOG = log.New(n.logHandle, + "LOG: ", + n.flags) + n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG} + + n.init() + return n +} + +// init creates the loggers for each level depending on the notepad thresholds. +func (n *Notepad) init() { + logAndOut := io.MultiWriter(n.outHandle, n.logHandle) + + for t, logger := range n.loggers { + threshold := Threshold(t) + counter := &logCounter{} + n.logCounters[t] = counter + prefix := n.prefix + threshold.String() + " " + + switch { + case threshold >= n.logThreshold && threshold >= n.stdoutThreshold: + *logger = log.New(io.MultiWriter(counter, logAndOut), prefix, n.flags) + + case threshold >= n.logThreshold: + *logger = log.New(io.MultiWriter(counter, n.logHandle), prefix, n.flags) + + case threshold >= n.stdoutThreshold: + *logger = log.New(io.MultiWriter(counter, n.outHandle), prefix, n.flags) + + default: + // counter doesn't care about prefix and flags, so don't use them + // for performance. + *logger = log.New(counter, "", 0) + } + } +} + +// SetLogThreshold changes the threshold above which messages are written to the +// log file. +func (n *Notepad) SetLogThreshold(threshold Threshold) { + n.logThreshold = threshold + n.init() +} + +// SetLogOutput changes the file where log messages are written. +func (n *Notepad) SetLogOutput(handle io.Writer) { + n.logHandle = handle + n.init() +} + +// GetStdoutThreshold returns the defined Treshold for the log logger. +func (n *Notepad) GetLogThreshold() Threshold { + return n.logThreshold +} + +// SetStdoutThreshold changes the threshold above which messages are written to the +// standard output. +func (n *Notepad) SetStdoutThreshold(threshold Threshold) { + n.stdoutThreshold = threshold + n.init() +} + +// GetStdoutThreshold returns the Treshold for the stdout logger. +func (n *Notepad) GetStdoutThreshold() Threshold { + return n.stdoutThreshold +} + +// SetPrefix changes the prefix used by the notepad. Prefixes are displayed between +// brackets at the beginning of the line. An empty prefix won't be displayed at all. +func (n *Notepad) SetPrefix(prefix string) { + if len(prefix) != 0 { + n.prefix = "[" + prefix + "] " + } else { + n.prefix = "" + } + n.init() +} + +// SetFlags choose which flags the logger will display (after prefix and message +// level). See the package log for more informations on this. +func (n *Notepad) SetFlags(flags int) { + n.flags = flags + n.init() +} + +// Feedback writes plainly to the outHandle while +// logging with the standard extra information (date, file, etc). +type Feedback struct { + out *log.Logger + log *log.Logger +} + +func (fb *Feedback) Println(v ...interface{}) { + fb.output(fmt.Sprintln(v...)) +} + +func (fb *Feedback) Printf(format string, v ...interface{}) { + fb.output(fmt.Sprintf(format, v...)) +} + +func (fb *Feedback) Print(v ...interface{}) { + fb.output(fmt.Sprint(v...)) +} + +func (fb *Feedback) output(s string) { + if fb.out != nil { + fb.out.Output(2, s) + } + if fb.log != nil { + fb.log.Output(2, s) + } +} diff --git a/vendor/github.com/keybase/go-keychain/.gitignore b/vendor/github.com/spf13/viper/.gitignore similarity index 78% rename from vendor/github.com/keybase/go-keychain/.gitignore rename to vendor/github.com/spf13/viper/.gitignore index daf913b..01b5c44 100644 --- a/vendor/github.com/keybase/go-keychain/.gitignore +++ b/vendor/github.com/spf13/viper/.gitignore @@ -21,4 +21,9 @@ _testmain.go *.exe *.test -*.prof +*.bench + +.vscode + +# exclude dependencies in the `/vendor` folder +vendor diff --git a/vendor/github.com/spf13/viper/.travis.yml b/vendor/github.com/spf13/viper/.travis.yml new file mode 100644 index 0000000..bb83057 --- /dev/null +++ b/vendor/github.com/spf13/viper/.travis.yml @@ -0,0 +1,31 @@ +go_import_path: github.com/spf13/viper + +language: go + +env: + global: + - GO111MODULE="on" + +go: + - 1.11.x + - tip + +os: + - linux + - osx + +matrix: + allow_failures: + - go: tip + fast_finish: true + +script: + - go install ./... + - diff -u <(echo -n) <(gofmt -d .) + - go test -v ./... + +after_success: + - go get -u -d github.com/spf13/hugo + - cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd - + +sudo: false diff --git a/vendor/github.com/99designs/keyring/LICENSE b/vendor/github.com/spf13/viper/LICENSE similarity index 96% rename from vendor/github.com/99designs/keyring/LICENSE rename to vendor/github.com/spf13/viper/LICENSE index 0fe9e46..4527efb 100644 --- a/vendor/github.com/99designs/keyring/LICENSE +++ b/vendor/github.com/spf13/viper/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 99designs +Copyright (c) 2014 Steve Francia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,5 +18,4 @@ 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. - +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/spf13/viper/README.md b/vendor/github.com/spf13/viper/README.md new file mode 100644 index 0000000..0208eac --- /dev/null +++ b/vendor/github.com/spf13/viper/README.md @@ -0,0 +1,691 @@ +![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png) + +Go configuration with fangs! + +Many Go projects are built using Viper including: + +* [Hugo](http://gohugo.io) +* [EMC RexRay](http://rexray.readthedocs.org/en/stable/) +* [Imgur’s Incus](https://github.com/Imgur/incus) +* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) +* [Docker Notary](https://github.com/docker/Notary) +* [BloomApi](https://www.bloomapi.com/) +* [doctl](https://github.com/digitalocean/doctl) +* [Clairctl](https://github.com/jgsqware/clairctl) + +[![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper) + + +## What is Viper? + +Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed +to work within an application, and can handle all types of configuration needs +and formats. It supports: + +* setting defaults +* reading from JSON, TOML, YAML, HCL, and Java properties config files +* live watching and re-reading of config files (optional) +* reading from environment variables +* reading from remote config systems (etcd or Consul), and watching changes +* reading from command line flags +* reading from buffer +* setting explicit values + +Viper can be thought of as a registry for all of your applications +configuration needs. + +## Why Viper? + +When building a modern application, you don’t want to worry about +configuration file formats; you want to focus on building awesome software. +Viper is here to help with that. + +Viper does the following for you: + +1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats. +2. Provide a mechanism to set default values for your different + configuration options. +3. Provide a mechanism to set override values for options specified through + command line flags. +4. Provide an alias system to easily rename parameters without breaking existing + code. +5. Make it easy to tell the difference between when a user has provided a + command line or config file which is the same as the default. + +Viper uses the following precedence order. Each item takes precedence over the +item below it: + + * explicit call to Set + * flag + * env + * config + * key/value store + * default + +Viper configuration keys are case insensitive. + +## Putting Values into Viper + +### Establishing Defaults + +A good configuration system will support default values. A default value is not +required for a key, but it’s useful in the event that a key hasn’t been set via +config file, environment variable, remote configuration or flag. + +Examples: + +```go +viper.SetDefault("ContentDir", "content") +viper.SetDefault("LayoutDir", "layouts") +viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) +``` + +### Reading Config Files + +Viper requires minimal configuration so it knows where to look for config files. +Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but +currently a single Viper instance only supports a single configuration file. +Viper does not default to any configuration search paths leaving defaults decision +to an application. + +Here is an example of how to use Viper to search for and read a configuration file. +None of the specific paths are required, but at least one path should be provided +where a configuration file is expected. + +```go +viper.SetConfigName("config") // name of config file (without extension) +viper.AddConfigPath("/etc/appname/") // path to look for the config file in +viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths +viper.AddConfigPath(".") // optionally look for config in the working directory +err := viper.ReadInConfig() // Find and read the config file +if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("Fatal error config file: %s \n", err)) +} +``` + +### Watching and re-reading config files + +Viper supports the ability to have your application live read a config file while running. + +Gone are the days of needing to restart a server to have a config take effect, +viper powered applications can read an update to a config file while running and +not miss a beat. + +Simply tell the viper instance to watchConfig. +Optionally you can provide a function for Viper to run each time a change occurs. + +**Make sure you add all of the configPaths prior to calling `WatchConfig()`** + +```go +viper.WatchConfig() +viper.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) +}) +``` + +### Reading Config from io.Reader + +Viper predefines many configuration sources such as files, environment +variables, flags, and remote K/V store, but you are not bound to them. You can +also implement your own required configuration source and feed it to viper. + +```go +viper.SetConfigType("yaml") // or viper.SetConfigType("YAML") + +// any approach to require this configuration into your program. +var yamlExample = []byte(` +Hacker: true +name: steve +hobbies: +- skateboarding +- snowboarding +- go +clothing: + jacket: leather + trousers: denim +age: 35 +eyes : brown +beard: true +`) + +viper.ReadConfig(bytes.NewBuffer(yamlExample)) + +viper.Get("name") // this would be "steve" +``` + +### Setting Overrides + +These could be from a command line flag, or from your own application logic. + +```go +viper.Set("Verbose", true) +viper.Set("LogFile", LogFile) +``` + +### Registering and Using Aliases + +Aliases permit a single value to be referenced by multiple keys + +```go +viper.RegisterAlias("loud", "Verbose") + +viper.Set("verbose", true) // same result as next line +viper.Set("loud", true) // same result as prior line + +viper.GetBool("loud") // true +viper.GetBool("verbose") // true +``` + +### Working with Environment Variables + +Viper has full support for environment variables. This enables 12 factor +applications out of the box. There are five methods that exist to aid working +with ENV: + + * `AutomaticEnv()` + * `BindEnv(string...) : error` + * `SetEnvPrefix(string)` + * `SetEnvKeyReplacer(string...) *strings.Replacer` + * `AllowEmptyEnvVar(bool)` + +_When working with ENV variables, it’s important to recognize that Viper +treats ENV variables as case sensitive._ + +Viper provides a mechanism to try to ensure that ENV variables are unique. By +using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from +the environment variables. Both `BindEnv` and `AutomaticEnv` will use this +prefix. + +`BindEnv` takes one or two parameters. The first parameter is the key name, the +second is the name of the environment variable. The name of the environment +variable is case sensitive. If the ENV variable name is not provided, then +Viper will automatically assume that the key name matches the ENV variable name, +but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV +variable name, it **does not** automatically add the prefix. + +One important thing to recognize when working with ENV variables is that the +value will be read each time it is accessed. Viper does not fix the value when +the `BindEnv` is called. + +`AutomaticEnv` is a powerful helper especially when combined with +`SetEnvPrefix`. When called, Viper will check for an environment variable any +time a `viper.Get` request is made. It will apply the following rules. It will +check for a environment variable with a name matching the key uppercased and +prefixed with the `EnvPrefix` if set. + +`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env +keys to an extent. This is useful if you want to use `-` or something in your +`Get()` calls, but want your environmental variables to use `_` delimiters. An +example of using it can be found in `viper_test.go`. + +By default empty environment variables are considered unset and will fall back to +the next configuration source. To treat empty environment variables as set, use +the `AllowEmptyEnv` method. + +#### Env example + +```go +SetEnvPrefix("spf") // will be uppercased automatically +BindEnv("id") + +os.Setenv("SPF_ID", "13") // typically done outside of the app + +id := Get("id") // 13 +``` + +### Working with Flags + +Viper has the ability to bind to flags. Specifically, Viper supports `Pflags` +as used in the [Cobra](https://github.com/spf13/cobra) library. + +Like `BindEnv`, the value is not set when the binding method is called, but when +it is accessed. This means you can bind as early as you want, even in an +`init()` function. + +For individual flags, the `BindPFlag()` method provides this functionality. + +Example: + +```go +serverCmd.Flags().Int("port", 1138, "Port to run Application server on") +viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) +``` + +You can also bind an existing set of pflags (pflag.FlagSet): + +Example: + +```go +pflag.Int("flagname", 1234, "help message for flagname") + +pflag.Parse() +viper.BindPFlags(pflag.CommandLine) + +i := viper.GetInt("flagname") // retrieve values from viper instead of pflag +``` + +The use of [pflag](https://github.com/spf13/pflag/) in Viper does not preclude +the use of other packages that use the [flag](https://golang.org/pkg/flag/) +package from the standard library. The pflag package can handle the flags +defined for the flag package by importing these flags. This is accomplished +by a calling a convenience function provided by the pflag package called +AddGoFlagSet(). + +Example: + +```go +package main + +import ( + "flag" + "github.com/spf13/pflag" +) + +func main() { + + // using standard library "flag" package + flag.Int("flagname", 1234, "help message for flagname") + + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) + + i := viper.GetInt("flagname") // retrieve value from viper + + ... +} +``` + +#### Flag interfaces + +Viper provides two Go interfaces to bind other flag systems if you don’t use `Pflags`. + +`FlagValue` represents a single flag. This is a very simple example on how to implement this interface: + +```go +type myFlag struct {} +func (f myFlag) HasChanged() bool { return false } +func (f myFlag) Name() string { return "my-flag-name" } +func (f myFlag) ValueString() string { return "my-flag-value" } +func (f myFlag) ValueType() string { return "string" } +``` + +Once your flag implements this interface, you can simply tell Viper to bind it: + +```go +viper.BindFlagValue("my-flag-name", myFlag{}) +``` + +`FlagValueSet` represents a group of flags. This is a very simple example on how to implement this interface: + +```go +type myFlagSet struct { + flags []myFlag +} + +func (f myFlagSet) VisitAll(fn func(FlagValue)) { + for _, flag := range flags { + fn(flag) + } +} +``` + +Once your flag set implements this interface, you can simply tell Viper to bind it: + +```go +fSet := myFlagSet{ + flags: []myFlag{myFlag{}, myFlag{}}, +} +viper.BindFlagValues("my-flags", fSet) +``` + +### Remote Key/Value Store Support + +To enable remote support in Viper, do a blank import of the `viper/remote` +package: + +`import _ "github.com/spf13/viper/remote"` + +Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path +in a Key/Value store such as etcd or Consul. These values take precedence over +default values, but are overridden by configuration values retrieved from disk, +flags, or environment variables. + +Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve +configuration from the K/V store, which means that you can store your +configuration values encrypted and have them automatically decrypted if you have +the correct gpg keyring. Encryption is optional. + +You can use remote configuration in conjunction with local configuration, or +independently of it. + +`crypt` has a command-line helper that you can use to put configurations in your +K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001. + +```bash +$ go get github.com/xordataexchange/crypt/bin/crypt +$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json +``` + +Confirm that your value was set: + +```bash +$ crypt get -plaintext /config/hugo.json +``` + +See the `crypt` documentation for examples of how to set encrypted values, or +how to use Consul. + +### Remote Key/Value Store Example - Unencrypted + +#### etcd +```go +viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +err := viper.ReadRemoteConfig() +``` + +#### Consul +You need to set a key to Consul key/value storage with JSON value containing your desired config. +For example, create a Consul key/value store key `MY_CONSUL_KEY` with value: + +```json +{ + "port": 8080, + "hostname": "myhostname.com" +} +``` + +```go +viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") +viper.SetConfigType("json") // Need to explicitly set this to json +err := viper.ReadRemoteConfig() + +fmt.Println(viper.Get("port")) // 8080 +fmt.Println(viper.Get("hostname")) // myhostname.com +``` + +### Remote Key/Value Store Example - Encrypted + +```go +viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +err := viper.ReadRemoteConfig() +``` + +### Watching Changes in etcd - Unencrypted + +```go +// alternatively, you can create a new viper instance. +var runtime_viper = viper.New() + +runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") +runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" + +// read from remote config the first time. +err := runtime_viper.ReadRemoteConfig() + +// unmarshal config +runtime_viper.Unmarshal(&runtime_conf) + +// open a goroutine to watch remote changes forever +go func(){ + for { + time.Sleep(time.Second * 5) // delay after each request + + // currently, only tested with etcd support + err := runtime_viper.WatchRemoteConfig() + if err != nil { + log.Errorf("unable to read remote config: %v", err) + continue + } + + // unmarshal new config into our runtime config struct. you can also use channel + // to implement a signal to notify the system of the changes + runtime_viper.Unmarshal(&runtime_conf) + } +}() +``` + +## Getting Values From Viper + +In Viper, there are a few ways to get a value depending on the value’s type. +The following functions and methods exist: + + * `Get(key string) : interface{}` + * `GetBool(key string) : bool` + * `GetFloat64(key string) : float64` + * `GetInt(key string) : int` + * `GetString(key string) : string` + * `GetStringMap(key string) : map[string]interface{}` + * `GetStringMapString(key string) : map[string]string` + * `GetStringSlice(key string) : []string` + * `GetTime(key string) : time.Time` + * `GetDuration(key string) : time.Duration` + * `IsSet(key string) : bool` + * `AllSettings() : map[string]interface{}` + +One important thing to recognize is that each Get function will return a zero +value if it’s not found. To check if a given key exists, the `IsSet()` method +has been provided. + +Example: +```go +viper.GetString("logfile") // case-insensitive Setting & Getting +if viper.GetBool("verbose") { + fmt.Println("verbose enabled") +} +``` +### Accessing nested keys + +The accessor methods also accept formatted paths to deeply nested keys. For +example, if the following JSON file is loaded: + +```json +{ + "host": { + "address": "localhost", + "port": 5799 + }, + "datastore": { + "metric": { + "host": "127.0.0.1", + "port": 3099 + }, + "warehouse": { + "host": "198.0.0.1", + "port": 2112 + } + } +} + +``` + +Viper can access a nested field by passing a `.` delimited path of keys: + +```go +GetString("datastore.metric.host") // (returns "127.0.0.1") +``` + +This obeys the precedence rules established above; the search for the path +will cascade through the remaining configuration registries until found. + +For example, given this configuration file, both `datastore.metric.host` and +`datastore.metric.port` are already defined (and may be overridden). If in addition +`datastore.metric.protocol` was defined in the defaults, Viper would also find it. + +However, if `datastore.metric` was overridden (by a flag, an environment variable, +the `Set()` method, …) with an immediate value, then all sub-keys of +`datastore.metric` become undefined, they are “shadowed” by the higher-priority +configuration level. + +Lastly, if there exists a key that matches the delimited key path, its value +will be returned instead. E.g. + +```json +{ + "datastore.metric.host": "0.0.0.0", + "host": { + "address": "localhost", + "port": 5799 + }, + "datastore": { + "metric": { + "host": "127.0.0.1", + "port": 3099 + }, + "warehouse": { + "host": "198.0.0.1", + "port": 2112 + } + } +} + +GetString("datastore.metric.host") // returns "0.0.0.0" +``` + +### Extract sub-tree + +Extract sub-tree from Viper. + +For example, `viper` represents: + +```json +app: + cache1: + max-items: 100 + item-size: 64 + cache2: + max-items: 200 + item-size: 80 +``` + +After executing: + +```go +subv := viper.Sub("app.cache1") +``` + +`subv` represents: + +```json +max-items: 100 +item-size: 64 +``` + +Suppose we have: + +```go +func NewCache(cfg *Viper) *Cache {...} +``` + +which creates a cache based on config information formatted as `subv`. +Now it’s easy to create these 2 caches separately as: + +```go +cfg1 := viper.Sub("app.cache1") +cache1 := NewCache(cfg1) + +cfg2 := viper.Sub("app.cache2") +cache2 := NewCache(cfg2) +``` + +### Unmarshaling + +You also have the option of Unmarshaling all or a specific value to a struct, map, +etc. + +There are two methods to do this: + + * `Unmarshal(rawVal interface{}) : error` + * `UnmarshalKey(key string, rawVal interface{}) : error` + +Example: + +```go +type config struct { + Port int + Name string + PathMap string `mapstructure:"path_map"` +} + +var C config + +err := Unmarshal(&C) +if err != nil { + t.Fatalf("unable to decode into struct, %v", err) +} +``` + +### Marshalling to string + +You may need to marhsal all the settings held in viper into a string rather than write them to a file. +You can use your favorite format's marshaller with the config returned by `AllSettings()`. + +```go +import ( + yaml "gopkg.in/yaml.v2" + // ... +) + +func yamlStringSettings() string { + c := viper.AllSettings() + bs, err := yaml.Marshal(c) + if err != nil { + t.Fatalf("unable to marshal config to YAML: %v", err) + } + return string(bs) +} +``` + +## Viper or Vipers? + +Viper comes ready to use out of the box. There is no configuration or +initialization needed to begin using Viper. Since most applications will want +to use a single central repository for their configuration, the viper package +provides this. It is similar to a singleton. + +In all of the examples above, they demonstrate using viper in its singleton +style approach. + +### Working with multiple vipers + +You can also create many different vipers for use in your application. Each will +have its own unique set of configurations and values. Each can read from a +different config file, key value store, etc. All of the functions that viper +package supports are mirrored as methods on a viper. + +Example: + +```go +x := viper.New() +y := viper.New() + +x.SetDefault("ContentDir", "content") +y.SetDefault("ContentDir", "foobar") + +//... +``` + +When working with multiple vipers, it is up to the user to keep track of the +different vipers. + +## Q & A + +Q: Why not INI files? + +A: Ini files are pretty awful. There’s no standard format, and they are hard to +validate. Viper is designed to work with JSON, TOML or YAML files. If someone +really wants to add this feature, I’d be happy to merge it. It’s easy to specify +which formats your application will permit. + +Q: Why is it called “Viper”? + +A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe)) +to [Cobra](https://github.com/spf13/cobra). While both can operate completely +independently, together they make a powerful pair to handle much of your +application foundation needs. + +Q: Why is it called “Cobra”? + +A: Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)? diff --git a/vendor/github.com/spf13/viper/flags.go b/vendor/github.com/spf13/viper/flags.go new file mode 100644 index 0000000..dd32f4e --- /dev/null +++ b/vendor/github.com/spf13/viper/flags.go @@ -0,0 +1,57 @@ +package viper + +import "github.com/spf13/pflag" + +// FlagValueSet is an interface that users can implement +// to bind a set of flags to viper. +type FlagValueSet interface { + VisitAll(fn func(FlagValue)) +} + +// FlagValue is an interface that users can implement +// to bind different flags to viper. +type FlagValue interface { + HasChanged() bool + Name() string + ValueString() string + ValueType() string +} + +// pflagValueSet is a wrapper around *pflag.ValueSet +// that implements FlagValueSet. +type pflagValueSet struct { + flags *pflag.FlagSet +} + +// VisitAll iterates over all *pflag.Flag inside the *pflag.FlagSet. +func (p pflagValueSet) VisitAll(fn func(flag FlagValue)) { + p.flags.VisitAll(func(flag *pflag.Flag) { + fn(pflagValue{flag}) + }) +} + +// pflagValue is a wrapper aroung *pflag.flag +// that implements FlagValue +type pflagValue struct { + flag *pflag.Flag +} + +// HasChanges returns whether the flag has changes or not. +func (p pflagValue) HasChanged() bool { + return p.flag.Changed +} + +// Name returns the name of the flag. +func (p pflagValue) Name() string { + return p.flag.Name +} + +// ValueString returns the value of the flag as a string. +func (p pflagValue) ValueString() string { + return p.flag.Value.String() +} + +// ValueType returns the type of the flag as a string. +func (p pflagValue) ValueType() string { + return p.flag.Value.Type() +} diff --git a/vendor/github.com/spf13/viper/go.mod b/vendor/github.com/spf13/viper/go.mod new file mode 100644 index 0000000..2794300 --- /dev/null +++ b/vendor/github.com/spf13/viper/go.mod @@ -0,0 +1,43 @@ +module github.com/spf13/viper + +require ( + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect + github.com/coreos/bbolt v1.3.2 // indirect + github.com/coreos/etcd v3.3.10+incompatible // indirect + github.com/coreos/go-semver v0.2.0 // indirect + github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.4.7 + github.com/gogo/protobuf v1.2.1 // indirect + github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect + github.com/google/btree v1.0.0 // indirect + github.com/gorilla/websocket v1.4.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect + github.com/hashicorp/hcl v1.0.0 + github.com/jonboulle/clockwork v0.1.0 // indirect + github.com/magiconair/properties v1.8.0 + github.com/mitchellh/mapstructure v1.1.2 + github.com/pelletier/go-toml v1.2.0 + github.com/prometheus/client_golang v0.9.3 // indirect + github.com/soheilhy/cmux v0.1.4 // indirect + github.com/spf13/afero v1.1.2 + github.com/spf13/cast v1.3.0 + github.com/spf13/jwalterweatherman v1.0.0 + github.com/spf13/pflag v1.0.3 + github.com/stretchr/testify v1.2.2 + github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect + github.com/ugorji/go v1.1.4 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 + go.etcd.io/bbolt v1.3.2 // indirect + go.uber.org/atomic v1.4.0 // indirect + go.uber.org/multierr v1.1.0 // indirect + go.uber.org/zap v1.10.0 // indirect + golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect + google.golang.org/grpc v1.21.0 // indirect + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/vendor/github.com/spf13/viper/go.sum b/vendor/github.com/spf13/viper/go.sum new file mode 100644 index 0000000..97afaff --- /dev/null +++ b/vendor/github.com/spf13/viper/go.sum @@ -0,0 +1,178 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/spf13/viper/util.go b/vendor/github.com/spf13/viper/util.go new file mode 100644 index 0000000..952cad4 --- /dev/null +++ b/vendor/github.com/spf13/viper/util.go @@ -0,0 +1,221 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Viper is a application configuration system. +// It believes that applications can be configured a variety of ways +// via flags, ENVIRONMENT variables, configuration files retrieved +// from the file system, or a remote key/value store. + +package viper + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "unicode" + + "github.com/spf13/afero" + "github.com/spf13/cast" + jww "github.com/spf13/jwalterweatherman" +) + +// ConfigParseError denotes failing to parse configuration file. +type ConfigParseError struct { + err error +} + +// Error returns the formatted configuration error. +func (pe ConfigParseError) Error() string { + return fmt.Sprintf("While parsing config: %s", pe.err.Error()) +} + +// toCaseInsensitiveValue checks if the value is a map; +// if so, create a copy and lower-case the keys recursively. +func toCaseInsensitiveValue(value interface{}) interface{} { + switch v := value.(type) { + case map[interface{}]interface{}: + value = copyAndInsensitiviseMap(cast.ToStringMap(v)) + case map[string]interface{}: + value = copyAndInsensitiviseMap(v) + } + + return value +} + +// copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of +// any map it makes case insensitive. +func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} { + nm := make(map[string]interface{}) + + for key, val := range m { + lkey := strings.ToLower(key) + switch v := val.(type) { + case map[interface{}]interface{}: + nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) + case map[string]interface{}: + nm[lkey] = copyAndInsensitiviseMap(v) + default: + nm[lkey] = v + } + } + + return nm +} + +func insensitiviseMap(m map[string]interface{}) { + for key, val := range m { + switch val.(type) { + case map[interface{}]interface{}: + // nested map: cast and recursively insensitivise + val = cast.ToStringMap(val) + insensitiviseMap(val.(map[string]interface{})) + case map[string]interface{}: + // nested map: recursively insensitivise + insensitiviseMap(val.(map[string]interface{})) + } + + lower := strings.ToLower(key) + if key != lower { + // remove old key (not lower-cased) + delete(m, key) + } + // update map + m[lower] = val + } +} + +func absPathify(inPath string) string { + jww.INFO.Println("Trying to resolve absolute path to", inPath) + + if strings.HasPrefix(inPath, "$HOME") { + inPath = userHomeDir() + inPath[5:] + } + + if strings.HasPrefix(inPath, "$") { + end := strings.Index(inPath, string(os.PathSeparator)) + inPath = os.Getenv(inPath[1:end]) + inPath[end:] + } + + if filepath.IsAbs(inPath) { + return filepath.Clean(inPath) + } + + p, err := filepath.Abs(inPath) + if err == nil { + return filepath.Clean(p) + } + + jww.ERROR.Println("Couldn't discover absolute path") + jww.ERROR.Println(err) + return "" +} + +// Check if File / Directory Exists +func exists(fs afero.Fs, path string) (bool, error) { + _, err := fs.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +func userHomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} + +func safeMul(a, b uint) uint { + c := a * b + if a > 1 && b > 1 && c/b != a { + return 0 + } + return c +} + +// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes +func parseSizeInBytes(sizeStr string) uint { + sizeStr = strings.TrimSpace(sizeStr) + lastChar := len(sizeStr) - 1 + multiplier := uint(1) + + if lastChar > 0 { + if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { + if lastChar > 1 { + switch unicode.ToLower(rune(sizeStr[lastChar-1])) { + case 'k': + multiplier = 1 << 10 + sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) + case 'm': + multiplier = 1 << 20 + sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) + case 'g': + multiplier = 1 << 30 + sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) + default: + multiplier = 1 + sizeStr = strings.TrimSpace(sizeStr[:lastChar]) + } + } + } + } + + size := cast.ToInt(sizeStr) + if size < 0 { + size = 0 + } + + return safeMul(uint(size), multiplier) +} + +// deepSearch scans deep maps, following the key indexes listed in the +// sequence "path". +// The last value is expected to be another map, and is returned. +// +// In case intermediate keys do not exist, or map to a non-map value, +// a new map is created and inserted, and the search continues from there: +// the initial map "m" may be modified! +func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { + for _, k := range path { + m2, ok := m[k] + if !ok { + // intermediate key does not exist + // => create it and continue from there + m3 := make(map[string]interface{}) + m[k] = m3 + m = m3 + continue + } + m3, ok := m2.(map[string]interface{}) + if !ok { + // intermediate key is a value + // => replace with a new map + m3 = make(map[string]interface{}) + m[k] = m3 + } + // continue search from here + m = m3 + } + return m +} diff --git a/vendor/github.com/spf13/viper/viper.go b/vendor/github.com/spf13/viper/viper.go new file mode 100644 index 0000000..a3d37f8 --- /dev/null +++ b/vendor/github.com/spf13/viper/viper.go @@ -0,0 +1,1868 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Viper is a application configuration system. +// It believes that applications can be configured a variety of ways +// via flags, ENVIRONMENT variables, configuration files retrieved +// from the file system, or a remote key/value store. + +// Each item takes precedence over the item below it: + +// overrides +// flag +// env +// config +// key/value store +// default + +package viper + +import ( + "bytes" + "encoding/csv" + "encoding/json" + "fmt" + "io" + "log" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + "time" + + yaml "gopkg.in/yaml.v2" + + "github.com/fsnotify/fsnotify" + "github.com/hashicorp/hcl" + "github.com/hashicorp/hcl/hcl/printer" + "github.com/magiconair/properties" + "github.com/mitchellh/mapstructure" + toml "github.com/pelletier/go-toml" + "github.com/spf13/afero" + "github.com/spf13/cast" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/pflag" +) + +// ConfigMarshalError happens when failing to marshal the configuration. +type ConfigMarshalError struct { + err error +} + +// Error returns the formatted configuration error. +func (e ConfigMarshalError) Error() string { + return fmt.Sprintf("While marshaling config: %s", e.err.Error()) +} + +var v *Viper + +type RemoteResponse struct { + Value []byte + Error error +} + +func init() { + v = New() +} + +type remoteConfigFactory interface { + Get(rp RemoteProvider) (io.Reader, error) + Watch(rp RemoteProvider) (io.Reader, error) + WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool) +} + +// RemoteConfig is optional, see the remote package +var RemoteConfig remoteConfigFactory + +// UnsupportedConfigError denotes encountering an unsupported +// configuration filetype. +type UnsupportedConfigError string + +// Error returns the formatted configuration error. +func (str UnsupportedConfigError) Error() string { + return fmt.Sprintf("Unsupported Config Type %q", string(str)) +} + +// UnsupportedRemoteProviderError denotes encountering an unsupported remote +// provider. Currently only etcd and Consul are supported. +type UnsupportedRemoteProviderError string + +// Error returns the formatted remote provider error. +func (str UnsupportedRemoteProviderError) Error() string { + return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str)) +} + +// RemoteConfigError denotes encountering an error while trying to +// pull the configuration from the remote provider. +type RemoteConfigError string + +// Error returns the formatted remote provider error +func (rce RemoteConfigError) Error() string { + return fmt.Sprintf("Remote Configurations Error: %s", string(rce)) +} + +// ConfigFileNotFoundError denotes failing to find configuration file. +type ConfigFileNotFoundError struct { + name, locations string +} + +// Error returns the formatted configuration error. +func (fnfe ConfigFileNotFoundError) Error() string { + return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations) +} + +// A DecoderConfigOption can be passed to viper.Unmarshal to configure +// mapstructure.DecoderConfig options +type DecoderConfigOption func(*mapstructure.DecoderConfig) + +// DecodeHook returns a DecoderConfigOption which overrides the default +// DecoderConfig.DecodeHook value, the default is: +// +// mapstructure.ComposeDecodeHookFunc( +// mapstructure.StringToTimeDurationHookFunc(), +// mapstructure.StringToSliceHookFunc(","), +// ) +func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption { + return func(c *mapstructure.DecoderConfig) { + c.DecodeHook = hook + } +} + +// Viper is a prioritized configuration registry. It +// maintains a set of configuration sources, fetches +// values to populate those, and provides them according +// to the source's priority. +// The priority of the sources is the following: +// 1. overrides +// 2. flags +// 3. env. variables +// 4. config file +// 5. key/value store +// 6. defaults +// +// For example, if values from the following sources were loaded: +// +// Defaults : { +// "secret": "", +// "user": "default", +// "endpoint": "https://localhost" +// } +// Config : { +// "user": "root" +// "secret": "defaultsecret" +// } +// Env : { +// "secret": "somesecretkey" +// } +// +// The resulting config will have the following values: +// +// { +// "secret": "somesecretkey", +// "user": "root", +// "endpoint": "https://localhost" +// } +type Viper struct { + // Delimiter that separates a list of keys + // used to access a nested value in one go + keyDelim string + + // A set of paths to look for the config file in + configPaths []string + + // The filesystem to read config from. + fs afero.Fs + + // A set of remote providers to search for the configuration + remoteProviders []*defaultRemoteProvider + + // Name of file to look for inside the path + configName string + configFile string + configType string + configPermissions os.FileMode + envPrefix string + + automaticEnvApplied bool + envKeyReplacer *strings.Replacer + allowEmptyEnv bool + + config map[string]interface{} + override map[string]interface{} + defaults map[string]interface{} + kvstore map[string]interface{} + pflags map[string]FlagValue + env map[string]string + aliases map[string]string + typeByDefValue bool + + // Store read properties on the object so that we can write back in order with comments. + // This will only be used if the configuration read is a properties file. + properties *properties.Properties + + onConfigChange func(fsnotify.Event) +} + +// New returns an initialized Viper instance. +func New() *Viper { + v := new(Viper) + v.keyDelim = "." + v.configName = "config" + v.configPermissions = os.FileMode(0644) + v.fs = afero.NewOsFs() + v.config = make(map[string]interface{}) + v.override = make(map[string]interface{}) + v.defaults = make(map[string]interface{}) + v.kvstore = make(map[string]interface{}) + v.pflags = make(map[string]FlagValue) + v.env = make(map[string]string) + v.aliases = make(map[string]string) + v.typeByDefValue = false + + return v +} + +// Intended for testing, will reset all to default settings. +// In the public interface for the viper package so applications +// can use it in their testing as well. +func Reset() { + v = New() + SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"} + SupportedRemoteProviders = []string{"etcd", "consul"} +} + +type defaultRemoteProvider struct { + provider string + endpoint string + path string + secretKeyring string +} + +func (rp defaultRemoteProvider) Provider() string { + return rp.provider +} + +func (rp defaultRemoteProvider) Endpoint() string { + return rp.endpoint +} + +func (rp defaultRemoteProvider) Path() string { + return rp.path +} + +func (rp defaultRemoteProvider) SecretKeyring() string { + return rp.secretKeyring +} + +// RemoteProvider stores the configuration necessary +// to connect to a remote key/value store. +// Optional secretKeyring to unencrypt encrypted values +// can be provided. +type RemoteProvider interface { + Provider() string + Endpoint() string + Path() string + SecretKeyring() string +} + +// SupportedExts are universally supported extensions. +var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"} + +// SupportedRemoteProviders are universally supported remote providers. +var SupportedRemoteProviders = []string{"etcd", "consul"} + +func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) } +func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) { + v.onConfigChange = run +} + +func WatchConfig() { v.WatchConfig() } + +func (v *Viper) WatchConfig() { + initWG := sync.WaitGroup{} + initWG.Add(1) + go func() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way + filename, err := v.getConfigFile() + if err != nil { + log.Printf("error: %v\n", err) + return + } + + configFile := filepath.Clean(filename) + configDir, _ := filepath.Split(configFile) + realConfigFile, _ := filepath.EvalSymlinks(filename) + + eventsWG := sync.WaitGroup{} + eventsWG.Add(1) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { // 'Events' channel is closed + eventsWG.Done() + return + } + currentConfigFile, _ := filepath.EvalSymlinks(filename) + // we only care about the config file with the following cases: + // 1 - if the config file was modified or created + // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) + const writeOrCreateMask = fsnotify.Write | fsnotify.Create + if (filepath.Clean(event.Name) == configFile && + event.Op&writeOrCreateMask != 0) || + (currentConfigFile != "" && currentConfigFile != realConfigFile) { + realConfigFile = currentConfigFile + err := v.ReadInConfig() + if err != nil { + log.Printf("error reading config file: %v\n", err) + } + if v.onConfigChange != nil { + v.onConfigChange(event) + } + } else if filepath.Clean(event.Name) == configFile && + event.Op&fsnotify.Remove&fsnotify.Remove != 0 { + eventsWG.Done() + return + } + + case err, ok := <-watcher.Errors: + if ok { // 'Errors' channel is not closed + log.Printf("watcher error: %v\n", err) + } + eventsWG.Done() + return + } + } + }() + watcher.Add(configDir) + initWG.Done() // done initalizing the watch in this go routine, so the parent routine can move on... + eventsWG.Wait() // now, wait for event loop to end in this go-routine... + }() + initWG.Wait() // make sure that the go routine above fully ended before returning +} + +// SetConfigFile explicitly defines the path, name and extension of the config file. +// Viper will use this and not check any of the config paths. +func SetConfigFile(in string) { v.SetConfigFile(in) } +func (v *Viper) SetConfigFile(in string) { + if in != "" { + v.configFile = in + } +} + +// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use. +// E.g. if your prefix is "spf", the env registry will look for env +// variables that start with "SPF_". +func SetEnvPrefix(in string) { v.SetEnvPrefix(in) } +func (v *Viper) SetEnvPrefix(in string) { + if in != "" { + v.envPrefix = in + } +} + +func (v *Viper) mergeWithEnvPrefix(in string) string { + if v.envPrefix != "" { + return strings.ToUpper(v.envPrefix + "_" + in) + } + + return strings.ToUpper(in) +} + +// AllowEmptyEnv tells Viper to consider set, +// but empty environment variables as valid values instead of falling back. +// For backward compatibility reasons this is false by default. +func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) } +func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) { + v.allowEmptyEnv = allowEmptyEnv +} + +// TODO: should getEnv logic be moved into find(). Can generalize the use of +// rewriting keys many things, Ex: Get('someKey') -> some_key +// (camel case to snake case for JSON keys perhaps) + +// getEnv is a wrapper around os.Getenv which replaces characters in the original +// key. This allows env vars which have different keys than the config object +// keys. +func (v *Viper) getEnv(key string) (string, bool) { + if v.envKeyReplacer != nil { + key = v.envKeyReplacer.Replace(key) + } + + val, ok := os.LookupEnv(key) + + return val, ok && (v.allowEmptyEnv || val != "") +} + +// ConfigFileUsed returns the file used to populate the config registry. +func ConfigFileUsed() string { return v.ConfigFileUsed() } +func (v *Viper) ConfigFileUsed() string { return v.configFile } + +// AddConfigPath adds a path for Viper to search for the config file in. +// Can be called multiple times to define multiple search paths. +func AddConfigPath(in string) { v.AddConfigPath(in) } +func (v *Viper) AddConfigPath(in string) { + if in != "" { + absin := absPathify(in) + jww.INFO.Println("adding", absin, "to paths to search") + if !stringInSlice(absin, v.configPaths) { + v.configPaths = append(v.configPaths, absin) + } + } +} + +// AddRemoteProvider adds a remote configuration source. +// Remote Providers are searched in the order they are added. +// provider is a string value, "etcd" or "consul" are currently supported. +// endpoint is the url. etcd requires http://ip:port consul requires ip:port +// path is the path in the k/v store to retrieve configuration +// To retrieve a config file called myapp.json from /configs/myapp.json +// you should set path to /configs and set config name (SetConfigName()) to +// "myapp" +func AddRemoteProvider(provider, endpoint, path string) error { + return v.AddRemoteProvider(provider, endpoint, path) +} +func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error { + if !stringInSlice(provider, SupportedRemoteProviders) { + return UnsupportedRemoteProviderError(provider) + } + if provider != "" && endpoint != "" { + jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) + rp := &defaultRemoteProvider{ + endpoint: endpoint, + provider: provider, + path: path, + } + if !v.providerPathExists(rp) { + v.remoteProviders = append(v.remoteProviders, rp) + } + } + return nil +} + +// AddSecureRemoteProvider adds a remote configuration source. +// Secure Remote Providers are searched in the order they are added. +// provider is a string value, "etcd" or "consul" are currently supported. +// endpoint is the url. etcd requires http://ip:port consul requires ip:port +// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg +// path is the path in the k/v store to retrieve configuration +// To retrieve a config file called myapp.json from /configs/myapp.json +// you should set path to /configs and set config name (SetConfigName()) to +// "myapp" +// Secure Remote Providers are implemented with github.com/xordataexchange/crypt +func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { + return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring) +} + +func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { + if !stringInSlice(provider, SupportedRemoteProviders) { + return UnsupportedRemoteProviderError(provider) + } + if provider != "" && endpoint != "" { + jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint) + rp := &defaultRemoteProvider{ + endpoint: endpoint, + provider: provider, + path: path, + secretKeyring: secretkeyring, + } + if !v.providerPathExists(rp) { + v.remoteProviders = append(v.remoteProviders, rp) + } + } + return nil +} + +func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool { + for _, y := range v.remoteProviders { + if reflect.DeepEqual(y, p) { + return true + } + } + return false +} + +// searchMap recursively searches for a value for path in source map. +// Returns nil if not found. +// Note: This assumes that the path entries and map keys are lower cased. +func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} { + if len(path) == 0 { + return source + } + + next, ok := source[path[0]] + if ok { + // Fast path + if len(path) == 1 { + return next + } + + // Nested case + switch next.(type) { + case map[interface{}]interface{}: + return v.searchMap(cast.ToStringMap(next), path[1:]) + case map[string]interface{}: + // Type assertion is safe here since it is only reached + // if the type of `next` is the same as the type being asserted + return v.searchMap(next.(map[string]interface{}), path[1:]) + default: + // got a value but nested key expected, return "nil" for not found + return nil + } + } + return nil +} + +// searchMapWithPathPrefixes recursively searches for a value for path in source map. +// +// While searchMap() considers each path element as a single map key, this +// function searches for, and prioritizes, merged path elements. +// e.g., if in the source, "foo" is defined with a sub-key "bar", and "foo.bar" +// is also defined, this latter value is returned for path ["foo", "bar"]. +// +// This should be useful only at config level (other maps may not contain dots +// in their keys). +// +// Note: This assumes that the path entries and map keys are lower cased. +func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []string) interface{} { + if len(path) == 0 { + return source + } + + // search for path prefixes, starting from the longest one + for i := len(path); i > 0; i-- { + prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim)) + + next, ok := source[prefixKey] + if ok { + // Fast path + if i == len(path) { + return next + } + + // Nested case + var val interface{} + switch next.(type) { + case map[interface{}]interface{}: + val = v.searchMapWithPathPrefixes(cast.ToStringMap(next), path[i:]) + case map[string]interface{}: + // Type assertion is safe here since it is only reached + // if the type of `next` is the same as the type being asserted + val = v.searchMapWithPathPrefixes(next.(map[string]interface{}), path[i:]) + default: + // got a value but nested key expected, do nothing and look for next prefix + } + if val != nil { + return val + } + } + } + + // not found + return nil +} + +// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere +// on its path in the map. +// e.g., if "foo.bar" has a value in the given map, it “shadows” +// "foo.bar.baz" in a lower-priority map +func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{}) string { + var parentVal interface{} + for i := 1; i < len(path); i++ { + parentVal = v.searchMap(m, path[0:i]) + if parentVal == nil { + // not found, no need to add more path elements + return "" + } + switch parentVal.(type) { + case map[interface{}]interface{}: + continue + case map[string]interface{}: + continue + default: + // parentVal is a regular value which shadows "path" + return strings.Join(path[0:i], v.keyDelim) + } + } + return "" +} + +// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere +// in a sub-path of the map. +// e.g., if "foo.bar" has a value in the given map, it “shadows” +// "foo.bar.baz" in a lower-priority map +func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string { + // unify input map + var m map[string]interface{} + switch mi.(type) { + case map[string]string, map[string]FlagValue: + m = cast.ToStringMap(mi) + default: + return "" + } + + // scan paths + var parentKey string + for i := 1; i < len(path); i++ { + parentKey = strings.Join(path[0:i], v.keyDelim) + if _, ok := m[parentKey]; ok { + return parentKey + } + } + return "" +} + +// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere +// in the environment, when automatic env is on. +// e.g., if "foo.bar" has a value in the environment, it “shadows” +// "foo.bar.baz" in a lower-priority map +func (v *Viper) isPathShadowedInAutoEnv(path []string) string { + var parentKey string + for i := 1; i < len(path); i++ { + parentKey = strings.Join(path[0:i], v.keyDelim) + if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok { + return parentKey + } + } + return "" +} + +// SetTypeByDefaultValue enables or disables the inference of a key value's +// type when the Get function is used based upon a key's default value as +// opposed to the value returned based on the normal fetch logic. +// +// For example, if a key has a default value of []string{} and the same key +// is set via an environment variable to "a b c", a call to the Get function +// would return a string slice for the key if the key's type is inferred by +// the default value and the Get function would return: +// +// []string {"a", "b", "c"} +// +// Otherwise the Get function would return: +// +// "a b c" +func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) } +func (v *Viper) SetTypeByDefaultValue(enable bool) { + v.typeByDefValue = enable +} + +// GetViper gets the global Viper instance. +func GetViper() *Viper { + return v +} + +// Get can retrieve any value given the key to use. +// Get is case-insensitive for a key. +// Get has the behavior of returning the value associated with the first +// place from where it is set. Viper will check in the following order: +// override, flag, env, config file, key/value store, default +// +// Get returns an interface. For a specific value use one of the Get____ methods. +func Get(key string) interface{} { return v.Get(key) } +func (v *Viper) Get(key string) interface{} { + lcaseKey := strings.ToLower(key) + val := v.find(lcaseKey) + if val == nil { + return nil + } + + if v.typeByDefValue { + // TODO(bep) this branch isn't covered by a single test. + valType := val + path := strings.Split(lcaseKey, v.keyDelim) + defVal := v.searchMap(v.defaults, path) + if defVal != nil { + valType = defVal + } + + switch valType.(type) { + case bool: + return cast.ToBool(val) + case string: + return cast.ToString(val) + case int32, int16, int8, int: + return cast.ToInt(val) + case uint: + return cast.ToUint(val) + case uint32: + return cast.ToUint32(val) + case uint64: + return cast.ToUint64(val) + case int64: + return cast.ToInt64(val) + case float64, float32: + return cast.ToFloat64(val) + case time.Time: + return cast.ToTime(val) + case time.Duration: + return cast.ToDuration(val) + case []string: + return cast.ToStringSlice(val) + } + } + + return val +} + +// Sub returns new Viper instance representing a sub tree of this instance. +// Sub is case-insensitive for a key. +func Sub(key string) *Viper { return v.Sub(key) } +func (v *Viper) Sub(key string) *Viper { + subv := New() + data := v.Get(key) + if data == nil { + return nil + } + + if reflect.TypeOf(data).Kind() == reflect.Map { + subv.config = cast.ToStringMap(data) + return subv + } + return nil +} + +// GetString returns the value associated with the key as a string. +func GetString(key string) string { return v.GetString(key) } +func (v *Viper) GetString(key string) string { + return cast.ToString(v.Get(key)) +} + +// GetBool returns the value associated with the key as a boolean. +func GetBool(key string) bool { return v.GetBool(key) } +func (v *Viper) GetBool(key string) bool { + return cast.ToBool(v.Get(key)) +} + +// GetInt returns the value associated with the key as an integer. +func GetInt(key string) int { return v.GetInt(key) } +func (v *Viper) GetInt(key string) int { + return cast.ToInt(v.Get(key)) +} + +// GetInt32 returns the value associated with the key as an integer. +func GetInt32(key string) int32 { return v.GetInt32(key) } +func (v *Viper) GetInt32(key string) int32 { + return cast.ToInt32(v.Get(key)) +} + +// GetInt64 returns the value associated with the key as an integer. +func GetInt64(key string) int64 { return v.GetInt64(key) } +func (v *Viper) GetInt64(key string) int64 { + return cast.ToInt64(v.Get(key)) +} + +// GetUint returns the value associated with the key as an unsigned integer. +func GetUint(key string) uint { return v.GetUint(key) } +func (v *Viper) GetUint(key string) uint { + return cast.ToUint(v.Get(key)) +} + +// GetUint32 returns the value associated with the key as an unsigned integer. +func GetUint32(key string) uint32 { return v.GetUint32(key) } +func (v *Viper) GetUint32(key string) uint32 { + return cast.ToUint32(v.Get(key)) +} + +// GetUint64 returns the value associated with the key as an unsigned integer. +func GetUint64(key string) uint64 { return v.GetUint64(key) } +func (v *Viper) GetUint64(key string) uint64 { + return cast.ToUint64(v.Get(key)) +} + +// GetFloat64 returns the value associated with the key as a float64. +func GetFloat64(key string) float64 { return v.GetFloat64(key) } +func (v *Viper) GetFloat64(key string) float64 { + return cast.ToFloat64(v.Get(key)) +} + +// GetTime returns the value associated with the key as time. +func GetTime(key string) time.Time { return v.GetTime(key) } +func (v *Viper) GetTime(key string) time.Time { + return cast.ToTime(v.Get(key)) +} + +// GetDuration returns the value associated with the key as a duration. +func GetDuration(key string) time.Duration { return v.GetDuration(key) } +func (v *Viper) GetDuration(key string) time.Duration { + return cast.ToDuration(v.Get(key)) +} + +// GetStringSlice returns the value associated with the key as a slice of strings. +func GetStringSlice(key string) []string { return v.GetStringSlice(key) } +func (v *Viper) GetStringSlice(key string) []string { + return cast.ToStringSlice(v.Get(key)) +} + +// GetStringMap returns the value associated with the key as a map of interfaces. +func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) } +func (v *Viper) GetStringMap(key string) map[string]interface{} { + return cast.ToStringMap(v.Get(key)) +} + +// GetStringMapString returns the value associated with the key as a map of strings. +func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) } +func (v *Viper) GetStringMapString(key string) map[string]string { + return cast.ToStringMapString(v.Get(key)) +} + +// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. +func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } +func (v *Viper) GetStringMapStringSlice(key string) map[string][]string { + return cast.ToStringMapStringSlice(v.Get(key)) +} + +// GetSizeInBytes returns the size of the value associated with the given key +// in bytes. +func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) } +func (v *Viper) GetSizeInBytes(key string) uint { + sizeStr := cast.ToString(v.Get(key)) + return parseSizeInBytes(sizeStr) +} + +// UnmarshalKey takes a single key and unmarshals it into a Struct. +func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { + return v.UnmarshalKey(key, rawVal, opts...) +} +func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { + err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...)) + + if err != nil { + return err + } + + return nil +} + +// Unmarshal unmarshals the config into a Struct. Make sure that the tags +// on the fields of the structure are properly set. +func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error { + return v.Unmarshal(rawVal, opts...) +} +func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error { + err := decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...)) + + if err != nil { + return err + } + + return nil +} + +// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot +// of time.Duration values & string slices +func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig { + c := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + ), + } + for _, opt := range opts { + opt(c) + } + return c +} + +// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality +func decode(input interface{}, config *mapstructure.DecoderConfig) error { + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + return decoder.Decode(input) +} + +// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent +// in the destination struct. +func (v *Viper) UnmarshalExact(rawVal interface{}) error { + config := defaultDecoderConfig(rawVal) + config.ErrorUnused = true + + err := decode(v.AllSettings(), config) + + if err != nil { + return err + } + + return nil +} + +// BindPFlags binds a full flag set to the configuration, using each flag's long +// name as the config key. +func BindPFlags(flags *pflag.FlagSet) error { return v.BindPFlags(flags) } +func (v *Viper) BindPFlags(flags *pflag.FlagSet) error { + return v.BindFlagValues(pflagValueSet{flags}) +} + +// BindPFlag binds a specific key to a pflag (as used by cobra). +// Example (where serverCmd is a Cobra instance): +// +// serverCmd.Flags().Int("port", 1138, "Port to run Application server on") +// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) +// +func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) } +func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error { + return v.BindFlagValue(key, pflagValue{flag}) +} + +// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long +// name as the config key. +func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(flags) } +func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) { + flags.VisitAll(func(flag FlagValue) { + if err = v.BindFlagValue(flag.Name(), flag); err != nil { + return + } + }) + return nil +} + +// BindFlagValue binds a specific key to a FlagValue. +// Example (where serverCmd is a Cobra instance): +// +// serverCmd.Flags().Int("port", 1138, "Port to run Application server on") +// Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port")) +// +func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) } +func (v *Viper) BindFlagValue(key string, flag FlagValue) error { + if flag == nil { + return fmt.Errorf("flag for %q is nil", key) + } + v.pflags[strings.ToLower(key)] = flag + return nil +} + +// BindEnv binds a Viper key to a ENV variable. +// ENV variables are case sensitive. +// If only a key is provided, it will use the env key matching the key, uppercased. +// EnvPrefix will be used when set when env name is not provided. +func BindEnv(input ...string) error { return v.BindEnv(input...) } +func (v *Viper) BindEnv(input ...string) error { + var key, envkey string + if len(input) == 0 { + return fmt.Errorf("BindEnv missing key to bind to") + } + + key = strings.ToLower(input[0]) + + if len(input) == 1 { + envkey = v.mergeWithEnvPrefix(key) + } else { + envkey = input[1] + } + + v.env[key] = envkey + + return nil +} + +// Given a key, find the value. +// Viper will check in the following order: +// flag, env, config file, key/value store, default. +// Viper will check to see if an alias exists first. +// Note: this assumes a lower-cased key given. +func (v *Viper) find(lcaseKey string) interface{} { + + var ( + val interface{} + exists bool + path = strings.Split(lcaseKey, v.keyDelim) + nested = len(path) > 1 + ) + + // compute the path through the nested maps to the nested value + if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" { + return nil + } + + // if the requested key is an alias, then return the proper key + lcaseKey = v.realKey(lcaseKey) + path = strings.Split(lcaseKey, v.keyDelim) + nested = len(path) > 1 + + // Set() override first + val = v.searchMap(v.override, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.override) != "" { + return nil + } + + // PFlag override next + flag, exists := v.pflags[lcaseKey] + if exists && flag.HasChanged() { + switch flag.ValueType() { + case "int", "int8", "int16", "int32", "int64": + return cast.ToInt(flag.ValueString()) + case "bool": + return cast.ToBool(flag.ValueString()) + case "stringSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res + default: + return flag.ValueString() + } + } + if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" { + return nil + } + + // Env override next + if v.automaticEnvApplied { + // even if it hasn't been registered, if automaticEnv is used, + // check any Get request + if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok { + return val + } + if nested && v.isPathShadowedInAutoEnv(path) != "" { + return nil + } + } + envkey, exists := v.env[lcaseKey] + if exists { + if val, ok := v.getEnv(envkey); ok { + return val + } + } + if nested && v.isPathShadowedInFlatMap(path, v.env) != "" { + return nil + } + + // Config file next + val = v.searchMapWithPathPrefixes(v.config, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.config) != "" { + return nil + } + + // K/V store next + val = v.searchMap(v.kvstore, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" { + return nil + } + + // Default next + val = v.searchMap(v.defaults, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" { + return nil + } + + // last chance: if no other value is returned and a flag does exist for the value, + // get the flag's value even if the flag's value has not changed + if flag, exists := v.pflags[lcaseKey]; exists { + switch flag.ValueType() { + case "int", "int8", "int16", "int32", "int64": + return cast.ToInt(flag.ValueString()) + case "bool": + return cast.ToBool(flag.ValueString()) + case "stringSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res + default: + return flag.ValueString() + } + } + // last item, no need to check shadowing + + return nil +} + +func readAsCSV(val string) ([]string, error) { + if val == "" { + return []string{}, nil + } + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + return csvReader.Read() +} + +// IsSet checks to see if the key has been set in any of the data locations. +// IsSet is case-insensitive for a key. +func IsSet(key string) bool { return v.IsSet(key) } +func (v *Viper) IsSet(key string) bool { + lcaseKey := strings.ToLower(key) + val := v.find(lcaseKey) + return val != nil +} + +// AutomaticEnv has Viper check ENV variables for all. +// keys set in config, default & flags +func AutomaticEnv() { v.AutomaticEnv() } +func (v *Viper) AutomaticEnv() { + v.automaticEnvApplied = true +} + +// SetEnvKeyReplacer sets the strings.Replacer on the viper object +// Useful for mapping an environmental variable to a key that does +// not match it. +func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) } +func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) { + v.envKeyReplacer = r +} + +// Aliases provide another accessor for the same key. +// This enables one to change a name without breaking the application +func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } +func (v *Viper) RegisterAlias(alias string, key string) { + v.registerAlias(alias, strings.ToLower(key)) +} + +func (v *Viper) registerAlias(alias string, key string) { + alias = strings.ToLower(alias) + if alias != key && alias != v.realKey(key) { + _, exists := v.aliases[alias] + + if !exists { + // if we alias something that exists in one of the maps to another + // name, we'll never be able to get that value using the original + // name, so move the config value to the new realkey. + if val, ok := v.config[alias]; ok { + delete(v.config, alias) + v.config[key] = val + } + if val, ok := v.kvstore[alias]; ok { + delete(v.kvstore, alias) + v.kvstore[key] = val + } + if val, ok := v.defaults[alias]; ok { + delete(v.defaults, alias) + v.defaults[key] = val + } + if val, ok := v.override[alias]; ok { + delete(v.override, alias) + v.override[key] = val + } + v.aliases[alias] = key + } + } else { + jww.WARN.Println("Creating circular reference alias", alias, key, v.realKey(key)) + } +} + +func (v *Viper) realKey(key string) string { + newkey, exists := v.aliases[key] + if exists { + jww.DEBUG.Println("Alias", key, "to", newkey) + return v.realKey(newkey) + } + return key +} + +// InConfig checks to see if the given key (or an alias) is in the config file. +func InConfig(key string) bool { return v.InConfig(key) } +func (v *Viper) InConfig(key string) bool { + // if the requested key is an alias, then return the proper key + key = v.realKey(key) + + _, exists := v.config[key] + return exists +} + +// SetDefault sets the default value for this key. +// SetDefault is case-insensitive for a key. +// Default only used when no value is provided by the user via flag, config or ENV. +func SetDefault(key string, value interface{}) { v.SetDefault(key, value) } +func (v *Viper) SetDefault(key string, value interface{}) { + // If alias passed in, then set the proper default + key = v.realKey(strings.ToLower(key)) + value = toCaseInsensitiveValue(value) + + path := strings.Split(key, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(v.defaults, path[0:len(path)-1]) + + // set innermost value + deepestMap[lastKey] = value +} + +// Set sets the value for the key in the override register. +// Set is case-insensitive for a key. +// Will be used instead of values obtained via +// flags, config file, ENV, default, or key/value store. +func Set(key string, value interface{}) { v.Set(key, value) } +func (v *Viper) Set(key string, value interface{}) { + // If alias passed in, then set the proper override + key = v.realKey(strings.ToLower(key)) + value = toCaseInsensitiveValue(value) + + path := strings.Split(key, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(v.override, path[0:len(path)-1]) + + // set innermost value + deepestMap[lastKey] = value +} + +// ReadInConfig will discover and load the configuration file from disk +// and key/value stores, searching in one of the defined paths. +func ReadInConfig() error { return v.ReadInConfig() } +func (v *Viper) ReadInConfig() error { + jww.INFO.Println("Attempting to read in config file") + filename, err := v.getConfigFile() + if err != nil { + return err + } + + if !stringInSlice(v.getConfigType(), SupportedExts) { + return UnsupportedConfigError(v.getConfigType()) + } + + jww.DEBUG.Println("Reading file: ", filename) + file, err := afero.ReadFile(v.fs, filename) + if err != nil { + return err + } + + config := make(map[string]interface{}) + + err = v.unmarshalReader(bytes.NewReader(file), config) + if err != nil { + return err + } + + v.config = config + return nil +} + +// MergeInConfig merges a new configuration with an existing config. +func MergeInConfig() error { return v.MergeInConfig() } +func (v *Viper) MergeInConfig() error { + jww.INFO.Println("Attempting to merge in config file") + filename, err := v.getConfigFile() + if err != nil { + return err + } + + if !stringInSlice(v.getConfigType(), SupportedExts) { + return UnsupportedConfigError(v.getConfigType()) + } + + file, err := afero.ReadFile(v.fs, filename) + if err != nil { + return err + } + + return v.MergeConfig(bytes.NewReader(file)) +} + +// ReadConfig will read a configuration file, setting existing keys to nil if the +// key does not exist in the file. +func ReadConfig(in io.Reader) error { return v.ReadConfig(in) } +func (v *Viper) ReadConfig(in io.Reader) error { + v.config = make(map[string]interface{}) + return v.unmarshalReader(in, v.config) +} + +// MergeConfig merges a new configuration with an existing config. +func MergeConfig(in io.Reader) error { return v.MergeConfig(in) } +func (v *Viper) MergeConfig(in io.Reader) error { + cfg := make(map[string]interface{}) + if err := v.unmarshalReader(in, cfg); err != nil { + return err + } + return v.MergeConfigMap(cfg) +} + +// MergeConfigMap merges the configuration from the map given with an existing config. +// Note that the map given may be modified. +func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) } +func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error { + if v.config == nil { + v.config = make(map[string]interface{}) + } + insensitiviseMap(cfg) + mergeMaps(cfg, v.config, nil) + return nil +} + +// WriteConfig writes the current configuration to a file. +func WriteConfig() error { return v.WriteConfig() } +func (v *Viper) WriteConfig() error { + filename, err := v.getConfigFile() + if err != nil { + return err + } + return v.writeConfig(filename, true) +} + +// SafeWriteConfig writes current configuration to file only if the file does not exist. +func SafeWriteConfig() error { return v.SafeWriteConfig() } +func (v *Viper) SafeWriteConfig() error { + filename, err := v.getConfigFile() + if err != nil { + return err + } + return v.writeConfig(filename, false) +} + +// WriteConfigAs writes current configuration to a given filename. +func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) } +func (v *Viper) WriteConfigAs(filename string) error { + return v.writeConfig(filename, true) +} + +// SafeWriteConfigAs writes current configuration to a given filename if it does not exist. +func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) } +func (v *Viper) SafeWriteConfigAs(filename string) error { + return v.writeConfig(filename, false) +} + +func writeConfig(filename string, force bool) error { return v.writeConfig(filename, force) } +func (v *Viper) writeConfig(filename string, force bool) error { + jww.INFO.Println("Attempting to write configuration to file.") + ext := filepath.Ext(filename) + if len(ext) <= 1 { + return fmt.Errorf("Filename: %s requires valid extension.", filename) + } + configType := ext[1:] + if !stringInSlice(configType, SupportedExts) { + return UnsupportedConfigError(configType) + } + if v.config == nil { + v.config = make(map[string]interface{}) + } + var flags int + if force == true { + flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY + } else { + if _, err := os.Stat(filename); os.IsNotExist(err) { + flags = os.O_WRONLY + } else { + return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename) + } + } + f, err := v.fs.OpenFile(filename, flags, v.configPermissions) + if err != nil { + return err + } + return v.marshalWriter(f, configType) +} + +// Unmarshal a Reader into a map. +// Should probably be an unexported function. +func unmarshalReader(in io.Reader, c map[string]interface{}) error { + return v.unmarshalReader(in, c) +} +func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { + buf := new(bytes.Buffer) + buf.ReadFrom(in) + + switch strings.ToLower(v.getConfigType()) { + case "yaml", "yml": + if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { + return ConfigParseError{err} + } + + case "json": + if err := json.Unmarshal(buf.Bytes(), &c); err != nil { + return ConfigParseError{err} + } + + case "hcl": + obj, err := hcl.Parse(string(buf.Bytes())) + if err != nil { + return ConfigParseError{err} + } + if err = hcl.DecodeObject(&c, obj); err != nil { + return ConfigParseError{err} + } + + case "toml": + tree, err := toml.LoadReader(buf) + if err != nil { + return ConfigParseError{err} + } + tmap := tree.ToMap() + for k, v := range tmap { + c[k] = v + } + + case "properties", "props", "prop": + v.properties = properties.NewProperties() + var err error + if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { + return ConfigParseError{err} + } + for _, key := range v.properties.Keys() { + value, _ := v.properties.Get(key) + // recursively build nested maps + path := strings.Split(key, ".") + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(c, path[0:len(path)-1]) + // set innermost value + deepestMap[lastKey] = value + } + } + + insensitiviseMap(c) + return nil +} + +// Marshal a map into Writer. +func marshalWriter(f afero.File, configType string) error { + return v.marshalWriter(f, configType) +} +func (v *Viper) marshalWriter(f afero.File, configType string) error { + c := v.AllSettings() + switch configType { + case "json": + b, err := json.MarshalIndent(c, "", " ") + if err != nil { + return ConfigMarshalError{err} + } + _, err = f.WriteString(string(b)) + if err != nil { + return ConfigMarshalError{err} + } + + case "hcl": + b, err := json.Marshal(c) + ast, err := hcl.Parse(string(b)) + if err != nil { + return ConfigMarshalError{err} + } + err = printer.Fprint(f, ast.Node) + if err != nil { + return ConfigMarshalError{err} + } + + case "prop", "props", "properties": + if v.properties == nil { + v.properties = properties.NewProperties() + } + p := v.properties + for _, key := range v.AllKeys() { + _, _, err := p.Set(key, v.GetString(key)) + if err != nil { + return ConfigMarshalError{err} + } + } + _, err := p.WriteComment(f, "#", properties.UTF8) + if err != nil { + return ConfigMarshalError{err} + } + + case "toml": + t, err := toml.TreeFromMap(c) + if err != nil { + return ConfigMarshalError{err} + } + s := t.String() + if _, err := f.WriteString(s); err != nil { + return ConfigMarshalError{err} + } + + case "yaml", "yml": + b, err := yaml.Marshal(c) + if err != nil { + return ConfigMarshalError{err} + } + if _, err = f.WriteString(string(b)); err != nil { + return ConfigMarshalError{err} + } + } + return nil +} + +func keyExists(k string, m map[string]interface{}) string { + lk := strings.ToLower(k) + for mk := range m { + lmk := strings.ToLower(mk) + if lmk == lk { + return mk + } + } + return "" +} + +func castToMapStringInterface( + src map[interface{}]interface{}) map[string]interface{} { + tgt := map[string]interface{}{} + for k, v := range src { + tgt[fmt.Sprintf("%v", k)] = v + } + return tgt +} + +func castMapStringToMapInterface(src map[string]string) map[string]interface{} { + tgt := map[string]interface{}{} + for k, v := range src { + tgt[k] = v + } + return tgt +} + +func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{} { + tgt := map[string]interface{}{} + for k, v := range src { + tgt[k] = v + } + return tgt +} + +// mergeMaps merges two maps. The `itgt` parameter is for handling go-yaml's +// insistence on parsing nested structures as `map[interface{}]interface{}` +// instead of using a `string` as the key for nest structures beyond one level +// deep. Both map types are supported as there is a go-yaml fork that uses +// `map[string]interface{}` instead. +func mergeMaps( + src, tgt map[string]interface{}, itgt map[interface{}]interface{}) { + for sk, sv := range src { + tk := keyExists(sk, tgt) + if tk == "" { + jww.TRACE.Printf("tk=\"\", tgt[%s]=%v", sk, sv) + tgt[sk] = sv + if itgt != nil { + itgt[sk] = sv + } + continue + } + + tv, ok := tgt[tk] + if !ok { + jww.TRACE.Printf("tgt[%s] != ok, tgt[%s]=%v", tk, sk, sv) + tgt[sk] = sv + if itgt != nil { + itgt[sk] = sv + } + continue + } + + svType := reflect.TypeOf(sv) + tvType := reflect.TypeOf(tv) + if svType != tvType { + jww.ERROR.Printf( + "svType != tvType; key=%s, st=%v, tt=%v, sv=%v, tv=%v", + sk, svType, tvType, sv, tv) + continue + } + + jww.TRACE.Printf("processing key=%s, st=%v, tt=%v, sv=%v, tv=%v", + sk, svType, tvType, sv, tv) + + switch ttv := tv.(type) { + case map[interface{}]interface{}: + jww.TRACE.Printf("merging maps (must convert)") + tsv := sv.(map[interface{}]interface{}) + ssv := castToMapStringInterface(tsv) + stv := castToMapStringInterface(ttv) + mergeMaps(ssv, stv, ttv) + case map[string]interface{}: + jww.TRACE.Printf("merging maps") + mergeMaps(sv.(map[string]interface{}), ttv, nil) + default: + jww.TRACE.Printf("setting value") + tgt[tk] = sv + if itgt != nil { + itgt[tk] = sv + } + } + } +} + +// ReadRemoteConfig attempts to get configuration from a remote source +// and read it in the remote configuration registry. +func ReadRemoteConfig() error { return v.ReadRemoteConfig() } +func (v *Viper) ReadRemoteConfig() error { + return v.getKeyValueConfig() +} + +func WatchRemoteConfig() error { return v.WatchRemoteConfig() } +func (v *Viper) WatchRemoteConfig() error { + return v.watchKeyValueConfig() +} + +func (v *Viper) WatchRemoteConfigOnChannel() error { + return v.watchKeyValueConfigOnChannel() +} + +// Retrieve the first found remote configuration. +func (v *Viper) getKeyValueConfig() error { + if RemoteConfig == nil { + return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'") + } + + for _, rp := range v.remoteProviders { + val, err := v.getRemoteConfig(rp) + if err != nil { + continue + } + v.kvstore = val + return nil + } + return RemoteConfigError("No Files Found") +} + +func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) { + reader, err := RemoteConfig.Get(provider) + if err != nil { + return nil, err + } + err = v.unmarshalReader(reader, v.kvstore) + return v.kvstore, err +} + +// Retrieve the first found remote configuration. +func (v *Viper) watchKeyValueConfigOnChannel() error { + for _, rp := range v.remoteProviders { + respc, _ := RemoteConfig.WatchChannel(rp) + //Todo: Add quit channel + go func(rc <-chan *RemoteResponse) { + for { + b := <-rc + reader := bytes.NewReader(b.Value) + v.unmarshalReader(reader, v.kvstore) + } + }(respc) + return nil + } + return RemoteConfigError("No Files Found") +} + +// Retrieve the first found remote configuration. +func (v *Viper) watchKeyValueConfig() error { + for _, rp := range v.remoteProviders { + val, err := v.watchRemoteConfig(rp) + if err != nil { + continue + } + v.kvstore = val + return nil + } + return RemoteConfigError("No Files Found") +} + +func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface{}, error) { + reader, err := RemoteConfig.Watch(provider) + if err != nil { + return nil, err + } + err = v.unmarshalReader(reader, v.kvstore) + return v.kvstore, err +} + +// AllKeys returns all keys holding a value, regardless of where they are set. +// Nested keys are returned with a v.keyDelim (= ".") separator +func AllKeys() []string { return v.AllKeys() } +func (v *Viper) AllKeys() []string { + m := map[string]bool{} + // add all paths, by order of descending priority to ensure correct shadowing + m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "") + m = v.flattenAndMergeMap(m, v.override, "") + m = v.mergeFlatMap(m, castMapFlagToMapInterface(v.pflags)) + m = v.mergeFlatMap(m, castMapStringToMapInterface(v.env)) + m = v.flattenAndMergeMap(m, v.config, "") + m = v.flattenAndMergeMap(m, v.kvstore, "") + m = v.flattenAndMergeMap(m, v.defaults, "") + + // convert set of paths to list + a := []string{} + for x := range m { + a = append(a, x) + } + return a +} + +// flattenAndMergeMap recursively flattens the given map into a map[string]bool +// of key paths (used as a set, easier to manipulate than a []string): +// - each path is merged into a single key string, delimited with v.keyDelim (= ".") +// - if a path is shadowed by an earlier value in the initial shadow map, +// it is skipped. +// The resulting set of paths is merged to the given shadow set at the same time. +func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool { + if shadow != nil && prefix != "" && shadow[prefix] { + // prefix is shadowed => nothing more to flatten + return shadow + } + if shadow == nil { + shadow = make(map[string]bool) + } + + var m2 map[string]interface{} + if prefix != "" { + prefix += v.keyDelim + } + for k, val := range m { + fullKey := prefix + k + switch val.(type) { + case map[string]interface{}: + m2 = val.(map[string]interface{}) + case map[interface{}]interface{}: + m2 = cast.ToStringMap(val) + default: + // immediate value + shadow[strings.ToLower(fullKey)] = true + continue + } + // recursively merge to shadow map + shadow = v.flattenAndMergeMap(shadow, m2, fullKey) + } + return shadow +} + +// mergeFlatMap merges the given maps, excluding values of the second map +// shadowed by values from the first map. +func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool { + // scan keys +outer: + for k, _ := range m { + path := strings.Split(k, v.keyDelim) + // scan intermediate paths + var parentKey string + for i := 1; i < len(path); i++ { + parentKey = strings.Join(path[0:i], v.keyDelim) + if shadow[parentKey] { + // path is shadowed, continue + continue outer + } + } + // add key + shadow[strings.ToLower(k)] = true + } + return shadow +} + +// AllSettings merges all settings and returns them as a map[string]interface{}. +func AllSettings() map[string]interface{} { return v.AllSettings() } +func (v *Viper) AllSettings() map[string]interface{} { + m := map[string]interface{}{} + // start from the list of keys, and construct the map one value at a time + for _, k := range v.AllKeys() { + value := v.Get(k) + if value == nil { + // should not happen, since AllKeys() returns only keys holding a value, + // check just in case anything changes + continue + } + path := strings.Split(k, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(m, path[0:len(path)-1]) + // set innermost value + deepestMap[lastKey] = value + } + return m +} + +// SetFs sets the filesystem to use to read configuration. +func SetFs(fs afero.Fs) { v.SetFs(fs) } +func (v *Viper) SetFs(fs afero.Fs) { + v.fs = fs +} + +// SetConfigName sets name for the config file. +// Does not include extension. +func SetConfigName(in string) { v.SetConfigName(in) } +func (v *Viper) SetConfigName(in string) { + if in != "" { + v.configName = in + v.configFile = "" + } +} + +// SetConfigType sets the type of the configuration returned by the +// remote source, e.g. "json". +func SetConfigType(in string) { v.SetConfigType(in) } +func (v *Viper) SetConfigType(in string) { + if in != "" { + v.configType = in + } +} + +// SetConfigPermissions sets the permissions for the config file. +func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) } +func (v *Viper) SetConfigPermissions(perm os.FileMode) { + v.configPermissions = perm.Perm() +} + +func (v *Viper) getConfigType() string { + if v.configType != "" { + return v.configType + } + + cf, err := v.getConfigFile() + if err != nil { + return "" + } + + ext := filepath.Ext(cf) + + if len(ext) > 1 { + return ext[1:] + } + + return "" +} + +func (v *Viper) getConfigFile() (string, error) { + if v.configFile == "" { + cf, err := v.findConfigFile() + if err != nil { + return "", err + } + v.configFile = cf + } + return v.configFile, nil +} + +func (v *Viper) searchInPath(in string) (filename string) { + jww.DEBUG.Println("Searching for config in ", in) + for _, ext := range SupportedExts { + jww.DEBUG.Println("Checking for", filepath.Join(in, v.configName+"."+ext)) + if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b { + jww.DEBUG.Println("Found: ", filepath.Join(in, v.configName+"."+ext)) + return filepath.Join(in, v.configName+"."+ext) + } + } + + return "" +} + +// Search all configPaths for any config file. +// Returns the first path that exists (and is a config file). +func (v *Viper) findConfigFile() (string, error) { + jww.INFO.Println("Searching for config in ", v.configPaths) + + for _, cp := range v.configPaths { + file := v.searchInPath(cp) + if file != "" { + return file, nil + } + } + return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)} +} + +// Debug prints all configuration registries for debugging +// purposes. +func Debug() { v.Debug() } +func (v *Viper) Debug() { + fmt.Printf("Aliases:\n%#v\n", v.aliases) + fmt.Printf("Override:\n%#v\n", v.override) + fmt.Printf("PFlags:\n%#v\n", v.pflags) + fmt.Printf("Env:\n%#v\n", v.env) + fmt.Printf("Key/Value Store:\n%#v\n", v.kvstore) + fmt.Printf("Config:\n%#v\n", v.config) + fmt.Printf("Defaults:\n%#v\n", v.defaults) +} diff --git a/vendor/github.com/whilp/git-urls/.travis.yml b/vendor/github.com/whilp/git-urls/.travis.yml deleted file mode 100644 index 2f050db..0000000 --- a/vendor/github.com/whilp/git-urls/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go - -go: - - 1.11 - -install: - - make deps install lint test diff --git a/vendor/github.com/whilp/git-urls/LICENSE b/vendor/github.com/whilp/git-urls/LICENSE deleted file mode 100644 index 2aa848d..0000000 --- a/vendor/github.com/whilp/git-urls/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Will Maier - -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/whilp/git-urls/Makefile b/vendor/github.com/whilp/git-urls/Makefile deleted file mode 100644 index 7d6c569..0000000 --- a/vendor/github.com/whilp/git-urls/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -default: test lint - -test: - go test -v ./... - go test -covermode=count -coverprofile=profile.cov . - -lint: - gometalinter ./... - -install: - go get -d -v ./... && go build -v ./... - gometalinter --install --update - -deps: - go get github.com/alecthomas/gometalinter - go get golang.org/x/tools/cmd/cover diff --git a/vendor/github.com/whilp/git-urls/README.md b/vendor/github.com/whilp/git-urls/README.md deleted file mode 100644 index 3acacc5..0000000 --- a/vendor/github.com/whilp/git-urls/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# git-urls - -Docs: https://pkg.go.dev/github.com/whilp/git-urls?tab=overview diff --git a/vendor/github.com/whilp/git-urls/go.mod b/vendor/github.com/whilp/git-urls/go.mod deleted file mode 100644 index 59ec93f..0000000 --- a/vendor/github.com/whilp/git-urls/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/whilp/git-urls - -go 1.13 diff --git a/vendor/github.com/whilp/git-urls/urls.go b/vendor/github.com/whilp/git-urls/urls.go deleted file mode 100644 index 0234103..0000000 --- a/vendor/github.com/whilp/git-urls/urls.go +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2014 Will Maier - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* -Package giturls parses Git URLs. - -These URLs include standard RFC 3986 URLs as well as special formats that -are specific to Git. Examples are provided in the Git documentation at -https://www.kernel.org/pub/software/scm/git/docs/git-clone.html. -*/ -package giturls - -import ( - "fmt" - "net/url" - "regexp" - "strings" -) - -var ( - // scpSyntax was modified from https://golang.org/src/cmd/go/vcs.go. - scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):([a-zA-Z0-9./._-]+)(?:\?||$)(.*)$`) - - // Transports is a set of known Git URL schemes. - Transports = NewTransportSet( - "ssh", - "git", - "git+ssh", - "http", - "https", - "ftp", - "ftps", - "rsync", - "file", - ) -) - -// Parser converts a string into a URL. -type Parser func(string) (*url.URL, error) - -// Parse parses rawurl into a URL structure. Parse first attempts to -// find a standard URL with a valid Git transport as its scheme. If -// that cannot be found, it then attempts to find a SCP-like URL. And -// if that cannot be found, it assumes rawurl is a local path. If none -// of these rules apply, Parse returns an error. -func Parse(rawurl string) (u *url.URL, err error) { - parsers := []Parser{ - ParseTransport, - ParseScp, - ParseLocal, - } - - // Apply each parser in turn; if the parser succeeds, accept its - // result and return. - for _, p := range parsers { - u, err = p(rawurl) - if err == nil { - return u, err - } - } - - // It's unlikely that none of the parsers will succeed, since - // ParseLocal is very forgiving. - return new(url.URL), fmt.Errorf("failed to parse %q", rawurl) -} - -// ParseTransport parses rawurl into a URL object. Unless the URL's -// scheme is a known Git transport, ParseTransport returns an error. -func ParseTransport(rawurl string) (*url.URL, error) { - u, err := url.Parse(rawurl) - if err == nil && !Transports.Valid(u.Scheme) { - err = fmt.Errorf("scheme %q is not a valid transport", u.Scheme) - } - return u, err -} - -// ParseScp parses rawurl into a URL object. The rawurl must be -// an SCP-like URL, otherwise ParseScp returns an error. -func ParseScp(rawurl string) (*url.URL, error) { - match := scpSyntax.FindAllStringSubmatch(rawurl, -1) - if len(match) == 0 { - return nil, fmt.Errorf("no scp URL found in %q", rawurl) - } - m := match[0] - user := strings.TrimRight(m[1], "@") - var userinfo *url.Userinfo - if user != "" { - userinfo = url.User(user) - } - rawquery := "" - if len(m) > 3 { - rawquery = m[4] - } - return &url.URL{ - Scheme: "ssh", - User: userinfo, - Host: m[2], - Path: m[3], - RawQuery: rawquery, - }, nil -} - -// ParseLocal parses rawurl into a URL object with a "file" -// scheme. This will effectively never return an error. -func ParseLocal(rawurl string) (*url.URL, error) { - return &url.URL{ - Scheme: "file", - Host: "", - Path: rawurl, - }, nil -} - -// TransportSet represents a set of valid Git transport schemes. It -// maps these schemes to empty structs, providing a set-like -// interface. -type TransportSet struct { - Transports map[string]struct{} -} - -// NewTransportSet returns a TransportSet with the items keys mapped -// to empty struct values. -func NewTransportSet(items ...string) *TransportSet { - t := &TransportSet{ - Transports: map[string]struct{}{}, - } - for _, i := range items { - t.Transports[i] = struct{}{} - } - return t -} - -// Valid returns true if transport is a known Git URL scheme and false -// if not. -func (t *TransportSet) Valid(transport string) bool { - _, ok := t.Transports[transport] - return ok -} diff --git a/vendor/github.com/zalando/go-keyring/.catwatch.yml b/vendor/github.com/zalando/go-keyring/.catwatch.yml new file mode 100644 index 0000000..7167a18 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/.catwatch.yml @@ -0,0 +1 @@ +title: go-keyring diff --git a/vendor/github.com/zalando/go-keyring/.travis.yml b/vendor/github.com/zalando/go-keyring/.travis.yml new file mode 100644 index 0000000..6fd9b09 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/.travis.yml @@ -0,0 +1,25 @@ +sudo: required +dist: trusty + +os: + - osx + - linux + +language: go + +addons: + apt: + packages: + - python-gnomekeyring + - gnome-keyring + +go: + - 1.x + - tip + +before_script: + - | + if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + dbus-launch /usr/bin/python -c \ + "import gnomekeyring;gnomekeyring.create_sync('login', '');" + fi diff --git a/vendor/github.com/zalando/go-keyring/.zappr.yml b/vendor/github.com/zalando/go-keyring/.zappr.yml new file mode 100644 index 0000000..736c105 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/.zappr.yml @@ -0,0 +1,8 @@ +approvals: + groups: + zalando: + minimum: 2 + from: + orgs: + - "zalando" +X-Zalando-Team: teapot diff --git a/vendor/github.com/zalando/go-keyring/CONTRIBUTING.md b/vendor/github.com/zalando/go-keyring/CONTRIBUTING.md new file mode 100644 index 0000000..317952e --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing to Go keyring library + +Please open an issue for bugs and feature requests. We are open to Pull Requests, but we would like to discuss features with you. +If you want to submit a PR, always use feature branches and let people discuss changes in pull requests. + +Pull requests should only be merged after all discussions have been concluded and at least 2 reviewers has given their +**approval**. + +## Guidelines + +- **every code change** should have a test +- keep the current code style diff --git a/vendor/github.com/dvsekhvalnov/jose2go/LICENSE b/vendor/github.com/zalando/go-keyring/LICENSE similarity index 97% rename from vendor/github.com/dvsekhvalnov/jose2go/LICENSE rename to vendor/github.com/zalando/go-keyring/LICENSE index 326fe25..1c494f9 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/LICENSE +++ b/vendor/github.com/zalando/go-keyring/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 +Copyright (c) 2016 Zalando SE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/zalando/go-keyring/MAINTAINERS b/vendor/github.com/zalando/go-keyring/MAINTAINERS new file mode 100644 index 0000000..755e7e3 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/MAINTAINERS @@ -0,0 +1,2 @@ +Mikkel Oscar Lyderik Larsen +Sandor Szücs diff --git a/vendor/github.com/zalando/go-keyring/README.md b/vendor/github.com/zalando/go-keyring/README.md new file mode 100644 index 0000000..e6b43d0 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/README.md @@ -0,0 +1,146 @@ +# Go Keyring library +[![Build Status](https://travis-ci.org/zalando/go-keyring.svg?branch=master)](https://travis-ci.org/zalando/go-keyring) +[![Build status](https://ci.appveyor.com/api/projects/status/l8hdbqng769sc2c5/branch/master?svg=true)](https://ci.appveyor.com/project/mikkeloscar/go-keyring/branch/master) +[![Go Report Card](https://goreportcard.com/badge/github.com/zalando/go-keyring)](https://goreportcard.com/report/github.com/zalando/go-keyring) +[![GoDoc](https://godoc.org/github.com/zalando/go-keyring?status.svg)](https://godoc.org/github.com/zalando/go-keyring) + +`go-keyring` is an OS-agnostic library for *setting*, *getting* and *deleting* +secrets from the system keyring. It supports **OS X**, **Linux (dbus)** and +**Windows**. + +go-keyring was created after its authors searched for, but couldn't find, a better alternative. It aims to simplify +using statically linked binaries, which is cumbersome when relying on C bindings (as other keyring libraries do). + +#### Potential Uses + +If you're working with an application that needs to store user credentials +locally on the user's machine, go-keyring might come in handy. For instance, if you are writing a CLI for an API +that requires a username and password, you can store this information in the +keyring instead of having the user type it on every invocation. + +## Dependencies + +#### OS X + +The OS X implementation depends on the `/usr/bin/security` binary for +interfacing with the OS X keychain. It should be available by default. + +#### Linux + +The Linux implementation depends on the [Secret Service][SecretService] dbus +interface, which is provided by [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring). + +It's expected that the default collection `login` exists in the keyring, because +it's the default in most distros. If it doesn't exist, you can create it through the +keyring frontend program [Seahorse](https://wiki.gnome.org/Apps/Seahorse): + + * Open `seahorse` + * Go to **File > New > Password Keyring** + * Click **Continue** + * When asked for a name, use: **login** + +## Example Usage + +How to *set* and *get* a secret from the keyring: + +```go +package main + +import ( + "log" + + "github.com/zalando/go-keyring" +) + +func main() { + service := "my-app" + user := "anon" + password := "secret" + + // set password + err := keyring.Set(service, user, password) + if err != nil { + log.Fatal(err) + } + + // get password + secret, err := keyring.Get(service, user) + if err != nil { + log.Fatal(err) + } + + log.Println(secret) +} + +``` + +## Tests +### Running tests +Running the tests is simple: + +``` +go test +``` + +Which OS you use *does* matter. If you're using **Linux**, it will +test the implementation in `keyring_linux.go`. If running the tests +on **OS X**, it will test the implementation in `keyring_darwin.go`. + +### Mocking +If you need to mock the keyring behavior for testing on systems without a keyring implementation you can call `MockInit()` which will replace the OS defined provider with an in-memory one. + +```go +package implementation + +import ( + "testing" + + "github.com/zalando/go-keyring" +) + +func TestMockedSetGet(t *testing.T) { + keyring.MockInit() + err := keyring.Set("service", "user", "password") + if err != nil { + t.Fatal(err) + } + + p, err := keyring.Get("service", "user") + if err != nil { + t.Fatal(err) + } + + if p != "password" { + t.Error("password was not the expected string") + } + +} + +``` + +## Contributing/TODO + +We welcome contributions from the community; please use [CONTRIBUTING.md](CONTRIBUTING.md) as your guidelines for getting started. Here are some items that we'd love help with: + +- The code base +- Better test coverage + +Please use GitHub issues as the starting point for contributions, new ideas and/or bug reports. + +## Contact + +* E-Mail: team-teapot@zalando.de +* Security issues: Please send an email to the [maintainers](MAINTAINERS), and we'll try to get back to you within two workdays. If you don't hear back, send an email to team-teapot@zalando.de and someone will respond within five days max. + +## Contributors + +Thanks to: + +- [your name here] + +## License + +See [LICENSE](LICENSE) file. + + +[SecretService]: https://specifications.freedesktop.org/secret-service/latest/ diff --git a/vendor/github.com/zalando/go-keyring/SECURITY.md b/vendor/github.com/zalando/go-keyring/SECURITY.md new file mode 100644 index 0000000..2d5b2dd --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/SECURITY.md @@ -0,0 +1,8 @@ +We acknowledge that every line of code that we write may potentially contain security issues. +We are trying to deal with it responsibly and provide patches as quickly as possible. + +We host our bug bounty program on HackerOne, it is currently private, therefore if you would like to report a vulnerability and get rewarded for it, please ask to join our program by filling this form: + +https://corporate.zalando.com/en/services-and-contact#security-form + +You can also send your report via this form if you do not want to join our bug bounty program and just want to report a vulnerability or security issue. diff --git a/vendor/github.com/zalando/go-keyring/appveyor.yml b/vendor/github.com/zalando/go-keyring/appveyor.yml new file mode 100644 index 0000000..5ff0102 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/appveyor.yml @@ -0,0 +1,11 @@ +version: "{build}" +clone_folder: c:\gopath\src\github.com\zalando\go-keyring +environment: + GOPATH: c:\gopath +install: + - go version + - go get -t -v ./... +build_script: + - go build +test_script: + - go test -v diff --git a/vendor/github.com/zalando/go-keyring/go.mod b/vendor/github.com/zalando/go-keyring/go.mod new file mode 100644 index 0000000..714e584 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/go.mod @@ -0,0 +1,9 @@ +module github.com/zalando/go-keyring + +go 1.13 + +require ( + github.com/danieljoos/wincred v1.0.2 + github.com/godbus/dbus v4.1.0+incompatible + github.com/stretchr/testify v1.4.0 // indirect +) diff --git a/vendor/github.com/zalando/go-keyring/go.sum b/vendor/github.com/zalando/go-keyring/go.sum new file mode 100644 index 0000000..3a7cdd0 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/go.sum @@ -0,0 +1,16 @@ +github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= +github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/zalando/go-keyring/keyring.go b/vendor/github.com/zalando/go-keyring/keyring.go new file mode 100644 index 0000000..0b94ad5 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/keyring.go @@ -0,0 +1,38 @@ +package keyring + +import "fmt" + +// provider set in the init function by the relevant os file e.g.: +// keyring_linux.go +var provider Keyring = fallbackServiceProvider{} + +var ( + // ErrNotFound is the expected error if the secret isn't found in the + // keyring. + ErrNotFound = fmt.Errorf("secret not found in keyring") +) + +// Keyring provides a simple set/get interface for a keyring service. +type Keyring interface { + // Set password in keyring for user. + Set(service, user, password string) error + // Get password from keyring given service and user name. + Get(service, user string) (string, error) + // Delete secret from keyring. + Delete(service, user string) error +} + +// Set password in keyring for user. +func Set(service, user, password string) error { + return provider.Set(service, user, password) +} + +// Get password from keyring given service and user name. +func Get(service, user string) (string, error) { + return provider.Get(service, user) +} + +// Delete secret from keyring. +func Delete(service, user string) error { + return provider.Delete(service, user) +} diff --git a/vendor/github.com/zalando/go-keyring/keyring_darwin.go b/vendor/github.com/zalando/go-keyring/keyring_darwin.go new file mode 100644 index 0000000..91569d8 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/keyring_darwin.go @@ -0,0 +1,94 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package keyring + +import ( + "encoding/hex" + "fmt" + "os/exec" + "strings" +) + +const ( + execPathKeychain = "/usr/bin/security" + + // encodingPrefix is a well-known prefix added to strings encoded by Set. + encodingPrefix = "go-keyring-encoded:" +) + +type macOSXKeychain struct{} + +// func (*MacOSXKeychain) IsAvailable() bool { +// return exec.Command(execPathKeychain).Run() != exec.ErrNotFound +// } + +// Set stores stores user and pass in the keyring under the defined service +// name. +func (k macOSXKeychain) Get(service, username string) (string, error) { + out, err := exec.Command( + execPathKeychain, + "find-generic-password", + "-s", service, + "-wa", username).CombinedOutput() + if err != nil { + if strings.Contains(fmt.Sprintf("%s", out), "could not be found") { + err = ErrNotFound + } + return "", err + } + + trimStr := strings.TrimSpace(string(out[:])) + // if the string has the well-known prefix, assume it's encoded + if strings.HasPrefix(trimStr, encodingPrefix) { + dec, err := hex.DecodeString(trimStr[len(encodingPrefix):]) + return string(dec), err + } + + return trimStr, nil +} + +// Set stores a secret in the keyring given a service name and a user. +func (k macOSXKeychain) Set(service, username, password string) error { + // if the added secret has multiple lines, osx will hex encode it + // identify this with a well-known prefix. + if strings.ContainsRune(password, '\n') { + password = encodingPrefix + hex.EncodeToString([]byte(password)) + } + + return exec.Command( + execPathKeychain, + "add-generic-password", + "-U", //update if exists + "-s", service, + "-a", username, + "-w", password).Run() +} + +// Delete deletes a secret, identified by service & user, from the keyring. +func (k macOSXKeychain) Delete(service, username string) error { + out, err := exec.Command( + execPathKeychain, + "delete-generic-password", + "-s", service, + "-a", username).CombinedOutput() + if strings.Contains(fmt.Sprintf("%s", out), "could not be found") { + err = ErrNotFound + } + return err +} + +func init() { + provider = macOSXKeychain{} +} diff --git a/vendor/github.com/zalando/go-keyring/keyring_fallback.go b/vendor/github.com/zalando/go-keyring/keyring_fallback.go new file mode 100644 index 0000000..cda7f6a --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/keyring_fallback.go @@ -0,0 +1,23 @@ +package keyring + +import ( + "errors" + "runtime" +) + +// All of the following methods error out on unsupported platforms +var ErrUnsupportedPlatform = errors.New("Unsupported platform: " + runtime.GOOS) + +type fallbackServiceProvider struct{} + +func (fallbackServiceProvider) Set(service, user, pass string) error { + return ErrUnsupportedPlatform +} + +func (fallbackServiceProvider) Get(service, user string) (string, error) { + return "", ErrUnsupportedPlatform +} + +func (fallbackServiceProvider) Delete(service, user string) error { + return ErrUnsupportedPlatform +} diff --git a/vendor/github.com/zalando/go-keyring/keyring_linux.go b/vendor/github.com/zalando/go-keyring/keyring_linux.go new file mode 100644 index 0000000..4148bd3 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/keyring_linux.go @@ -0,0 +1,120 @@ +package keyring + +import ( + "fmt" + "github.com/godbus/dbus" + "github.com/zalando/go-keyring/secret_service" +) + +type secretServiceProvider struct{} + +// Set stores user and pass in the keyring under the defined service +// name. +func (s secretServiceProvider) Set(service, user, pass string) error { + svc, err := ss.NewSecretService() + if err != nil { + return err + } + + // open a session + session, err := svc.OpenSession() + if err != nil { + return err + } + defer svc.Close(session) + + attributes := map[string]string{ + "username": user, + "service": service, + } + + secret := ss.NewSecret(session.Path(), pass) + + collection := svc.GetLoginCollection() + + err = svc.Unlock(collection.Path()) + if err != nil { + return err + } + + err = svc.CreateItem(collection, + fmt.Sprintf("Password for '%s' on '%s'", user, service), + attributes, secret) + if err != nil { + return err + } + + return nil +} + +// findItem looksup an item by service and user. +func (s secretServiceProvider) findItem(svc *ss.SecretService, service, user string) (dbus.ObjectPath, error) { + collection := svc.GetLoginCollection() + + search := map[string]string{ + "username": user, + "service": service, + } + + err := svc.Unlock(collection.Path()) + if err != nil { + return "", err + } + + results, err := svc.SearchItems(collection, search) + if err != nil { + return "", err + } + + if len(results) == 0 { + return "", ErrNotFound + } + + return results[0], nil +} + +// Get gets a secret from the keyring given a service name and a user. +func (s secretServiceProvider) Get(service, user string) (string, error) { + svc, err := ss.NewSecretService() + if err != nil { + return "", err + } + + item, err := s.findItem(svc, service, user) + if err != nil { + return "", err + } + + // open a session + session, err := svc.OpenSession() + if err != nil { + return "", err + } + defer svc.Close(session) + + secret, err := svc.GetSecret(item, session.Path()) + if err != nil { + return "", err + } + + return string(secret.Value), nil +} + +// Delete deletes a secret, identified by service & user, from the keyring. +func (s secretServiceProvider) Delete(service, user string) error { + svc, err := ss.NewSecretService() + if err != nil { + return err + } + + item, err := s.findItem(svc, service, user) + if err != nil { + return err + } + + return svc.Delete(item) +} + +func init() { + provider = secretServiceProvider{} +} diff --git a/vendor/github.com/zalando/go-keyring/keyring_mock.go b/vendor/github.com/zalando/go-keyring/keyring_mock.go new file mode 100644 index 0000000..693f8cf --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/keyring_mock.go @@ -0,0 +1,46 @@ +package keyring + +type mockProvider struct { + mockStore map[string]map[string]string +} + +// Set stores user and pass in the keyring under the defined service +// name. +func (m *mockProvider) Set(service, user, pass string) error { + if m.mockStore == nil { + m.mockStore = make(map[string]map[string]string) + } + if m.mockStore[service] == nil { + m.mockStore[service] = make(map[string]string) + } + m.mockStore[service][user] = pass + return nil +} + +// Get gets a secret from the keyring given a service name and a user. +func (m *mockProvider) Get(service, user string) (string, error) { + if b, ok := m.mockStore[service]; ok { + if v, ok := b[user]; ok { + return v, nil + } + } + return "", ErrNotFound +} + +// Delete deletes a secret, identified by service & user, from the keyring. +func (m *mockProvider) Delete(service, user string) error { + if m.mockStore != nil { + if _, ok := m.mockStore[service]; ok { + if _, ok := m.mockStore[service][user]; ok { + delete(m.mockStore[service], user) + return nil + } + } + } + return ErrNotFound +} + +// MockInit sets the provider to a mocked memory store +func MockInit() { + provider = &mockProvider{} +} diff --git a/vendor/github.com/zalando/go-keyring/keyring_windows.go b/vendor/github.com/zalando/go-keyring/keyring_windows.go new file mode 100644 index 0000000..85345d5 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/keyring_windows.go @@ -0,0 +1,52 @@ +package keyring + +import ( + "github.com/danieljoos/wincred" + "syscall" +) + +type windowsKeychain struct{} + +// Get gets a secret from the keyring given a service name and a user. +func (k windowsKeychain) Get(service, username string) (string, error) { + cred, err := wincred.GetGenericCredential(k.credName(service, username)) + if err != nil { + if err == syscall.ERROR_NOT_FOUND { + return "", ErrNotFound + } + return "", err + } + + return string(cred.CredentialBlob), nil +} + +// Set stores stores user and pass in the keyring under the defined service +// name. +func (k windowsKeychain) Set(service, username, password string) error { + cred := wincred.NewGenericCredential(k.credName(service, username)) + cred.UserName = username + cred.CredentialBlob = []byte(password) + return cred.Write() +} + +// Delete deletes a secret, identified by service & user, from the keyring. +func (k windowsKeychain) Delete(service, username string) error { + cred, err := wincred.GetGenericCredential(k.credName(service, username)) + if err != nil { + if err == syscall.ERROR_NOT_FOUND { + return ErrNotFound + } + return err + } + + return cred.Delete() +} + +// credName combines service and username to a single string. +func (k windowsKeychain) credName(service, username string) string { + return service + ":" + username +} + +func init() { + provider = windowsKeychain{} +} diff --git a/vendor/github.com/zalando/go-keyring/secret_service/secret_service.go b/vendor/github.com/zalando/go-keyring/secret_service/secret_service.go new file mode 100644 index 0000000..591c976 --- /dev/null +++ b/vendor/github.com/zalando/go-keyring/secret_service/secret_service.go @@ -0,0 +1,245 @@ +package ss + +import ( + "fmt" + + "errors" + "github.com/godbus/dbus" +) + +const ( + serviceName = "org.freedesktop.secrets" + servicePath = "/org/freedesktop/secrets" + serviceInterface = "org.freedesktop.Secret.Service" + collectionInterface = "org.freedesktop.Secret.Collection" + collectionsInterface = "org.freedesktop.Secret.Service.Collections" + itemInterface = "org.freedesktop.Secret.Item" + sessionInterface = "org.freedesktop.Secret.Session" + promptInterface = "org.freedesktop.Secret.Prompt" + + loginCollectionAlias = "/org/freedesktop/secrets/aliases/default" + collectionBasePath = "/org/freedesktop/secrets/collection/" +) + +// Secret defines a org.freedesk.Secret.Item secret struct. +type Secret struct { + Session dbus.ObjectPath + Parameters []byte + Value []byte + ContentType string `dbus:"content_type"` +} + +// NewSecret initializes a new Secret. +func NewSecret(session dbus.ObjectPath, secret string) Secret { + return Secret{ + Session: session, + Parameters: []byte{}, + Value: []byte(secret), + ContentType: "text/plain; charset=utf8", + } +} + +// SecretService is an interface for the Secret Service dbus API. +type SecretService struct { + *dbus.Conn + object dbus.BusObject +} + +// NewSecretService inializes a new SecretService object. +func NewSecretService() (*SecretService, error) { + conn, err := dbus.SessionBus() + if err != nil { + return nil, err + } + + return &SecretService{ + conn, + conn.Object(serviceName, servicePath), + }, nil +} + +// OpenSession opens a secret service session. +func (s *SecretService) OpenSession() (dbus.BusObject, error) { + var disregard dbus.Variant + var sessionPath dbus.ObjectPath + err := s.object.Call(serviceInterface+".OpenSession", 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath) + if err != nil { + return nil, err + } + + return s.Object(serviceName, sessionPath), nil +} + +// CheckCollectionPath accepts dbus path and returns nil if the path is found +// in the collection interface (and can be used). +func (s *SecretService) CheckCollectionPath(path dbus.ObjectPath) error { + obj := s.Conn.Object(serviceName, servicePath) + val, err := obj.GetProperty(collectionsInterface) + if err != nil { + return err + } + paths := val.Value().([]dbus.ObjectPath) + for _, p := range paths { + if p == path { + return nil + } + } + return errors.New("path not found") +} + +// GetCollection returns a collection from a name. +func (s *SecretService) GetCollection(name string) dbus.BusObject { + return s.Object(serviceName, dbus.ObjectPath(collectionBasePath+name)) +} + +// GetLoginCollection decides and returns the dbus collection to be used for login. +func (s *SecretService) GetLoginCollection() dbus.BusObject { + path := dbus.ObjectPath(collectionBasePath + "login") + if err := s.CheckCollectionPath(path); err != nil { + path = dbus.ObjectPath(loginCollectionAlias) + } + return s.Object(serviceName, path) +} + +// Unlock unlocks a collection. +func (s *SecretService) Unlock(collection dbus.ObjectPath) error { + var unlocked []dbus.ObjectPath + var prompt dbus.ObjectPath + err := s.object.Call(serviceInterface+".Unlock", 0, []dbus.ObjectPath{collection}).Store(&unlocked, &prompt) + if err != nil { + return err + } + + _, v, err := s.handlePrompt(prompt) + if err != nil { + return err + } + + collections := v.Value() + switch c := collections.(type) { + case []dbus.ObjectPath: + unlocked = append(unlocked, c...) + } + + if len(unlocked) != 1 || (collection != loginCollectionAlias && unlocked[0] != collection) { + return fmt.Errorf("failed to unlock correct collection '%v'", collection) + } + + return nil +} + +// Close closes a secret service dbus session. +func (s *SecretService) Close(session dbus.BusObject) error { + return session.Call(sessionInterface+".Close", 0).Err +} + +// CreateCollection with the supplied label. +func (s *SecretService) CreateCollection(label string) (dbus.BusObject, error) { + properties := map[string]dbus.Variant{ + collectionInterface + ".Label": dbus.MakeVariant(label), + } + var collection, prompt dbus.ObjectPath + err := s.object.Call(serviceInterface+".CreateCollection", 0, properties, ""). + Store(&collection, &prompt) + if err != nil { + return nil, err + } + + _, v, err := s.handlePrompt(prompt) + if err != nil { + return nil, err + } + + if v.String() != "" { + collection = dbus.ObjectPath(v.String()) + } + + return s.Object(serviceName, collection), nil +} + +// CreateItem creates an item in a collection, with label, attributes and a +// related secret. +func (s *SecretService) CreateItem(collection dbus.BusObject, label string, attributes map[string]string, secret Secret) error { + properties := map[string]dbus.Variant{ + itemInterface + ".Label": dbus.MakeVariant(label), + itemInterface + ".Attributes": dbus.MakeVariant(attributes), + } + + var item, prompt dbus.ObjectPath + err := collection.Call(collectionInterface+".CreateItem", 0, + properties, secret, true).Store(&item, &prompt) + if err != nil { + return err + } + + _, _, err = s.handlePrompt(prompt) + if err != nil { + return err + } + + return nil +} + +// handlePrompt checks if a prompt should be handles and handles it by +// triggering the prompt and waiting for the Sercret service daemon to display +// the prompt to the user. +func (s *SecretService) handlePrompt(prompt dbus.ObjectPath) (bool, dbus.Variant, error) { + if prompt != dbus.ObjectPath("/") { + err := s.Object(serviceName, prompt).Call(promptInterface+".Prompt", 0, "").Err + if err != nil { + return false, dbus.MakeVariant(""), err + } + + promptSignal := make(chan *dbus.Signal, 1) + s.Signal(promptSignal) + + signal := <-promptSignal + switch signal.Name { + case promptInterface + ".Completed": + dismissed := signal.Body[0].(bool) + result := signal.Body[1].(dbus.Variant) + return dismissed, result, nil + } + + } + + return false, dbus.MakeVariant(""), nil +} + +// SearchItems returns a list of items matching the search object. +func (s *SecretService) SearchItems(collection dbus.BusObject, search interface{}) ([]dbus.ObjectPath, error) { + var results []dbus.ObjectPath + err := collection.Call(collectionInterface+".SearchItems", 0, search).Store(&results) + if err != nil { + return nil, err + } + + return results, nil +} + +// GetSecret gets secret from an item in a given session. +func (s *SecretService) GetSecret(itemPath dbus.ObjectPath, session dbus.ObjectPath) (*Secret, error) { + var secret Secret + err := s.Object(serviceName, itemPath).Call(itemInterface+".GetSecret", 0, session).Store(&secret) + if err != nil { + return nil, err + } + + return &secret, nil +} + +// Delete deletes an item from the collection. +func (s *SecretService) Delete(itemPath dbus.ObjectPath) error { + var prompt dbus.ObjectPath + err := s.Object(serviceName, itemPath).Call(itemInterface+".Delete", 0).Store(&prompt) + if err != nil { + return err + } + + _, _, err = s.handlePrompt(prompt) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index cedbaa4..28c3244 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,5 @@ # github.com/99designs/keyring v1.1.6 ## explicit -github.com/99designs/keyring # github.com/AlecAivazis/survey/v2 v2.1.1 ## explicit github.com/AlecAivazis/survey/v2 @@ -18,15 +17,6 @@ github.com/Masterminds/sprig/v3 ## explicit # github.com/danieljoos/wincred v1.0.2 github.com/danieljoos/wincred -# github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b -github.com/dvsekhvalnov/jose2go -github.com/dvsekhvalnov/jose2go/aes -github.com/dvsekhvalnov/jose2go/arrays -github.com/dvsekhvalnov/jose2go/base64url -github.com/dvsekhvalnov/jose2go/compact -github.com/dvsekhvalnov/jose2go/kdf -github.com/dvsekhvalnov/jose2go/keys/ecc -github.com/dvsekhvalnov/jose2go/padding # github.com/emirpasic/gods v1.12.0 github.com/emirpasic/gods/containers github.com/emirpasic/gods/lists @@ -37,6 +27,8 @@ github.com/emirpasic/gods/utils # github.com/fatih/color v1.9.0 ## explicit github.com/fatih/color +# github.com/fsnotify/fsnotify v1.4.9 +github.com/fsnotify/fsnotify # github.com/ghodss/yaml v1.0.0 ## explicit # github.com/go-git/gcfg v1.5.0 @@ -135,8 +127,17 @@ github.com/grafana/tanka/pkg/spec github.com/grafana/tanka/pkg/spec/v1alpha1 github.com/grafana/tanka/pkg/tanka github.com/grafana/tanka/pkg/term -# github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c -github.com/gsterjov/go-libsecret +# github.com/hashicorp/hcl v1.0.0 +github.com/hashicorp/hcl +github.com/hashicorp/hcl/hcl/ast +github.com/hashicorp/hcl/hcl/parser +github.com/hashicorp/hcl/hcl/printer +github.com/hashicorp/hcl/hcl/scanner +github.com/hashicorp/hcl/hcl/strconv +github.com/hashicorp/hcl/hcl/token +github.com/hashicorp/hcl/json/parser +github.com/hashicorp/hcl/json/scanner +github.com/hashicorp/hcl/json/token # github.com/huandu/xstrings v1.3.2 github.com/huandu/xstrings # github.com/imdario/mergo v0.3.9 @@ -157,14 +158,14 @@ github.com/karrick/godirwalk github.com/kballard/go-shellquote # github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd github.com/kevinburke/ssh_config -# github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d -github.com/keybase/go-keychain # github.com/labstack/echo/v4 v4.1.16 ## explicit github.com/labstack/echo/v4 # github.com/labstack/gommon v0.3.0 github.com/labstack/gommon/color github.com/labstack/gommon/log +# github.com/magiconair/properties v1.8.0 +github.com/magiconair/properties # github.com/manifoldco/promptui v0.7.0 ## explicit # github.com/mattn/go-colorable v0.1.6 @@ -181,27 +182,36 @@ github.com/mitchellh/copystructure # github.com/mitchellh/go-homedir v1.1.0 ## explicit github.com/mitchellh/go-homedir +# github.com/mitchellh/mapstructure v1.1.2 +github.com/mitchellh/mapstructure # github.com/mitchellh/reflectwalk v1.0.0 github.com/mitchellh/reflectwalk -# github.com/mtibben/percent v0.2.1 -github.com/mtibben/percent # github.com/olekukonko/tablewriter v0.0.4 ## explicit github.com/olekukonko/tablewriter # github.com/otiai10/copy v1.2.0 ## explicit -github.com/otiai10/copy +# github.com/pelletier/go-toml v1.2.0 +github.com/pelletier/go-toml # github.com/pkg/errors v0.9.1 github.com/pkg/errors # github.com/sergi/go-diff v1.1.0 github.com/sergi/go-diff/diffmatchpatch +# github.com/spf13/afero v1.1.2 +github.com/spf13/afero +github.com/spf13/afero/mem # github.com/spf13/cast v1.3.1 github.com/spf13/cast # github.com/spf13/cobra v1.0.0 ## explicit github.com/spf13/cobra +# github.com/spf13/jwalterweatherman v1.0.0 +github.com/spf13/jwalterweatherman # github.com/spf13/pflag v1.0.5 github.com/spf13/pflag +# github.com/spf13/viper v1.4.0 +## explicit +github.com/spf13/viper # github.com/stretchr/objx v0.2.0 github.com/stretchr/objx # github.com/stretchr/testify v1.6.1 @@ -214,11 +224,12 @@ github.com/valyala/bytebufferpool github.com/valyala/fasttemplate # github.com/whilp/git-urls v1.0.0 ## explicit -github.com/whilp/git-urls # github.com/xanzy/ssh-agent v0.2.1 github.com/xanzy/ssh-agent # github.com/zalando/go-keyring v0.1.0 ## explicit +github.com/zalando/go-keyring +github.com/zalando/go-keyring/secret_service # golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/crypto/acme golang.org/x/crypto/acme/autocert