From d8197ac7f9ab2db4c597fbb02c83c7ebb709facc Mon Sep 17 00:00:00 2001 From: Adam Engebretson Date: Sat, 27 Mar 2021 03:03:57 -0500 Subject: [PATCH 1/4] Adding Doppler Provider --- README.md | 26 + go.mod | 3 +- go.sum | 50 +- pkg/providers.go | 3 + pkg/providers/doppler.go | 99 + pkg/providers/doppler_test.go | 38 + pkg/providers/mock_providers/doppler_mock.go | 50 + vendor/github.com/DopplerHQ/cli/LICENSE | 202 ++ .../DopplerHQ/cli/pkg/configuration/config.go | 527 +++ .../cli/pkg/configuration/migration.go | 73 + .../DopplerHQ/cli/pkg/controllers/fallback.go | 128 + .../DopplerHQ/cli/pkg/controllers/keyring.go | 74 + .../cli/pkg/controllers/repo_config.go | 65 + .../DopplerHQ/cli/pkg/controllers/update.go | 111 + .../DopplerHQ/cli/pkg/controllers/version.go | 65 + .../DopplerHQ/cli/pkg/crypto/aes.go | 120 + .../DopplerHQ/cli/pkg/crypto/util.go | 33 + .../github.com/DopplerHQ/cli/pkg/http/api.go | 796 +++++ .../DopplerHQ/cli/pkg/http/config.go | 24 + .../DopplerHQ/cli/pkg/http/github.go | 81 + .../github.com/DopplerHQ/cli/pkg/http/http.go | 243 ++ .../DopplerHQ/cli/pkg/http/retry.go | 51 + .../DopplerHQ/cli/pkg/models/api.go | 110 + .../DopplerHQ/cli/pkg/models/config.go | 139 + .../DopplerHQ/cli/pkg/models/files.go | 40 + .../DopplerHQ/cli/pkg/models/parse.go | 262 ++ .../DopplerHQ/cli/pkg/models/repo_config.go | 25 + .../cli/pkg/models/secrets_format.go | 50 + .../DopplerHQ/cli/pkg/models/workers.go | 49 + .../DopplerHQ/cli/pkg/utils/config.go | 25 + .../github.com/DopplerHQ/cli/pkg/utils/io.go | 78 + .../github.com/DopplerHQ/cli/pkg/utils/log.go | 105 + .../DopplerHQ/cli/pkg/utils/random.go | 32 + .../DopplerHQ/cli/pkg/utils/util.go | 396 +++ .../DopplerHQ/cli/pkg/version/config.go | 19 + .../DopplerHQ/cli/pkg/version/version.go | 112 + .../github.com/atotto/clipboard/.travis.yml | 20 + vendor/github.com/atotto/clipboard/LICENSE | 27 + vendor/github.com/atotto/clipboard/README.md | 48 + .../github.com/atotto/clipboard/clipboard.go | 20 + .../atotto/clipboard/clipboard_darwin.go | 52 + .../atotto/clipboard/clipboard_unix.go | 129 + .../atotto/clipboard/clipboard_windows.go | 128 + vendor/github.com/atotto/clipboard/go.mod | 1 + .../danieljoos/wincred/.gitattributes | 1 + .../github.com/danieljoos/wincred/.gitignore | 23 + vendor/github.com/danieljoos/wincred/LICENSE | 21 + .../github.com/danieljoos/wincred/README.md | 98 + .../danieljoos/wincred/conversion.go | 131 + .../wincred/conversion_unsupported.go | 11 + vendor/github.com/danieljoos/wincred/go.mod | 5 + vendor/github.com/danieljoos/wincred/go.sum | 12 + vendor/github.com/danieljoos/wincred/sys.go | 143 + .../danieljoos/wincred/sys_unsupported.go | 36 + vendor/github.com/danieljoos/wincred/types.go | 69 + .../github.com/danieljoos/wincred/wincred.go | 111 + vendor/github.com/godbus/dbus/v5/.travis.yml | 50 + .../github.com/godbus/dbus/v5/CONTRIBUTING.md | 50 + vendor/github.com/godbus/dbus/v5/LICENSE | 25 + vendor/github.com/godbus/dbus/v5/MAINTAINERS | 3 + .../github.com/godbus/dbus/v5/README.markdown | 44 + vendor/github.com/godbus/dbus/v5/auth.go | 252 ++ .../godbus/dbus/v5/auth_anonymous.go | 16 + .../godbus/dbus/v5/auth_external.go | 26 + vendor/github.com/godbus/dbus/v5/auth_sha1.go | 102 + vendor/github.com/godbus/dbus/v5/call.go | 60 + vendor/github.com/godbus/dbus/v5/conn.go | 912 +++++ .../github.com/godbus/dbus/v5/conn_darwin.go | 37 + .../github.com/godbus/dbus/v5/conn_other.go | 93 + vendor/github.com/godbus/dbus/v5/conn_unix.go | 17 + .../github.com/godbus/dbus/v5/conn_windows.go | 15 + vendor/github.com/godbus/dbus/v5/dbus.go | 428 +++ vendor/github.com/godbus/dbus/v5/decoder.go | 286 ++ .../godbus/dbus/v5/default_handler.go | 328 ++ vendor/github.com/godbus/dbus/v5/doc.go | 69 + vendor/github.com/godbus/dbus/v5/encoder.go | 210 ++ vendor/github.com/godbus/dbus/v5/export.go | 412 +++ vendor/github.com/godbus/dbus/v5/go.mod | 3 + vendor/github.com/godbus/dbus/v5/go.sum | 0 vendor/github.com/godbus/dbus/v5/homedir.go | 28 + .../godbus/dbus/v5/homedir_dynamic.go | 15 + .../godbus/dbus/v5/homedir_static.go | 45 + vendor/github.com/godbus/dbus/v5/match.go | 62 + vendor/github.com/godbus/dbus/v5/message.go | 353 ++ vendor/github.com/godbus/dbus/v5/object.go | 211 ++ .../godbus/dbus/v5/server_interfaces.go | 107 + vendor/github.com/godbus/dbus/v5/sig.go | 259 ++ .../godbus/dbus/v5/transport_darwin.go | 6 + .../godbus/dbus/v5/transport_generic.go | 50 + .../godbus/dbus/v5/transport_nonce_tcp.go | 39 + .../godbus/dbus/v5/transport_tcp.go | 41 + .../godbus/dbus/v5/transport_unix.go | 214 ++ .../dbus/v5/transport_unixcred_dragonfly.go | 95 + .../dbus/v5/transport_unixcred_freebsd.go | 91 + .../dbus/v5/transport_unixcred_linux.go | 25 + .../dbus/v5/transport_unixcred_openbsd.go | 14 + vendor/github.com/godbus/dbus/v5/variant.go | 144 + .../godbus/dbus/v5/variant_lexer.go | 284 ++ .../godbus/dbus/v5/variant_parser.go | 817 +++++ .../inconshreveable/mousetrap/LICENSE | 13 + .../inconshreveable/mousetrap/README.md | 23 + .../inconshreveable/mousetrap/trap_others.go | 15 + .../inconshreveable/mousetrap/trap_windows.go | 98 + .../mousetrap/trap_windows_1.4.go | 46 + vendor/github.com/spf13/cobra/.gitignore | 38 + vendor/github.com/spf13/cobra/.mailmap | 3 + vendor/github.com/spf13/cobra/.travis.yml | 31 + vendor/github.com/spf13/cobra/LICENSE.txt | 174 + vendor/github.com/spf13/cobra/README.md | 741 ++++ vendor/github.com/spf13/cobra/args.go | 101 + .../spf13/cobra/bash_completions.go | 547 +++ .../spf13/cobra/bash_completions.md | 256 ++ vendor/github.com/spf13/cobra/cobra.go | 207 ++ vendor/github.com/spf13/cobra/command.go | 1594 +++++++++ .../github.com/spf13/cobra/command_notwin.go | 5 + vendor/github.com/spf13/cobra/command_win.go | 26 + vendor/github.com/spf13/cobra/go.mod | 13 + vendor/github.com/spf13/cobra/go.sum | 51 + .../spf13/cobra/powershell_completions.go | 100 + .../spf13/cobra/powershell_completions.md | 14 + .../spf13/cobra/shell_completions.go | 85 + .../github.com/spf13/cobra/zsh_completions.go | 336 ++ .../github.com/spf13/cobra/zsh_completions.md | 39 + vendor/github.com/spf13/pflag/.gitignore | 2 + vendor/github.com/spf13/pflag/.travis.yml | 22 + vendor/github.com/spf13/pflag/LICENSE | 28 + vendor/github.com/spf13/pflag/README.md | 296 ++ vendor/github.com/spf13/pflag/bool.go | 94 + vendor/github.com/spf13/pflag/bool_slice.go | 185 + vendor/github.com/spf13/pflag/bytes.go | 209 ++ vendor/github.com/spf13/pflag/count.go | 96 + vendor/github.com/spf13/pflag/duration.go | 86 + .../github.com/spf13/pflag/duration_slice.go | 166 + vendor/github.com/spf13/pflag/flag.go | 1239 +++++++ vendor/github.com/spf13/pflag/float32.go | 88 + .../github.com/spf13/pflag/float32_slice.go | 174 + vendor/github.com/spf13/pflag/float64.go | 84 + .../github.com/spf13/pflag/float64_slice.go | 166 + vendor/github.com/spf13/pflag/go.mod | 3 + vendor/github.com/spf13/pflag/go.sum | 0 vendor/github.com/spf13/pflag/golangflag.go | 105 + vendor/github.com/spf13/pflag/int.go | 84 + vendor/github.com/spf13/pflag/int16.go | 88 + vendor/github.com/spf13/pflag/int32.go | 88 + vendor/github.com/spf13/pflag/int32_slice.go | 174 + vendor/github.com/spf13/pflag/int64.go | 84 + vendor/github.com/spf13/pflag/int64_slice.go | 166 + vendor/github.com/spf13/pflag/int8.go | 88 + vendor/github.com/spf13/pflag/int_slice.go | 158 + vendor/github.com/spf13/pflag/ip.go | 94 + vendor/github.com/spf13/pflag/ip_slice.go | 186 + vendor/github.com/spf13/pflag/ipmask.go | 122 + vendor/github.com/spf13/pflag/ipnet.go | 98 + vendor/github.com/spf13/pflag/string.go | 80 + vendor/github.com/spf13/pflag/string_array.go | 129 + vendor/github.com/spf13/pflag/string_slice.go | 163 + .../github.com/spf13/pflag/string_to_int.go | 149 + .../github.com/spf13/pflag/string_to_int64.go | 149 + .../spf13/pflag/string_to_string.go | 160 + vendor/github.com/spf13/pflag/uint.go | 88 + vendor/github.com/spf13/pflag/uint16.go | 88 + vendor/github.com/spf13/pflag/uint32.go | 88 + vendor/github.com/spf13/pflag/uint64.go | 88 + vendor/github.com/spf13/pflag/uint8.go | 88 + vendor/github.com/spf13/pflag/uint_slice.go | 168 + .../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 + vendor/github.com/zalando/go-keyring/LICENSE | 21 + .../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 | 8 + 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/gopkg.in/gookit/color.v1/.gitignore | 20 + vendor/gopkg.in/gookit/color.v1/.travis.yml | 13 + vendor/gopkg.in/gookit/color.v1/LICENSE | 20 + vendor/gopkg.in/gookit/color.v1/README.md | 272 ++ vendor/gopkg.in/gookit/color.v1/README_cn.md | 294 ++ vendor/gopkg.in/gookit/color.v1/color.go | 198 ++ vendor/gopkg.in/gookit/color.v1/color_16.go | 296 ++ vendor/gopkg.in/gookit/color.v1/color_256.go | 210 ++ .../gopkg.in/gookit/color.v1/color_not_win.go | 17 + vendor/gopkg.in/gookit/color.v1/color_rgb.go | 319 ++ .../gopkg.in/gookit/color.v1/color_windows.go | 349 ++ vendor/gopkg.in/gookit/color.v1/style.go | 241 ++ vendor/gopkg.in/gookit/color.v1/tag.go | 364 ++ vendor/gopkg.in/gookit/color.v1/utils.go | 77 + vendor/gopkg.in/yaml.v3/.travis.yml | 16 + vendor/gopkg.in/yaml.v3/LICENSE | 50 + vendor/gopkg.in/yaml.v3/NOTICE | 13 + vendor/gopkg.in/yaml.v3/README.md | 150 + vendor/gopkg.in/yaml.v3/apic.go | 746 ++++ vendor/gopkg.in/yaml.v3/decode.go | 931 +++++ vendor/gopkg.in/yaml.v3/emitterc.go | 1992 +++++++++++ vendor/gopkg.in/yaml.v3/encode.go | 561 +++ vendor/gopkg.in/yaml.v3/go.mod | 5 + vendor/gopkg.in/yaml.v3/parserc.go | 1229 +++++++ vendor/gopkg.in/yaml.v3/readerc.go | 434 +++ vendor/gopkg.in/yaml.v3/resolve.go | 326 ++ vendor/gopkg.in/yaml.v3/scannerc.go | 3025 +++++++++++++++++ vendor/gopkg.in/yaml.v3/sorter.go | 134 + vendor/gopkg.in/yaml.v3/writerc.go | 48 + vendor/gopkg.in/yaml.v3/yaml.go | 662 ++++ vendor/gopkg.in/yaml.v3/yamlh.go | 805 +++++ vendor/gopkg.in/yaml.v3/yamlprivateh.go | 198 ++ vendor/modules.txt | 28 + 216 files changed, 37112 insertions(+), 2 deletions(-) create mode 100644 pkg/providers/doppler.go create mode 100644 pkg/providers/doppler_test.go create mode 100644 pkg/providers/mock_providers/doppler_mock.go create mode 100644 vendor/github.com/DopplerHQ/cli/LICENSE create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/configuration/config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/configuration/migration.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/controllers/fallback.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/controllers/keyring.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/controllers/repo_config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/controllers/update.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/controllers/version.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/crypto/aes.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/crypto/util.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/http/api.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/http/config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/http/github.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/http/http.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/http/retry.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/api.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/files.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/parse.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/repo_config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/secrets_format.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/models/workers.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/utils/config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/utils/io.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/utils/log.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/utils/random.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/utils/util.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/version/config.go create mode 100644 vendor/github.com/DopplerHQ/cli/pkg/version/version.go create mode 100644 vendor/github.com/atotto/clipboard/.travis.yml create mode 100644 vendor/github.com/atotto/clipboard/LICENSE create mode 100644 vendor/github.com/atotto/clipboard/README.md create mode 100644 vendor/github.com/atotto/clipboard/clipboard.go create mode 100644 vendor/github.com/atotto/clipboard/clipboard_darwin.go create mode 100644 vendor/github.com/atotto/clipboard/clipboard_unix.go create mode 100644 vendor/github.com/atotto/clipboard/clipboard_windows.go create mode 100644 vendor/github.com/atotto/clipboard/go.mod create mode 100644 vendor/github.com/danieljoos/wincred/.gitattributes create mode 100644 vendor/github.com/danieljoos/wincred/.gitignore create mode 100644 vendor/github.com/danieljoos/wincred/LICENSE create mode 100644 vendor/github.com/danieljoos/wincred/README.md create mode 100644 vendor/github.com/danieljoos/wincred/conversion.go create mode 100644 vendor/github.com/danieljoos/wincred/conversion_unsupported.go create mode 100644 vendor/github.com/danieljoos/wincred/go.mod create mode 100644 vendor/github.com/danieljoos/wincred/go.sum create mode 100644 vendor/github.com/danieljoos/wincred/sys.go create mode 100644 vendor/github.com/danieljoos/wincred/sys_unsupported.go create mode 100644 vendor/github.com/danieljoos/wincred/types.go create mode 100644 vendor/github.com/danieljoos/wincred/wincred.go create mode 100644 vendor/github.com/godbus/dbus/v5/.travis.yml create mode 100644 vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md create mode 100644 vendor/github.com/godbus/dbus/v5/LICENSE create mode 100644 vendor/github.com/godbus/dbus/v5/MAINTAINERS create mode 100644 vendor/github.com/godbus/dbus/v5/README.markdown create mode 100644 vendor/github.com/godbus/dbus/v5/auth.go create mode 100644 vendor/github.com/godbus/dbus/v5/auth_anonymous.go create mode 100644 vendor/github.com/godbus/dbus/v5/auth_external.go create mode 100644 vendor/github.com/godbus/dbus/v5/auth_sha1.go create mode 100644 vendor/github.com/godbus/dbus/v5/call.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_darwin.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_other.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_unix.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_windows.go create mode 100644 vendor/github.com/godbus/dbus/v5/dbus.go create mode 100644 vendor/github.com/godbus/dbus/v5/decoder.go create mode 100644 vendor/github.com/godbus/dbus/v5/default_handler.go create mode 100644 vendor/github.com/godbus/dbus/v5/doc.go create mode 100644 vendor/github.com/godbus/dbus/v5/encoder.go create mode 100644 vendor/github.com/godbus/dbus/v5/export.go create mode 100644 vendor/github.com/godbus/dbus/v5/go.mod create mode 100644 vendor/github.com/godbus/dbus/v5/go.sum create mode 100644 vendor/github.com/godbus/dbus/v5/homedir.go create mode 100644 vendor/github.com/godbus/dbus/v5/homedir_dynamic.go create mode 100644 vendor/github.com/godbus/dbus/v5/homedir_static.go create mode 100644 vendor/github.com/godbus/dbus/v5/match.go create mode 100644 vendor/github.com/godbus/dbus/v5/message.go create mode 100644 vendor/github.com/godbus/dbus/v5/object.go create mode 100644 vendor/github.com/godbus/dbus/v5/server_interfaces.go create mode 100644 vendor/github.com/godbus/dbus/v5/sig.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_darwin.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_generic.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_nonce_tcp.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_tcp.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unix.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_dragonfly.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_linux.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_openbsd.go create mode 100644 vendor/github.com/godbus/dbus/v5/variant.go create mode 100644 vendor/github.com/godbus/dbus/v5/variant_lexer.go create mode 100644 vendor/github.com/godbus/dbus/v5/variant_parser.go create mode 100644 vendor/github.com/inconshreveable/mousetrap/LICENSE create mode 100644 vendor/github.com/inconshreveable/mousetrap/README.md create mode 100644 vendor/github.com/inconshreveable/mousetrap/trap_others.go create mode 100644 vendor/github.com/inconshreveable/mousetrap/trap_windows.go create mode 100644 vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go create mode 100644 vendor/github.com/spf13/cobra/.gitignore create mode 100644 vendor/github.com/spf13/cobra/.mailmap create mode 100644 vendor/github.com/spf13/cobra/.travis.yml create mode 100644 vendor/github.com/spf13/cobra/LICENSE.txt create mode 100644 vendor/github.com/spf13/cobra/README.md create mode 100644 vendor/github.com/spf13/cobra/args.go create mode 100644 vendor/github.com/spf13/cobra/bash_completions.go create mode 100644 vendor/github.com/spf13/cobra/bash_completions.md create mode 100644 vendor/github.com/spf13/cobra/cobra.go create mode 100644 vendor/github.com/spf13/cobra/command.go create mode 100644 vendor/github.com/spf13/cobra/command_notwin.go create mode 100644 vendor/github.com/spf13/cobra/command_win.go create mode 100644 vendor/github.com/spf13/cobra/go.mod create mode 100644 vendor/github.com/spf13/cobra/go.sum create mode 100644 vendor/github.com/spf13/cobra/powershell_completions.go create mode 100644 vendor/github.com/spf13/cobra/powershell_completions.md create mode 100644 vendor/github.com/spf13/cobra/shell_completions.go create mode 100644 vendor/github.com/spf13/cobra/zsh_completions.go create mode 100644 vendor/github.com/spf13/cobra/zsh_completions.md create mode 100644 vendor/github.com/spf13/pflag/.gitignore create mode 100644 vendor/github.com/spf13/pflag/.travis.yml create mode 100644 vendor/github.com/spf13/pflag/LICENSE create mode 100644 vendor/github.com/spf13/pflag/README.md create mode 100644 vendor/github.com/spf13/pflag/bool.go create mode 100644 vendor/github.com/spf13/pflag/bool_slice.go create mode 100644 vendor/github.com/spf13/pflag/bytes.go create mode 100644 vendor/github.com/spf13/pflag/count.go create mode 100644 vendor/github.com/spf13/pflag/duration.go create mode 100644 vendor/github.com/spf13/pflag/duration_slice.go create mode 100644 vendor/github.com/spf13/pflag/flag.go create mode 100644 vendor/github.com/spf13/pflag/float32.go create mode 100644 vendor/github.com/spf13/pflag/float32_slice.go create mode 100644 vendor/github.com/spf13/pflag/float64.go create mode 100644 vendor/github.com/spf13/pflag/float64_slice.go create mode 100644 vendor/github.com/spf13/pflag/go.mod create mode 100644 vendor/github.com/spf13/pflag/go.sum create mode 100644 vendor/github.com/spf13/pflag/golangflag.go create mode 100644 vendor/github.com/spf13/pflag/int.go create mode 100644 vendor/github.com/spf13/pflag/int16.go create mode 100644 vendor/github.com/spf13/pflag/int32.go create mode 100644 vendor/github.com/spf13/pflag/int32_slice.go create mode 100644 vendor/github.com/spf13/pflag/int64.go create mode 100644 vendor/github.com/spf13/pflag/int64_slice.go create mode 100644 vendor/github.com/spf13/pflag/int8.go create mode 100644 vendor/github.com/spf13/pflag/int_slice.go create mode 100644 vendor/github.com/spf13/pflag/ip.go create mode 100644 vendor/github.com/spf13/pflag/ip_slice.go create mode 100644 vendor/github.com/spf13/pflag/ipmask.go create mode 100644 vendor/github.com/spf13/pflag/ipnet.go create mode 100644 vendor/github.com/spf13/pflag/string.go create mode 100644 vendor/github.com/spf13/pflag/string_array.go create mode 100644 vendor/github.com/spf13/pflag/string_slice.go create mode 100644 vendor/github.com/spf13/pflag/string_to_int.go create mode 100644 vendor/github.com/spf13/pflag/string_to_int64.go create mode 100644 vendor/github.com/spf13/pflag/string_to_string.go create mode 100644 vendor/github.com/spf13/pflag/uint.go create mode 100644 vendor/github.com/spf13/pflag/uint16.go create mode 100644 vendor/github.com/spf13/pflag/uint32.go create mode 100644 vendor/github.com/spf13/pflag/uint64.go create mode 100644 vendor/github.com/spf13/pflag/uint8.go create mode 100644 vendor/github.com/spf13/pflag/uint_slice.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 create mode 100644 vendor/github.com/zalando/go-keyring/LICENSE 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 create mode 100644 vendor/gopkg.in/gookit/color.v1/.gitignore create mode 100644 vendor/gopkg.in/gookit/color.v1/.travis.yml create mode 100644 vendor/gopkg.in/gookit/color.v1/LICENSE create mode 100644 vendor/gopkg.in/gookit/color.v1/README.md create mode 100644 vendor/gopkg.in/gookit/color.v1/README_cn.md create mode 100644 vendor/gopkg.in/gookit/color.v1/color.go create mode 100644 vendor/gopkg.in/gookit/color.v1/color_16.go create mode 100644 vendor/gopkg.in/gookit/color.v1/color_256.go create mode 100644 vendor/gopkg.in/gookit/color.v1/color_not_win.go create mode 100644 vendor/gopkg.in/gookit/color.v1/color_rgb.go create mode 100644 vendor/gopkg.in/gookit/color.v1/color_windows.go create mode 100644 vendor/gopkg.in/gookit/color.v1/style.go create mode 100644 vendor/gopkg.in/gookit/color.v1/tag.go create mode 100644 vendor/gopkg.in/gookit/color.v1/utils.go create mode 100644 vendor/gopkg.in/yaml.v3/.travis.yml create mode 100644 vendor/gopkg.in/yaml.v3/LICENSE create mode 100644 vendor/gopkg.in/yaml.v3/NOTICE create mode 100644 vendor/gopkg.in/yaml.v3/README.md create mode 100644 vendor/gopkg.in/yaml.v3/apic.go create mode 100644 vendor/gopkg.in/yaml.v3/decode.go create mode 100644 vendor/gopkg.in/yaml.v3/emitterc.go create mode 100644 vendor/gopkg.in/yaml.v3/encode.go create mode 100644 vendor/gopkg.in/yaml.v3/go.mod create mode 100644 vendor/gopkg.in/yaml.v3/parserc.go create mode 100644 vendor/gopkg.in/yaml.v3/readerc.go create mode 100644 vendor/gopkg.in/yaml.v3/resolve.go create mode 100644 vendor/gopkg.in/yaml.v3/scannerc.go create mode 100644 vendor/gopkg.in/yaml.v3/sorter.go create mode 100644 vendor/gopkg.in/yaml.v3/writerc.go create mode 100644 vendor/gopkg.in/yaml.v3/yaml.go create mode 100644 vendor/gopkg.in/yaml.v3/yamlh.go create mode 100644 vendor/gopkg.in/yaml.v3/yamlprivateh.go diff --git a/README.md b/README.md index 106a4b8c..8732815f 100644 --- a/README.md +++ b/README.md @@ -411,6 +411,32 @@ dotenv: path: ~/my-dot-env.env ``` +## Doppler + +### Authentication + +Install the [doppler cli][dopplercli] then run `doppler login`. You'll also need to configure your desired "project" for any given directory using `doppler configure`. Alternatively, you can set a global project by running `doppler configure set project ` from your home directory. + +### Features + +* Sync - `yes` +* Mapping - `yes` +* Key format + * `env` - env key like + +### Example Config + +```yaml +doppler: + env_sync: + path: prd + env: + MG_KEY: + path: prd + field: OTHER_MG_KEY # (optional) +``` + +[dopplercli]: https://docs.doppler.com/docs/cli # Security Model diff --git a/go.mod b/go.mod index 7540f00c..79d711a3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spectralops/teller -go 1.15 +go 1.16 require ( cloud.google.com/go v0.78.0 @@ -10,6 +10,7 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect + github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/kong v0.2.15 diff --git a/go.sum b/go.sum index 9c3efcf4..b1bab645 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AlecAivazis/survey/v2 v2.0.8/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= github.com/AlecAivazis/survey/v2 v2.2.8 h1:TgxCwybKdBckmC+/P9/5h49rw/nAHe/itZL0dgHs+Q0= github.com/AlecAivazis/survey/v2 v2.2.8/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= github.com/Azure/azure-sdk-for-go v52.5.0+incompatible h1:/NLBWHCnIHtZyLPc1P7WIqi4Te4CC23kIQyK3Ep/7lA= @@ -68,6 +69,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e h1:X5yHJzv4R12/zF9PR6OlroESbv6Bbz+L5LlZWY+qGkw= +github.com/DopplerHQ/cli v0.0.0-20210309042056-414bede8a50e/go.mod h1:BYiWQL3TJGl8TjMYVeWYxJJK46arOR4J/2urnIJE/jE= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= @@ -86,10 +89,14 @@ github.com/alexsasharegan/dotenv v0.0.0-20171113213728-090a4d1b5d42 h1:Mj1wcfVgY github.com/alexsasharegan/dotenv v0.0.0-20171113213728-090a4d1b5d42/go.mod h1:8KjIUiYilt2g1LINCCvsOtJ+f2uAdSTYO2A/b97lPw8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= +github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.2.0 h1:BS+UYpbsElC82gB+2E2jiCBg36i8HlubTB/dO/moQ9c= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2/config v1.1.1 h1:ZAoq32boMzcaTW9bcUacBswAmHTbvlvDJICgHFZuECo= @@ -125,11 +132,17 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= 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= @@ -152,6 +165,7 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -161,8 +175,11 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= 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-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= @@ -228,6 +245,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -269,6 +287,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= @@ -293,7 +312,10 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDG github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/interagent/schematic v0.0.0-20180830170528-b5e8ba7aa570/go.mod h1:4X9u5iNUePRrRDdwjok6skjlQBXTcNfWa4C3uS1+5SQ= +github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jftuga/ellipsis v1.0.0 h1:ERi1XBFERM2YpadkvM1P9bxQKgOC40Hr6TCKkvLBDtY= github.com/jftuga/ellipsis v1.0.0/go.mod h1:phJ3vQPi8MPrtRKdo0aESNJdw56f09SLVX0k/FY+jr0= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -319,6 +341,7 @@ github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/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/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -331,6 +354,7 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -362,6 +386,7 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -386,6 +411,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -396,9 +422,20 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -408,16 +445,23 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y= github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zalando/go-keyring v0.1.1-0.20210112083600-4d37811583ad h1:aGgLtBPvZSG8l+Uo7Ko23p44iVOcrmZsZO2shJZHer8= +github.com/zalando/go-keyring v0.1.1-0.20210112083600-4d37811583ad/go.mod h1:OIC+OZ28XbmwFxU/Rp9V7eKzZjamBJwRzC8UFJH9+L8= go.etcd.io/etcd/api/v3 v3.5.0-alpha.0 h1:+e5nrluATIy3GP53znpkHMFzPTHGYyzvJGFCbuI6ZLc= go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw= go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 h1:dr1EOILak2pu4Nf5XbRIOCNIBjcz6UmkQd7hHRXwxaM= go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8= go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 h1:3yLUEC0nFCxw/RArImOyRUI4OAFbg4PFpBbAhSNzKNY= go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -435,6 +479,7 @@ go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -537,13 +582,13 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -767,6 +812,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/gookit/color.v1 v1.1.6 h1:5fB10p6AUFjhd2ayq9JgmJWr9WlTrguFdw3qlYtKNHk= +gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY= gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -777,6 +824,7 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/providers.go b/pkg/providers.go index 3128cf7b..deac6cb6 100644 --- a/pkg/providers.go +++ b/pkg/providers.go @@ -27,6 +27,7 @@ func (p *BuiltinProviders) ProviderHumanToMachine() map[string]string { ".env": "dotenv", "Vercel": "vercel", "Azure Key Vault": "azure_keyvault", + "Doppler": "doppler", } } @@ -52,6 +53,8 @@ func (p *BuiltinProviders) GetProvider(name string) (core.Provider, error) { return providers.NewVercel() case "azure_keyvault": return providers.NewAzureKeyVault() + case "doppler": + return providers.NewDoppler() default: return nil, fmt.Errorf("provider %s does not exist", name) } diff --git a/pkg/providers/doppler.go b/pkg/providers/doppler.go new file mode 100644 index 00000000..3025887c --- /dev/null +++ b/pkg/providers/doppler.go @@ -0,0 +1,99 @@ +package providers + +import ( + "fmt" + "sort" + + "github.com/DopplerHQ/cli/pkg/configuration" + "github.com/DopplerHQ/cli/pkg/http" + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "github.com/spectralops/teller/pkg/core" +) + +type DopplerClient interface { + GetSecrets(host string, verifyTLS bool, apiKey string, project string, config string) ([]byte, http.Error) +} + +type dopplerClient struct{} + +func (dopplerClient) GetSecrets(host string, verifyTLS bool, apiKey string, project string, config string) ([]byte, http.Error) { + return http.GetSecrets(host, verifyTLS, apiKey, project, config) +} + +type Doppler struct { + client DopplerClient + config models.ScopedOptions +} + +func NewDoppler() (core.Provider, error) { + configuration.Setup() + configuration.LoadConfig() + + return &Doppler{ + client: dopplerClient{}, + config: configuration.Get(configuration.Scope), + }, nil +} + +func (h *Doppler) Name() string { + return "doppler" +} + +func (h *Doppler) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) { + s, err := h.getConfig(p.Path) + if err != nil { + return nil, err + } + + entries := []core.EnvEntry{} + for k, v := range s { + entries = append(entries, core.EnvEntry{ + Key: k, + Value: v.ComputedValue, + Provider: h.Name(), + ResolvedPath: p.Path, + }) + } + sort.Sort(core.EntriesByKey(entries)) + return entries, nil +} + +func (h *Doppler) Get(p core.KeyPath) (*core.EnvEntry, error) { + s, err := h.getConfig(p.Path) + if err != nil { + return nil, err + } + + key := p.Env + if p.Field != "" { + key = p.Field + } + + v, ok := s[key] + if !ok { + return nil, fmt.Errorf("field at '%s' does not exist", p.Path) + } + + return &core.EnvEntry{ + Key: p.Env, + Value: v.ComputedValue, + ResolvedPath: p.Path, + Provider: h.Name(), + }, nil +} + +func (h *Doppler) getConfig(config string) (map[string]models.ComputedSecret, error) { + r, herr := h.client.GetSecrets( + h.config.APIHost.Value, + utils.GetBool(h.config.VerifyTLS.Value, true), + h.config.Token.Value, + h.config.EnclaveProject.Value, + config, + ) + if !herr.IsNil() { + return nil, herr.Err + } + + return models.ParseSecrets(r) +} diff --git a/pkg/providers/doppler_test.go b/pkg/providers/doppler_test.go new file mode 100644 index 00000000..254eb3ce --- /dev/null +++ b/pkg/providers/doppler_test.go @@ -0,0 +1,38 @@ +package providers + +import ( + "testing" + + "github.com/DopplerHQ/cli/pkg/http" + "github.com/DopplerHQ/cli/pkg/models" + "github.com/golang/mock/gomock" + "github.com/spectralops/teller/pkg/providers/mock_providers" +) + +func TestDoppler(t *testing.T) { + ctrl := gomock.NewController(t) + // Assert that Bar is invoked. + defer ctrl.Finish() + client := mock_providers.NewMockDopplerClient(ctrl) + path := "settings/prod/billing-svc" + pathmap := "settings/prod/billing-svc/all" + out := []byte(`{ + "secrets": { + "MG_KEY": { + "computed": "shazam" + }, + "SMTP_PASS": { + "computed": "mailman" + } + } + }`) + + client.EXPECT().GetSecrets(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Eq(path)).Return(out, http.Error{}).AnyTimes() + client.EXPECT().GetSecrets(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Eq(pathmap)).Return(out, http.Error{}).AnyTimes() + + s := Doppler{ + client: client, + config: models.ScopedOptions{}, + } + AssertProvider(t, &s, true) +} diff --git a/pkg/providers/mock_providers/doppler_mock.go b/pkg/providers/mock_providers/doppler_mock.go new file mode 100644 index 00000000..dec6c87a --- /dev/null +++ b/pkg/providers/mock_providers/doppler_mock.go @@ -0,0 +1,50 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/providers/doppler.go + +// Package mock_providers is a generated GoMock package. +package mock_providers + +import ( + reflect "reflect" + + http "github.com/DopplerHQ/cli/pkg/http" + gomock "github.com/golang/mock/gomock" +) + +// MockDopplerClient is a mock of DopplerClient interface. +type MockDopplerClient struct { + ctrl *gomock.Controller + recorder *MockDopplerClientMockRecorder +} + +// MockDopplerClientMockRecorder is the mock recorder for MockDopplerClient. +type MockDopplerClientMockRecorder struct { + mock *MockDopplerClient +} + +// NewMockDopplerClient creates a new mock instance. +func NewMockDopplerClient(ctrl *gomock.Controller) *MockDopplerClient { + mock := &MockDopplerClient{ctrl: ctrl} + mock.recorder = &MockDopplerClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDopplerClient) EXPECT() *MockDopplerClientMockRecorder { + return m.recorder +} + +// GetSecrets mocks base method. +func (m *MockDopplerClient) GetSecrets(host string, verifyTLS bool, apiKey, project, config string) ([]byte, http.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSecrets", host, verifyTLS, apiKey, project, config) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(http.Error) + return ret0, ret1 +} + +// GetSecrets indicates an expected call of GetSecrets. +func (mr *MockDopplerClientMockRecorder) GetSecrets(host, verifyTLS, apiKey, project, config interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecrets", reflect.TypeOf((*MockDopplerClient)(nil).GetSecrets), host, verifyTLS, apiKey, project, config) +} diff --git a/vendor/github.com/DopplerHQ/cli/LICENSE b/vendor/github.com/DopplerHQ/cli/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/LICENSE @@ -0,0 +1,202 @@ + + 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. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/DopplerHQ/cli/pkg/configuration/config.go b/vendor/github.com/DopplerHQ/cli/pkg/configuration/config.go new file mode 100644 index 00000000..341355e8 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/configuration/config.go @@ -0,0 +1,527 @@ +/* +Copyright © 2019 Doppler + +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 configuration + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/DopplerHQ/cli/pkg/controllers" + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +// baseConfigDir (e.g. /home/user/) +var baseConfigDir string + +// UserConfigDir (e.g. /home/user/.doppler) +var UserConfigDir string + +// UserConfigFile (e.g. /home/user/doppler/.doppler.yaml) +var UserConfigFile string + +// Scope to use for config file +var Scope = "." + +var configFileName = ".doppler.yaml" +var configContents models.ConfigFile + +func init() { + baseConfigDir = utils.HomeDir() + UserConfigDir = filepath.Join(baseConfigDir, ".doppler") + UserConfigFile = filepath.Join(UserConfigDir, configFileName) +} + +// Setup the config directory and config file +func Setup() { + utils.LogDebug(fmt.Sprintf("Using config file %s", UserConfigFile)) + + if !utils.Exists(UserConfigDir) { + utils.LogDebug(fmt.Sprintf("Creating the config directory %s", UserConfigDir)) + err := os.Mkdir(UserConfigDir, 0700) + if err != nil { + utils.HandleError(err, fmt.Sprintf("Unable to create config directory %s", UserConfigDir)) + } + } + + // This may be different from `UserConfigDir` if `--configuration` was provided + configDir := filepath.Dir(UserConfigFile) + if !utils.Exists(configDir) { + utils.HandleError(fmt.Errorf("Configuration file directory does not exist %s", configDir)) + } + + if !utils.Exists(UserConfigFile) { + v1ConfigA := filepath.Join(utils.ConfigDir(), configFileName) + v1ConfigB := filepath.Join(utils.HomeDir(), configFileName) + if utils.Exists(v1ConfigA) { + utils.LogDebug("Migrating the config from CLI v1") + err := os.Rename(v1ConfigA, UserConfigFile) + if err != nil { + utils.HandleError(err, "Unable to migrate config from CLI v1") + } + } else if utils.Exists(v1ConfigB) { + utils.LogDebug("Migrating the config from CLI v1") + err := os.Rename(v1ConfigB, UserConfigFile) + if err != nil { + utils.HandleError(err, "Unable to migrate config from CLI v1") + } + } else if jsonExists() { + utils.LogDebug("Migrating the config from the Node CLI") + migrateJSONToYaml() + } else { + utils.LogDebug("Creating a new config file") + var blankConfig models.ConfigFile + writeConfig(blankConfig) + } + } +} + +// LoadConfig load the configuration file +func LoadConfig() { + configContents = readConfig() +} + +// VersionCheck the last version check +func VersionCheck() models.VersionCheck { + return configContents.VersionCheck +} + +// SetVersionCheck the last version check +func SetVersionCheck(version models.VersionCheck) { + configContents.VersionCheck = version + writeConfig(configContents) +} + +// Get the config at the specified scope +func Get(scope string) models.ScopedOptions { + var normalizedScope string + var err error + if normalizedScope, err = NormalizeScope(scope); err != nil { + utils.HandleError(err, fmt.Sprintf("Invalid scope: %s", scope)) + } + if !strings.HasSuffix(normalizedScope, string(filepath.Separator)) { + normalizedScope = normalizedScope + string(filepath.Separator) + } + var scopedConfig models.ScopedOptions + + for confScope, conf := range configContents.Scoped { + confScopePath := confScope + // both paths must end in / to prevent partial match (e.g. /test matching /test123) + if !strings.HasSuffix(confScopePath, string(filepath.Separator)) { + confScopePath = confScopePath + string(filepath.Separator) + } + + if !strings.HasPrefix(normalizedScope, confScopePath) { + continue + } + + pairs := models.Pairs(conf) + scopedPairs := models.ScopedPairs(&scopedConfig) + for name, pair := range pairs { + if pair != "" { + scopedPair := scopedPairs[name] + if *scopedPair == (models.ScopedOption{}) || len(confScope) > len(scopedPair.Scope) { + scopedPair.Value = pair + scopedPair.Scope = confScope + scopedPair.Source = models.ConfigFileSource.String() + } + } + } + } + + if controllers.IsKeyringSecret(scopedConfig.Token.Value) { + utils.LogDebug(fmt.Sprintf("Retrieving %s from system keyring", models.ConfigToken.String())) + token, err := controllers.GetKeyring(scopedConfig.Token.Value) + if !err.IsNil() { + utils.HandleError(err.Unwrap(), err.Message) + } + + scopedConfig.Token.Value = token + } + + return scopedConfig +} + +// LocalConfig retrieves the config for the scoped directory +func LocalConfig(cmd *cobra.Command) models.ScopedOptions { + // config file (lowest priority) + localConfig := Get(Scope) + + // environment variables + if !utils.GetBoolFlag(cmd, "no-read-env") { + pairs := models.EnvPairs(&localConfig) + envVars := []string{} + for envVar := range pairs { + envVars = append(envVars, envVar) + } + + // sort variables so that they are processed in a deterministic order + // this also ensures ENCLAVE_ variables are given precedence over (i.e. read after) DOPPLER_ variables, + // which is necessary for backwards compatibility until we drop support for ENCLAVE_ variables + sort.Strings(envVars) + + for _, envVar := range envVars { + envValue := os.Getenv(envVar) + if envValue != "" { + pair := pairs[envVar] + pair.Value = envValue + pair.Scope = "/" + pair.Source = models.EnvironmentSource.String() + } + } + } + + // individual flags (highest priority) + flagSet := cmd.Flags().Changed("token") + if flagSet || localConfig.Token.Value == "" { + localConfig.Token.Value = cmd.Flag("token").Value.String() + localConfig.Token.Scope = "/" + + if flagSet { + localConfig.Token.Source = models.FlagSource.String() + } else { + localConfig.Token.Source = models.DefaultValueSource.String() + } + } + + flagSet = cmd.Flags().Changed("api-host") + if flagSet || localConfig.APIHost.Value == "" { + localConfig.APIHost.Value = cmd.Flag("api-host").Value.String() + localConfig.APIHost.Scope = "/" + + if flagSet { + localConfig.APIHost.Source = models.FlagSource.String() + } else { + localConfig.APIHost.Source = models.DefaultValueSource.String() + } + } + + flagSet = cmd.Flags().Changed("dashboard-host") + if flagSet || localConfig.DashboardHost.Value == "" { + localConfig.DashboardHost.Value = cmd.Flag("dashboard-host").Value.String() + localConfig.DashboardHost.Scope = "/" + + if flagSet { + localConfig.DashboardHost.Source = models.FlagSource.String() + } else { + localConfig.DashboardHost.Source = models.DefaultValueSource.String() + } + } + + flagSet = cmd.Flags().Changed("no-verify-tls") + if flagSet || localConfig.VerifyTLS.Value == "" { + noVerifyTLS := cmd.Flag("no-verify-tls").Value.String() + localConfig.VerifyTLS.Value = strconv.FormatBool(!utils.GetBool(noVerifyTLS, false)) + localConfig.VerifyTLS.Scope = "/" + + if flagSet { + localConfig.VerifyTLS.Source = models.FlagSource.String() + } else { + localConfig.VerifyTLS.Source = models.DefaultValueSource.String() + } + } + + // these flags below do not have a default value and should only be used if specified by the user (or will cause invalid memory access) + flagSet = cmd.Flags().Changed("project") + if flagSet { + localConfig.EnclaveProject.Value = cmd.Flag("project").Value.String() + localConfig.EnclaveProject.Scope = "/" + + if flagSet { + localConfig.EnclaveProject.Source = models.FlagSource.String() + } else { + localConfig.EnclaveProject.Source = models.DefaultValueSource.String() + } + } + + flagSet = cmd.Flags().Changed("config") + if flagSet { + localConfig.EnclaveConfig.Value = cmd.Flag("config").Value.String() + localConfig.EnclaveConfig.Scope = "/" + + if flagSet { + localConfig.EnclaveConfig.Source = models.FlagSource.String() + } else { + localConfig.EnclaveConfig.Source = models.DefaultValueSource.String() + } + } + + return localConfig +} + +// AllConfigs get all configs we know about +func AllConfigs() map[string]models.FileScopedOptions { + all := map[string]models.FileScopedOptions{} + for scope, scopedOptions := range configContents.Scoped { + options := scopedOptions + + if controllers.IsKeyringSecret(options.Token) { + utils.LogDebug(fmt.Sprintf("Retrieving %s from system keyring", models.ConfigToken.String())) + token, err := controllers.GetKeyring(options.Token) + if !err.IsNil() { + utils.HandleError(err.Unwrap(), err.Message) + } + + options.Token = token + } + + all[scope] = options + } + return all +} + +// Set properties on a scoped config +func Set(scope string, options map[string]string) { + var normalizedScope string + var err error + if normalizedScope, err = NormalizeScope(scope); err != nil { + utils.HandleError(err, fmt.Sprintf("Invalid scope: %s", scope)) + } + + config := configContents.Scoped[normalizedScope] + previousToken := config.Token + + for key, value := range options { + if !IsValidConfigOption(key) { + utils.HandleError(errors.New("invalid option "+key), "") + } + + if key == models.ConfigToken.String() { + utils.LogDebug(fmt.Sprintf("Saving %s to system keyring", key)) + uuid, err := utils.UUID() + if err != nil { + utils.HandleError(err, "Unable to generate UUID for keyring") + } + id := controllers.GenerateKeyringID(uuid) + + if controllerError := controllers.SetKeyring(id, value); !controllerError.IsNil() { + utils.LogDebugError(controllerError.Unwrap()) + utils.LogDebug(controllerError.Message) + } else { + value = id + + // remove old token from keyring + if controllers.IsKeyringSecret(previousToken) { + utils.LogDebug("Removing previous token from system keyring") + if controllerError := controllers.DeleteKeyring(previousToken); !controllerError.IsNil() { + utils.LogDebugError(controllerError.Unwrap()) + utils.LogDebug(controllerError.Message) + } + } + } + } + + SetConfigValue(&config, key, value) + configContents.Scoped[normalizedScope] = config + } + + writeConfig(configContents) +} + +// Unset a local config +func Unset(scope string, options []string) { + var normalizedScope string + var err error + if normalizedScope, err = NormalizeScope(scope); err != nil { + utils.HandleError(err, fmt.Sprintf("Invalid scope: %s", scope)) + } + + if configContents.Scoped[normalizedScope] == (models.FileScopedOptions{}) { + return + } + + for _, key := range options { + if !IsValidConfigOption(key) { + utils.HandleError(errors.New("invalid option "+key), "") + } + + config := configContents.Scoped[normalizedScope] + + if key == models.ConfigToken.String() { + previousToken := config.Token + // remove old token from keyring + if controllers.IsKeyringSecret(previousToken) { + if controllerError := controllers.DeleteKeyring(previousToken); !controllerError.IsNil() { + utils.LogDebugError(controllerError.Unwrap()) + utils.LogDebug(controllerError.Message) + } + } + } + + SetConfigValue(&config, key, "") + configContents.Scoped[normalizedScope] = config + } + + if configContents.Scoped[normalizedScope] == (models.FileScopedOptions{}) { + delete(configContents.Scoped, normalizedScope) + } + + writeConfig(configContents) +} + +// Write config to filesystem +func writeConfig(config models.ConfigFile) { + bytes, err := yaml.Marshal(config) + if err != nil { + utils.HandleError(err) + } + + utils.LogDebug(fmt.Sprintf("Writing user config to %s", UserConfigFile)) + if err := utils.WriteFile(UserConfigFile, bytes, os.FileMode(0600)); err != nil { + utils.HandleError(err) + } +} + +func readConfig() models.ConfigFile { + utils.LogDebug("Reading config file") + + fileContents, err := ioutil.ReadFile(UserConfigFile) // #nosec G304 + if err != nil { + utils.HandleError(err, "Unable to read user config file") + } + + var config models.ConfigFile + err = yaml.Unmarshal(fileContents, &config) + if err != nil { + utils.HandleError(err, "Unable to parse user config file") + } + + // sort scopes before normalizing so that if multiple scopes normalize to + // the same value (like '/' and '*') they'll apply in a deterministic order + var sorted []string + for scope := range config.Scoped { + sorted = append(sorted, scope) + } + sort.Strings(sorted) + + // normalize config scope and merge options from conflicting scopes + normalizedOptions := map[string]models.FileScopedOptions{} + for _, scope := range sorted { + var normalizedScope string + if normalizedScope, err = NormalizeScope(scope); err != nil { + utils.HandleError(err, fmt.Sprintf("Invalid scope: %s", scope)) + } + scopedOption := normalizedOptions[normalizedScope] + + options := config.Scoped[scope] + if options.APIHost != "" { + scopedOption.APIHost = options.APIHost + } + if options.DashboardHost != "" { + scopedOption.DashboardHost = options.DashboardHost + } + if options.EnclaveConfig != "" { + scopedOption.EnclaveConfig = options.EnclaveConfig + } + if options.EnclaveProject != "" { + scopedOption.EnclaveProject = options.EnclaveProject + } + if options.Token != "" { + scopedOption.Token = options.Token + } + if options.VerifyTLS != "" { + scopedOption.VerifyTLS = options.VerifyTLS + } + + normalizedOptions[normalizedScope] = scopedOption + } + + config.Scoped = normalizedOptions + return config +} + +// IsValidConfigOption whether the specified key is a valid config option +func IsValidConfigOption(key string) bool { + configOptions := map[string]interface{}{ + models.ConfigToken.String(): nil, + models.ConfigAPIHost.String(): nil, + models.ConfigDashboardHost.String(): nil, + models.ConfigVerifyTLS.String(): nil, + models.ConfigEnclaveProject.String(): nil, + models.ConfigEnclaveConfig.String(): nil, + } + + _, exists := configOptions[key] + return exists +} + +// IsTranslatableConfigOption checks whether the key can be translated to a valid config option +func IsTranslatableConfigOption(key string) bool { + // TODO remove this function when releasing CLI v4 (DPLR-435) + if key == "config" || key == "project" { + return true + } + + return false +} + +// TranslateFriendlyOption to its config option name +func TranslateFriendlyOption(key string) string { + // TODO remove this function when releasing CLI v4 (DPLR-435) + if key == "config" { + return models.ConfigEnclaveConfig.String() + } + if key == "project" { + return models.ConfigEnclaveProject.String() + } + return key +} + +// TranslateConfigOption to its friendly name +func TranslateConfigOption(key string) string { + // TODO remove this function when releasing CLI v4 (DPLR-435) + if key == models.ConfigEnclaveConfig.String() { + return "config" + } + if key == models.ConfigEnclaveProject.String() { + return "project" + } + return key +} + +// SetConfigValue set the value for the specified key in the config +func SetConfigValue(conf *models.FileScopedOptions, key string, value string) { + if key == models.ConfigToken.String() { + (*conf).Token = value + } else if key == models.ConfigAPIHost.String() { + (*conf).APIHost = value + } else if key == models.ConfigDashboardHost.String() { + (*conf).DashboardHost = value + } else if key == models.ConfigVerifyTLS.String() { + (*conf).VerifyTLS = value + } else if key == models.ConfigEnclaveProject.String() { + (*conf).EnclaveProject = value + } else if key == models.ConfigEnclaveConfig.String() { + (*conf).EnclaveConfig = value + } +} + +// NormalizeScope from legacy '*' to '/' +func NormalizeScope(scope string) (string, error) { + if scope == "*" { + return "/", nil + } + + return utils.ParsePath(scope) +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/configuration/migration.go b/vendor/github.com/DopplerHQ/cli/pkg/configuration/migration.go new file mode 100644 index 00000000..699b0835 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/configuration/migration.go @@ -0,0 +1,73 @@ +/* +Copyright © 2019 Doppler + +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 configuration + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" +) + +type oldConfig struct { + Pipeline string + Environment string + Key string +} + +var jsonFile = filepath.Join(utils.HomeDir(), ".doppler.json") + +func jsonExists() bool { + return utils.Exists(jsonFile) +} + +// migrateJSONToYaml migrate ~/.doppler.json to yaml config +func migrateJSONToYaml() { + jsonConfig := parseJSONConfig() + newConfig := convertOldConfig(jsonConfig) + writeConfig(newConfig) +} + +func convertOldConfig(oldConfig map[string]oldConfig) models.ConfigFile { + config := map[string]models.FileScopedOptions{} + + for key, val := range oldConfig { + var err error + // skip items that fail to parse + if key, err = NormalizeScope(key); err == nil { + config[key] = models.FileScopedOptions{EnclaveProject: val.Pipeline, EnclaveConfig: val.Environment, Token: val.Key} + } + } + + return models.ConfigFile{Scoped: config} +} + +func parseJSONConfig() map[string]oldConfig { + fileContents, err := ioutil.ReadFile(jsonFile) // #nosec G304 + if err != nil { + utils.HandleError(err) + } + + var config map[string]oldConfig + err = json.Unmarshal(fileContents, &config) + if err != nil { + utils.HandleError(err) + } + + return config +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/controllers/fallback.go b/vendor/github.com/DopplerHQ/cli/pkg/controllers/fallback.go new file mode 100644 index 00000000..cc03f24b --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/controllers/fallback.go @@ -0,0 +1,128 @@ +/* +Copyright © 2020 Doppler + +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 controllers + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/DopplerHQ/cli/pkg/crypto" + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "gopkg.in/yaml.v3" +) + +// DefaultMetadataDir the directory containing metadata files +var DefaultMetadataDir string + +// MetadataFilePath calculates the name of the metadata file +func MetadataFilePath(token string, project string, config string) string { + var name string + if project == "" && config == "" { + name = fmt.Sprintf("%s", token) + } else { + name = fmt.Sprintf("%s:%s:%s", token, project, config) + } + + fileName := fmt.Sprintf(".metadata-%s.json", crypto.Hash(name)) + path := filepath.Join(DefaultMetadataDir, fileName) + if absPath, err := filepath.Abs(path); err == nil { + return absPath + } + return path +} + +// MetadataFile reads the contents of the metadata file +func MetadataFile(path string) (models.SecretsFileMetadata, Error) { + utils.LogDebug(fmt.Sprintf("Using metadata file %s", path)) + + if _, err := os.Stat(path); err != nil { + var e Error + e.Err = err + if os.IsNotExist(err) { + e.Message = "Metadata file does not exist" + } else { + e.Message = "Unable to read metadata file" + } + return models.SecretsFileMetadata{}, e + } + + utils.LogDebug(fmt.Sprintf("Reading metadata file %s", path)) + response, err := ioutil.ReadFile(path) // #nosec G304 + if err != nil { + return models.SecretsFileMetadata{}, Error{Err: err, Message: "Unable to read metadata file"} + } + + var metadata models.SecretsFileMetadata + if err := yaml.Unmarshal(response, &metadata); err != nil { + return models.SecretsFileMetadata{}, Error{Err: err, Message: "Unable to parse metadata file"} + } + + return metadata, Error{} +} + +// WriteMetadataFile writes the contents of the metadata file +func WriteMetadataFile(path string, etag string, hash string) Error { + utils.LogDebug(fmt.Sprintf("Writing ETag to metadata file %s", path)) + + metadata := models.SecretsFileMetadata{ + Version: "1", + ETag: etag, + Hash: hash, + } + + metadataBytes, err := yaml.Marshal(metadata) + if err != nil { + return Error{Err: err, Message: "Unable to marshal metadata to YAML"} + } + + if err := utils.WriteFile(path, []byte(metadataBytes), utils.RestrictedFilePerms()); err != nil { + return Error{Err: err, Message: "Unable to write metadata file"} + } + + return Error{} +} + +// SecretsCacheFile reads the contents of the cache file +func SecretsCacheFile(path string, passphrase string) (map[string]string, Error) { + utils.LogDebug(fmt.Sprintf("Using fallback file for cache %s", path)) + + if _, err := os.Stat(path); err != nil { + return nil, Error{Err: err, Message: "Unable to stat cache file"} + } + + response, err := ioutil.ReadFile(path) // #nosec G304 + if err != nil { + return nil, Error{Err: err, Message: "Unable to read cache file"} + } + + utils.LogDebug("Decrypting cache file") + decryptedSecrets, err := crypto.Decrypt(passphrase, response) + if err != nil { + return nil, Error{Err: err, Message: "Unable to decrypt cache file"} + } + + secrets := map[string]string{} + err = json.Unmarshal([]byte(decryptedSecrets), &secrets) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse cache file"} + } + + return secrets, Error{} +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/controllers/keyring.go b/vendor/github.com/DopplerHQ/cli/pkg/controllers/keyring.go new file mode 100644 index 00000000..a547cd33 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/controllers/keyring.go @@ -0,0 +1,74 @@ +/* +Copyright © 2020 Doppler + +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 controllers + +import ( + "fmt" + "strings" + + "github.com/zalando/go-keyring" +) + +const keyringService = "doppler-cli" +const keyringSecretPrefix = "secret" + +// IsKeyringSecret checks whether the secret is stored in keyring +func IsKeyringSecret(value string) bool { + return strings.HasPrefix(value, fmt.Sprintf("%s-", keyringSecretPrefix)) +} + +// GenerateKeyringID generates a keyring-compliant key +func GenerateKeyringID(id string) string { + return fmt.Sprintf("%s-%s", keyringSecretPrefix, id) +} + +// GetKeyring fetches a secret from the keyring +func GetKeyring(id string) (string, Error) { + value, err := keyring.Get(keyringService, id) + if err != nil { + if err == keyring.ErrUnsupportedPlatform { + return "", Error{Err: err, Message: "Your OS does not support keyring"} + } else if err == keyring.ErrNotFound { + return "", Error{Err: err, Message: "Token not found in system keyring"} + } else { + return "", Error{Err: err, Message: "Unable to retrieve value from system keyring"} + } + } + + return value, Error{} +} + +// SetKeyring saves a value to the keyring +func SetKeyring(key string, value string) Error { + if err := keyring.Set(keyringService, key, value); err != nil { + if err == keyring.ErrUnsupportedPlatform { + return Error{Err: err, Message: "Your OS does not support keyring"} + } else { + return Error{Err: err, Message: "Unable to access system keyring for secure storage"} + } + } + + return Error{} +} + +// DeleteKeyring removes a value from the keyring +func DeleteKeyring(key string) Error { + if err := keyring.Delete(keyringService, key); err != nil { + return Error{Err: err, Message: "Unable to remove value from keyring"} + } + + return Error{} +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/controllers/repo_config.go b/vendor/github.com/DopplerHQ/cli/pkg/controllers/repo_config.go new file mode 100644 index 00000000..1aed5637 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/controllers/repo_config.go @@ -0,0 +1,65 @@ +/* +Copyright © 2020 Doppler + +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 controllers + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "gopkg.in/yaml.v3" +) + +// repoConfigFileName (doppler.yaml) +const repoConfigFileName = "doppler.yaml" +// ymlRepoConfigFileName (doppler.yml) +const ymlRepoConfigFileName = "doppler.yml" + +// RepoConfig Reads the configuration file (doppler.yaml) if exists and returns the set configuration +func RepoConfig() (models.RepoConfig, Error) { + + repoConfigFile := filepath.Join("./", repoConfigFileName) + ymlRepoConfigFile := filepath.Join("./", ymlRepoConfigFileName) + + if utils.Exists(repoConfigFile) { + utils.LogDebug(fmt.Sprintf("Reading repo config file %s", repoConfigFile)) + + yamlFile, err := ioutil.ReadFile(repoConfigFile) // #nosec G304 + + if err != nil { + var e Error + e.Err = err + e.Message = "Unable to read doppler repo config file" + return models.RepoConfig{}, e + } + + var repoConfig models.RepoConfig + + if err := yaml.Unmarshal(yamlFile, &repoConfig); err != nil { + var e Error + e.Err = err + e.Message = "Unable to parse doppler repo config file" + return models.RepoConfig{}, e + } + + return repoConfig, Error{} + } else if utils.Exists(ymlRepoConfigFile) { + utils.LogWarning(fmt.Sprintf("Found %s file, please rename to %s for repo configuration", ymlRepoConfigFile, repoConfigFileName)) + } + return models.RepoConfig{}, Error{} +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/controllers/update.go b/vendor/github.com/DopplerHQ/cli/pkg/controllers/update.go new file mode 100644 index 00000000..616b1a63 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/controllers/update.go @@ -0,0 +1,111 @@ +/* +Copyright © 2020 Doppler + +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 controllers + +import ( + "errors" + "fmt" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/DopplerHQ/cli/pkg/http" + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "github.com/DopplerHQ/cli/pkg/version" +) + +// Error controller errors +type Error struct { + Err error + Message string +} + +// Unwrap get the original error +func (e *Error) Unwrap() error { return e.Err } + +// IsNil whether the error is nil +func (e *Error) IsNil() bool { return e.Err == nil && e.Message == "" } + +// RunInstallScript downloads and executes the CLI install scriptm, returning true if an update was installed +func RunInstallScript() (bool, string, Error) { + // download script + script, apiErr := http.GetCLIInstallScript() + if !apiErr.IsNil() { + return false, "", Error{Err: apiErr.Unwrap(), Message: apiErr.Message} + } + + // write script to temp file + tmpFile, err := utils.WriteTempFile("install.sh", script, 0555) + // clean up temp file once we're done with it + defer os.Remove(tmpFile) + + // execute script + utils.LogDebug("Executing install script") + command := []string{tmpFile, "--debug"} + out, err := exec.Command(command[0], command[1:]...).CombinedOutput() // #nosec G204 + strOut := string(out) + // log output before checking error + utils.LogDebug(fmt.Sprintf("Executing \"%s\"", strings.Join(command, " "))) + if utils.Debug { + fmt.Println(strOut) + } + if err != nil { + return false, "", Error{Err: err, Message: "Unable to install the latest Doppler CLI"} + } + + // find installed version within script output + // Ex: `Installed Doppler CLI v3.7.1` + re := regexp.MustCompile(`Installed Doppler CLI v(\d+\.\d+\.\d+)`) + matches := re.FindStringSubmatch(strOut) + if matches == nil || len(matches) != 2 { + return false, "", Error{Err: errors.New("Unable to determine new CLI version")} + } + // parse latest version string + newVersion, err := version.ParseVersion(matches[1]) + if err != nil { + return false, "", Error{Err: err, Message: "Unable to parse new CLI version"} + } + + wasUpdated := false + // parse current version string + currentVersion, currVersionErr := version.ParseVersion(version.ProgramVersion) + if currVersionErr != nil { + // unexpected error; just consider it an update and continue executing + wasUpdated = true + utils.LogDebug("Unable to parse current CLI version") + utils.LogDebugError(currVersionErr) + } + + if !wasUpdated { + wasUpdated = version.CompareVersions(currentVersion, newVersion) == 1 + } + + return wasUpdated, newVersion.String(), Error{} +} + +// CLIChangeLog fetches the latest changelog +func CLIChangeLog() (map[string]models.ChangeLog, http.Error) { + response, apiError := http.GetChangelog() + if !apiError.IsNil() { + return nil, apiError + + } + + changes := models.ParseChangeLog(response) + return changes, http.Error{} +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/controllers/version.go b/vendor/github.com/DopplerHQ/cli/pkg/controllers/version.go new file mode 100644 index 00000000..483ccbbf --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/controllers/version.go @@ -0,0 +1,65 @@ +/* +Copyright © 2020 Doppler + +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 controllers + +import ( + "time" + + "github.com/DopplerHQ/cli/pkg/http" + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "github.com/DopplerHQ/cli/pkg/version" +) + +// NewVersionAvailable checks whether a CLI version is available that's newer than this CLI +func NewVersionAvailable(prevVersionCheck models.VersionCheck) (bool, models.VersionCheck, error) { + now := time.Now() + check, err := http.GetLatestCLIVersion() + if err != nil { + utils.LogDebug("Unable to fetch latest CLI version") + utils.LogDebugError(err) + return false, models.VersionCheck{}, err + } + + versionCheck := models.VersionCheck{CheckedAt: now, LatestVersion: version.Normalize(check.LatestVersion)} + + // skip if available version is unchanged from previous check + if versionCheck.LatestVersion == prevVersionCheck.LatestVersion { + utils.LogDebug("Previous version check is still latest version") + return false, versionCheck, nil + } + + newVersion, err := version.ParseVersion(versionCheck.LatestVersion) + if err != nil { + utils.LogDebug("Unable to parse new CLI version") + return false, models.VersionCheck{}, err + } + + currentVersion, err := version.ParseVersion(version.ProgramVersion) + if err != nil { + // if current version can't be parsed, consider an update available + utils.LogDebug("Unable to parse current CLI version") + utils.LogDebugError(err) + return true, versionCheck, nil + } + + compare := version.CompareVersions(currentVersion, newVersion) + if compare == 1 { + return true, versionCheck, nil + } + + return false, versionCheck, nil +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/crypto/aes.go b/vendor/github.com/DopplerHQ/cli/pkg/crypto/aes.go new file mode 100644 index 00000000..eae46303 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/crypto/aes.go @@ -0,0 +1,120 @@ +/* +Copyright © 2020 Doppler + +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. +*/ + +// From https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28 + +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "strings" + "time" + + "github.com/DopplerHQ/cli/pkg/utils" + "golang.org/x/crypto/pbkdf2" +) + +func deriveKey(passphrase string, salt []byte) ([]byte, []byte, error) { + if salt == nil { + salt = make([]byte, 8) + // http://www.ietf.org/rfc/rfc2898.txt + // Salt. + _, err := rand.Read(salt) + if err != nil { + return nil, nil, err + } + } + + return pbkdf2.Key([]byte(passphrase), salt, 50000, 32, sha256.New), salt, nil +} + +// Encrypt plaintext with a passphrase; uses pbkdf2 for key deriv and aes-256-gcm for encryption +func Encrypt(passphrase string, plaintext []byte) (string, error) { + now := time.Now() + key, salt, err := deriveKey(passphrase, nil) + if err != nil { + return "", err + } + + utils.LogDebug(fmt.Sprintf("PBKDF2 key derivation took %d ms", time.Now().Sub(now).Milliseconds())) + + iv := make([]byte, 12) + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf + // Section 8.2 + _, err = rand.Read(iv) + if err != nil { + return "", err + } + + b, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + aesgcm, err := cipher.NewGCM(b) + if err != nil { + return "", err + } + + data := aesgcm.Seal(nil, iv, plaintext, nil) + return hex.EncodeToString(salt) + "-" + hex.EncodeToString(iv) + "-" + hex.EncodeToString(data), nil +} + +// Decrypt ciphertext with a passphrase +func Decrypt(passphrase string, ciphertext []byte) (string, error) { + arr := strings.Split(string(ciphertext), "-") + salt, err := hex.DecodeString(arr[0]) + if err != nil { + return "", err + } + + iv, err := hex.DecodeString(arr[1]) + if err != nil { + return "", err + } + + data, err := hex.DecodeString(arr[2]) + if err != nil { + return "", err + } + + key, _, err := deriveKey(passphrase, salt) + if err != nil { + return "", err + } + + b, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + aesgcm, err := cipher.NewGCM(b) + if err != nil { + return "", err + } + + data, err = aesgcm.Open(nil, iv, data, nil) + if err != nil { + return "", err + } + + return string(data), nil +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/crypto/util.go b/vendor/github.com/DopplerHQ/cli/pkg/crypto/util.go new file mode 100644 index 00000000..7efdd50a --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/crypto/util.go @@ -0,0 +1,33 @@ +/* +Copyright © 2020 Doppler + +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 crypto + +import ( + "crypto/sha256" + "fmt" + "github.com/DopplerHQ/cli/pkg/utils" +) + +// Hash a string +func Hash(s string) string { + hash := sha256.New() + _, err := hash.Write([]byte(s)) + if err != nil { + utils.HandleError(err, "Unable to generate hash") + } + return fmt.Sprintf("%x", hash.Sum(nil)) +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/http/api.go b/vendor/github.com/DopplerHQ/cli/pkg/http/api.go new file mode 100644 index 00000000..86343706 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/http/api.go @@ -0,0 +1,796 @@ +/* +Copyright © 2019 Doppler + +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 http + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/version" +) + +// Error API errors +type Error struct { + Err error + Message string + Code int +} + +// Unwrap get the original error +func (e *Error) Unwrap() error { return e.Err } + +// IsNil whether the error is nil +func (e *Error) IsNil() bool { return e.Err == nil && e.Message == "" } + +func apiKeyHeader(apiKey string) map[string]string { + encoded := base64.StdEncoding.EncodeToString([]byte(apiKey + ":")) + return map[string]string{"Authorization": fmt.Sprintf("Basic %s", encoded)} +} + +// GenerateAuthCode generate an auth code +func GenerateAuthCode(host string, verifyTLS bool, hostname string, os string, arch string) (map[string]interface{}, Error) { + var params []queryParam + params = append(params, queryParam{Key: "hostname", Value: hostname}) + params = append(params, queryParam{Key: "version", Value: version.ProgramVersion}) + params = append(params, queryParam{Key: "os", Value: os}) + params = append(params, queryParam{Key: "arch", Value: arch}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, nil, "/v3/auth/cli/generate", params) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch auth code", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + return result, Error{} +} + +// GetAuthToken get an auth token +func GetAuthToken(host string, verifyTLS bool, code string) (map[string]interface{}, Error) { + reqBody := map[string]interface{}{} + reqBody["code"] = code + body, err := json.Marshal(reqBody) + if err != nil { + return nil, Error{Err: err, Message: "Invalid auth code"} + } + + statusCode, _, response, err := PostRequest(host, verifyTLS, nil, "/v3/auth/cli/authorize", []queryParam{}, body) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch auth token", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch auth token", Code: statusCode} + } + + return result, Error{} +} + +// RollAuthToken roll an auth token +func RollAuthToken(host string, verifyTLS bool, token string) (map[string]interface{}, Error) { + reqBody := map[string]interface{}{} + reqBody["token"] = token + body, err := json.Marshal(reqBody) + if err != nil { + return nil, Error{Err: err, Message: "Invalid auth token"} + } + + statusCode, _, response, err := PostRequest(host, verifyTLS, nil, "/v3/auth/cli/roll", []queryParam{}, body) + if err != nil { + return nil, Error{Err: err, Message: "Unable to roll auth token", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + return result, Error{} +} + +// RevokeAuthToken revoke an auth token +func RevokeAuthToken(host string, verifyTLS bool, token string) (map[string]interface{}, Error) { + reqBody := map[string]interface{}{} + reqBody["token"] = token + body, err := json.Marshal(reqBody) + if err != nil { + return nil, Error{Err: err, Message: "Invalid auth token"} + } + + statusCode, _, response, err := PostRequest(host, verifyTLS, nil, "/v3/auth/cli/revoke", []queryParam{}, body) + if err != nil { + return nil, Error{Err: err, Message: "Unable to revoke auth token", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + return result, Error{} +} + +// DownloadSecrets for specified project and config +func DownloadSecrets(host string, verifyTLS bool, apiKey string, project string, config string, format models.SecretsFormat, etag string) (int, http.Header, []byte, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + params = append(params, queryParam{Key: "format", Value: format.String()}) + + headers := apiKeyHeader(apiKey) + if etag != "" { + headers["If-None-Match"] = etag + } + + statusCode, respHeaders, response, err := GetRequest(host, verifyTLS, headers, "/v3/configs/config/secrets/download", params) + if err != nil { + return statusCode, respHeaders, nil, Error{Err: err, Message: "Unable to download secrets", Code: statusCode} + } + + return statusCode, respHeaders, response, Error{} +} + +// GetSecrets for specified project and config +func GetSecrets(host string, verifyTLS bool, apiKey string, project string, config string) ([]byte, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + headers := apiKeyHeader(apiKey) + headers["Accept"] = "application/json" + statusCode, _, response, err := GetRequest(host, verifyTLS, headers, "/v3/configs/config/secrets", params) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch secrets", Code: statusCode} + } + + return response, Error{} +} + +// SetSecrets for specified project and config +func SetSecrets(host string, verifyTLS bool, apiKey string, project string, config string, secrets map[string]interface{}) (map[string]models.ComputedSecret, Error) { + reqBody := map[string]interface{}{} + reqBody["secrets"] = secrets + body, err := json.Marshal(reqBody) + if err != nil { + return nil, Error{Err: err, Message: "Invalid secrets"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/secrets", params, body) + if err != nil { + return nil, Error{Err: err, Message: "Unable to set secrets", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + computed := map[string]models.ComputedSecret{} + for key, secret := range result["secrets"].(map[string]interface{}) { + val := secret.(map[string]interface{}) + computed[key] = models.ComputedSecret{Name: key, RawValue: val["raw"].(string), ComputedValue: val["computed"].(string)} + } + + return computed, Error{} +} + +// UploadSecrets for specified project and config +func UploadSecrets(host string, verifyTLS bool, apiKey string, project string, config string, secrets string) (map[string]models.ComputedSecret, Error) { + reqBody := map[string]interface{}{} + reqBody["file"] = secrets + body, err := json.Marshal(reqBody) + if err != nil { + return nil, Error{Err: err, Message: "Invalid file"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/secrets/upload", params, body) + if err != nil { + return nil, Error{Err: err, Message: "Unable to upload secrets", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + computed := map[string]models.ComputedSecret{} + for key, secret := range result["secrets"].(map[string]interface{}) { + val := secret.(map[string]interface{}) + computed[key] = models.ComputedSecret{Name: key, RawValue: val["raw"].(string), ComputedValue: val["computed"].(string)} + } + + return computed, Error{} +} + +// GetWorkplaceSettings get specified workplace settings +func GetWorkplaceSettings(host string, verifyTLS bool, apiKey string) (models.WorkplaceSettings, Error) { + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/workplace/v1", []queryParam{}) + if err != nil { + return models.WorkplaceSettings{}, Error{Err: err, Message: "Unable to fetch workplace settings", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.WorkplaceSettings{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + settings := models.ParseWorkplaceSettings(result["workplace"].(map[string]interface{})) + return settings, Error{} +} + +// SetWorkplaceSettings set workplace settings +func SetWorkplaceSettings(host string, verifyTLS bool, apiKey string, values models.WorkplaceSettings) (models.WorkplaceSettings, Error) { + body, err := json.Marshal(values) + if err != nil { + return models.WorkplaceSettings{}, Error{Err: err, Message: "Invalid workplace settings"} + } + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/workplace/v1", []queryParam{}, body) + if err != nil { + return models.WorkplaceSettings{}, Error{Err: err, Message: "Unable to update workplace settings", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.WorkplaceSettings{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + settings := models.ParseWorkplaceSettings(result["workplace"].(map[string]interface{})) + return settings, Error{} +} + +// GetProjects get projects +func GetProjects(host string, verifyTLS bool, apiKey string) ([]models.ProjectInfo, Error) { + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/projects", []queryParam{}) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch projects", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + var info []models.ProjectInfo + for _, project := range result["projects"].([]interface{}) { + projectInfo := models.ParseProjectInfo(project.(map[string]interface{})) + info = append(info, projectInfo) + } + return info, Error{} +} + +// GetProject get specified project +func GetProject(host string, verifyTLS bool, apiKey string, project string) (models.ProjectInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/projects/project", params) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Unable to fetch project", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + projectInfo := models.ParseProjectInfo(result["project"].(map[string]interface{})) + return projectInfo, Error{} +} + +// CreateProject create a project +func CreateProject(host string, verifyTLS bool, apiKey string, name string, description string) (models.ProjectInfo, Error) { + postBody := map[string]string{"name": name, "description": description} + body, err := json.Marshal(postBody) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Invalid project info"} + } + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/projects", []queryParam{}, body) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Unable to create project", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + projectInfo := models.ParseProjectInfo(result["project"].(map[string]interface{})) + return projectInfo, Error{} +} + +// UpdateProject update a project's name and (optional) description +func UpdateProject(host string, verifyTLS bool, apiKey string, project string, name string, description ...string) (models.ProjectInfo, Error) { + postBody := map[string]string{"name": name} + if len(description) > 0 { + desc := description[0] + postBody["description"] = desc + } + + body, err := json.Marshal(postBody) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Invalid project info"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/projects/project", params, body) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Unable to update project", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ProjectInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + projectInfo := models.ParseProjectInfo(result["project"].(map[string]interface{})) + return projectInfo, Error{} +} + +// DeleteProject create a project +func DeleteProject(host string, verifyTLS bool, apiKey string, project string) Error { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + + statusCode, _, response, err := DeleteRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/projects/project", params) + if err != nil { + return Error{Err: err, Message: "Unable to delete project", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + return Error{} +} + +// GetEnvironments get environments +func GetEnvironments(host string, verifyTLS bool, apiKey string, project string) ([]models.EnvironmentInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/environments", params) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch environments", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + var info []models.EnvironmentInfo + for _, environment := range result["environments"].([]interface{}) { + environmentInfo := models.ParseEnvironmentInfo(environment.(map[string]interface{})) + info = append(info, environmentInfo) + } + return info, Error{} +} + +// GetEnvironment get specified environment +func GetEnvironment(host string, verifyTLS bool, apiKey string, project string, environment string) (models.EnvironmentInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "environment", Value: environment}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/environments/environment", params) + if err != nil { + return models.EnvironmentInfo{}, Error{Err: err, Message: "Unable to fetch environment", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.EnvironmentInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseEnvironmentInfo(result["environment"].(map[string]interface{})) + return info, Error{} +} + +// GetConfigs get configs +func GetConfigs(host string, verifyTLS bool, apiKey string, project string) ([]models.ConfigInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs", params) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch configs", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + var info []models.ConfigInfo + for _, config := range result["configs"].([]interface{}) { + configInfo := models.ParseConfigInfo(config.(map[string]interface{})) + info = append(info, configInfo) + } + return info, Error{} +} + +// GetConfig get a config +func GetConfig(host string, verifyTLS bool, apiKey string, project string, config string) (models.ConfigInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config", params) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to fetch configs", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigInfo(result["config"].(map[string]interface{})) + return info, Error{} +} + +// CreateConfig create a config +func CreateConfig(host string, verifyTLS bool, apiKey string, project string, name string, environment string) (models.ConfigInfo, Error) { + postBody := map[string]interface{}{"name": name, "environment": environment} + body, err := json.Marshal(postBody) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Invalid config info"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs", params, body) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to create config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigInfo(result["config"].(map[string]interface{})) + return info, Error{} +} + +// DeleteConfig delete a config +func DeleteConfig(host string, verifyTLS bool, apiKey string, project string, config string) Error { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := DeleteRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config", params) + if err != nil { + return Error{Err: err, Message: "Unable to delete config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + return Error{} +} + +// LockConfig lock a config +func LockConfig(host string, verifyTLS bool, apiKey string, project string, config string) (models.ConfigInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/lock", params, nil) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to lock config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigInfo(result["config"].(map[string]interface{})) + return info, Error{} +} + +// UnlockConfig unlock a config +func UnlockConfig(host string, verifyTLS bool, apiKey string, project string, config string) (models.ConfigInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/unlock", params, nil) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to unlock config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigInfo(result["config"].(map[string]interface{})) + return info, Error{} +} + +// CloneConfig clone a config +func CloneConfig(host string, verifyTLS bool, apiKey string, project string, config string) (models.ConfigInfo, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/clone", params, nil) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to clone config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigInfo(result["config"].(map[string]interface{})) + return info, Error{} +} + +// UpdateConfig update a config +func UpdateConfig(host string, verifyTLS bool, apiKey string, project string, config string, name string) (models.ConfigInfo, Error) { + postBody := map[string]interface{}{"name": name} + body, err := json.Marshal(postBody) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Invalid config info"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config", params, body) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to update config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigInfo(result["config"].(map[string]interface{})) + return info, Error{} +} + +// GetActivityLogs get activity logs +func GetActivityLogs(host string, verifyTLS bool, apiKey string) ([]models.ActivityLog, Error) { + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/logs/v1", []queryParam{}) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch activity logs", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + var logs []models.ActivityLog + for _, log := range result["logs"].([]interface{}) { + parsedLog := models.ParseActivityLog(log.(map[string]interface{})) + logs = append(logs, parsedLog) + } + return logs, Error{} +} + +// GetActivityLog get specified activity log +func GetActivityLog(host string, verifyTLS bool, apiKey string, log string) (models.ActivityLog, Error) { + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/logs/v1/"+log, []queryParam{}) + if err != nil { + return models.ActivityLog{}, Error{Err: err, Message: "Unable to fetch activity log", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ActivityLog{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + parsedLog := models.ParseActivityLog(result["log"].(map[string]interface{})) + return parsedLog, Error{} +} + +// GetConfigLogs get config audit logs +func GetConfigLogs(host string, verifyTLS bool, apiKey string, project string, config string) ([]models.ConfigLog, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/logs", params) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch config logs", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + var logs []models.ConfigLog + for _, log := range result["logs"].([]interface{}) { + parsedLog := models.ParseConfigLog(log.(map[string]interface{})) + logs = append(logs, parsedLog) + } + return logs, Error{} +} + +// GetConfigLog get config audit log +func GetConfigLog(host string, verifyTLS bool, apiKey string, project string, config string, log string) (models.ConfigLog, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + params = append(params, queryParam{Key: "log", Value: log}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/logs/log", params) + if err != nil { + return models.ConfigLog{}, Error{Err: err, Message: "Unable to fetch config log", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigLog{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + parsedLog := models.ParseConfigLog(result["log"].(map[string]interface{})) + return parsedLog, Error{} +} + +// RollbackConfigLog rollback a config log +func RollbackConfigLog(host string, verifyTLS bool, apiKey string, project string, config string, log string) (models.ConfigLog, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + params = append(params, queryParam{Key: "log", Value: log}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/logs/log/rollback", params, nil) + if err != nil { + return models.ConfigLog{}, Error{Err: err, Message: "Unable to rollback config log", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigLog{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + parsedLog := models.ParseConfigLog(result["log"].(map[string]interface{})) + return parsedLog, Error{} +} + +// GetConfigServiceTokens get config service tokens +func GetConfigServiceTokens(host string, verifyTLS bool, apiKey string, project string, config string) ([]models.ConfigServiceToken, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := GetRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/tokens", params) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch service tokens", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return nil, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + var tokens []models.ConfigServiceToken + for _, log := range result["tokens"].([]interface{}) { + parsedToken := models.ParseConfigServiceToken(log.(map[string]interface{})) + tokens = append(tokens, parsedToken) + } + return tokens, Error{} +} + +// CreateConfigServiceToken create a config service token +func CreateConfigServiceToken(host string, verifyTLS bool, apiKey string, project string, config string, name string) (models.ConfigServiceToken, Error) { + postBody := map[string]interface{}{"name": name} + body, err := json.Marshal(postBody) + if err != nil { + return models.ConfigServiceToken{}, Error{Err: err, Message: "Invalid service token info"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + statusCode, _, response, err := PostRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/tokens", params, body) + if err != nil { + return models.ConfigServiceToken{}, Error{Err: err, Message: "Unable to create service token", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigServiceToken{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + info := models.ParseConfigServiceToken(result["token"].(map[string]interface{})) + return info, Error{} +} + +// DeleteConfigServiceToken delete a config service token +func DeleteConfigServiceToken(host string, verifyTLS bool, apiKey string, project string, config string, slug string) Error { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + params = append(params, queryParam{Key: "slug", Value: slug}) + + statusCode, _, response, err := DeleteRequest(host, verifyTLS, apiKeyHeader(apiKey), "/v3/configs/config/tokens/token", params) + if err != nil { + return Error{Err: err, Message: "Unable to delete service token", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + return Error{} +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/http/config.go b/vendor/github.com/DopplerHQ/cli/pkg/http/config.go new file mode 100644 index 00000000..b70f97cb --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/http/config.go @@ -0,0 +1,24 @@ +/* +Copyright © 2019 Doppler + +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 http + +import "time" + +// UseTimeout whether to timeout long-running requests +var UseTimeout = true + +// TimeoutDuration how long to wait for a request to complete before timing out +var TimeoutDuration = 10 * time.Second diff --git a/vendor/github.com/DopplerHQ/cli/pkg/http/github.go b/vendor/github.com/DopplerHQ/cli/pkg/http/github.go new file mode 100644 index 00000000..b7fe0a84 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/http/github.go @@ -0,0 +1,81 @@ +/* +Copyright © 2019 Doppler + +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 http + +import ( + "encoding/json" + "errors" + "time" + + "github.com/DopplerHQ/cli/pkg/models" + "github.com/DopplerHQ/cli/pkg/utils" + "github.com/DopplerHQ/cli/pkg/version" +) + +func getLatestVersion() (string, error) { + origTimeout := TimeoutDuration + TimeoutDuration = 2 * time.Second + _, _, resp, err := GetRequest("https://api.github.com", true, nil, "/repos/DopplerHQ/cli/releases/latest", nil) + TimeoutDuration = origTimeout + if err != nil { + return "", err + } + + var body map[string]interface{} + err = json.Unmarshal(resp, &body) + if err != nil { + return "", err + } + + if version, exists := body["tag_name"]; exists { + return version.(string), nil + } + + return "", errors.New("unable to retrieve tag_name of latest release") +} + +// GetLatestCLIVersion fetches the latest CLI version +func GetLatestCLIVersion() (models.VersionCheck, error) { + utils.LogDebug("Checking for latest version of the CLI") + tag, err := getLatestVersion() + if err != nil { + utils.LogDebug("Unable to check for CLI updates") + utils.LogDebugError(err) + return models.VersionCheck{}, err + } + + versionCheck := models.VersionCheck{CheckedAt: time.Now(), LatestVersion: version.Normalize(tag)} + return versionCheck, nil +} + +// GetCLIInstallScript from cli.doppler.com +func GetCLIInstallScript() ([]byte, Error) { + _, _, resp, err := GetRequest("https://cli.doppler.com", true, nil, "/install.sh", nil) + if err != nil { + return nil, Error{Err: err, Message: "Unable to download CLI install script"} + } + return resp, Error{} +} + +// GetChangelog of CLI releases +func GetChangelog() ([]byte, Error) { + headers := map[string]string{"Accept": "application/json"} + _, _, resp, err := GetRequest("https://cli.doppler.com", true, headers, "/changes", nil) + if err != nil { + return nil, Error{Err: err, Message: "Unable to fetch changelog"} + } + return resp, Error{} +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/http/http.go b/vendor/github.com/DopplerHQ/cli/pkg/http/http.go new file mode 100644 index 00000000..ba2c12d0 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/http/http.go @@ -0,0 +1,243 @@ +/* +Copyright © 2019 Doppler + +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 http + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/DopplerHQ/cli/pkg/utils" + "github.com/DopplerHQ/cli/pkg/version" +) + +type queryParam struct { + Key string + Value string +} + +type errorResponse struct { + Messages []string + Success bool +} + +// GetRequest perform HTTP GET +func GetRequest(host string, verifyTLS bool, headers map[string]string, uri string, params []queryParam) (int, http.Header, []byte, error) { + url := fmt.Sprintf("%s%s", host, uri) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return 0, nil, nil, err + } + + for key, value := range headers { + req.Header.Set(key, value) + } + + statusCode, respHeaders, body, err := performRequest(req, verifyTLS, params) + if err != nil { + return statusCode, respHeaders, body, err + } + + return statusCode, respHeaders, body, nil +} + +// PostRequest perform HTTP POST +func PostRequest(host string, verifyTLS bool, headers map[string]string, uri string, params []queryParam, body []byte) (int, http.Header, []byte, error) { + url := fmt.Sprintf("%s%s", host, uri) + req, err := http.NewRequest("POST", url, bytes.NewReader(body)) + if err != nil { + return 0, nil, nil, err + } + + for key, value := range headers { + req.Header.Set(key, value) + } + + statusCode, respHeaders, body, err := performRequest(req, verifyTLS, params) + if err != nil { + return statusCode, respHeaders, body, err + } + + return statusCode, respHeaders, body, nil +} + +// DeleteRequest perform HTTP DELETE +func DeleteRequest(host string, verifyTLS bool, headers map[string]string, uri string, params []queryParam) (int, http.Header, []byte, error) { + url := fmt.Sprintf("%s%s", host, uri) + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return 0, nil, nil, err + } + + for key, value := range headers { + req.Header.Set(key, value) + } + + statusCode, respHeaders, body, err := performRequest(req, verifyTLS, params) + if err != nil { + return statusCode, respHeaders, body, err + } + + return statusCode, respHeaders, body, nil +} + +func performRequest(req *http.Request, verifyTLS bool, params []queryParam) (int, http.Header, []byte, error) { + // set headers + req.Header.Set("client-sdk", "go-cli") + req.Header.Set("client-version", version.ProgramVersion) + req.Header.Set("user-agent", "doppler-go-cli-"+version.ProgramVersion) + if req.Header.Get("Accept") == "" { + req.Header.Set("Accept", "application/json") + } + req.Header.Set("Content-Type", "application/json") + + // set url query parameters + query := req.URL.Query() + for _, param := range params { + query.Add(param.Key, param.Value) + } + req.URL.RawQuery = query.Encode() + + // close the connection after reading the response, to help prevent socket exhaustion + req.Close = true + + client := &http.Client{} + // set http timeout + if UseTimeout { + client.Timeout = TimeoutDuration + } + + transport := &http.Transport{ + // disable keep alives to prevent multiple CLI instances from exhausting the + // OS's available network sockets. this adds a negligible performance penalty + DisableKeepAlives: true, + } + // set TLS config + // #nosec G402 + if !verifyTLS { + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + client.Transport = transport + + startTime := time.Now() + var response *http.Response + response = nil + + requestErr := retry(5, 100*time.Millisecond, func() error { + resp, err := client.Do(req) + if err != nil { + if resp != nil { + defer resp.Body.Close() + } + + utils.LogDebug(err.Error()) + + if isTimeout(err) { + // retry request + return err + } + + return StopRetry{err} + } + + response = resp + + utils.LogDebug(fmt.Sprintf("Performing HTTP %s to %s", req.Method, req.URL)) + if requestID := resp.Header.Get("x-request-id"); requestID != "" { + utils.LogDebug(fmt.Sprintf("Request ID %s", requestID)) + } + + if isSuccess(resp.StatusCode) { + return nil + } + + contentType := resp.Header.Get("content-type") + if isRetry(resp.StatusCode, contentType) { + // start logging retries after 10 seconds so it doesn't feel like we've frozen + // we subtract 1 millisecond so that we always win the race against a request that exhausts its full 10 second time out + if time.Now().After(startTime.Add(10 * time.Second).Add(-1 * time.Millisecond)) { + utils.Log(fmt.Sprintf("Request failed with HTTP %d, retrying", resp.StatusCode)) + } + return errors.New("Request failed") + } + + // we cannot recover from this error code; accept defeat + return StopRetry{errors.New("Request failed")} + }) + + if response != nil { + defer response.Body.Close() + } + + if requestErr != nil && response == nil { + return 0, nil, nil, requestErr + } + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return response.StatusCode, nil, nil, err + } + + headers := response.Header.Clone() + + // success + if requestErr == nil { + return response.StatusCode, headers, body, nil + } + + // print the response body error messages + if contentType := response.Header.Get("content-type"); strings.HasPrefix(contentType, "application/json") { + var errResponse errorResponse + err = json.Unmarshal(body, &errResponse) + if err != nil { + utils.LogDebug(fmt.Sprintf("Unable to parse response body: \n%s", string(body))) + return response.StatusCode, headers, nil, err + } + + return response.StatusCode, headers, body, errors.New(strings.Join(errResponse.Messages, "\n")) + } + + return response.StatusCode, headers, nil, fmt.Errorf("Request failed with HTTP %d", response.StatusCode) +} + +func isSuccess(statusCode int) bool { + return (statusCode >= 200 && statusCode <= 299) || (statusCode >= 300 && statusCode <= 399) +} + +func isRetry(statusCode int, contentType string) bool { + return (statusCode == 429) || + (statusCode >= 100 && statusCode <= 199) || + // don't retry 5xx errors w/ a JSON body + (statusCode >= 500 && statusCode <= 599 && !strings.HasPrefix(contentType, "application/json")) +} + +func isTimeout(err error) bool { + if urlErr, ok := err.(*url.Error); ok { + if netErr, ok := urlErr.Err.(net.Error); ok { + return netErr.Timeout() + } + } + + return false +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/http/retry.go b/vendor/github.com/DopplerHQ/cli/pkg/http/retry.go new file mode 100644 index 00000000..b664b634 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/http/retry.go @@ -0,0 +1,51 @@ +/* +Copyright © 2019 Doppler + +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 http + +import ( + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func retry(attempts int, sleep time.Duration, f func() error) error { + if err := f(); err != nil { + if s, ok := err.(StopRetry); ok { + // Return the original error for later checking + return s.error + } + + if attempts--; attempts > 0 { + // Add some randomness to prevent creating a Thundering Herd + jitter := time.Duration(rand.Int63n(int64(sleep))) // #nosec G404 + sleep = sleep + jitter/2 + + time.Sleep(sleep) + return retry(attempts, 2*sleep, f) + } + return err + } + + return nil +} + +// StopRetry indicates to stop attempting retries. wraps an error +type StopRetry struct { + error +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/api.go b/vendor/github.com/DopplerHQ/cli/pkg/models/api.go new file mode 100644 index 00000000..2499a0a0 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/api.go @@ -0,0 +1,110 @@ +/* +Copyright © 2019 Doppler + +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 models + +// ComputedSecret holds all info about a secret +type ComputedSecret struct { + Name string `json:"name"` + RawValue string `json:"raw"` + ComputedValue string `json:"computed"` +} + +// WorkplaceSettings workplace settings +type WorkplaceSettings struct { + ID string `json:"id"` + Name string `json:"name"` + BillingEmail string `json:"billing_email"` +} + +// ProjectInfo project info +type ProjectInfo struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` +} + +// EnvironmentInfo environment info +type EnvironmentInfo struct { + ID string `json:"id"` + Name string `json:"name"` + CreatedAt string `json:"created_at"` + InitialFetchAt string `json:"initial_fetch_at"` + Project string `json:"project"` +} + +// ConfigInfo project info +type ConfigInfo struct { + Name string `json:"name"` + Root bool `json:"root"` + Locked bool `json:"locked"` + Environment string `json:"environment"` + Project string `json:"project"` + CreatedAt string `json:"created_at"` + InitialFetchAt string `json:"initial_fetch_at"` + LastFetchAt string `json:"last_fetch_at"` +} + +// ConfigLog a log +type ConfigLog struct { + ID string `json:"id"` + Text string `json:"text"` + HTML string `json:"html"` + CreatedAt string `json:"created_at"` + Config string `json:"config"` + Environment string `json:"environment"` + Project string `json:"project"` + User User `json:"user"` + Diff []LogDiff `json:"diff"` +} + +// ActivityLog an activity log +type ActivityLog struct { + ID string `json:"id"` + Text string `json:"text"` + HTML string `json:"html"` + CreatedAt string `json:"created_at"` + EnclaveConfig string `json:"enclave_config"` + EnclaveEnvironment string `json:"enclave_environment"` + EnclaveProject string `json:"enclave_project"` + User User `json:"user"` +} + +// User user profile +type User struct { + Email string `json:"email"` + Name string `json:"name"` + Username string `json:"username"` + ProfileImage string `json:"profile_image_url"` +} + +// LogDiff diff of log entries +type LogDiff struct { + Name string `json:"name"` + Added string `json:"added"` + Removed string `json:"removed"` +} + +// ConfigServiceToken a service token +type ConfigServiceToken struct { + Name string `json:"name"` + Token string `json:"token"` + Slug string `json:"slug"` + CreatedAt string `json:"created_at"` + Project string `json:"project"` + Environment string `json:"environment"` + Config string `json:"config"` +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/config.go b/vendor/github.com/DopplerHQ/cli/pkg/models/config.go new file mode 100644 index 00000000..3d2568bb --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/config.go @@ -0,0 +1,139 @@ +/* +Copyright © 2019 Doppler + +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 models + +import "time" + +// ConfigFile structure of the config file +type ConfigFile struct { + Scoped map[string]FileScopedOptions `yaml:"scoped"` + VersionCheck VersionCheck `yaml:"version-check"` +} + +// FileScopedOptions config options +type FileScopedOptions struct { + Token string `json:"token,omitempty" yaml:"token,omitempty"` + APIHost string `json:"api-host,omitempty" yaml:"api-host,omitempty"` + DashboardHost string `json:"dashboard-host,omitempty" yaml:"dashboard-host,omitempty"` + VerifyTLS string `json:"verify-tls,omitempty" yaml:"verify-tls,omitempty"` + EnclaveProject string `json:"enclave.project,omitempty" yaml:"enclave.project,omitempty"` + EnclaveConfig string `json:"enclave.config,omitempty" yaml:"enclave.config,omitempty"` +} + +// VersionCheck info about the last check for the latest cli version +type VersionCheck struct { + LatestVersion string `yaml:"latest-version,omitempty"` + CheckedAt time.Time `yaml:"checked-at,omitempty"` +} + +// ScopedOptions options with their scope +type ScopedOptions struct { + Token ScopedOption `json:"token,omitempty" yaml:"token,omitempty"` + APIHost ScopedOption `json:"api-host,omitempty" yaml:"api-host,omitempty"` + DashboardHost ScopedOption `json:"dashboard-host,omitempty" yaml:"dashboard-host,omitempty"` + VerifyTLS ScopedOption `json:"verify-tls,omitempty" yaml:"verify-tls,omitempty"` + EnclaveProject ScopedOption `json:"enclave.project,omitempty" yaml:"enclave.project,omitempty"` + EnclaveConfig ScopedOption `json:"enclave.config,omitempty" yaml:"enclave.config,omitempty"` +} + +// ScopedOption value and its scope +type ScopedOption struct { + Value string `json:"value"` + Scope string `json:"scope"` + Source string `json:"source"` +} + +type source int + +// the source of the value +const ( + FlagSource source = iota + ConfigFileSource + EnvironmentSource + DefaultValueSource +) + +func (s source) String() string { + return [...]string{"Flag", "Config File", "Environment", "Default Value"}[s] +} + +var allConfigOptions = []string{ + "token", + "api-host", + "dashboard-host", + "verify-tls", + "enclave.project", + "enclave.config", +} + +type configOption int + +// valid config options +const ( + ConfigToken configOption = iota + ConfigAPIHost + ConfigDashboardHost + ConfigVerifyTLS + ConfigEnclaveProject + ConfigEnclaveConfig +) + +func (s configOption) String() string { + return allConfigOptions[s] +} + +// AllConfigOptions all supported options +func AllConfigOptions() []string { + return allConfigOptions +} + +// Pairs get the pairs for the given config +func Pairs(conf FileScopedOptions) map[string]string { + return map[string]string{ + ConfigToken.String(): conf.Token, + ConfigAPIHost.String(): conf.APIHost, + ConfigDashboardHost.String(): conf.DashboardHost, + ConfigVerifyTLS.String(): conf.VerifyTLS, + ConfigEnclaveProject.String(): conf.EnclaveProject, + ConfigEnclaveConfig.String(): conf.EnclaveConfig, + } +} + +// ScopedPairs get the pairs for the given scoped config +func ScopedPairs(conf *ScopedOptions) map[string]*ScopedOption { + return map[string]*ScopedOption{ + ConfigToken.String(): &conf.Token, + ConfigAPIHost.String(): &conf.APIHost, + ConfigDashboardHost.String(): &conf.DashboardHost, + ConfigVerifyTLS.String(): &conf.VerifyTLS, + ConfigEnclaveProject.String(): &conf.EnclaveProject, + ConfigEnclaveConfig.String(): &conf.EnclaveConfig, + } +} + +// EnvPairs get the scoped config pairs for each environment variable +func EnvPairs(conf *ScopedOptions) map[string]*ScopedOption { + return map[string]*ScopedOption{ + "DOPPLER_TOKEN": &conf.Token, + "DOPPLER_API_HOST": &conf.APIHost, + "DOPPLER_DASHBOARD_HOST": &conf.DashboardHost, + "DOPPLER_VERIFY_TLS": &conf.VerifyTLS, + "DOPPLER_PROJECT": &conf.EnclaveProject, + "DOPPLER_CONFIG": &conf.EnclaveConfig, + "ENCLAVE_PROJECT": &conf.EnclaveProject, // deprecated, remove in v4 + "ENCLAVE_CONFIG": &conf.EnclaveConfig, // deprecated, remove in v4 + } +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/files.go b/vendor/github.com/DopplerHQ/cli/pkg/models/files.go new file mode 100644 index 00000000..c0414024 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/files.go @@ -0,0 +1,40 @@ +/* +Copyright © 2020 Doppler + +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 models + +// SecretsFileMetadata contains metadata about a secrets file +type SecretsFileMetadata struct { + Version string `json:"version,omitempty" yaml:"version,omitempty"` + ETag string `json:"etag,omitempty" yaml:"etag,omitempty"` + Hash string `json:"hash,omitempty" yaml:"hash,omitempty"` +} + +// ParseSecretsFileMetadata parse secrets file metadata +func ParseSecretsFileMetadata(data map[string]interface{}) SecretsFileMetadata { + var parsedMetadata SecretsFileMetadata + + if data["version"] != nil { + parsedMetadata.Version = data["version"].(string) + } + if data["etag"] != nil { + parsedMetadata.ETag = data["etag"].(string) + } + if data["hash"] != nil { + parsedMetadata.Hash = data["hash"].(string) + } + + return parsedMetadata +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/parse.go b/vendor/github.com/DopplerHQ/cli/pkg/models/parse.go new file mode 100644 index 00000000..454a814f --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/parse.go @@ -0,0 +1,262 @@ +/* +Copyright © 2019 Doppler + +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 models + +import ( + "encoding/json" +) + +// ParseWorkplaceSettings parse workplace settings +func ParseWorkplaceSettings(info map[string]interface{}) WorkplaceSettings { + var workplaceInfo WorkplaceSettings + + if info["id"] != nil { + workplaceInfo.ID = info["id"].(string) + } + if info["name"] != nil { + workplaceInfo.Name = info["name"].(string) + } + if info["billing_email"] != nil { + workplaceInfo.BillingEmail = info["billing_email"].(string) + } + + return workplaceInfo +} + +// ParseProjectInfo parse project info +func ParseProjectInfo(info map[string]interface{}) ProjectInfo { + var projectInfo ProjectInfo + + if info["id"] != nil { + projectInfo.ID = info["id"].(string) + } + if info["name"] != nil { + projectInfo.Name = info["name"].(string) + } + if info["description"] != nil { + projectInfo.Description = info["description"].(string) + } + if info["created_at"] != nil { + projectInfo.CreatedAt = info["created_at"].(string) + } + + return projectInfo +} + +// ParseEnvironmentInfo parse environment info +func ParseEnvironmentInfo(info map[string]interface{}) EnvironmentInfo { + var environmentInfo EnvironmentInfo + + if info["id"] != nil { + environmentInfo.ID = info["id"].(string) + } + if info["name"] != nil { + environmentInfo.Name = info["name"].(string) + } + if info["created_at"] != nil { + environmentInfo.CreatedAt = info["created_at"].(string) + } + if info["initial_fetch_at"] != nil { + environmentInfo.InitialFetchAt = info["initial_fetch_at"].(string) + } + if info["project"] != nil { + environmentInfo.Project = info["project"].(string) + } + + return environmentInfo +} + +// ParseConfigInfo parse config info +func ParseConfigInfo(info map[string]interface{}) ConfigInfo { + var configInfo ConfigInfo + + if info["name"] != nil { + configInfo.Name = info["name"].(string) + } + if info["root"] != nil { + configInfo.Root = info["root"].(bool) + } + if info["locked"] != nil { + configInfo.Locked = info["locked"].(bool) + } + if info["environment"] != nil { + configInfo.Environment = info["environment"].(string) + } + if info["project"] != nil { + configInfo.Project = info["project"].(string) + } + if info["created_at"] != nil { + configInfo.CreatedAt = info["created_at"].(string) + } + if info["initial_fetch_at"] != nil { + configInfo.InitialFetchAt = info["initial_fetch_at"].(string) + } + if info["last_fetch_at"] != nil { + configInfo.LastFetchAt = info["last_fetch_at"].(string) + } + + return configInfo +} + +// ParseConfigLog parse config log +func ParseConfigLog(log map[string]interface{}) ConfigLog { + var parsedLog ConfigLog + + if log["id"] != nil { + parsedLog.ID = log["id"].(string) + } + if log["text"] != nil { + parsedLog.Text = log["text"].(string) + } + if log["html"] != nil { + parsedLog.HTML = log["html"].(string) + } + if log["created_at"] != nil { + parsedLog.CreatedAt = log["created_at"].(string) + } + if log["config"] != nil { + parsedLog.Config = log["config"].(string) + } + if log["environment"] != nil { + parsedLog.Environment = log["environment"].(string) + } + if log["project"] != nil { + parsedLog.Project = log["project"].(string) + } + if log["user"] != nil { + user := log["user"].(map[string]interface{}) + parsedLog.User.Email = user["email"].(string) + parsedLog.User.Name = user["name"].(string) + parsedLog.User.Username = user["username"].(string) + parsedLog.User.ProfileImage = user["profile_image_url"].(string) + } + if log["diff"] != nil { + for _, diff := range log["diff"].([]interface{}) { + diffMap := diff.(map[string]interface{}) + d := LogDiff{} + if diffMap["name"] != nil { + d.Name = diffMap["name"].(string) + } + if diffMap["added"] != nil { + d.Added = diffMap["added"].(string) + } + if diffMap["removed"] != nil { + d.Removed = diffMap["removed"].(string) + } + parsedLog.Diff = append(parsedLog.Diff, d) + } + } + + return parsedLog +} + +// ParseActivityLog parse activity log +func ParseActivityLog(log map[string]interface{}) ActivityLog { + var parsedLog ActivityLog + + if log["id"] != nil { + parsedLog.ID = log["id"].(string) + } + if log["text"] != nil { + parsedLog.Text = log["text"].(string) + } + if log["html"] != nil { + parsedLog.HTML = log["html"].(string) + } + if log["created_at"] != nil { + parsedLog.CreatedAt = log["created_at"].(string) + } + if log["enclave_config"] != nil { + parsedLog.EnclaveConfig = log["enclave_config"].(string) + } + if log["enclave_environment"] != nil { + parsedLog.EnclaveEnvironment = log["enclave_environment"].(string) + } + if log["enclave_project"] != nil { + parsedLog.EnclaveProject = log["enclave_project"].(string) + } + if log["user"] != nil { + user := log["user"].(map[string]interface{}) + if user["email"] != nil { + parsedLog.User.Email = user["email"].(string) + } + if user["name"] != nil { + parsedLog.User.Name = user["name"].(string) + } + if user["username"] != nil { + parsedLog.User.Username = user["username"].(string) + } + if user["profile_image_url"] != nil { + parsedLog.User.ProfileImage = user["profile_image_url"].(string) + } + } + + return parsedLog +} + +// ParseSecrets parse secrets +func ParseSecrets(response []byte) (map[string]ComputedSecret, error) { + var result map[string]interface{} + err := json.Unmarshal(response, &result) + if err != nil { + return nil, err + } + + computed := map[string]ComputedSecret{} + secrets := result["secrets"].(map[string]interface{}) + for key, secret := range secrets { + computedSecret := ComputedSecret{Name: key} + val := secret.(map[string]interface{}) + if val["raw"] != nil { + computedSecret.RawValue = val["raw"].(string) + } + if val["computed"] != nil { + computedSecret.ComputedValue = val["computed"].(string) + } + computed[key] = computedSecret + } + + return computed, nil +} + +// ParseConfigServiceToken parse config service token +func ParseConfigServiceToken(token map[string]interface{}) ConfigServiceToken { + var parsedToken ConfigServiceToken + + if token["name"] != nil { + parsedToken.Name = token["name"].(string) + } + if token["key"] != nil { + parsedToken.Token = token["key"].(string) + } + if token["slug"] != nil { + parsedToken.Slug = token["slug"].(string) + } + if token["project"] != nil { + parsedToken.Project = token["project"].(string) + } + if token["environment"] != nil { + parsedToken.Environment = token["environment"].(string) + } + if token["config"] != nil { + parsedToken.Config = token["config"].(string) + } + if token["created_at"] != nil { + parsedToken.CreatedAt = token["created_at"].(string) + } + + return parsedToken +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/repo_config.go b/vendor/github.com/DopplerHQ/cli/pkg/models/repo_config.go new file mode 100644 index 00000000..daebbfdb --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/repo_config.go @@ -0,0 +1,25 @@ +/* +Copyright © 2020 Doppler + +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 models + +// RepoConfig holds all repo configuration +type RepoConfig struct { + Setup struct { + Config string `yaml:"config"` + Project string `yaml:"project"` + } `yaml:"setup"` +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/secrets_format.go b/vendor/github.com/DopplerHQ/cli/pkg/models/secrets_format.go new file mode 100644 index 00000000..b3183b30 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/secrets_format.go @@ -0,0 +1,50 @@ +/* +Copyright © 2020 Doppler + +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 models + +// SecretsFormat the format secrets should use +type SecretsFormat int + +// the source of the value +const ( + JSON SecretsFormat = iota + ENV + YAML + DOCKER + ENV_NO_FILE +) + +var SecretFormats = []string{"json", "env", "yaml", "docker", "env-no-quotes"} + +func (s SecretsFormat) String() string { + return SecretFormats[s] +} + +// OutputFile the default secrets file name +func (s SecretsFormat) OutputFile() string { + return [...]string{"doppler.json", "doppler.env", "secrets.yaml", "doppler.env", "doppler.env"}[s] +} + +// SecretsFormatList list of supported secrets formats +var SecretsFormatList []SecretsFormat + +func init() { + SecretsFormatList = append(SecretsFormatList, JSON) + SecretsFormatList = append(SecretsFormatList, ENV) + SecretsFormatList = append(SecretsFormatList, YAML) + SecretsFormatList = append(SecretsFormatList, DOCKER) + SecretsFormatList = append(SecretsFormatList, ENV_NO_FILE) +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/models/workers.go b/vendor/github.com/DopplerHQ/cli/pkg/models/workers.go new file mode 100644 index 00000000..22395a74 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/models/workers.go @@ -0,0 +1,49 @@ +/* +Copyright © 2020 Doppler + +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 models + +import ( + "encoding/json" + + "github.com/DopplerHQ/cli/pkg/utils" +) + +// ChangeLog lists changes +type ChangeLog struct { + Changes []string `json:"changes"` +} + +// ParseChangeLog parse change log +func ParseChangeLog(response []byte) map[string]ChangeLog { + var releaseMap []map[string]interface{} + if err := json.Unmarshal(response, &releaseMap); err != nil { + utils.HandleError(err, "Unable to parse changelog") + } + + changes := map[string]ChangeLog{} + + for _, release := range releaseMap { + v := release["version"].(string) + var list []string + for _, change := range release["changes"].([]interface{}) { + list = append(list, change.(string)) + } + + changes[v] = ChangeLog{Changes: list} + } + + return changes +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/utils/config.go b/vendor/github.com/DopplerHQ/cli/pkg/utils/config.go new file mode 100644 index 00000000..22a2c325 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/utils/config.go @@ -0,0 +1,25 @@ +/* +Copyright © 2019 Doppler + +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 utils + +// Debug whether we're running in debug mode +var Debug = false + +// Silent whether we should display Info messages +var Silent = false + +// OutputJSON whether to print OutputJSON +var OutputJSON = false diff --git a/vendor/github.com/DopplerHQ/cli/pkg/utils/io.go b/vendor/github.com/DopplerHQ/cli/pkg/utils/io.go new file mode 100644 index 00000000..b283135b --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/utils/io.go @@ -0,0 +1,78 @@ +/* +Copyright © 2020 Doppler + +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 utils + +import ( + "fmt" + "io/ioutil" + "os" +) + +// RestrictedFilePerms perms used for creating restrictied files meant to be accessible only to the user +func RestrictedFilePerms() os.FileMode { + // windows disallows overwriting an existing file with 0400 perms + if IsWindows() { + return 0600 + } + + return 0400 +} + +// WriteFile atomically writes data to a file named by filename. +func WriteFile(filename string, data []byte, perm os.FileMode) error { + temp := fmt.Sprintf("%s.%s", filename, RandomBase64String(8)) + + // write to a unique temp file first before performing an atomic move to the actual file name + // this prevents a race condition between multiple CLIs reading/writing the same file + LogDebug(fmt.Sprintf("Writing to temp file %s", temp)) + if err := ioutil.WriteFile(temp, data, os.FileMode(perm)); err != nil { + return err + } + + LogDebug(fmt.Sprintf("Renaming temp file to %s", filename)) + if err := os.Rename(temp, filename); err != nil { + // clean up temp file + _ = os.Remove(temp) + return err + } + + return nil +} + +// WriteTempFile writes data to a unique temp file and returns the file name +func WriteTempFile(name string, data []byte, perm os.FileMode) (string, error) { + // create hidden file in user's home dir to ensure no other users have write access + tmpFile, err := ioutil.TempFile(HomeDir(), fmt.Sprintf(".%s.", name)) + if err != nil { + return "", err + } + + LogDebug(fmt.Sprintf("Writing to temp file %s", tmpFile.Name())) + if _, err := tmpFile.Write(data); err != nil { + return "", err + } + + tmpFileName := tmpFile.Name() + if err := tmpFile.Close(); err != nil { + return "", err + } + + if err := os.Chmod(tmpFileName, perm); err != nil { + return "", err + } + + return tmpFileName, nil +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/utils/log.go b/vendor/github.com/DopplerHQ/cli/pkg/utils/log.go new file mode 100644 index 00000000..58056977 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/utils/log.go @@ -0,0 +1,105 @@ +/* +Copyright © 2019 Doppler + +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 utils + +import ( + "encoding/json" + "fmt" + "os" + + "gopkg.in/gookit/color.v1" +) + +// Log info message to stdout +func Log(info string) { + if CanLogInfo() { + fmt.Println(info) + } +} + +// LogWarning message to stdout +func LogWarning(s string) { + if CanLogInfo() { + fmt.Println(color.Yellow.Render("Warning:"), s) + } +} + +// LogError prints an error message to stderr +func LogError(e error) { + if CanLogInfo() { + printError(e) + } +} + +// CanLogInfo messages to stdout +func CanLogInfo() bool { + silent := Silent || OutputJSON + return Debug || !silent +} + +// LogDebug prints a debug message to stdout +func LogDebug(s string) { + if CanLogDebug() { + // log debug messages to stderr + fmt.Fprintln(os.Stderr, color.Blue.Render("Debug:"), s) + } +} + +// LogDebugError prints an error message to stderr when in debug mode +func LogDebugError(e error) { + if CanLogDebug() { + printError(e) + } +} + +// CanLogDebug messages to stdout +func CanLogDebug() bool { + return Debug +} + +// HandleError prints the error and exits with code 1 +func HandleError(e error, messages ...string) { + ErrExit(e, 1, messages...) +} + +// ErrExit prints the error and exits with the specified code +func ErrExit(e error, exitCode int, messages ...string) { + if OutputJSON { + resp, err := json.Marshal(map[string]string{"error": e.Error()}) + if err != nil { + panic(err) + } + fmt.Fprintln(os.Stderr, string(resp)) + } else { + if len(messages) > 0 && messages[0] != "" { + fmt.Fprintln(os.Stderr, messages[0]) + } + + printError(e) + + if len(messages) > 0 { + for _, message := range messages[1:] { + fmt.Fprintln(os.Stderr, message) + } + } + } + + os.Exit(exitCode) +} + +func printError(e error) { + fmt.Fprintln(os.Stderr, color.Red.Render("Doppler Error:"), e) +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/utils/random.go b/vendor/github.com/DopplerHQ/cli/pkg/utils/random.go new file mode 100644 index 00000000..2d020de5 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/utils/random.go @@ -0,0 +1,32 @@ +/* +Copyright © 2020 Doppler + +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 utils + +import ( + "crypto/rand" + "encoding/base64" + "math" +) + +// RandomBase64String cryptographically secure random string +// from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go +func RandomBase64String(l int) string { + // Base 64 text is 1/3 longer than base 256. (2^8 vs 2^6; 8bits/6bits = 1.333 ratio) + buffer := make([]byte, int(math.Round(float64(l)/float64(4/3)))) + rand.Read(buffer) // #nosec G104 + str := base64.RawURLEncoding.EncodeToString(buffer) + return str[:l] // strip 1 extra character we get from odd length results +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/utils/util.go b/vendor/github.com/DopplerHQ/cli/pkg/utils/util.go new file mode 100644 index 00000000..093ed466 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/utils/util.go @@ -0,0 +1,396 @@ +/* +Copyright © 2019 Doppler + +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 utils + +import ( + "errors" + "fmt" + "os" + "os/exec" + "os/signal" + "os/user" + "path/filepath" + "runtime" + "strconv" + "strings" + "syscall" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/AlecAivazis/survey/v2/terminal" + "github.com/atotto/clipboard" + "github.com/google/uuid" + "github.com/spf13/cobra" +) + +// ConfigDir DEPRECATED get configuration directory +func ConfigDir() string { + // this function is deprecated and should not be used. + // in testing, node:12-alpine creates the ~/.config directory at some indeterminate point + // in the build. this means some doppler commands called by docker RUN may use the home + // directory to store config, while doppler commands called by ENTRYPOINT will use ~/config. + dir, err := os.UserConfigDir() + if err != nil { + HandleError(err, "Unable to determine configuration directory") + } + + return dir +} + +// HomeDir get home directory +func HomeDir() string { + dir, err := os.UserHomeDir() + if err != nil { + HandleError(err, "Unable to determine home directory") + } + + return dir +} + +// ParsePath returns an absolute path, parsing ~ . .. etc +func ParsePath(path string) (string, error) { + if path == "" { + return "", errors.New("Path cannot be blank") + } + + if strings.HasPrefix(path, "~") { + firstPath := strings.Split(path, string(filepath.Separator))[0] + + if firstPath != "~" { + username, err := user.Current() + if err != nil || firstPath != fmt.Sprintf("~%s", username.Username) { + return "", fmt.Errorf("unable to parse path, please specify an absolute path (e.g. /home/%s)", path[1:]) + } + } + + path = strings.Replace(path, firstPath, HomeDir(), 1) + } + + absolutePath, err := filepath.Abs(filepath.Clean(path)) + if err != nil { + return "", err + } + + return absolutePath, nil +} + +// Exists whether path exists and the user has permission +func Exists(path string) bool { + if _, err := os.Stat(path); err != nil { + return false + } + return true +} + +// Cwd current working directory of user's shell +func Cwd() string { + cwd, err := os.Getwd() + if err != nil { + HandleError(err) + } + return cwd +} + +// RunCommand runs the specified command +func RunCommand(command []string, env []string, inFile *os.File, outFile *os.File, errFile *os.File, forwardSignals bool) (int, error) { + cmd := exec.Command(command[0], command[1:]...) // #nosec G204 + cmd.Env = env + cmd.Stdin = inFile + cmd.Stdout = outFile + cmd.Stderr = errFile + + return execCommand(cmd, forwardSignals) +} + +// RunCommandString runs the specified command string +func RunCommandString(command string, env []string, inFile *os.File, outFile *os.File, errFile *os.File, forwardSignals bool) (int, error) { + shell := [2]string{"sh", "-c"} + if IsWindows() { + shell = [2]string{"cmd", "/C"} + } else { + // these shells all support the same options we use for sh + shells := []string{"/bash", "/dash", "/fish", "/zsh", "/ksh", "/csh", "/tcsh"} + envShell := os.Getenv("SHELL") + for _, s := range shells { + if strings.HasSuffix(envShell, s) { + shell[0] = envShell + break + } + } + } + cmd := exec.Command(shell[0], shell[1], command) // #nosec G204 + cmd.Env = env + cmd.Stdin = inFile + cmd.Stdout = outFile + cmd.Stderr = errFile + + return execCommand(cmd, forwardSignals) +} + +func execCommand(cmd *exec.Cmd, forwardSignals bool) (int, error) { + // signal handling logic adapted from aws-vault https://github.com/99designs/aws-vault/ + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan) + + if err := cmd.Start(); err != nil { + return 1, err + } + + // handle all signals + go func() { + for { + // When running with a TTY, user-generated signals (like SIGINT) are sent to the entire process group. + // If we forward the signal, the child process will end up receiving the signal twice. + if forwardSignals { + // forward to process + sig := <-sigChan + cmd.Process.Signal(sig) // #nosec G104 + } else { + // ignore + <-sigChan + } + } + }() + + if err := cmd.Wait(); err != nil { + // ignore errors + cmd.Process.Signal(os.Kill) // #nosec G104 + + if exitError, ok := err.(*exec.ExitError); ok { + return exitError.ExitCode(), exitError + } + + return 2, err + } + + waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus) + return waitStatus.ExitStatus(), nil +} + +// RequireValue throws an error if a value is blank +func RequireValue(name string, value string) { + value = strings.TrimSpace(value) + if value == "" { + HandleError(fmt.Errorf("you must provide a %s", name)) + } +} + +// GetBool parse string into a boolean +func GetBool(value string, def bool) bool { + b, err := strconv.ParseBool(value) + if err != nil { + return def + } + return b +} + +// GetBoolFlag gets the flag's boolean value +func GetBoolFlag(cmd *cobra.Command, flag string) bool { + b, err := strconv.ParseBool(cmd.Flag(flag).Value.String()) + if err != nil { + HandleError(err) + } + return b +} + +// GetBoolFlagIfChanged gets the flag's boolean value, if specified; +// protects against reading an undefined flag +func GetBoolFlagIfChanged(cmd *cobra.Command, flag string, def bool) bool { + if !cmd.Flags().Changed(flag) { + return def + } + + return GetBoolFlag(cmd, flag) +} + +// GetFlagIfChanged gets the flag's value, if specified; +// protects against reading an undefined flag +func GetFlagIfChanged(cmd *cobra.Command, flag string, def string) string { + if !cmd.Flags().Changed(flag) { + return def + } + + return cmd.Flag(flag).Value.String() +} + +// GetPathFlagIfChanged gets the flag's path, if specified; +// always returns an absolute path +func GetPathFlagIfChanged(cmd *cobra.Command, flag string, def string) string { + if !cmd.Flags().Changed(flag) { + return def + } + + path, err := ParsePath(cmd.Flag(flag).Value.String()) + if err != nil { + HandleError(err, "Unable to parse path") + } + return path +} + +// GetIntFlag gets the flag's int value +func GetIntFlag(cmd *cobra.Command, flag string, bits int) int { + number, err := strconv.ParseInt(cmd.Flag(flag).Value.String(), 10, bits) + if err != nil { + HandleError(err) + } + + return int(number) +} + +// GetDurationFlag gets the flag's duration +func GetDurationFlag(cmd *cobra.Command, flag string) time.Duration { + value, err := time.ParseDuration(cmd.Flag(flag).Value.String()) + if err != nil { + HandleError(err) + } + return value +} + +// GetDurationFlagIfChanged gets the flag's duration, if specified; +// protects against reading an undefined flag +func GetDurationFlagIfChanged(cmd *cobra.Command, flag string, def time.Duration) time.Duration { + if !cmd.Flags().Changed(flag) { + return def + } + + return GetDurationFlag(cmd, flag) +} + +// GetFilePath verify a file path and name are provided +func GetFilePath(fullPath string) (string, error) { + if fullPath == "" { + return "", errors.New("Invalid file path") + } + + fullPath, err := ParsePath(fullPath) + if err != nil { + return "", errors.New("Invalid file path") + } + + parsedPath := filepath.Dir(fullPath) + parsedName := filepath.Base(fullPath) + + isNameValid := (parsedName != ".") && (parsedName != "..") && (parsedName != "/") && (parsedName != string(filepath.Separator)) + if !isNameValid { + return "", errors.New("Invalid file path") + } + + return filepath.Join(parsedPath, parsedName), nil +} + +// ConfirmationPrompt prompt user to confirm yes/no +func ConfirmationPrompt(message string, defaultValue bool) bool { + confirm := false + prompt := &survey.Confirm{ + Message: message, + Default: defaultValue, + } + + err := survey.AskOne(prompt, &confirm) + if err != nil { + if err == terminal.InterruptErr { + Log("Exiting") + os.Exit(1) + } + HandleError(err) + } + return confirm +} + +// SelectPrompt prompt user to select from a list of options +func SelectPrompt(message string, options []string, defaultOption string) string { + prompt := &survey.Select{ + Message: message, + Options: options, + PageSize: 25, + } + if defaultOption != "" { + prompt.Default = defaultOption + } + + selectedProject := "" + err := survey.AskOne(prompt, &selectedProject) + if err != nil { + if err == terminal.InterruptErr { + Log("Exiting") + os.Exit(1) + } + HandleError(err) + } + + return selectedProject +} + +// CopyToClipboard copies text to the user's clipboard +func CopyToClipboard(text string) error { + if !clipboard.Unsupported { + err := clipboard.WriteAll(text) + if err != nil { + return err + } + } + return nil +} + +// HostOS the host OS +func HostOS() string { + os := runtime.GOOS + + switch os { + case "darwin": + return "macOS" + case "windows": + return "Windows" + } + + return os +} + +// HostArch the host architecture +func HostArch() string { + arch := runtime.GOARCH + + switch arch { + case "amd64": + return "64-bit" + case "amd64p32": + case "386": + return "32-bit" + } + + return arch +} + +// IsWindows whether the host os is Windows +func IsWindows() bool { + return runtime.GOOS == "windows" +} + +// IsMacOS whether the host os is macOS +func IsMacOS() bool { + return runtime.GOOS == "darwin" +} + +// UUID generates a random UUID +func UUID() (string, error) { + uuid, err := uuid.NewRandom() + if err != nil { + LogDebug("Unable to generate random UUID") + return "", err + } + + return uuid.String(), nil +} diff --git a/vendor/github.com/DopplerHQ/cli/pkg/version/config.go b/vendor/github.com/DopplerHQ/cli/pkg/version/config.go new file mode 100644 index 00000000..9ca86505 --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/version/config.go @@ -0,0 +1,19 @@ +/* +Copyright © 2019 Doppler + +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 version + +// PerformVersionCheck whether to check for new versions of the Doppler CLI +var PerformVersionCheck = true diff --git a/vendor/github.com/DopplerHQ/cli/pkg/version/version.go b/vendor/github.com/DopplerHQ/cli/pkg/version/version.go new file mode 100644 index 00000000..9c17400c --- /dev/null +++ b/vendor/github.com/DopplerHQ/cli/pkg/version/version.go @@ -0,0 +1,112 @@ +/* +Copyright © 2019 Doppler + +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 version + +import ( + "fmt" + "strconv" + "strings" +) + +// ProgramVersion the current version of this program +var ProgramVersion = "dev" + +// Version semver +type Version struct { + Major int16 + Minor int16 + Patch int16 +} + +// Unwrap get the original error +func (v Version) String() string { + return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) +} + +// IsDevelopment whether the CLI is running in development mode (not a released version) +func IsDevelopment() bool { + return strings.HasPrefix(ProgramVersion, "dev") +} + +// CompareVersions returns -1 if first is greater, 1 if second is greater, and 0 otherwise +func CompareVersions(a Version, b Version) int { + // major version + if a.Major > b.Major { + return -1 + } + if b.Major > a.Major { + return 1 + } + + // minor version + if a.Minor > b.Minor { + return -1 + } + if b.Minor > a.Minor { + return 1 + } + + // patch version + if a.Patch > b.Patch { + return -1 + } + if b.Patch > a.Patch { + return 1 + } + + return 0 +} + +// ParseVersion from a string +func ParseVersion(s string) (Version, error) { + if strings.HasPrefix(s, "v") { + s = s[1:] + } + parts := strings.Split(s, ".") + if len(parts) != 3 { + return Version{}, fmt.Errorf("Invalid version %s", s) + } + + var v Version + var major int64 + var minor int64 + var patch int64 + var err error + if major, err = strconv.ParseInt(parts[0], 10, 16); err != nil { + return Version{}, fmt.Errorf("Invalid version %s", s) + } + if minor, err = strconv.ParseInt(parts[1], 10, 16); err != nil { + return Version{}, fmt.Errorf("Invalid version %s", s) + } + if patch, err = strconv.ParseInt(parts[2], 10, 16); err != nil { + return Version{}, fmt.Errorf("Invalid version %s", s) + } + + v.Major = int16(major) + v.Minor = int16(minor) + v.Patch = int16(patch) + return v, nil +} + +// Normalize prepends a 'v' to a version (e.g. 1.0.0 -> v1.0.0) +func Normalize(version string) string { + version = strings.TrimSpace(version) + if !strings.HasPrefix(version, "v") { + return "v" + version + } + + return version +} diff --git a/vendor/github.com/atotto/clipboard/.travis.yml b/vendor/github.com/atotto/clipboard/.travis.yml new file mode 100644 index 00000000..5bd5ae3b --- /dev/null +++ b/vendor/github.com/atotto/clipboard/.travis.yml @@ -0,0 +1,20 @@ +language: go + +go: + - go1.4.3 + - go1.5.4 + - go1.6.4 + - go1.7.6 + - go1.8.7 + - go1.9.4 + - go1.10 + +before_install: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + +script: + - sudo apt-get install xsel + - go test -v . + - sudo apt-get install xclip + - go test -v . diff --git a/vendor/github.com/atotto/clipboard/LICENSE b/vendor/github.com/atotto/clipboard/LICENSE new file mode 100644 index 00000000..dee3257b --- /dev/null +++ b/vendor/github.com/atotto/clipboard/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 Ato Araki. 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 @atotto. 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/atotto/clipboard/README.md b/vendor/github.com/atotto/clipboard/README.md new file mode 100644 index 00000000..41fdd57b --- /dev/null +++ b/vendor/github.com/atotto/clipboard/README.md @@ -0,0 +1,48 @@ +[![Build Status](https://travis-ci.org/atotto/clipboard.svg?branch=master)](https://travis-ci.org/atotto/clipboard) + +[![GoDoc](https://godoc.org/github.com/atotto/clipboard?status.svg)](http://godoc.org/github.com/atotto/clipboard) + +# Clipboard for Go + +Provide copying and pasting to the Clipboard for Go. + +Build: + + $ go get github.com/atotto/clipboard + +Platforms: + +* OSX +* Windows 7 (probably work on other Windows) +* Linux, Unix (requires 'xclip' or 'xsel' command to be installed) + + +Document: + +* http://godoc.org/github.com/atotto/clipboard + +Notes: + +* Text string only +* UTF-8 text encoding only (no conversion) + +TODO: + +* Clipboard watcher(?) + +## Commands: + +paste shell command: + + $ go get github.com/atotto/clipboard/cmd/gopaste + $ # example: + $ gopaste > document.txt + +copy shell command: + + $ go get github.com/atotto/clipboard/cmd/gocopy + $ # example: + $ cat document.txt | gocopy + + + diff --git a/vendor/github.com/atotto/clipboard/clipboard.go b/vendor/github.com/atotto/clipboard/clipboard.go new file mode 100644 index 00000000..d7907d3a --- /dev/null +++ b/vendor/github.com/atotto/clipboard/clipboard.go @@ -0,0 +1,20 @@ +// Copyright 2013 @atotto. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package clipboard read/write on clipboard +package clipboard + +// ReadAll read string from clipboard +func ReadAll() (string, error) { + return readAll() +} + +// WriteAll write string to clipboard +func WriteAll(text string) error { + return writeAll(text) +} + +// Unsupported might be set true during clipboard init, to help callers decide +// whether or not to offer clipboard options. +var Unsupported bool diff --git a/vendor/github.com/atotto/clipboard/clipboard_darwin.go b/vendor/github.com/atotto/clipboard/clipboard_darwin.go new file mode 100644 index 00000000..6f33078d --- /dev/null +++ b/vendor/github.com/atotto/clipboard/clipboard_darwin.go @@ -0,0 +1,52 @@ +// Copyright 2013 @atotto. 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 clipboard + +import ( + "os/exec" +) + +var ( + pasteCmdArgs = "pbpaste" + copyCmdArgs = "pbcopy" +) + +func getPasteCommand() *exec.Cmd { + return exec.Command(pasteCmdArgs) +} + +func getCopyCommand() *exec.Cmd { + return exec.Command(copyCmdArgs) +} + +func readAll() (string, error) { + pasteCmd := getPasteCommand() + out, err := pasteCmd.Output() + if err != nil { + return "", err + } + return string(out), nil +} + +func writeAll(text string) error { + copyCmd := getCopyCommand() + in, err := copyCmd.StdinPipe() + if err != nil { + return err + } + + if err := copyCmd.Start(); err != nil { + return err + } + if _, err := in.Write([]byte(text)); err != nil { + return err + } + if err := in.Close(); err != nil { + return err + } + return copyCmd.Wait() +} diff --git a/vendor/github.com/atotto/clipboard/clipboard_unix.go b/vendor/github.com/atotto/clipboard/clipboard_unix.go new file mode 100644 index 00000000..8f5e23c3 --- /dev/null +++ b/vendor/github.com/atotto/clipboard/clipboard_unix.go @@ -0,0 +1,129 @@ +// Copyright 2013 @atotto. 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 linux netbsd openbsd solaris dragonfly + +package clipboard + +import ( + "errors" + "os" + "os/exec" +) + +const ( + xsel = "xsel" + xclip = "xclip" + wlcopy = "wl-copy" + wlpaste = "wl-paste" + termuxClipboardGet = "termux-clipboard-get" + termuxClipboardSet = "termux-clipboard-set" +) + +var ( + Primary bool + + pasteCmdArgs []string + copyCmdArgs []string + + xselPasteArgs = []string{xsel, "--output", "--clipboard"} + xselCopyArgs = []string{xsel, "--input", "--clipboard"} + + xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"} + xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"} + + wlpasteArgs = []string{wlpaste, "--no-newline"} + wlcopyArgs = []string{wlcopy} + + termuxPasteArgs = []string{termuxClipboardGet} + termuxCopyArgs = []string{termuxClipboardSet} + + missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.") +) + +func init() { + if os.Getenv("WAYLAND_DISPLAY") != "" { + pasteCmdArgs = wlpasteArgs; + copyCmdArgs = wlcopyArgs; + + if _, err := exec.LookPath(wlcopy); err == nil { + if _, err := exec.LookPath(wlpaste); err == nil { + return + } + } + } + + pasteCmdArgs = xclipPasteArgs + copyCmdArgs = xclipCopyArgs + + if _, err := exec.LookPath(xclip); err == nil { + return + } + + pasteCmdArgs = xselPasteArgs + copyCmdArgs = xselCopyArgs + + if _, err := exec.LookPath(xsel); err == nil { + return + } + + pasteCmdArgs = termuxPasteArgs + copyCmdArgs = termuxCopyArgs + + if _, err := exec.LookPath(termuxClipboardSet); err == nil { + if _, err := exec.LookPath(termuxClipboardGet); err == nil { + return + } + } + + Unsupported = true +} + +func getPasteCommand() *exec.Cmd { + if Primary { + pasteCmdArgs = pasteCmdArgs[:1] + } + return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...) +} + +func getCopyCommand() *exec.Cmd { + if Primary { + copyCmdArgs = copyCmdArgs[:1] + } + return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...) +} + +func readAll() (string, error) { + if Unsupported { + return "", missingCommands + } + pasteCmd := getPasteCommand() + out, err := pasteCmd.Output() + if err != nil { + return "", err + } + return string(out), nil +} + +func writeAll(text string) error { + if Unsupported { + return missingCommands + } + copyCmd := getCopyCommand() + in, err := copyCmd.StdinPipe() + if err != nil { + return err + } + + if err := copyCmd.Start(); err != nil { + return err + } + if _, err := in.Write([]byte(text)); err != nil { + return err + } + if err := in.Close(); err != nil { + return err + } + return copyCmd.Wait() +} diff --git a/vendor/github.com/atotto/clipboard/clipboard_windows.go b/vendor/github.com/atotto/clipboard/clipboard_windows.go new file mode 100644 index 00000000..4b4aedb6 --- /dev/null +++ b/vendor/github.com/atotto/clipboard/clipboard_windows.go @@ -0,0 +1,128 @@ +// Copyright 2013 @atotto. 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 clipboard + +import ( + "syscall" + "time" + "unsafe" +) + +const ( + cfUnicodetext = 13 + gmemMoveable = 0x0002 +) + +var ( + user32 = syscall.MustLoadDLL("user32") + openClipboard = user32.MustFindProc("OpenClipboard") + closeClipboard = user32.MustFindProc("CloseClipboard") + emptyClipboard = user32.MustFindProc("EmptyClipboard") + getClipboardData = user32.MustFindProc("GetClipboardData") + setClipboardData = user32.MustFindProc("SetClipboardData") + + kernel32 = syscall.NewLazyDLL("kernel32") + globalAlloc = kernel32.NewProc("GlobalAlloc") + globalFree = kernel32.NewProc("GlobalFree") + globalLock = kernel32.NewProc("GlobalLock") + globalUnlock = kernel32.NewProc("GlobalUnlock") + lstrcpy = kernel32.NewProc("lstrcpyW") +) + +// waitOpenClipboard opens the clipboard, waiting for up to a second to do so. +func waitOpenClipboard() error { + started := time.Now() + limit := started.Add(time.Second) + var r uintptr + var err error + for time.Now().Before(limit) { + r, _, err = openClipboard.Call(0) + if r != 0 { + return nil + } + time.Sleep(time.Millisecond) + } + return err +} + +func readAll() (string, error) { + err := waitOpenClipboard() + if err != nil { + return "", err + } + defer closeClipboard.Call() + + h, _, err := getClipboardData.Call(cfUnicodetext) + if h == 0 { + return "", err + } + + l, _, err := globalLock.Call(h) + if l == 0 { + return "", err + } + + text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) + + r, _, err := globalUnlock.Call(h) + if r == 0 { + return "", err + } + + return text, nil +} + +func writeAll(text string) error { + err := waitOpenClipboard() + if err != nil { + return err + } + defer closeClipboard.Call() + + r, _, err := emptyClipboard.Call(0) + if r == 0 { + return err + } + + data := syscall.StringToUTF16(text) + + // "If the hMem parameter identifies a memory object, the object must have + // been allocated using the function with the GMEM_MOVEABLE flag." + h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) + if h == 0 { + return err + } + defer func() { + if h != 0 { + globalFree.Call(h) + } + }() + + l, _, err := globalLock.Call(h) + if l == 0 { + return err + } + + r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) + if r == 0 { + return err + } + + r, _, err = globalUnlock.Call(h) + if r == 0 { + if err.(syscall.Errno) != 0 { + return err + } + } + + r, _, err = setClipboardData.Call(cfUnicodetext, h) + if r == 0 { + return err + } + h = 0 // suppress deferred cleanup + return nil +} diff --git a/vendor/github.com/atotto/clipboard/go.mod b/vendor/github.com/atotto/clipboard/go.mod new file mode 100644 index 00000000..68ec980e --- /dev/null +++ b/vendor/github.com/atotto/clipboard/go.mod @@ -0,0 +1 @@ +module github.com/atotto/clipboard diff --git a/vendor/github.com/danieljoos/wincred/.gitattributes b/vendor/github.com/danieljoos/wincred/.gitattributes new file mode 100644 index 00000000..d207b180 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/vendor/github.com/danieljoos/wincred/.gitignore b/vendor/github.com/danieljoos/wincred/.gitignore new file mode 100644 index 00000000..83656241 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/danieljoos/wincred/LICENSE b/vendor/github.com/danieljoos/wincred/LICENSE new file mode 100644 index 00000000..2f436f1b --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Daniel Joos + +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. \ No newline at end of file diff --git a/vendor/github.com/danieljoos/wincred/README.md b/vendor/github.com/danieljoos/wincred/README.md new file mode 100644 index 00000000..064efb76 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/README.md @@ -0,0 +1,98 @@ +wincred +======= + +Go wrapper around the Windows Credential Manager API functions. + +![Go](https://github.com/danieljoos/wincred/workflows/Go/badge.svg) +[![GoDoc](https://godoc.org/github.com/danieljoos/wincred?status.svg)](https://godoc.org/github.com/danieljoos/wincred) + + +Installation +------------ + +```Go +go get github.com/danieljoos/wincred +``` + + +Usage +----- + +See the following examples: + +### Create and store a new generic credential object +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + cred := wincred.NewGenericCredential("myGoApplication") + cred.CredentialBlob = []byte("my secret") + err := cred.Write() + + if err != nil { + fmt.Println(err) + } +} +``` + +### Retrieve a credential object +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + cred, err := wincred.GetGenericCredential("myGoApplication") + if err == nil { + fmt.Println(string(cred.CredentialBlob)) + } +} +``` + +### Remove a credential object +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + cred, err := wincred.GetGenericCredential("myGoApplication") + if err != nil { + fmt.Println(err) + return + } + cred.Delete() +} +``` + +### List all available credentials +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + creds, err := wincred.List() + if err != nil { + fmt.Println(err) + return + } + for i := range(creds) { + fmt.Println(creds[i].TargetName) + } +} +``` diff --git a/vendor/github.com/danieljoos/wincred/conversion.go b/vendor/github.com/danieljoos/wincred/conversion.go new file mode 100644 index 00000000..685f90a8 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/conversion.go @@ -0,0 +1,131 @@ +// +build windows + +package wincred + +import ( + "encoding/binary" + "reflect" + "syscall" + "time" + "unicode/utf16" + "unsafe" +) + +// uf16PtrToString creates a Go string from a pointer to a UTF16 encoded zero-terminated string. +// Such pointers are returned from the Windows API calls. +// The function creates a copy of the string. +func utf16PtrToString(wstr *uint16) string { + if wstr != nil { + for len := 0; ; len++ { + ptr := unsafe.Pointer(uintptr(unsafe.Pointer(wstr)) + uintptr(len)*unsafe.Sizeof(*wstr)) // see https://golang.org/pkg/unsafe/#Pointer (3) + if *(*uint16)(ptr) == 0 { + return string(utf16.Decode(*(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(wstr)), + Len: len, + Cap: len, + })))) + } + } + } + return "" +} + +// utf16ToByte creates a byte array from a given UTF 16 char array. +func utf16ToByte(wstr []uint16) (result []byte) { + result = make([]byte, len(wstr)*2) + for i := range wstr { + binary.LittleEndian.PutUint16(result[(i*2):(i*2)+2], wstr[i]) + } + return +} + +// utf16FromString creates a UTF16 char array from a string. +func utf16FromString(str string) []uint16 { + return syscall.StringToUTF16(str) +} + +// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`). +// This function avoids having cgo as dependency. +func goBytes(src uintptr, len uint32) []byte { + if src == uintptr(0) { + return []byte{} + } + rv := make([]byte, len) + copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ + Data: src, + Len: int(len), + Cap: int(len), + }))) + return rv +} + +// Convert the given CREDENTIAL struct to a more usable structure +func sysToCredential(cred *sysCREDENTIAL) (result *Credential) { + if cred == nil { + return nil + } + result = new(Credential) + result.Comment = utf16PtrToString(cred.Comment) + result.TargetName = utf16PtrToString(cred.TargetName) + result.TargetAlias = utf16PtrToString(cred.TargetAlias) + result.UserName = utf16PtrToString(cred.UserName) + result.LastWritten = time.Unix(0, cred.LastWritten.Nanoseconds()) + result.Persist = CredentialPersistence(cred.Persist) + result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize) + result.Attributes = make([]CredentialAttribute, cred.AttributeCount) + attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{ + Data: cred.Attributes, + Len: int(cred.AttributeCount), + Cap: int(cred.AttributeCount), + })) + for i, attr := range attrSlice { + resultAttr := &result.Attributes[i] + resultAttr.Keyword = utf16PtrToString(attr.Keyword) + resultAttr.Value = goBytes(attr.Value, attr.ValueSize) + } + return result +} + +// Convert the given Credential object back to a CREDENTIAL struct, which can be used for calling the +// Windows APIs +func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) { + if cred == nil { + return nil + } + result = new(sysCREDENTIAL) + result.Flags = 0 + result.Type = 0 + result.TargetName, _ = syscall.UTF16PtrFromString(cred.TargetName) + result.Comment, _ = syscall.UTF16PtrFromString(cred.Comment) + result.LastWritten = syscall.NsecToFiletime(cred.LastWritten.UnixNano()) + result.CredentialBlobSize = uint32(len(cred.CredentialBlob)) + if len(cred.CredentialBlob) > 0 { + result.CredentialBlob = uintptr(unsafe.Pointer(&cred.CredentialBlob[0])) + } else { + result.CredentialBlob = 0 + } + result.Persist = uint32(cred.Persist) + result.AttributeCount = uint32(len(cred.Attributes)) + attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes)) + if len(attributes) > 0 { + result.Attributes = uintptr(unsafe.Pointer(&attributes[0])) + } else { + result.Attributes = 0 + } + for i := range cred.Attributes { + inAttr := &cred.Attributes[i] + outAttr := &attributes[i] + outAttr.Keyword, _ = syscall.UTF16PtrFromString(inAttr.Keyword) + outAttr.Flags = 0 + outAttr.ValueSize = uint32(len(inAttr.Value)) + if len(inAttr.Value) > 0 { + outAttr.Value = uintptr(unsafe.Pointer(&inAttr.Value[0])) + } else { + outAttr.Value = 0 + } + } + result.TargetAlias, _ = syscall.UTF16PtrFromString(cred.TargetAlias) + result.UserName, _ = syscall.UTF16PtrFromString(cred.UserName) + + return +} diff --git a/vendor/github.com/danieljoos/wincred/conversion_unsupported.go b/vendor/github.com/danieljoos/wincred/conversion_unsupported.go new file mode 100644 index 00000000..a1ea7207 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/conversion_unsupported.go @@ -0,0 +1,11 @@ +// +build !windows + +package wincred + +func utf16ToByte(...interface{}) []byte { + return nil +} + +func utf16FromString(...interface{}) []uint16 { + return nil +} diff --git a/vendor/github.com/danieljoos/wincred/go.mod b/vendor/github.com/danieljoos/wincred/go.mod new file mode 100644 index 00000000..b5615804 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/go.mod @@ -0,0 +1,5 @@ +module github.com/danieljoos/wincred + +go 1.13 + +require github.com/stretchr/testify v1.5.1 diff --git a/vendor/github.com/danieljoos/wincred/go.sum b/vendor/github.com/danieljoos/wincred/go.sum new file mode 100644 index 00000000..c0565d71 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/go.sum @@ -0,0 +1,12 @@ +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/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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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/danieljoos/wincred/sys.go b/vendor/github.com/danieljoos/wincred/sys.go new file mode 100644 index 00000000..7b83e843 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/sys.go @@ -0,0 +1,143 @@ +// +build windows + +package wincred + +import ( + "reflect" + "syscall" + "unsafe" +) + +var ( + modadvapi32 = syscall.NewLazyDLL("advapi32.dll") + + procCredRead proc = modadvapi32.NewProc("CredReadW") + procCredWrite proc = modadvapi32.NewProc("CredWriteW") + procCredDelete proc = modadvapi32.NewProc("CredDeleteW") + procCredFree proc = modadvapi32.NewProc("CredFree") + procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW") +) + +// Interface for syscall.Proc: helps testing +type proc interface { + Call(a ...uintptr) (r1, r2 uintptr, lastErr error) +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw +type sysCREDENTIAL struct { + Flags uint32 + Type uint32 + TargetName *uint16 + Comment *uint16 + LastWritten syscall.Filetime + CredentialBlobSize uint32 + CredentialBlob uintptr + Persist uint32 + AttributeCount uint32 + Attributes uintptr + TargetAlias *uint16 + UserName *uint16 +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributew +type sysCREDENTIAL_ATTRIBUTE struct { + Keyword *uint16 + Flags uint32 + ValueSize uint32 + Value uintptr +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw +type sysCRED_TYPE uint32 + +const ( + sysCRED_TYPE_GENERIC sysCRED_TYPE = 0x1 + sysCRED_TYPE_DOMAIN_PASSWORD sysCRED_TYPE = 0x2 + sysCRED_TYPE_DOMAIN_CERTIFICATE sysCRED_TYPE = 0x3 + sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4 + sysCRED_TYPE_GENERIC_CERTIFICATE sysCRED_TYPE = 0x5 + sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6 + + // https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes + sysERROR_NOT_FOUND = syscall.Errno(1168) + sysERROR_INVALID_PARAMETER = syscall.Errno(87) +) + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw +func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) { + var pcred *sysCREDENTIAL + targetNamePtr, _ := syscall.UTF16PtrFromString(targetName) + ret, _, err := procCredRead.Call( + uintptr(unsafe.Pointer(targetNamePtr)), + uintptr(typ), + 0, + uintptr(unsafe.Pointer(&pcred)), + ) + if ret == 0 { + return nil, err + } + defer procCredFree.Call(uintptr(unsafe.Pointer(pcred))) + + return sysToCredential(pcred), nil +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credwritew +func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error { + ncred := sysFromCredential(cred) + ncred.Type = uint32(typ) + ret, _, err := procCredWrite.Call( + uintptr(unsafe.Pointer(ncred)), + 0, + ) + if ret == 0 { + return err + } + + return nil +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew +func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error { + targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName) + ret, _, err := procCredDelete.Call( + uintptr(unsafe.Pointer(targetNamePtr)), + uintptr(typ), + 0, + ) + if ret == 0 { + return err + } + + return nil +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credenumeratew +func sysCredEnumerate(filter string, all bool) ([]*Credential, error) { + var count int + var pcreds uintptr + var filterPtr *uint16 + if !all { + filterPtr, _ = syscall.UTF16PtrFromString(filter) + } + ret, _, err := procCredEnumerate.Call( + uintptr(unsafe.Pointer(filterPtr)), + 0, + uintptr(unsafe.Pointer(&count)), + uintptr(unsafe.Pointer(&pcreds)), + ) + if ret == 0 { + return nil, err + } + defer procCredFree.Call(pcreds) + credsSlice := *(*[]*sysCREDENTIAL)(unsafe.Pointer(&reflect.SliceHeader{ + Data: pcreds, + Len: count, + Cap: count, + })) + creds := make([]*Credential, count, count) + for i, cred := range credsSlice { + creds[i] = sysToCredential(cred) + } + + return creds, nil +} diff --git a/vendor/github.com/danieljoos/wincred/sys_unsupported.go b/vendor/github.com/danieljoos/wincred/sys_unsupported.go new file mode 100644 index 00000000..b47bccf8 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/sys_unsupported.go @@ -0,0 +1,36 @@ +// +build !windows + +package wincred + +import ( + "errors" + "syscall" +) + +const ( + sysCRED_TYPE_GENERIC = 0 + sysCRED_TYPE_DOMAIN_PASSWORD = 0 + sysCRED_TYPE_DOMAIN_CERTIFICATE = 0 + sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0 + sysCRED_TYPE_GENERIC_CERTIFICATE = 0 + sysCRED_TYPE_DOMAIN_EXTENDED = 0 + + sysERROR_NOT_FOUND = syscall.Errno(1) + sysERROR_INVALID_PARAMETER = syscall.Errno(1) +) + +func sysCredRead(...interface{}) (*Credential, error) { + return nil, errors.New("Operation not supported") +} + +func sysCredWrite(...interface{}) error { + return errors.New("Operation not supported") +} + +func sysCredDelete(...interface{}) error { + return errors.New("Operation not supported") +} + +func sysCredEnumerate(...interface{}) ([]*Credential, error) { + return nil, errors.New("Operation not supported") +} diff --git a/vendor/github.com/danieljoos/wincred/types.go b/vendor/github.com/danieljoos/wincred/types.go new file mode 100644 index 00000000..28debc93 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/types.go @@ -0,0 +1,69 @@ +package wincred + +import ( + "time" +) + +// CredentialPersistence describes one of three persistence modes of a credential. +// A detailed description of the available modes can be found on +// Docs: https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw +type CredentialPersistence uint32 + +const ( + // PersistSession indicates that the credential only persists for the life + // of the current Windows login session. Such a credential is not visible in + // any other logon session, even from the same user. + PersistSession CredentialPersistence = 0x1 + + // PersistLocalMachine indicates that the credential persists for this and + // all subsequent logon sessions on this local machine/computer. It is + // however not visible for logon sessions of this user on a different + // machine. + PersistLocalMachine CredentialPersistence = 0x2 + + // PersistEnterprise indicates that the credential persists for this and all + // subsequent logon sessions for this user. It is also visible for logon + // sessions on different computers. + PersistEnterprise CredentialPersistence = 0x3 +) + +// CredentialAttribute represents an application-specific attribute of a credential. +type CredentialAttribute struct { + Keyword string + Value []byte +} + +// Credential is the basic credential structure. +// A credential is identified by its target name. +// The actual credential secret is available in the CredentialBlob field. +type Credential struct { + TargetName string + Comment string + LastWritten time.Time + CredentialBlob []byte + Attributes []CredentialAttribute + TargetAlias string + UserName string + Persist CredentialPersistence +} + +// GenericCredential holds a credential for generic usage. +// It is typically defined and used by applications that need to manage user +// secrets. +// +// More information about the available kinds of credentials of the Windows +// Credential Management API can be found on Docs: +// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials +type GenericCredential struct { + Credential +} + +// DomainPassword holds a domain credential that is typically used by the +// operating system for user logon. +// +// More information about the available kinds of credentials of the Windows +// Credential Management API can be found on Docs: +// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials +type DomainPassword struct { + Credential +} diff --git a/vendor/github.com/danieljoos/wincred/wincred.go b/vendor/github.com/danieljoos/wincred/wincred.go new file mode 100644 index 00000000..c5dbc2c9 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/wincred.go @@ -0,0 +1,111 @@ +// Package wincred provides primitives for accessing the Windows Credentials Management API. +// This includes functions for retrieval, listing and storage of credentials as well as Go structures for convenient access to the credential data. +// +// A more detailed description of Windows Credentials Management can be found on +// Docs: https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/credentials-management +package wincred + +import "errors" + +const ( + // ErrElementNotFound is the error that is returned if a requested element cannot be found. + // This error constant can be used to check if a credential could not be found. + ErrElementNotFound = sysERROR_NOT_FOUND + + // ErrInvalidParameter is the error that is returned for invalid parameters. + // This error constant can be used to check if the given function parameters were invalid. + // For example when trying to create a new generic credential with an empty target name. + ErrInvalidParameter = sysERROR_INVALID_PARAMETER +) + +// GetGenericCredential fetches the generic credential with the given name from Windows credential manager. +// It returns nil and an error if the credential could not be found or an error occurred. +func GetGenericCredential(targetName string) (*GenericCredential, error) { + cred, err := sysCredRead(targetName, sysCRED_TYPE_GENERIC) + if cred != nil { + return &GenericCredential{*cred}, err + } + return nil, err +} + +// NewGenericCredential creates a new generic credential object with the given name. +// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage. +// The credential object is NOT yet persisted to the Windows credential vault. +func NewGenericCredential(targetName string) (result *GenericCredential) { + result = new(GenericCredential) + result.TargetName = targetName + result.Persist = PersistLocalMachine + return +} + +// Write persists the generic credential object to Windows credential manager. +func (t *GenericCredential) Write() (err error) { + err = sysCredWrite(&t.Credential, sysCRED_TYPE_GENERIC) + return +} + +// Delete removes the credential object from Windows credential manager. +func (t *GenericCredential) Delete() (err error) { + err = sysCredDelete(&t.Credential, sysCRED_TYPE_GENERIC) + return +} + +// GetDomainPassword fetches the domain-password credential with the given target host name from Windows credential manager. +// It returns nil and an error if the credential could not be found or an error occurred. +func GetDomainPassword(targetName string) (*DomainPassword, error) { + cred, err := sysCredRead(targetName, sysCRED_TYPE_DOMAIN_PASSWORD) + if cred != nil { + return &DomainPassword{*cred}, err + } + return nil, err +} + +// NewDomainPassword creates a new domain-password credential used for login to the given target host name. +// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage. +// The credential object is NOT yet persisted to the Windows credential vault. +func NewDomainPassword(targetName string) (result *DomainPassword) { + result = new(DomainPassword) + result.TargetName = targetName + result.Persist = PersistLocalMachine + return +} + +// Write persists the domain-password credential to Windows credential manager. +func (t *DomainPassword) Write() (err error) { + err = sysCredWrite(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD) + return +} + +// Delete removes the domain-password credential from Windows credential manager. +func (t *DomainPassword) Delete() (err error) { + err = sysCredDelete(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD) + return +} + +// SetPassword sets the CredentialBlob field of a domain password credential to the given string. +func (t *DomainPassword) SetPassword(pw string) { + t.CredentialBlob = utf16ToByte(utf16FromString(pw)) +} + +// List retrieves all credentials of the Credentials store. +func List() ([]*Credential, error) { + creds, err := sysCredEnumerate("", true) + if err != nil && errors.Is(err, ErrElementNotFound) { + // Ignore ERROR_NOT_FOUND and return an empty list instead + creds = []*Credential{} + err = nil + } + return creds, err +} + +// FilteredList retrieves the list of credentials from the Credentials store that match the given filter. +// The filter string defines the prefix followed by an asterisk for the `TargetName` attribute of the credentials. +func FilteredList(filter string) ([]*Credential, error) { + creds, err := sysCredEnumerate(filter, false) + if err != nil && errors.Is(err, ErrElementNotFound) { + // Ignore ERROR_NOT_FOUND and return an empty list instead + creds = []*Credential{} + err = nil + } + return creds, err +} diff --git a/vendor/github.com/godbus/dbus/v5/.travis.yml b/vendor/github.com/godbus/dbus/v5/.travis.yml new file mode 100644 index 00000000..dd676720 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/.travis.yml @@ -0,0 +1,50 @@ +dist: bionic +language: go +go_import_path: github.com/godbus/dbus + +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +matrix: + fast_finish: true + allow_failures: + - go: tip + +addons: + apt: + packages: + - dbus + - dbus-x11 + +before_install: + - export GO111MODULE=on + +script: + - go test -v -race -mod=readonly ./... # Run all the tests with the race detector enabled + - go vet ./... # go vet is the official Go static analyzer + +jobs: + include: + # The build matrix doesn't cover build stages, so manually expand + # the jobs with anchors + - &multiarch + stage: "Multiarch Test" + go: 1.11.x + env: TARGETS="386 arm arm64 ppc64le" + before_install: + - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + script: + - | + set -e + for target in $TARGETS; do + printf "\e[1mRunning test suite under ${target}.\e[0m\n" + GOARCH="$target" go test -v ./... + printf "\n\n" + done + - <<: *multiarch + go: 1.12.x + - <<: *multiarch + go: 1.13.x diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md new file mode 100644 index 00000000..c88f9b2b --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +