diff --git a/.download-tfproviderlint.sh b/.download-tfproviderlint.sh new file mode 100644 index 0000000000..4946c943e0 --- /dev/null +++ b/.download-tfproviderlint.sh @@ -0,0 +1,378 @@ +#!/bin/sh +set -e +# Code generated by godownloader on 2020-05-15T21:01:24Z. DO NOT EDIT. +# + +usage() { + this=$1 + cat </dev/null +} +echoerr() { + echo "$@" 1>&2 +} +log_prefix() { + echo "$0" +} +_logp=6 +log_set_priority() { + _logp="$1" +} +log_priority() { + if test -z "$1"; then + echo "$_logp" + return + fi + [ "$1" -le "$_logp" ] +} +log_tag() { + case $1 in + 0) echo "emerg" ;; + 1) echo "alert" ;; + 2) echo "crit" ;; + 3) echo "err" ;; + 4) echo "warning" ;; + 5) echo "notice" ;; + 6) echo "info" ;; + 7) echo "debug" ;; + *) echo "$1" ;; + esac +} +log_debug() { + log_priority 7 || return 0 + echoerr "$(log_prefix)" "$(log_tag 7)" "$@" +} +log_info() { + log_priority 6 || return 0 + echoerr "$(log_prefix)" "$(log_tag 6)" "$@" +} +log_err() { + log_priority 3 || return 0 + echoerr "$(log_prefix)" "$(log_tag 3)" "$@" +} +log_crit() { + log_priority 2 || return 0 + echoerr "$(log_prefix)" "$(log_tag 2)" "$@" +} +uname_os() { + os=$(uname -s | tr '[:upper:]' '[:lower:]') + case "$os" in + cygwin_nt*) os="windows" ;; + mingw*) os="windows" ;; + msys_nt*) os="windows" ;; + esac + echo "$os" +} +uname_arch() { + arch=$(uname -m) + case $arch in + x86_64) arch="amd64" ;; + x86) arch="386" ;; + i686) arch="386" ;; + i386) arch="386" ;; + aarch64) arch="arm64" ;; + armv5*) arch="armv5" ;; + armv6*) arch="armv6" ;; + armv7*) arch="armv7" ;; + esac + echo ${arch} +} +uname_os_check() { + os=$(uname_os) + case "$os" in + darwin) return 0 ;; + dragonfly) return 0 ;; + freebsd) return 0 ;; + linux) return 0 ;; + android) return 0 ;; + nacl) return 0 ;; + netbsd) return 0 ;; + openbsd) return 0 ;; + plan9) return 0 ;; + solaris) return 0 ;; + windows) return 0 ;; + esac + log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" + return 1 +} +uname_arch_check() { + arch=$(uname_arch) + case "$arch" in + 386) return 0 ;; + amd64) return 0 ;; + arm64) return 0 ;; + armv5) return 0 ;; + armv6) return 0 ;; + armv7) return 0 ;; + ppc64) return 0 ;; + ppc64le) return 0 ;; + mips) return 0 ;; + mipsle) return 0 ;; + mips64) return 0 ;; + mips64le) return 0 ;; + s390x) return 0 ;; + amd64p32) return 0 ;; + esac + log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" + return 1 +} +untar() { + tarball=$1 + case "${tarball}" in + *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; + *.tar) tar --no-same-owner -xf "${tarball}" ;; + *.zip) unzip "${tarball}" ;; + *) + log_err "untar unknown archive format for ${tarball}" + return 1 + ;; + esac +} +http_download_curl() { + local_file=$1 + source_url=$2 + header=$3 + if [ -z "$header" ]; then + code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") + else + code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") + fi + if [ "$code" != "200" ]; then + log_debug "http_download_curl received HTTP status $code" + return 1 + fi + return 0 +} +http_download_wget() { + local_file=$1 + source_url=$2 + header=$3 + if [ -z "$header" ]; then + wget -q -O "$local_file" "$source_url" + else + wget -q --header "$header" -O "$local_file" "$source_url" + fi +} +http_download() { + log_debug "http_download $2" + if is_command curl; then + http_download_curl "$@" + return + elif is_command wget; then + http_download_wget "$@" + return + fi + log_crit "http_download unable to find wget or curl" + return 1 +} +http_copy() { + tmp=$(mktemp) + http_download "${tmp}" "$1" "$2" || return 1 + body=$(cat "$tmp") + rm -f "${tmp}" + echo "$body" +} +github_release() { + owner_repo=$1 + version=$2 + test -z "$version" && version="latest" + giturl="https://github.com/${owner_repo}/releases/${version}" + json=$(http_copy "$giturl" "Accept:application/json") + test -z "$json" && return 1 + version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') + test -z "$version" && return 1 + echo "$version" +} +hash_sha256() { + TARGET=${1:-/dev/stdin} + if is_command gsha256sum; then + hash=$(gsha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command sha256sum; then + hash=$(sha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command shasum; then + hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command openssl; then + hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f a + else + log_crit "hash_sha256 unable to find command to compute sha-256 hash" + return 1 + fi +} +hash_sha256_verify() { + TARGET=$1 + checksums=$2 + if [ -z "$checksums" ]; then + log_err "hash_sha256_verify checksum file not specified in arg2" + return 1 + fi + BASENAME=${TARGET##*/} + want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) + if [ -z "$want" ]; then + log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" + return 1 + fi + got=$(hash_sha256 "$TARGET") + if [ "$want" != "$got" ]; then + log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" + return 1 + fi +} +cat /dev/null < | | table_name | string | The name of the table on which to grant privileges immediately (only valid if on_future is unset). | true | false | false | | +### snowflake_task + +#### properties + +| NAME | TYPE | DESCRIPTION | OPTIONAL | REQUIRED | COMPUTED | DEFAULT | +|----------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------|----------|---------| +| after | string | Specifies the predecessor task in the same database and schema of the current task. When a run of the predecessor task finishes successfully, it triggers this task (after a brief lag). | true | false | false | | +| comment | string | Specifies a comment for the task. | true | false | false | | +| database | string | The database in which to create the task. | false | true | false | | +| enabled | bool | Specifies if the task should be started (enabled) after creation or should remain suspended (default). | true | false | false | false | +| name | string | Specifies the identifier for the task; must be unique for the database and schema in which the task is created. | false | true | false | | +| schedule | string | The schedule for periodically running the task. This can be a cron or interval in minutes. | true | false | false | | +| schema | string | The schema in which to create the task. | false | true | false | | +| session_parameters | map | Specifies session parameters to set for the session when the task runs. A task supports all session parameters. | true | false | false | | +| sql_statement | string | Any single SQL statement, or a call to a stored procedure, executed when the task runs. | false | true | false | | +| user_task_timeout_ms | int | Specifies the time limit on a single run of the task before it times out (in milliseconds). | true | false | false | | +| warehouse | string | The warehouse the task will use. | false | true | false | | +| when | string | Specifies a Boolean SQL expression; multiple conditions joined with AND/OR are supported. | true | false | false | | + ### snowflake_user #### properties @@ -456,3 +483,10 @@ If you are using the Standard Snowflake plan, it's recommended you also set up t * `SKIP_DATABASE_TESTS` - to skip tests with retention time larger than 1 day * `SKIP_WAREHOUSE_TESTS` - to skip tests with multi warehouses +## Releasing + +**Note: Currently only @ryanking and @edulop91 have keys that are whitelisted in the terraform registry, so only they can do releases.** + +Releases are done by [goreleaser](https://goreleaser.com/) and run by our make files. There two goreleaser configs, `.goreleaser.yml` for regular releases and `.goreleaser.prerelease.yml` for doing prereleases (for testing). + +As of recently releases are also [published to the terraform registry](https://registry.terraform.io/providers/chanzuckerberg/snowflake/latest). That publishing requires that releases by signed. We do that signing via goreleaser using individual keybase keys. They key you want to use to sign must be passed in with the `KEYBASE_KEY_ID` environment variable. diff --git a/VERSION b/VERSION index d33c3a2128..3f8dcd03d2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12.0 \ No newline at end of file +0.13.2 \ No newline at end of file diff --git a/download.sh b/download.sh index dc047003ae..8750c29d0c 100644 --- a/download.sh +++ b/download.sh @@ -50,7 +50,7 @@ execute() { srcdir="${tmpdir}" (cd "${tmpdir}" && untar "${TARBALL}") install -d "${BINDIR}" - for binexe in "terraform-provider-snowflake" ; do + for binexe in "terraform-provider-snowflake_${TAG}" ; do if [ "$OS" = "windows" ]; then binexe="${binexe}.exe" fi @@ -341,7 +341,7 @@ PROJECT_NAME="terraform-provider-snowflake" OWNER=chanzuckerberg REPO="terraform-provider-snowflake" BINARY=terraform-provider-snowflake -FORMAT=tar.gz +FORMAT=zip OS=$(uname_os) ARCH=$(uname_arch) PREFIX="$OWNER/$REPO" @@ -373,7 +373,7 @@ log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}" NAME=${PROJECT_NAME}_${VERSION}_${OS}_${ARCH} TARBALL=${NAME}.${FORMAT} TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL} -CHECKSUM=${PROJECT_NAME}_${VERSION}_checksums.txt +CHECKSUM=${PROJECT_NAME}_${VERSION}_SHA256SUMS CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM} diff --git a/go.mod b/go.mod index 63df9db9dd..66217012c2 100644 --- a/go.mod +++ b/go.mod @@ -7,16 +7,16 @@ require ( github.com/DATA-DOG/go-sqlmock v1.4.1 github.com/ExpansiveWorlds/instrumentedsql v0.0.0-20171218214018-45abb4b1947d github.com/Pallinder/go-randomdata v1.2.0 - github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc // indirect - github.com/chanzuckerberg/go-misc v0.0.0-20191016143922-52a18771c2dc - github.com/hashicorp/terraform-plugin-sdk v1.3.0 + github.com/chanzuckerberg/go-misc v0.0.0-20200713202614-1c7b6844ebd6 + github.com/hashicorp/terraform-plugin-sdk v1.12.0 github.com/jmoiron/sqlx v1.2.0 + github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/olekukonko/tablewriter v0.0.2 + github.com/olekukonko/tablewriter v0.0.4 github.com/opentracing/opentracing-go v1.1.0 // indirect - github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 // indirect github.com/pkg/errors v0.9.1 - github.com/snowflakedb/gosnowflake v1.3.2 + github.com/snowflakedb/gosnowflake v1.3.4 github.com/stretchr/testify v1.5.1 - golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0 ) diff --git a/go.sum b/go.sum index 8646f6aa79..f6a17f2828 100644 --- a/go.sum +++ b/go.sum @@ -16,17 +16,18 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM= github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/ExpansiveWorlds/instrumentedsql v0.0.0-20171218214018-45abb4b1947d h1:r+whow+VHd9kAd4UTtQ/rtvcvmwkdryKUcGofpYOp+8= github.com/ExpansiveWorlds/instrumentedsql v0.0.0-20171218214018-45abb4b1947d/go.mod h1:Lm6NFlzU3HvZIo5l8GykZn6MH8/wq/A/X/d+7P/hgZU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg= github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= -github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc h1:MhBvG7RLaLqlyjxMR6of35vt6MVQ+eXMcgn9X/sy0FE= -github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= @@ -34,54 +35,100 @@ github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFU github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-lambda-go v1.12.1/go.mod h1:z4ywteZ5WwbIEzG0tXizIAUlUwkTNNknX4upd5Z5XJM= +github.com/aws/aws-lambda-go v1.17.0/go.mod h1:FEwgPLE6+8wcGBTe5cJN3JWurd1Ztm9zN4jsXsjzKKw= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= -github.com/aws/aws-sdk-go v1.23.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.3 h1:uM16hIw9BotjZKMZlX05SN2EFtaWfi/NonPKIARiBLQ= github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.33.5 h1:p2fr1ryvNTU6avUWLI+/H7FGv0TBIjzVM5WDgXBBv4U= +github.com/aws/aws-sdk-go v1.33.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k= -github.com/chanzuckerberg/go-misc v0.0.0-20191016143922-52a18771c2dc h1:Um5b35r3H4V+1PdI/53ZBDt9ilWUnXWIruMi7TG4aIM= -github.com/chanzuckerberg/go-misc v0.0.0-20191016143922-52a18771c2dc/go.mod h1:yl4FcpRtJ0jkRJa0LeALu7aXbxTjNKp3FL37XjQ2Q/E= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chanzuckerberg/go-misc v0.0.0-20200713202614-1c7b6844ebd6 h1:DQgqrnZGFJI7fZVBBZ6mv+onA92/tOtu6WK9EZhs79Y= +github.com/chanzuckerberg/go-misc v0.0.0-20200713202614-1c7b6844ebd6/go.mod h1:ymZ7sg5TNum55aUrUSvUjxwq1h26OBn81QFthsUVROM= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= +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= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= -github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01/go.mod h1:ypD5nozFk9vcGw1ATYefw6jHe/jZP++Z15/+VTMcWhc= github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52/go.mod h1:yIquW87NGRw1FU5p5lEkpnt/QxoH5uPAOUlOVkAUuMg= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= -github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -89,7 +136,11 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -100,8 +151,12 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -112,9 +167,10 @@ github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUC github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= @@ -124,42 +180,53 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +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 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws= github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8= github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= -github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6 h1:JImQpEeUQ+0DPFMaWzLA0GdUNPaUlCXLpfiqkSZBUfc= -github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6/go.mod h1:Cxv+IJLuBiEhQ7pBYGEuORa0nr4U994pE8mYLuFd7v0= -github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI= -github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-config-inspect v0.0.0-20190821133035-82a99dc22ef4 h1:fTkL0YwjohGyN7AqsDhz6bwcGBpT+xBqi3Qhpw58Juw= -github.com/hashicorp/terraform-config-inspect v0.0.0-20190821133035-82a99dc22ef4/go.mod h1:JDmizlhaP5P0rYTTZB0reDMefAiJyfWPEtugV4in1oI= -github.com/hashicorp/terraform-plugin-sdk v1.3.0 h1:leoaxwa8dUs7v11SX4xe33AZOwcb8dX5S+VXUjWPndM= -github.com/hashicorp/terraform-plugin-sdk v1.3.0/go.mod h1:fY7uWsWuLHi/hsGmBC2Sm7lic5RlFFjcM4zwua5VDuk= +github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8 h1:+RyjwU+Gnd/aTJBPZVDNm903eXVjjqhbaR4Ypx3xYyY= +github.com/hashicorp/terraform-config-inspect v0.0.0-20191115094559-17f92b0546e8/go.mod h1:p+ivJws3dpqbp1iP84+npOyAmTTOLMgCzrXd3GSdn/A= +github.com/hashicorp/terraform-json v0.4.0 h1:KNh29iNxozP5adfUFBJ4/fWd0Cu3taGgjHB38JYqOF4= +github.com/hashicorp/terraform-json v0.4.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU= +github.com/hashicorp/terraform-plugin-sdk v1.12.0 h1:HPp65ShSsKUMPf6jD50UQn/xAjyrGVO4FxI63bvu+pc= +github.com/hashicorp/terraform-plugin-sdk v1.12.0/go.mod h1:HiWIPD/T9HixIhQUwaSoDQxo4BLFdmiBi/Qz5gjB8Q0= +github.com/hashicorp/terraform-plugin-test v1.3.0 h1:hU5LoxrOn9qvOo+LTKN6mSav2J+dAMprbdxJPEQvp4U= +github.com/hashicorp/terraform-plugin-test v1.3.0/go.mod h1:QIJHYz8j+xJtdtLrFTlzQVC0ocr3rf/OjIpgZLK56Hs= github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596 h1:hjyO2JsNZUKT1ym+FAdlBEkGPevazYsmVgIMw7dVELg= github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/honeycombio/libhoney-go v1.12.0/go.mod h1:jdLxh51fcBTy6XIpx1efuJmHePs2xUfVkw25lr+hsmg= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/honeycombio/libhoney-go v1.12.4/go.mod h1:tp2qtK0xMZyG/ZfykkebQESKFS78xpyPr2wEswZ1j6U= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= -github.com/klauspost/compress v1.7.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -168,11 +235,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= -github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= +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.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -182,8 +248,13 @@ github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= @@ -204,22 +275,21 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= +github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -227,17 +297,40 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/snowflakedb/gosnowflake v1.3.2 h1:ZMUN2+ggjvqsCm99uosiedLCsJ8oAuHrS8YdjSBwKcQ= -github.com/snowflakedb/gosnowflake v1.3.2/go.mod h1:NsRq2QeiMUuoNUJhp5Q6xGC4uBrsS9g6LwZVEkTWgsE= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/snowflakedb/gosnowflake v1.3.4 h1:Gyoi6g4lMHsilEwW9+KV+bgYkJTgf5pVfvL7Utus920= +github.com/snowflakedb/gosnowflake v1.3.4/go.mod h1:NsRq2QeiMUuoNUJhp5Q6xGC4uBrsS9g6LwZVEkTWgsE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -245,27 +338,43 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0= github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8= +github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8= github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4= -golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0 h1:eIYIE7EC5/Wv5Kbz8bJPaq+TN3kq3W8S+LSm62vM0DY= +golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 h1:OeRHuibLsmZkFj773W4LcfAGsSxJgfPONhr8cmO+eLA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -280,23 +389,30 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb h1:TR699M2v0qoKTOHxeLgp6zPqaQNs74f01a/ob9W0qko= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -305,7 +421,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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= @@ -317,8 +435,11 @@ golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -326,6 +447,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -338,6 +460,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -348,6 +472,9 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -356,21 +483,43 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a h1:lRlI5zu6AFy3iU/F8YWyNrAmn/tPCnhiTxfwhWb76eU= +google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -379,5 +528,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index be36c7f06f..29f35af15a 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,8 @@ import ( "sort" "strings" + "github.com/chanzuckerberg/go-misc/ver" "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider" - "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/version" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/plugin" "github.com/hashicorp/terraform-plugin-sdk/terraform" @@ -18,7 +18,7 @@ import ( func main() { doc := flag.Bool("doc", false, "spit out docs for resources here") - ver := flag.Bool("version", false, "spit out version for resources here") + version := flag.Bool("version", false, "spit out version for resources here") flag.Parse() if *doc { @@ -26,8 +26,8 @@ func main() { return } - if *ver { - verString, err := version.VersionString() + if *version { + verString, err := ver.VersionStr() if err != nil { log.Fatal(err) } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 5dce515674..f18347d927 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -34,19 +34,26 @@ func Provider() *schema.Provider { Sensitive: true, ConflictsWith: []string{"browser_auth", "private_key_path"}, }, + "oauth_access_token": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("SNOWFLAKE_OAUTH_ACCESS_TOKEN", nil), + Sensitive: true, + ConflictsWith: []string{"browser_auth", "private_key_path", "password"}, + }, "browser_auth": &schema.Schema{ Type: schema.TypeBool, Optional: true, DefaultFunc: schema.EnvDefaultFunc("SNOWFLAKE_USE_BROWSER_AUTH", nil), Sensitive: false, - ConflictsWith: []string{"password", "private_key_path"}, + ConflictsWith: []string{"password", "private_key_path", "oauth_access_token"}, }, "private_key_path": &schema.Schema{ Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("SNOWFLAKE_PRIVATE_KEY_PATH", nil), Sensitive: true, - ConflictsWith: []string{"browser_auth", "password"}, + ConflictsWith: []string{"browser_auth", "password", "oauth_access_token"}, }, "role": &schema.Schema{ Type: schema.TypeString, @@ -79,6 +86,7 @@ func Provider() *schema.Provider { "snowflake_user": resources.User(), "snowflake_view": resources.View(), "snowflake_view_grant": resources.ViewGrant(), + "snowflake_task": resources.Task(), "snowflake_table_grant": resources.TableGrant(), "snowflake_warehouse": resources.Warehouse(), "snowflake_warehouse_grant": resources.WarehouseGrant(), @@ -89,7 +97,16 @@ func Provider() *schema.Provider { } func ConfigureProvider(s *schema.ResourceData) (interface{}, error) { - dsn, err := DSN(s) + account := s.Get("account").(string) + user := s.Get("username").(string) + password := s.Get("password").(string) + browserAuth := s.Get("browser_auth").(bool) + privateKeyPath := s.Get("private_key_path").(string) + oauthAccessToken := s.Get("oauth_access_token").(string) + region := s.Get("region").(string) + role := s.Get("role").(string) + + dsn, err := DSN(account, user, password, browserAuth, privateKeyPath, oauthAccessToken, region, role) if err != nil { return nil, errors.Wrap(err, "could not build dsn for snowflake connection") @@ -103,14 +120,15 @@ func ConfigureProvider(s *schema.ResourceData) (interface{}, error) { return db, nil } -func DSN(s *schema.ResourceData) (string, error) { - account := s.Get("account").(string) - user := s.Get("username").(string) - password := s.Get("password").(string) - browserAuth := s.Get("browser_auth").(bool) - privateKeyPath := s.Get("private_key_path").(string) - region := s.Get("region").(string) - role := s.Get("role").(string) +func DSN( + account, + user, + password string, + browserAuth bool, + privateKeyPath, + oauthAccessToken, + region, + role string) (string, error) { // us-west-2 is their default region, but if you actually specify that it won't trigger their default code // https://github.com/snowflakedb/gosnowflake/blob/52137ce8c32eaf93b0bd22fc5c7297beff339812/dsn.go#L61 @@ -136,8 +154,13 @@ func DSN(s *schema.ResourceData) (string, error) { } else if browserAuth { config.Authenticator = gosnowflake.AuthTypeExternalBrowser - } else { + } else if oauthAccessToken != "" { + config.Authenticator = gosnowflake.AuthTypeOAuth + config.Token = oauthAccessToken + } else if password != "" { config.Password = password + } else { + return "", errors.New("no authentication method provided") } return gosnowflake.DSN(&config) diff --git a/pkg/provider/provider_test.go b/pkg/provider/provider_test.go index c505909163..5cd2210689 100644 --- a/pkg/provider/provider_test.go +++ b/pkg/provider/provider_test.go @@ -21,7 +21,12 @@ func TestProvider(t *testing.T) { func TestDSN(t *testing.T) { type args struct { - s *schema.ResourceData + account, + user, + password string + browserAuth bool + region, + role string } tests := []struct { name string @@ -29,14 +34,14 @@ func TestDSN(t *testing.T) { want string wantErr bool }{ - {"simple", args{resourceData(t, "acct", "user", "pass", "region", "role")}, + {"simple", args{"acct", "user", "pass", false, "region", "role"}, "user:pass@acct.region.snowflakecomputing.com:443?ocspFailOpen=true®ion=region&role=role&validateDefaultParameters=true", false}, - {"us-west-2 special case", args{resourceData(t, "acct2", "user2", "pass2", "us-west-2", "role2")}, + {"us-west-2 special case", args{"acct2", "user2", "pass2", false, "us-west-2", "role2"}, "user2:pass2@acct2.snowflakecomputing.com:443?ocspFailOpen=true&role=role2&validateDefaultParameters=true", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := provider.DSN(tt.args.s) + got, err := provider.DSN(tt.args.account, tt.args.user, tt.args.password, tt.args.browserAuth, "", "", tt.args.region, tt.args.role) if (err != nil) != tt.wantErr { t.Errorf("DSN() error = %v, wantErr %v", err, tt.wantErr) return @@ -48,13 +53,13 @@ func TestDSN(t *testing.T) { } } -func resourceData(t *testing.T, account, username, password, region, role string) *schema.ResourceData { +func resourceData(t *testing.T, account, username, token, region, role string) *schema.ResourceData { r := require.New(t) in := map[string]interface{}{ "account": account, "username": username, - "password": password, + "password": token, "region": region, "role": role, } @@ -63,3 +68,40 @@ func resourceData(t *testing.T, account, username, password, region, role string r.NotNil(d) return d } + +func TestOAuthDSN(t *testing.T) { + type args struct { + account, + user, + oauthAccessToken, + region, + role string + } + pseudorandom_access_token := "ETMsjLOLvQ-C/bzGmmdvbEM/RSQFFX-a+sefbQeQoJqwdFNXZ+ftBIdwlasApA+/MItZLNRRW-rYJiEZMvAAdzpGLxaghIoww+vDOuIeAFBDUxTAY-I+qGbQOXipkNcmzwuAaugjYtlTjPXGjqKw-OSsVacQXzsQyAMnbMyUrbdhRQEETIqTAdMuDqJBeaSj+LMsKDXzLd-guSlm-mmv+=" + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"simple_oauth", args{"acct", "user", pseudorandom_access_token, "region", "role"}, + "user:@acct.region.snowflakecomputing.com:443?authenticator=oauth&ocspFailOpen=true®ion=region&role=role&token=ETMsjLOLvQ-C%2FbzGmmdvbEM%2FRSQFFX-a%2BsefbQeQoJqwdFNXZ%2BftBIdwlasApA%2B%2FMItZLNRRW-rYJiEZMvAAdzpGLxaghIoww%2BvDOuIeAFBDUxTAY-I%2BqGbQOXipkNcmzwuAaugjYtlTjPXGjqKw-OSsVacQXzsQyAMnbMyUrbdhRQEETIqTAdMuDqJBeaSj%2BLMsKDXzLd-guSlm-mmv%2B%3D&validateDefaultParameters=true", false}, + {"oauth_over_password", args{"acct", "user", pseudorandom_access_token, "region", "role"}, + "user:@acct.region.snowflakecomputing.com:443?authenticator=oauth&ocspFailOpen=true®ion=region&role=role&token=ETMsjLOLvQ-C%2FbzGmmdvbEM%2FRSQFFX-a%2BsefbQeQoJqwdFNXZ%2BftBIdwlasApA%2B%2FMItZLNRRW-rYJiEZMvAAdzpGLxaghIoww%2BvDOuIeAFBDUxTAY-I%2BqGbQOXipkNcmzwuAaugjYtlTjPXGjqKw-OSsVacQXzsQyAMnbMyUrbdhRQEETIqTAdMuDqJBeaSj%2BLMsKDXzLd-guSlm-mmv%2B%3D&validateDefaultParameters=true", false}, + {"empty_token_no_password_errors_out", args{"acct", "user", "", "region", "role"}, + "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := provider.DSN(tt.args.account, tt.args.user, "", false, "", tt.args.oauthAccessToken, tt.args.region, tt.args.role) + + if (err != nil) != tt.wantErr { + t.Errorf("DSN() error = %v, dsn = %v, wantErr %v", err, got, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("DSN() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/resources/account_grant.go b/pkg/resources/account_grant.go index 40262254e7..09b83a1b8b 100644 --- a/pkg/resources/account_grant.go +++ b/pkg/resources/account_grant.go @@ -15,6 +15,8 @@ var validAccountPrivileges = newPrivilegeSet( privilegeCreateIntegration, privilegeManageGrants, privilegeMonitorUsage, + privilegeMonitorExecution, + privilegeExecuteTask, ) var accountGrantSchema = map[string]*schema.Schema{ diff --git a/pkg/resources/account_grant_test.go b/pkg/resources/account_grant_test.go index c166cd743d..86151dea45 100644 --- a/pkg/resources/account_grant_test.go +++ b/pkg/resources/account_grant_test.go @@ -57,6 +57,40 @@ func TestAccountGrantRead(t *testing.T) { }) } +func TestMonitorExecution(t *testing.T) { + r := require.New(t) + + d := accountGrant(t, "ACCOUNT|||MONITOR EXECUTION", map[string]interface{}{ + "privilege": "MONITOR EXECUTION", + "roles": []interface{}{"test-role-1", "test-role-2"}, + }) + + r.NotNil(d) + + WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { + expectReadAccountGrant(mock) + err := resources.ReadAccountGrant(d, db) + r.NoError(err) + }) +} + +func TestExecuteTask(t *testing.T) { + r := require.New(t) + + d := accountGrant(t, "ACCOUNT|||EXECUTE TASK", map[string]interface{}{ + "privilege": "EXECUTE TASK", + "roles": []interface{}{"test-role-1", "test-role-2"}, + }) + + r.NotNil(d) + + WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { + expectReadAccountGrant(mock) + err := resources.ReadAccountGrant(d, db) + r.NoError(err) + }) +} + func expectReadAccountGrant(mock sqlmock.Sqlmock) { rows := sqlmock.NewRows([]string{ "created_on", "privilege", "granted_on", "name", "granted_to", "grantee_name", "grant_option", "granted_by", diff --git a/pkg/resources/database.go b/pkg/resources/database.go index 36207a4ddb..e8d99b3f61 100644 --- a/pkg/resources/database.go +++ b/pkg/resources/database.go @@ -8,7 +8,6 @@ import ( "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) @@ -87,7 +86,7 @@ func createDatabaseFromShare(data *schema.ResourceData, meta interface{}) error name := data.Get("name").(string) builder := snowflake.DatabaseFromShare(name, prov.(string), share.(string)) - err := DBExec(db, builder.Create()) + err := snowflake.Exec(db, builder.Create()) if err != nil { return errors.Wrapf(err, "error creating database %v from share %v.%v", name, prov, share) } @@ -104,7 +103,7 @@ func createDatabaseFromDatabase(data *schema.ResourceData, meta interface{}) err name := data.Get("name").(string) builder := snowflake.DatabaseFromDatabase(name, sourceDb) - err := DBExec(db, builder.Create()) + err := snowflake.Exec(db, builder.Create()) if err != nil { return errors.Wrapf(err, "error creating a clone database %v from database %v", name, sourceDb) } @@ -114,31 +113,14 @@ func createDatabaseFromDatabase(data *schema.ResourceData, meta interface{}) err return ReadDatabase(data, meta) } -type database struct { - CreatedOn sql.NullString `db:"created_on"` - DBName sql.NullString `db:"name"` - IsDefault sql.NullString `db:"is_default"` - IsCurrent sql.NullString `db:"is_current"` - Origin sql.NullString `db:"origin"` - Owner sql.NullString `db:"owner"` - Comment sql.NullString `db:"comment"` - Options sql.NullString `db:"options"` - RetentionTime sql.NullString `db:"retention_time"` -} - func ReadDatabase(data *schema.ResourceData, meta interface{}) error { db := meta.(*sql.DB) - sdb := sqlx.NewDb(db, "snowflake") - name := data.Id() stmt := snowflake.Database(name).Show() + row := snowflake.QueryRow(db, stmt) - log.Printf("[DEBUG] stmt %s", stmt) - row := sdb.QueryRowx(stmt) - - database := &database{} - err := row.StructScan(database) + database, err := snowflake.ScanDatabase(row) if err != nil { if err == sql.ErrNoRows { diff --git a/pkg/resources/grant_helpers.go b/pkg/resources/grant_helpers.go index 943080d05f..7624980734 100644 --- a/pkg/resources/grant_helpers.go +++ b/pkg/resources/grant_helpers.go @@ -171,14 +171,14 @@ func createGenericGrant(data *schema.ResourceData, meta interface{}, builder sno } for _, role := range roles { - err := DBExec(db, builder.Role(role).Grant(priv)) + err := snowflake.Exec(db, builder.Role(role).Grant(priv)) if err != nil { return err } } for _, share := range shares { - err := DBExec(db, builder.Share(share).Grant(priv)) + err := snowflake.Exec(db, builder.Share(share).Grant(priv)) if err != nil { return err } @@ -268,10 +268,8 @@ func readGenericGrant(data *schema.ResourceData, meta interface{}, builder snowf } func readGenericCurrentGrants(db *sql.DB, builder snowflake.GrantBuilder) ([]*grant, error) { - conn := sqlx.NewDb(db, "snowflake") - stmt := builder.Show() - rows, err := conn.Queryx(stmt) + rows, err := snowflake.Query(db, stmt) if err != nil { return nil, err } @@ -353,14 +351,14 @@ func deleteGenericGrant(data *schema.ResourceData, meta interface{}, builder sno } for _, role := range roles { - err := DBExec(db, builder.Role(role).Revoke(priv)) + err := snowflake.Exec(db, builder.Role(role).Revoke(priv)) if err != nil { return err } } for _, share := range shares { - err := DBExec(db, builder.Share(share).Revoke(priv)) + err := snowflake.Exec(db, builder.Share(share).Revoke(priv)) if err != nil { return err } diff --git a/pkg/resources/managed_account.go b/pkg/resources/managed_account.go index c9c62a46a0..95e1fe12cd 100644 --- a/pkg/resources/managed_account.go +++ b/pkg/resources/managed_account.go @@ -127,45 +127,42 @@ func ReadManagedAccount(data *schema.ResourceData, meta interface{}) error { id := data.Id() stmt := snowflake.ManagedAccount(id).Show() - row := db.QueryRow(stmt) - var name, cloud, region, locator, createdOn, url, comment sql.NullString - var isReader bool - err := row.Scan(&name, &cloud, ®ion, &locator, &createdOn, &url, &isReader, &comment) + row := snowflake.QueryRow(db, stmt) + a, err := snowflake.ScanManagedAccount(row) if err != nil { return err } - // TODO turn this into a loop after we switch to scaning in a struct - err = data.Set("name", name.String) + err = data.Set("name", a.Name.String) if err != nil { return err } - err = data.Set("cloud", cloud.String) + err = data.Set("cloud", a.Cloud.String) if err != nil { return err } - err = data.Set("region", region.String) + err = data.Set("region", a.Region.String) if err != nil { return err } - err = data.Set("locator", locator.String) + err = data.Set("locator", a.Locator.String) if err != nil { return err } - err = data.Set("created_on", createdOn.String) + err = data.Set("created_on", a.CreatedOn.String) if err != nil { return err } - err = data.Set("url", url.String) + err = data.Set("url", a.Url.String) if err != nil { return err } - if isReader { + if a.IsReader { err = data.Set("type", "READER") if err != nil { return err @@ -174,7 +171,7 @@ func ReadManagedAccount(data *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Unable to determine the account type") } - err = data.Set("comment", comment.String) + err = data.Set("comment", a.Comment.String) return err } diff --git a/pkg/resources/pipe.go b/pkg/resources/pipe.go index 6e829f4aad..acf519d9e2 100644 --- a/pkg/resources/pipe.go +++ b/pkg/resources/pipe.go @@ -156,7 +156,7 @@ func CreatePipe(data *schema.ResourceData, meta interface{}) error { q := builder.Create() - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error creating pipe %v", name) } @@ -185,50 +185,51 @@ func ReadPipe(data *schema.ResourceData, meta interface{}) error { dbName := pipeID.DatabaseName schema := pipeID.SchemaName - pipe := pipeID.PipeName + name := pipeID.PipeName - sq := snowflake.Pipe(pipe, dbName, schema).Show() - pipeShow, err := showPipe(db, sq) + sq := snowflake.Pipe(name, dbName, schema).Show() + row := snowflake.QueryRow(db, sq) + pipe, err := snowflake.ScanPipe(row) if err != nil { return err } - err = data.Set("name", pipe) + err = data.Set("name", pipe.Name) if err != nil { return err } - err = data.Set("database", dbName) + err = data.Set("database", pipe.DatabaseName) if err != nil { return err } - err = data.Set("schema", schema) + err = data.Set("schema", pipe.SchemaName) if err != nil { return err } - err = data.Set("copy_statement", pipeShow.definition) + err = data.Set("copy_statement", pipe.Definition) if err != nil { return err } - err = data.Set("owner", pipeShow.owner) + err = data.Set("owner", pipe.Owner) if err != nil { return err } - err = data.Set("comment", pipeShow.comment) + err = data.Set("comment", pipe.Comment) if err != nil { return err } - err = data.Set("notification_channel", pipeShow.notificationChannel) + err = data.Set("notification_channel", pipe.NotificationChannel) if err != nil { return err } - err = data.Set("auto_ingest", pipeShow.notificationChannel != "") + err = data.Set("auto_ingest", pipe.NotificationChannel != "") if err != nil { return err } @@ -256,7 +257,7 @@ func UpdatePipe(data *schema.ResourceData, meta interface{}) error { if data.HasChange("comment") { _, comment := data.GetChange("comment") q := builder.ChangeComment(comment.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating pipe comment on %v", data.Id()) } @@ -281,7 +282,7 @@ func DeletePipe(data *schema.ResourceData, meta interface{}) error { q := snowflake.Pipe(pipe, dbName, schema).Drop() - err = DBExec(db, q) + err = snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error deleting pipe %v", data.Id()) } @@ -316,33 +317,3 @@ func PipeExists(data *schema.ResourceData, meta interface{}) (bool, error) { return false, nil } - -type showPipeResult struct { - createdOn string - name string - databaseName string - schemaName string - definition string - owner string - notificationChannel string - comment string -} - -func showPipe(db *sql.DB, query string) (showPipeResult, error) { - var r showPipeResult - row := db.QueryRow(query) - err := row.Scan( - &r.createdOn, - &r.name, - &r.databaseName, - &r.schemaName, - &r.definition, - &r.owner, - &r.notificationChannel, - &r.comment, - ) - if err != nil { - return r, err - } - return r, nil -} diff --git a/pkg/resources/privileges.go b/pkg/resources/privileges.go index ae3e271f4b..0f77df19d0 100644 --- a/pkg/resources/privileges.go +++ b/pkg/resources/privileges.go @@ -45,6 +45,8 @@ const ( privilegeCreateIntegration privilege = "CREATE INTEGRATION" privilegeManageGrants privilege = "MANAGE GRANTS" privilegeMonitorUsage privilege = "MONITOR USAGE" + privilegeMonitorExecution privilege = "MONITOR EXECUTION" + privilegeExecuteTask privilege = "EXECUTE TASK" ) type privilegeSet map[privilege]struct{} diff --git a/pkg/resources/resource.go b/pkg/resources/resource.go index dc12799efe..70fdb63c79 100644 --- a/pkg/resources/resource.go +++ b/pkg/resources/resource.go @@ -2,8 +2,6 @@ package resources import ( "database/sql" - "fmt" - "log" "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -39,7 +37,7 @@ func CreateResource( } } } - err := DBExec(db, qb.Statement()) + err := snowflake.Exec(db, qb.Statement()) if err != nil { return errors.Wrapf(err, "error creating %s", t) @@ -71,7 +69,7 @@ func UpdateResource( stmt := builder(oldName).Rename(newName) - err := DBExec(db, stmt) + err := snowflake.Exec(db, stmt) if err != nil { return errors.Wrapf(err, "error renaming %s %s to %s", t, oldName, newName) } @@ -107,7 +105,7 @@ func UpdateResource( } } - err := DBExec(db, qb.Statement()) + err := snowflake.Exec(db, qb.Statement()) if err != nil { return errors.Wrapf(err, "error altering %s", t) } @@ -123,7 +121,7 @@ func DeleteResource(t string, builder func(string) *snowflake.Builder) func(*sch stmt := builder(name).Drop() - err := DBExec(db, stmt) + err := snowflake.Exec(db, stmt) if err != nil { return errors.Wrapf(err, "error dropping %s %s", t, name) } @@ -132,11 +130,3 @@ func DeleteResource(t string, builder func(string) *snowflake.Builder) func(*sch return nil } } - -func DBExec(db *sql.DB, query string, args ...interface{}) error { - stmt := fmt.Sprintf(query, args...) - log.Printf("[DEBUG] stmt %s", stmt) - - _, err := db.Exec(stmt) - return err -} diff --git a/pkg/resources/resource_monitor.go b/pkg/resources/resource_monitor.go index 9158d75122..99fc77aa17 100644 --- a/pkg/resources/resource_monitor.go +++ b/pkg/resources/resource_monitor.go @@ -2,35 +2,16 @@ package resources import ( "database/sql" - "log" "strconv" "strings" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" ) -type resourceMonitor struct { - Name sql.NullString `db:"name"` - CreditQuota sql.NullFloat64 `db:"credit_quota"` - UsedCredits sql.NullString `db:"used_credits"` - RemainingCredits sql.NullString `db:"remaining_credits"` - Level sql.NullString `db:"level"` - Frequency sql.NullString `db:"frequency"` - StartTime sql.NullString `db:"start_time"` - EndTime sql.NullString `db:"end_time"` - NotifyAt sql.NullString `db:"notify_at"` - SuspendAt sql.NullString `db:"suspend_at"` - SuspendImmediatelyAt sql.NullString `db:"suspend_immediately_at"` - CreatedOn sql.NullString `db:"created_on"` - Owner sql.NullString `db:"owner"` - Comment sql.NullString `db:"comment"` -} - var validFrequencies = []string{"MONTHLY", "DAILY", "WEEKLY", "YEARLY", "NEVER"} var resourceMonitorSchema = map[string]*schema.Schema{ @@ -142,7 +123,7 @@ func CreateResourceMonitor(data *schema.ResourceData, meta interface{}) error { stmt := cb.Statement() - err := DBExec(db, stmt) + err := snowflake.Exec(db, stmt) if err != nil { return errors.Wrapf(err, "error creating resource monitor %v", name) } @@ -155,16 +136,11 @@ func CreateResourceMonitor(data *schema.ResourceData, meta interface{}) error { // ReadResourceMonitor implements schema.ReadFunc func ReadResourceMonitor(data *schema.ResourceData, meta interface{}) error { db := meta.(*sql.DB) - sdb := sqlx.NewDb(db, "snowflake") - stmt := snowflake.ResourceMonitor(data.Id()).Show() - log.Printf("[DEBUG] stmt %v\n", stmt) - row := sdb.QueryRowx(stmt) - - rm := &resourceMonitor{} + row := snowflake.QueryRow(db, stmt) - err := row.StructScan(rm) + rm, err := snowflake.ScanResourceMonitor(row) if err != nil { return err } @@ -258,7 +234,7 @@ func DeleteResourceMonitor(data *schema.ResourceData, meta interface{}) error { stmt := snowflake.ResourceMonitor(data.Id()).Drop() - err := DBExec(db, stmt) + err := snowflake.Exec(db, stmt) if err != nil { return errors.Wrapf(err, "error deleting resource monitor %v", data.Id()) } diff --git a/pkg/resources/role.go b/pkg/resources/role.go index 2f88cb886c..668776b3bd 100644 --- a/pkg/resources/role.go +++ b/pkg/resources/role.go @@ -43,18 +43,17 @@ func ReadRole(data *schema.ResourceData, meta interface{}) error { db := meta.(*sql.DB) id := data.Id() - row := db.QueryRow(fmt.Sprintf("SHOW ROLES LIKE '%s'", id)) - var createdOn, name, isDefault, isCurrent, isInherited, assignedToUsers, grantedToRoles, grantedRoles, owner, comment sql.NullString - err := row.Scan(&createdOn, &name, &isDefault, &isCurrent, &isInherited, &assignedToUsers, &grantedToRoles, &grantedRoles, &owner, &comment) + row := snowflake.QueryRow(db, fmt.Sprintf("SHOW ROLES LIKE '%s'", id)) + role, err := snowflake.ScanRole(row) if err != nil { return err } - err = data.Set("name", name.String) + err = data.Set("name", role.Name.String) if err != nil { return err } - err = data.Set("comment", comment.String) + err = data.Set("comment", role.Comment.String) if err != nil { return err } diff --git a/pkg/resources/role_grants.go b/pkg/resources/role_grants.go index 6d450ff863..666e6056a0 100644 --- a/pkg/resources/role_grants.go +++ b/pkg/resources/role_grants.go @@ -76,13 +76,13 @@ func CreateRoleGrants(data *schema.ResourceData, meta interface{}) error { func grantRoleToRole(db *sql.DB, role1, role2 string) error { g := snowflake.RoleGrant(role1) - err := DBExec(db, g.Role(role2).Grant()) + err := snowflake.Exec(db, g.Role(role2).Grant()) return err } func grantRoleToUser(db *sql.DB, role1, user string) error { g := snowflake.RoleGrant(role1) - err := DBExec(db, g.User(user).Grant()) + err := snowflake.Exec(db, g.User(user).Grant()) return err } @@ -193,13 +193,13 @@ func DeleteRoleGrants(data *schema.ResourceData, meta interface{}) error { func revokeRoleFromRole(db *sql.DB, role1, role2 string) error { rg := snowflake.RoleGrant(role1).Role(role2) - err := DBExec(db, rg.Revoke()) + err := snowflake.Exec(db, rg.Revoke()) return err } func revokeRoleFromUser(db *sql.DB, role1, user string) error { rg := snowflake.RoleGrant(role1).User(user) - err := DBExec(db, rg.Revoke()) + err := snowflake.Exec(db, rg.Revoke()) return err } diff --git a/pkg/resources/schema.go b/pkg/resources/schema.go index a4496e426c..f5347cfb99 100644 --- a/pkg/resources/schema.go +++ b/pkg/resources/schema.go @@ -145,7 +145,7 @@ func CreateSchema(data *schema.ResourceData, meta interface{}) error { q := builder.Create() - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error creating schema %v", name) } @@ -175,31 +175,29 @@ func ReadSchema(data *schema.ResourceData, meta interface{}) error { schema := schemaID.SchemaName q := snowflake.Schema(schema).WithDB(dbName).Show() - row := db.QueryRow(q) - var createdOn, name, isDefault, isCurrent, databaseName, owner, comment, options sql.NullString - var retentionTime sql.NullInt64 - err = row.Scan(&createdOn, &name, &isDefault, &isCurrent, &databaseName, &owner, &comment, &options, &retentionTime) + row := snowflake.QueryRow(db, q) + + s, err := snowflake.ScanSchema(row) if err != nil { return err } - // TODO turn this into a loop after we switch to scaning in a struct - err = data.Set("name", name.String) + err = data.Set("name", s.Name.String) if err != nil { return err } - err = data.Set("database", databaseName.String) + err = data.Set("database", s.DatabaseName.String) if err != nil { return err } - err = data.Set("comment", comment.String) + err = data.Set("comment", s.Comment.String) if err != nil { return err } - err = data.Set("data_retention_days", retentionTime.Int64) + err = data.Set("data_retention_days", s.RetentionTime.Int64) if err != nil { return err } @@ -215,7 +213,7 @@ func ReadSchema(data *schema.ResourceData, meta interface{}) error { return err } - if opts := options.String; opts != "" { + if opts := s.Options.String; opts != "" { for _, opt := range strings.Split(opts, ", ") { switch opt { case "TRANSIENT": @@ -254,7 +252,7 @@ func UpdateSchema(data *schema.ResourceData, meta interface{}) error { if data.HasChange("comment") { _, comment := data.GetChange("comment") q := builder.ChangeComment(comment.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating schema comment on %v", data.Id()) } @@ -271,7 +269,7 @@ func UpdateSchema(data *schema.ResourceData, meta interface{}) error { q = builder.Unmanage() } - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error changing management state on %v", data.Id()) } @@ -284,7 +282,7 @@ func UpdateSchema(data *schema.ResourceData, meta interface{}) error { _, days := data.GetChange("data_retention_days") q := builder.ChangeDataRetentionDays(days.(int)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating data retention days on %v", data.Id()) } @@ -306,7 +304,7 @@ func DeleteSchema(data *schema.ResourceData, meta interface{}) error { q := snowflake.Schema(schema).WithDB(dbName).Drop() - err = DBExec(db, q) + err = snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error deleting schema %v", data.Id()) } diff --git a/pkg/resources/share.go b/pkg/resources/share.go index 6db125c3f6..614cdcd9d5 100644 --- a/pkg/resources/share.go +++ b/pkg/resources/share.go @@ -61,7 +61,7 @@ func CreateShare(data *schema.ResourceData, meta interface{}) error { builder := snowflake.Share(name).Create() builder.SetString("COMMENT", data.Get("comment").(string)) - err := DBExec(db, builder.Statement()) + err := snowflake.Exec(db, builder.Statement()) if err != nil { return errors.Wrapf(err, "error creating share") } @@ -92,30 +92,30 @@ func setAccounts(data *schema.ResourceData, meta interface{}) error { // 1. Create new temporary DB tempName := fmt.Sprintf("TEMP_%v_%d", name, time.Now().Unix()) tempDB := snowflake.Database(tempName) - err := DBExec(db, tempDB.Create().Statement()) + err := snowflake.Exec(db, tempDB.Create().Statement()) if err != nil { return errors.Wrapf(err, "error creating temporary DB %v", tempName) } // 2. Create temporary DB grant to the share tempDBGrant := snowflake.DatabaseGrant(tempName) - err = DBExec(db, tempDBGrant.Share(name).Grant("USAGE")) + err = snowflake.Exec(db, tempDBGrant.Share(name).Grant("USAGE")) if err != nil { return errors.Wrapf(err, "error creating temporary DB grant %v", tempName) } // 3. Add the accounts to the share q := fmt.Sprintf(`ALTER SHARE "%v" SET ACCOUNTS=%v`, name, strings.Join(accs, ",")) - err = DBExec(db, q) + err = snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error adding accounts to share %v", name) } // 4. Revoke temporary DB grant to the share - err = DBExec(db, tempDBGrant.Share(name).Revoke("USAGE")) + err = snowflake.Exec(db, tempDBGrant.Share(name).Revoke("USAGE")) if err != nil { return errors.Wrapf(err, "error revoking temporary DB grant %v", tempName) } // 5. Remove the temporary DB - err = DBExec(db, tempDB.Drop()) + err = snowflake.Exec(db, tempDB.Drop()) if err != nil { return errors.Wrapf(err, "error dropping temporary DB %v", tempName) } @@ -130,26 +130,23 @@ func ReadShare(data *schema.ResourceData, meta interface{}) error { id := data.Id() stmt := snowflake.Share(id).Show() - row := db.QueryRow(stmt) + row := snowflake.QueryRow(db, stmt) - var createdOn, kind, name, databaseName, to, owner, comment sql.NullString - err := row.Scan(&createdOn, &kind, &name, &databaseName, &to, &owner, &comment) + s, err := snowflake.ScanShare(row) if err != nil { return err } - // TODO turn this into a loop after we switch to scanning in a struct - err = data.Set("name", StripAccountFromName(name.String)) + err = data.Set("name", StripAccountFromName(s.Name.String)) if err != nil { return err } - err = data.Set("comment", comment.String) + err = data.Set("comment", s.Comment.String) if err != nil { return err } - // accs := strings.Split(to.String, ", ") - accs := strings.FieldsFunc(to.String, func(c rune) bool { return c == ',' }) + accs := strings.FieldsFunc(s.To.String, func(c rune) bool { return c == ',' }) err = data.Set("accounts", accs) return err diff --git a/pkg/resources/stage.go b/pkg/resources/stage.go index 9a53382537..26d1136583 100644 --- a/pkg/resources/stage.go +++ b/pkg/resources/stage.go @@ -44,6 +44,7 @@ var stageSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, Description: "Specifies the credentials for the stage.", + Sensitive: true, }, "storage_integration": &schema.Schema{ Type: schema.TypeString, @@ -184,7 +185,7 @@ func CreateStage(data *schema.ResourceData, meta interface{}) error { q := builder.Create() - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error creating stage %v", name) } @@ -217,63 +218,65 @@ func ReadStage(data *schema.ResourceData, meta interface{}) error { stage := stageID.StageName q := snowflake.Stage(stage, dbName, schema).Describe() - stageDesc, err := descStage(db, q) + stageDesc, err := snowflake.DescStage(db, q) if err != nil { return err } sq := snowflake.Stage(stage, dbName, schema).Show() - stageShow, err := showStage(db, sq) + row := snowflake.QueryRow(db, sq) + + s, err := snowflake.ScanStageShow(row) if err != nil { return err } - err = data.Set("name", stage) + err = data.Set("name", s.Name) if err != nil { return err } - err = data.Set("database", dbName) + err = data.Set("database", s.DatabaseName) if err != nil { return err } - err = data.Set("schema", schema) + err = data.Set("schema", s.SchemaName) if err != nil { return err } - err = data.Set("url", stageDesc.url) + err = data.Set("url", stageDesc.Url) if err != nil { return err } - err = data.Set("file_format", stageDesc.fileFormat) + err = data.Set("file_format", stageDesc.FileFormat) if err != nil { return err } - err = data.Set("copy_options", stageDesc.copyOptions) + err = data.Set("copy_options", stageDesc.CopyOptions) if err != nil { return err } - err = data.Set("storage_integration", stageShow.storageIntegration) + err = data.Set("storage_integration", s.StorageIntegration) if err != nil { return err } - err = data.Set("comment", stageShow.comment) + err = data.Set("comment", s.Comment) if err != nil { return err } - err = data.Set("aws_external_id", stageDesc.awsExternalID) + err = data.Set("aws_external_id", stageDesc.AwsExternalID) if err != nil { return err } - err = data.Set("snowflake_iam_user", stageDesc.snowflakeIamUser) + err = data.Set("snowflake_iam_user", stageDesc.SnowflakeIamUser) if err != nil { return err } @@ -301,7 +304,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("url") { _, url := data.GetChange("url") q := builder.ChangeURL(url.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage url on %v", data.Id()) } @@ -312,7 +315,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("credentials") { _, credentials := data.GetChange("credentials") q := builder.ChangeCredentials(credentials.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage credentials on %v", data.Id()) } @@ -323,7 +326,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("storage_integration") { _, si := data.GetChange("storage_integration") q := builder.ChangeStorageIntegration(si.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage storage integration on %v", data.Id()) } @@ -334,7 +337,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("encryption") { _, encryption := data.GetChange("encryption") q := builder.ChangeEncryption(encryption.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage encryption on %v", data.Id()) } @@ -344,7 +347,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("file_format") { _, fileFormat := data.GetChange("file_format") q := builder.ChangeFileFormat(fileFormat.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage file formaat on %v", data.Id()) } @@ -354,7 +357,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("copy_options") { _, copyOptions := data.GetChange("copy_options") q := builder.ChangeCopyOptions(copyOptions.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage copy options on %v", data.Id()) } @@ -364,7 +367,7 @@ func UpdateStage(data *schema.ResourceData, meta interface{}) error { if data.HasChange("comment") { _, comment := data.GetChange("comment") q := builder.ChangeComment(comment.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating stage comment on %v", data.Id()) } @@ -389,7 +392,7 @@ func DeleteStage(data *schema.ResourceData, meta interface{}) error { q := snowflake.Stage(stage, dbName, schema).Drop() - err = DBExec(db, q) + err = snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error deleting stage %v", data.Id()) } @@ -424,99 +427,3 @@ func StageExists(data *schema.ResourceData, meta interface{}) (bool, error) { return false, nil } - -type showStageResult struct { - createdOn *string - name *string - databaseName *string - schemaName *string - url *string - hasCredentials *string - hasEncryptionKey *string - owner *string - comment *string - region *string - stageType *string - cloud *string - notificationChannel *string - storageIntegration *string -} - -func showStage(db *sql.DB, query string) (showStageResult, error) { - var r showStageResult - row := db.QueryRow(query) - err := row.Scan( - &r.createdOn, - &r.name, - &r.databaseName, - &r.schemaName, - &r.url, - &r.hasCredentials, - &r.hasEncryptionKey, - &r.owner, - &r.comment, - &r.region, - &r.stageType, - &r.cloud, - &r.notificationChannel, - &r.storageIntegration, - ) - if err != nil { - return r, err - } - - return r, nil -} - -type descStageResult struct { - url string - awsExternalID string - snowflakeIamUser string - fileFormat string - copyOptions string -} - -func descStage(db *sql.DB, query string) (descStageResult, error) { - var r descStageResult - var ff []string - var co []string - rows, err := db.Query(query) - if err != nil { - return r, err - } - defer rows.Close() - for rows.Next() { - var parentProperty string - var property string - var propertyType string - var propertyValue string - var propertyDefault string - if err := rows.Scan(&parentProperty, &property, &propertyType, &propertyValue, &propertyDefault); err != nil { - return r, err - } - - switch property { - case "URL": - r.url = strings.Trim(propertyValue, "[\"]") - case "AWS_EXTERNAL_ID": - r.awsExternalID = propertyValue - case "SNOWFLAKE_IAM_USER": - r.snowflakeIamUser = propertyValue - } - - switch parentProperty { - case "STAGE_FILE_FORMAT": - if propertyValue != propertyDefault { - ff = append(ff, fmt.Sprintf("%s = %s", property, propertyValue)) - } - case "STAGE_COPY_OPTIONS": - if propertyValue != propertyDefault { - co = append(co, fmt.Sprintf("%s = %s", property, propertyValue)) - } - } - } - - r.fileFormat = strings.Join(ff, " ") - r.copyOptions = strings.Join(co, " ") - return r, nil -} diff --git a/pkg/resources/storage_integration.go b/pkg/resources/storage_integration.go index 809b6b4b72..8edaab19eb 100644 --- a/pkg/resources/storage_integration.go +++ b/pkg/resources/storage_integration.go @@ -123,7 +123,7 @@ func CreateStorageIntegration(data *schema.ResourceData, meta interface{}) error return err } - err = DBExec(db, stmt.Statement()) + err = snowflake.Exec(db, stmt.Statement()) if err != nil { return fmt.Errorf("error creating storage integration: %w", err) } @@ -139,33 +139,33 @@ func ReadStorageIntegration(data *schema.ResourceData, meta interface{}) error { id := data.Id() stmt := snowflake.StorageIntegration(data.Id()).Show() - row := db.QueryRow(stmt) + row := snowflake.QueryRow(db, stmt) // Some properties can come from the SHOW INTEGRATION call - var name, integrationType, category, createdOn sql.NullString - var enabled sql.NullBool - if err := row.Scan(&name, &integrationType, &category, &enabled, &createdOn); err != nil { + + s, err := snowflake.ScanStorageIntegration(row) + if err != nil { return fmt.Errorf("Could not show storage integration: %w", err) } // Note: category must be STORAGE or something is broken - if c := category.String; c != "STORAGE" { + if c := s.Category.String; c != "STORAGE" { return fmt.Errorf("Expected %v to be a STORAGE integration, got %v", id, c) } - if err := data.Set("name", name.String); err != nil { + if err := data.Set("name", s.Name.String); err != nil { return err } - if err := data.Set("type", integrationType.String); err != nil { + if err := data.Set("type", s.IntegrationType.String); err != nil { return err } - if err := data.Set("created_on", createdOn.String); err != nil { + if err := data.Set("created_on", s.CreatedOn.String); err != nil { return err } - if err := data.Set("enabled", enabled.Bool); err != nil { + if err := data.Set("enabled", s.Enabled.Bool); err != nil { return err } @@ -258,7 +258,7 @@ func UpdateStorageIntegration(data *schema.ResourceData, meta interface{}) error if data.HasChange("storage_blocked_locations") { v := data.Get("storage_blocked_locations").([]interface{}) if len(v) == 0 { - err := DBExec(db, fmt.Sprintf(`ALTER STORAGE INTEGRATION %v UNSET STORAGE_BLOCKED_LOCATIONS`, data.Id())) + err := snowflake.Exec(db, fmt.Sprintf(`ALTER STORAGE INTEGRATION %v UNSET STORAGE_BLOCKED_LOCATIONS`, data.Id())) if err != nil { return fmt.Errorf("error unsetting storage_blocked_locations: %w", err) } @@ -283,7 +283,7 @@ func UpdateStorageIntegration(data *schema.ResourceData, meta interface{}) error } if runSetStatement { - if err := DBExec(db, stmt.Statement()); err != nil { + if err := snowflake.Exec(db, stmt.Statement()); err != nil { return fmt.Errorf("error updating storage integration: %w", err) } } diff --git a/pkg/resources/task.go b/pkg/resources/task.go new file mode 100644 index 0000000000..2c48395838 --- /dev/null +++ b/pkg/resources/task.go @@ -0,0 +1,673 @@ +package resources + +import ( + "bytes" + "database/sql" + "encoding/csv" + "fmt" + "log" + "strings" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/pkg/errors" +) + +const ( + taskIDDelimiter = '|' +) + +var taskSchema = map[string]*schema.Schema{ + "enabled": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Specifies if the task should be started (enabled) after creation or should remain suspended (default).", + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Specifies the identifier for the task; must be unique for the database and schema in which the task is created.", + ForceNew: true, + }, + "database": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The database in which to create the task.", + ForceNew: true, + }, + "schema": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The schema in which to create the task.", + ForceNew: true, + }, + "warehouse": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The warehouse the task will use.", + ForceNew: false, + }, + "schedule": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The schedule for periodically running the task. This can be a cron or interval in minutes.", + }, + "session_parameters": &schema.Schema{ + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Specifies session parameters to set for the session when the task runs. A task supports all session parameters.", + }, + "user_task_timeout_ms": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 86400000), + Description: "Specifies the time limit on a single run of the task before it times out (in milliseconds).", + }, + "comment": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Specifies a comment for the task.", + }, + "after": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Specifies the predecessor task in the same database and schema of the current task. When a run of the predecessor task finishes successfully, it triggers this task (after a brief lag).", + }, + "when": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Specifies a Boolean SQL expression; multiple conditions joined with AND/OR are supported.", + }, + "sql_statement": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Any single SQL statement, or a call to a stored procedure, executed when the task runs.", + ForceNew: false, + }, +} + +type taskID struct { + DatabaseName string + SchemaName string + TaskName string +} + +//String() takes in a taskID object and returns a pipe-delimited string: +//DatabaseName|SchemaName|TaskName +func (t *taskID) String() (string, error) { + var buf bytes.Buffer + csvWriter := csv.NewWriter(&buf) + csvWriter.Comma = taskIDDelimiter + dataIdentifiers := [][]string{{t.DatabaseName, t.SchemaName, t.TaskName}} + err := csvWriter.WriteAll(dataIdentifiers) + if err != nil { + return "", err + } + strTaskID := strings.TrimSpace(buf.String()) + return strTaskID, nil +} + +// difference find keys in a but not in b +func difference(a, b map[string]interface{}) map[string]interface{} { + diff := make(map[string]interface{}) + for k := range a { + if _, ok := b[k]; !ok { + diff[k] = a[k] + } + } + return diff +} + +// getActiveRootTask tries to retrieve the root of current task or returns the current (standalone) task +func getActiveRootTask(data *schema.ResourceData, meta interface{}) (*snowflake.TaskBuilder, error) { + log.Println("[DEBUG] retrieving root task") + + db := meta.(*sql.DB) + database := data.Get("database").(string) + dbSchema := data.Get("schema").(string) + name := data.Get("name").(string) + after := data.Get("after").(string) + + if name == "" { + return nil, nil + } + + // always start from first predecessor + // or the current task when standalone + if after != "" { + name = after + } + + for { + builder := snowflake.Task(name, database, dbSchema) + q := builder.Show() + row := snowflake.QueryRow(db, q) + task, err := snowflake.ScanTask(row) + + if err != nil && name != data.Get("name").(string) { + return nil, errors.Wrapf(err, "failed to locate the root node of: %v", name) + } + + if task.Predecessors == nil { + log.Println(fmt.Sprintf("[DEBUG] found root task: %v", name)) + // we only want to deal with suspending the root task when its enabled (started) + if task.IsEnabled() { + return snowflake.Task(name, database, dbSchema), nil + } + return nil, nil + } + + name = task.GetPredecessorName() + } +} + +// getActiveRootTaskAndSuspend retrieves the root task and suspends it +func getActiveRootTaskAndSuspend(data *schema.ResourceData, meta interface{}) (*snowflake.TaskBuilder, error) { + db := meta.(*sql.DB) + name := data.Get("name").(string) + + root, err := getActiveRootTask(data, meta) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving root task %v", name) + } + + if root != nil { + qr := root.Suspend() + err = snowflake.Exec(db, qr) + if err != nil { + return nil, errors.Wrapf(err, "error suspending root task %v", name) + } + } + + return root, nil +} + +func resumeTask(root *snowflake.TaskBuilder, meta interface{}) { + if root == nil { + return + } + + if root.IsDisabled() { + return + } + + db := meta.(*sql.DB) + qr := root.Resume() + err := snowflake.Exec(db, qr) + if err != nil { + log.Fatal(errors.Wrapf(err, "error resuming root task %v", root.QualifiedName())) + } +} + +// taskIDFromString() takes in a pipe-delimited string: DatabaseName|SchemaName|TaskName +// and returns a taskID object +func taskIDFromString(stringID string) (*taskID, error) { + reader := csv.NewReader(strings.NewReader(stringID)) + reader.Comma = pipeIDDelimiter + lines, err := reader.ReadAll() + if err != nil { + return nil, fmt.Errorf("Not CSV compatible") + } + + if len(lines) != 1 { + return nil, fmt.Errorf("1 line per task") + } + if len(lines[0]) != 3 { + return nil, fmt.Errorf("3 fields allowed") + } + + taskResult := &taskID{ + DatabaseName: lines[0][0], + SchemaName: lines[0][1], + TaskName: lines[0][2], + } + return taskResult, nil +} + +// Task returns a pointer to the resource representing a task +func Task() *schema.Resource { + return &schema.Resource{ + Create: CreateTask, + Read: ReadTask, + Update: UpdateTask, + Delete: DeleteTask, + Exists: TaskExists, + + Schema: taskSchema, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + } +} + +// ReadTask implements schema.ReadFunc +func ReadTask(data *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + taskID, err := taskIDFromString(data.Id()) + if err != nil { + return err + } + + database := taskID.DatabaseName + schema := taskID.SchemaName + name := taskID.TaskName + + builder := snowflake.Task(name, database, schema) + q := builder.Show() + row := snowflake.QueryRow(db, q) + t, err := snowflake.ScanTask(row) + if err != nil { + return err + } + + err = data.Set("enabled", t.IsEnabled()) + if err != nil { + return err + } + + err = data.Set("name", t.Name) + if err != nil { + return err + } + + err = data.Set("database", t.DatabaseName) + if err != nil { + return err + } + + err = data.Set("schema", t.SchemaName) + if err != nil { + return err + } + + err = data.Set("warehouse", t.Warehouse) + if err != nil { + return err + } + + err = data.Set("schedule", t.Schedule) + if err != nil { + return err + } + + err = data.Set("comment", t.Comment) + if err != nil { + return err + } + + if t.Predecessors != nil { + err = data.Set("after", t.GetPredecessorName()) + if err != nil { + return err + } + } + + err = data.Set("when", t.Condition) + if err != nil { + return err + } + + err = data.Set("sql_statement", t.Definition) + if err != nil { + return err + } + + q = builder.ShowParameters() + paramRows, err := snowflake.Query(db, q) + if err != nil { + return err + } + params, err := snowflake.ScanTaskParameters(paramRows) + if err != nil { + return err + } + + if len(params) > 0 { + paramMap := map[string]interface{}{} + for _, param := range params { + log.Printf("[TRACE] %+v\n", param) + if param.Value == param.DefaultValue { + continue + } + + paramMap[param.Key] = param.Value + } + + data.Set("session_parameters", paramMap) + } + + return nil +} + +// CreateTask implements schema.CreateFunc +func CreateTask(data *schema.ResourceData, meta interface{}) error { + + var err error + db := meta.(*sql.DB) + database := data.Get("database").(string) + dbSchema := data.Get("schema").(string) + name := data.Get("name").(string) + warehouse := data.Get("warehouse").(string) + sql := data.Get("sql_statement").(string) + enabled := data.Get("enabled").(bool) + + builder := snowflake.Task(name, database, dbSchema) + builder.WithWarehouse(warehouse) + builder.WithStatement(sql) + + // Set optionals + if v, ok := data.GetOk("schedule"); ok { + builder.WithSchedule(v.(string)) + } + + if v, ok := data.GetOk("session_parameters"); ok { + builder.WithSessionParameters(v.(map[string]interface{})) + } + + if v, ok := data.GetOk("user_task_timeout_ms"); ok { + builder.WithTimeout(v.(int)) + } + + if v, ok := data.GetOk("comment"); ok { + builder.WithComment(v.(string)) + } + + if v, ok := data.GetOk("after"); ok { + root, err := getActiveRootTaskAndSuspend(data, meta) + if err != nil { + return err + } + defer resumeTask(root, meta) + + builder.WithDependency(v.(string)) + } + + if v, ok := data.GetOk("when"); ok { + builder.WithCondition(v.(string)) + } + + q := builder.Create() + err = snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error creating task %v", name) + } + + if enabled { + q = builder.Resume() + err = snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error starting task %v", name) + } + } + + taskID := &taskID{ + DatabaseName: database, + SchemaName: dbSchema, + TaskName: name, + } + dataIDInput, err := taskID.String() + if err != nil { + return err + } + data.SetId(dataIDInput) + + return ReadTask(data, meta) +} + +// UpdateTask implements schema.UpdateFunc +func UpdateTask(data *schema.ResourceData, meta interface{}) error { + // https://www.terraform.io/docs/extend/writing-custom-providers.html#error-handling-amp-partial-state + data.Partial(true) + + taskID, err := taskIDFromString(data.Id()) + if err != nil { + return err + } + + db := meta.(*sql.DB) + database := taskID.DatabaseName + dbSchema := taskID.SchemaName + name := taskID.TaskName + builder := snowflake.Task(name, database, dbSchema) + + root, err := getActiveRootTaskAndSuspend(data, meta) + if err != nil { + return err + } + defer resumeTask(root, meta) + + if data.HasChange("warehouse") { + _, new := data.GetChange("warehouse") + q := builder.ChangeWarehouse(new.(string)) + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating warehouse on task %v", data.Id()) + } + data.SetPartial("warehouse") + } + + if data.HasChange("schedule") { + var q string + old, new := data.GetChange("schedule") + if old != "" && new == "" { + q = builder.RemoveSchedule() + } else { + q = builder.ChangeSchedule(new.(string)) + } + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating schedule on task %v", data.Id()) + } + data.SetPartial("schedule") + } + + if data.HasChange("user_task_timeout_ms") { + var q string + old, new := data.GetChange("user_task_timeout_ms") + if old.(int) > 0 && new.(int) == 0 { + q = builder.RemoveTimeout() + } else { + q = builder.ChangeTimeout(new.(int)) + } + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating user task timeout on task %v", data.Id()) + } + data.SetPartial("user_task_timeout_ms") + } + + if data.HasChange("comment") { + var q string + old, new := data.GetChange("comment") + if old != "" && new == "" { + q = builder.RemoveComment() + } else { + q = builder.ChangeComment(new.(string)) + } + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating comment on task %v", data.Id()) + } + data.SetPartial("comment") + } + + if data.HasChange("after") { + var ( + q string + err error + ) + + old, new := data.GetChange("after") + enabled := data.Get("enabled").(bool) + + if enabled { + q = builder.Suspend() + err = snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error suspending task %v", data.Id()) + } + defer resumeTask(builder, meta) + } + + if old != "" { + q = builder.RemoveDependency(old.(string)) + err = snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error removing old after dependency from task %v", data.Id()) + } + } + + if new != "" { + q = builder.AddDependency(new.(string)) + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error adding after dependency on task %v", data.Id()) + } + } + + data.SetPartial("after") + } + + if data.HasChange("session_parameters") { + var q string + o, n := data.GetChange("session_parameters") + + if o == nil { + o = make(map[string]interface{}) + } + if n == nil { + n = make(map[string]interface{}) + } + os := o.(map[string]interface{}) + ns := n.(map[string]interface{}) + + remove := difference(os, ns) + add := difference(ns, os) + + if len(remove) > 0 { + q = builder.RemoveSessionParameters(remove) + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error removing session_parameters on task %v", data.Id()) + } + } + + if len(add) > 0 { + q = builder.AddSessionParameters(add) + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error adding session_parameters to task %v", data.Id()) + } + } + + data.SetPartial("session_parameters") + } + + if data.HasChange("when") { + _, new := data.GetChange("when") + q := builder.ChangeCondition(new.(string)) + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating when condition on task %v", data.Id()) + } + data.SetPartial("when") + } + + if data.HasChange("sql_statement") { + _, new := data.GetChange("sql_statement") + q := builder.ChangeSqlStatement(new.(string)) + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating sql statement on task %v", data.Id()) + } + data.SetPartial("sql_statement") + } + + if data.HasChange("enabled") { + var q string + _, n := data.GetChange("enabled") + enable := n.(bool) + + if enable { + q = builder.Resume() + } else { + q = builder.Suspend() + // make sure defer doesn't enable task again + // when standalone or root task and status is supsended + if root != nil && builder.QualifiedName() == root.QualifiedName() { + root = root.SetDisabled() + } + } + + err := snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error updating task state %v", data.Id()) + } + + data.SetPartial("enabled") + } + + return ReadTask(data, meta) +} + +// DeleteTask implements schema.DeleteFunc +func DeleteTask(data *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + taskID, err := taskIDFromString(data.Id()) + if err != nil { + return err + } + + database := taskID.DatabaseName + schema := taskID.SchemaName + name := taskID.TaskName + + root, err := getActiveRootTaskAndSuspend(data, meta) + if err != nil { + return err + } + + // only resume the root when not a standalone task + if root != nil && name != root.Name() { + defer resumeTask(root, meta) + } + + q := snowflake.Task(name, database, schema).Drop() + err = snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error deleting task %v", data.Id()) + } + + data.SetId("") + + return nil +} + +// TaskExists implements schema.ExistsFunc +func TaskExists(data *schema.ResourceData, meta interface{}) (bool, error) { + db := meta.(*sql.DB) + taskID, err := taskIDFromString(data.Id()) + if err != nil { + return false, err + } + + database := taskID.DatabaseName + schema := taskID.SchemaName + name := taskID.TaskName + + q := snowflake.Task(name, database, schema).Show() + rows, err := db.Query(q) + if err != nil { + return false, err + } + defer rows.Close() + + if rows.Next() { + return true, nil + } + + return false, nil +} diff --git a/pkg/resources/task_acceptance_test.go b/pkg/resources/task_acceptance_test.go new file mode 100644 index 0000000000..85ae01a0e6 --- /dev/null +++ b/pkg/resources/task_acceptance_test.go @@ -0,0 +1,299 @@ +package resources_test + +import ( + "bytes" + "fmt" + "testing" + "text/template" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +type ( + AccTaskTestSettings struct { + WarehouseName string + DatabaseName string + RootTask *TaskSettings + ChildTask *TaskSettings + SoloTask *TaskSettings + } + + TaskSettings struct { + Name string + Enabled bool + Schema string + SQL string + Schedule string + Comment string + When string + SessionParams bool + } +) + +var ( + rootname = "root_task" + childname = "child_task" + soloname = "standalone_task" + warehousename = acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + databasename = acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + + initialState = &AccTaskTestSettings{ + WarehouseName: warehousename, + DatabaseName: databasename, + + RootTask: &TaskSettings{ + Name: rootname, + Schema: "PUBLIC", + SQL: "SHOW FUNCTIONS", + Enabled: true, + Schedule: "5 MINUTE", + }, + + ChildTask: &TaskSettings{ + Name: childname, + SQL: "SELECT 1", + Enabled: false, + Comment: "initial state", + }, + + SoloTask: &TaskSettings{ + Name: soloname, + Schema: "PUBLIC", + SQL: "SELECT 1", + When: "TRUE", + Enabled: false, + SessionParams: true, + }, + } + + // Enables the Child and changes the SQL + stepOne = &AccTaskTestSettings{ + WarehouseName: warehousename, + DatabaseName: databasename, + + RootTask: &TaskSettings{ + Name: rootname, + Schema: "PUBLIC", + SQL: "SHOW FUNCTIONS", + Enabled: true, + Schedule: "5 MINUTE", + }, + + ChildTask: &TaskSettings{ + Name: childname, + SQL: "SELECT *", + Enabled: true, + Comment: "secondary state", + }, + + SoloTask: &TaskSettings{ + Name: soloname, + Schema: "PUBLIC", + SQL: "SELECT *", + When: "TRUE", + Enabled: true, + SessionParams: false, + }, + } + + // Changes Root Schedule and SQL + stepTwo = &AccTaskTestSettings{ + WarehouseName: warehousename, + DatabaseName: databasename, + + RootTask: &TaskSettings{ + Name: rootname, + Schema: "PUBLIC", + SQL: "SHOW TABLES", + Enabled: true, + Schedule: "15 MINUTE", + }, + + ChildTask: &TaskSettings{ + Name: childname, + SQL: "SELECT 1", + Enabled: true, + Comment: "third state", + }, + + SoloTask: &TaskSettings{ + Name: soloname, + Schema: "PUBLIC", + SQL: "SELECT *", + When: "FALSE", + Enabled: true, + }, + } + + stepThree = &AccTaskTestSettings{ + WarehouseName: warehousename, + DatabaseName: databasename, + + RootTask: &TaskSettings{ + Name: rootname, + Schema: "PUBLIC", + SQL: "SHOW FUNCTIONS", + Enabled: false, + Schedule: "5 MINUTE", + }, + + ChildTask: &TaskSettings{ + Name: childname, + SQL: "SELECT 1", + Enabled: false, + Comment: "reset", + }, + + SoloTask: &TaskSettings{ + Name: soloname, + Schema: "PUBLIC", + SQL: "SELECT 1", + When: "TRUE", + Enabled: true, + SessionParams: true, + }, + } +) + +func Test_AccTask(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: taskConfig(initialState), + Check: resource.ComposeTestCheckFunc( + checkBool("snowflake_task.root_task", "enabled", true), + checkBool("snowflake_task.child_task", "enabled", false), + resource.TestCheckResourceAttr("snowflake_task.root_task", "name", rootname), + resource.TestCheckResourceAttr("snowflake_task.child_task", "name", childname), + resource.TestCheckResourceAttr("snowflake_task.root_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.child_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.root_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.child_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.root_task", "sql_statement", initialState.RootTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "sql_statement", initialState.ChildTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "after", rootname), + resource.TestCheckResourceAttr("snowflake_task.child_task", "comment", initialState.ChildTask.Comment), + ), + }, + { + Config: taskConfig(stepOne), + Check: resource.ComposeTestCheckFunc( + checkBool("snowflake_task.root_task", "enabled", true), + checkBool("snowflake_task.child_task", "enabled", true), + resource.TestCheckResourceAttr("snowflake_task.root_task", "name", rootname), + resource.TestCheckResourceAttr("snowflake_task.child_task", "name", childname), + resource.TestCheckResourceAttr("snowflake_task.root_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.child_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.root_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.child_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.root_task", "sql_statement", stepOne.RootTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "sql_statement", stepOne.ChildTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "comment", stepOne.ChildTask.Comment), + ), + }, + { + Config: taskConfig(stepTwo), + Check: resource.ComposeTestCheckFunc( + checkBool("snowflake_task.root_task", "enabled", true), + checkBool("snowflake_task.child_task", "enabled", true), + resource.TestCheckResourceAttr("snowflake_task.root_task", "name", rootname), + resource.TestCheckResourceAttr("snowflake_task.child_task", "name", childname), + resource.TestCheckResourceAttr("snowflake_task.root_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.child_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.root_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.child_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.root_task", "sql_statement", stepTwo.RootTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "sql_statement", stepTwo.ChildTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "comment", stepTwo.ChildTask.Comment), + ), + }, + { + Config: taskConfig(stepThree), + Check: resource.ComposeTestCheckFunc( + checkBool("snowflake_task.root_task", "enabled", false), + checkBool("snowflake_task.child_task", "enabled", false), + resource.TestCheckResourceAttr("snowflake_task.root_task", "name", rootname), + resource.TestCheckResourceAttr("snowflake_task.child_task", "name", childname), + resource.TestCheckResourceAttr("snowflake_task.root_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.child_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.root_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.child_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.root_task", "sql_statement", stepThree.RootTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "sql_statement", stepThree.ChildTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "comment", stepThree.ChildTask.Comment), + ), + }, + { + Config: taskConfig(initialState), + Check: resource.ComposeTestCheckFunc( + checkBool("snowflake_task.root_task", "enabled", true), + checkBool("snowflake_task.child_task", "enabled", false), + resource.TestCheckResourceAttr("snowflake_task.root_task", "name", rootname), + resource.TestCheckResourceAttr("snowflake_task.child_task", "name", childname), + resource.TestCheckResourceAttr("snowflake_task.root_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.child_task", "database", databasename), + resource.TestCheckResourceAttr("snowflake_task.root_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.child_task", "schema", "PUBLIC"), + resource.TestCheckResourceAttr("snowflake_task.root_task", "sql_statement", initialState.RootTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "sql_statement", initialState.ChildTask.SQL), + resource.TestCheckResourceAttr("snowflake_task.child_task", "comment", initialState.ChildTask.Comment), + ), + }, + }, + }) +} + +func taskConfig(settings *AccTaskTestSettings) string { + config, err := template.New("task_acceptance_test_config").Parse(` +resource "snowflake_warehouse" "test_wh" { + name = "{{ .WarehouseName }}" +} +resource "snowflake_database" "test_db" { + name = "{{ .DatabaseName }}" +} +resource "snowflake_task" "root_task" { + name = "{{ .RootTask.Name }}" + database = snowflake_database.test_db.name + schema = "{{ .RootTask.Schema }}" + warehouse = snowflake_warehouse.test_wh.name + sql_statement = "{{ .RootTask.SQL }}" + enabled = {{ .RootTask.Enabled }} + schedule = "{{ .RootTask.Schedule }}" +} +resource "snowflake_task" "child_task" { + name = "{{ .ChildTask.Name }}" + database = snowflake_task.root_task.database + schema = snowflake_task.root_task.schema + warehouse = snowflake_task.root_task.warehouse + sql_statement = "{{ .ChildTask.SQL }}" + enabled = {{ .ChildTask.Enabled }} + after = snowflake_task.root_task.name + comment = "{{ .ChildTask.Comment }}" +} +resource "snowflake_task" "solo_task" { + name = "{{ .SoloTask.Name }}" + database = snowflake_database.test_db.name + schema = "{{ .SoloTask.Schema }}" + warehouse = snowflake_warehouse.test_wh.name + sql_statement = "{{ .SoloTask.SQL }}" + enabled = {{ .SoloTask.Enabled }} + when = "{{ .SoloTask.When }}" + {{ if .SoloTask.SessionParams}} + session_parameters = { + TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24", + } + {{- end }} +} + `) + + if err != nil { + fmt.Println(err) + } + + var result bytes.Buffer + config.Execute(&result, settings) + + return result.String() +} diff --git a/pkg/resources/task_internal_test.go b/pkg/resources/task_internal_test.go new file mode 100644 index 0000000000..9d6319e637 --- /dev/null +++ b/pkg/resources/task_internal_test.go @@ -0,0 +1,48 @@ +package resources + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestStringFromTaskID(t *testing.T) { + r := require.New(t) + task := taskID{DatabaseName: "test_db", SchemaName: "test_schema", TaskName: "test_task"} + id, err := task.String() + r.NoError(err) + r.Equal(id, "test_db|test_schema|test_task") +} + +func TestTaskIDFromString(t *testing.T) { + r := require.New(t) + + id := "test_db|test_schema|test_task" + task, err := taskIDFromString(id) + r.NoError(err) + r.Equal("test_db", task.DatabaseName) + r.Equal("test_schema", task.SchemaName) + r.Equal("test_task", task.TaskName) + + id = "test_db" + _, err = taskIDFromString(id) + r.Equal(fmt.Errorf("3 fields allowed"), err) + + // Bad ID + id = "|" + _, err = taskIDFromString(id) + r.Equal(fmt.Errorf("3 fields allowed"), err) + + // 0 lines + id = "" + _, err = taskIDFromString(id) + r.Equal(fmt.Errorf("1 line per task"), err) + + // 2 lines + id = `database|schema|task + database|schema|task` + _, err = taskIDFromString(id) + r.Equal(fmt.Errorf("1 line per task"), err) + +} diff --git a/pkg/resources/task_test.go b/pkg/resources/task_test.go new file mode 100644 index 0000000000..048a541bc0 --- /dev/null +++ b/pkg/resources/task_test.go @@ -0,0 +1,65 @@ +package resources_test + +import ( + "database/sql" + "testing" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider" + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/resources" + . "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/testhelpers" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/stretchr/testify/require" +) + +func TestTask(t *testing.T) { + r := require.New(t) + err := resources.Task().InternalValidate(provider.Provider().Schema, true) + r.NoError(err) +} + +func TestTaskCreate(t *testing.T) { + r := require.New(t) + + in := map[string]interface{}{ + "enabled": true, + "name": "test_task", + "database": "test_db", + "schema": "test_schema", + "warehouse": "much_warehouse", + "sql_statement": "select hi from hello", + "comment": "wow comment", + } + + d := schema.TestResourceDataRaw(t, resources.Task().Schema, in) + r.NotNil(d) + + WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { + mock.ExpectExec( + `^CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "much_warehouse" COMMENT = 'wow comment' AS select hi from hello$`, + ).WillReturnResult(sqlmock.NewResult(1, 1)) + + mock.ExpectExec( + `^ALTER TASK "test_db"."test_schema"."test_task" RESUME$`, + ).WillReturnResult(sqlmock.NewResult(1, 1)) + + expectReadTask(mock) + expectReadTaskParams(mock) + err := resources.CreateTask(d, db) + r.NoError(err) + }) +} + +func expectReadTask(mock sqlmock.Sqlmock) { + rows := sqlmock.NewRows([]string{ + "created_on", "name", "database_name", "schema_name", "owner", "comment", "warehouse", "schedule", "predecessors", "state", "definition", "condition"}, + ).AddRow("2020-05-14 17:20:50.088 +0000", "test_task", "test_db", "test_schema", "ACCOUNTADMIN", "wow comment", "", "", "", "started", "select hi from hello", "") + mock.ExpectQuery(`^SHOW TASKS LIKE 'test_task' IN DATABASE "test_db"$`).WillReturnRows(rows) +} + +func expectReadTaskParams(mock sqlmock.Sqlmock) { + rows := sqlmock.NewRows([]string{ + "key", "value", "default", "level", "description", "type"}, + ).AddRow("ABORT_DETACHED_QUERY", "false", "false", "", "wow desc", "BOOLEAN") + mock.ExpectQuery(`^SHOW PARAMETERS IN TASK "test_db"."test_schema"."test_task"$`).WillReturnRows(rows) +} diff --git a/pkg/resources/user.go b/pkg/resources/user.go index 6b5d3f8461..08339f3e4c 100644 --- a/pkg/resources/user.go +++ b/pkg/resources/user.go @@ -153,50 +153,48 @@ func ReadUser(data *schema.ResourceData, meta interface{}) error { id := data.Id() stmt := snowflake.User(id).Show() - row := db.QueryRow(stmt) - var name, createdOn, loginName, displayName, firstName, lastName, email, minsToUnlock, daysToExpiry, comment, mustChangePassword, snowflakeLock, defaultWarehouse, defaultNamespace, defaultRole, extAuthnDuo, extAuthnUID, minsToBypassMfa, owner, lastSuccessLogin, expiresAtTime, lockedUntilTime, hasPassword sql.NullString - var disabled, hasRsaPublicKey bool - err := row.Scan(&name, &createdOn, &loginName, &displayName, &firstName, &lastName, &email, &minsToUnlock, &daysToExpiry, &comment, &disabled, &mustChangePassword, &snowflakeLock, &defaultWarehouse, &defaultNamespace, &defaultRole, &extAuthnDuo, &extAuthnUID, &minsToBypassMfa, &owner, &lastSuccessLogin, &expiresAtTime, &lockedUntilTime, &hasPassword, &hasRsaPublicKey) + row := snowflake.QueryRow(db, stmt) + + u, err := snowflake.ScanUser(row) if err != nil { return err } - // TODO turn this into a loop after we switch to scaning in a struct - err = data.Set("name", name.String) + err = data.Set("name", u.Name.String) if err != nil { return err } - err = data.Set("comment", comment.String) + err = data.Set("comment", u.Comment.String) if err != nil { return err } - err = data.Set("login_name", loginName.String) + err = data.Set("login_name", u.LoginName.String) if err != nil { return err } - err = data.Set("disabled", disabled) + err = data.Set("disabled", u.Disabled) if err != nil { return err } - err = data.Set("default_role", defaultRole.String) + err = data.Set("default_role", u.DefaultRole.String) if err != nil { return err } - err = data.Set("default_namespace", defaultNamespace.String) + err = data.Set("default_namespace", u.DefaultNamespace.String) if err != nil { return err } - err = data.Set("default_warehouse", defaultWarehouse.String) + err = data.Set("default_warehouse", u.DefaultWarehouse.String) if err != nil { return err } - err = data.Set("has_rsa_public_key", hasRsaPublicKey) + err = data.Set("has_rsa_public_key", u.HasRsaPublicKey) return err } diff --git a/pkg/resources/view.go b/pkg/resources/view.go index 5733c16f30..f4bc5bddcb 100644 --- a/pkg/resources/view.go +++ b/pkg/resources/view.go @@ -3,6 +3,7 @@ package resources import ( "database/sql" "fmt" + "log" "regexp" "strings" @@ -110,8 +111,8 @@ func CreateView(data *schema.ResourceData, meta interface{}) error { } q := builder.Create() - - err := DBExec(db, q) + log.Print("[DEBUG] xxx ", q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error creating view %v", name) } @@ -130,38 +131,35 @@ func ReadView(data *schema.ResourceData, meta interface{}) error { } q := snowflake.View(view).WithDB(dbName).WithSchema(schema).Show() - row := db.QueryRow(q) - var createdOn, name, reserved, databaseName, schemaName, owner, comment, text sql.NullString - var isSecure, isMaterialized bool - err = row.Scan(&createdOn, &name, &reserved, &databaseName, &schemaName, &owner, &comment, &text, &isSecure, &isMaterialized) + row := snowflake.QueryRow(db, q) + v, err := snowflake.ScanView(row) if err != nil { return err } - // TODO turn this into a loop after we switch to scaning in a struct - err = data.Set("name", name.String) + err = data.Set("name", v.Name.String) if err != nil { return err } - err = data.Set("is_secure", isSecure) + err = data.Set("is_secure", v.IsSecure) if err != nil { return err } - err = data.Set("comment", comment.String) + err = data.Set("comment", v.Comment.String) if err != nil { return err } - err = data.Set("schema", schemaName.String) + err = data.Set("schema", v.SchemaName.String) if err != nil { return err } // Want to only capture the Select part of the query because before that is the Create part of the view which we no longer care about - extractor := snowflake.NewViewSelectStatementExtractor(text.String) + extractor := snowflake.NewViewSelectStatementExtractor(v.Text.String) substringOfQuery, err := extractor.Extract() if err != nil { return err @@ -172,7 +170,7 @@ func ReadView(data *schema.ResourceData, meta interface{}) error { return err } - return data.Set("database", databaseName.String) + return data.Set("database", v.DatabaseName.String) } // UpdateView implements schema.UpdateFunc @@ -192,7 +190,7 @@ func UpdateView(data *schema.ResourceData, meta interface{}) error { _, name := data.GetChange("name") q := builder.Rename(name.(string)) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error renaming view %v", data.Id()) } @@ -206,13 +204,13 @@ func UpdateView(data *schema.ResourceData, meta interface{}) error { if c := comment.(string); c == "" { q := builder.RemoveComment() - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error unsetting comment for view %v", data.Id()) } } else { q := builder.ChangeComment(c) - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error updating comment for view %v", data.Id()) } @@ -227,13 +225,13 @@ func UpdateView(data *schema.ResourceData, meta interface{}) error { if secure.(bool) { q := builder.Secure() - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error setting secure for view %v", data.Id()) } } else { q := builder.Unsecure() - err := DBExec(db, q) + err := snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error unsetting secure for view %v", data.Id()) } @@ -253,7 +251,7 @@ func DeleteView(data *schema.ResourceData, meta interface{}) error { q := snowflake.View(view).WithDB(dbName).WithSchema(schema).Drop() - err = DBExec(db, q) + err = snowflake.Exec(db, q) if err != nil { return errors.Wrapf(err, "error deleting view %v", data.Id()) } diff --git a/pkg/resources/view_acceptance_test.go b/pkg/resources/view_acceptance_test.go index 9907747735..44f4703cca 100644 --- a/pkg/resources/view_acceptance_test.go +++ b/pkg/resources/view_acceptance_test.go @@ -15,7 +15,22 @@ func TestAccView(t *testing.T) { Providers: providers(), Steps: []resource.TestStep{ { - Config: viewConfig(accName), + Config: viewConfig(accName, "SELECT ROLE_NAME, ROLE_OWNER FROM INFORMATION_SCHEMA.APPLICABLE_ROLES"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_view.test", "name", accName), + resource.TestCheckResourceAttr("snowflake_view.test", "database", accName), + resource.TestCheckResourceAttr("snowflake_view.test", "comment", "Terraform test resource"), + checkBool("snowflake_view.test", "is_secure", true), // this is from user_acceptance_test.go + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: viewConfig(accName, "SELECT ROLE_NAME, ROLE_OWNER FROM INFORMATION_SCHEMA.APPLICABLE_ROLES where ROLE_OWNER like 'foo%%';"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("snowflake_view.test", "name", accName), resource.TestCheckResourceAttr("snowflake_view.test", "database", accName), @@ -27,7 +42,7 @@ func TestAccView(t *testing.T) { }) } -func viewConfig(n string) string { +func viewConfig(n string, q string) string { return fmt.Sprintf(` resource "snowflake_database" "test" { name = "%v" @@ -38,7 +53,7 @@ resource "snowflake_view" "test" { comment = "Terraform test resource" database = snowflake_database.test.name is_secure = true - statement = "SELECT ROLE_NAME, ROLE_OWNER FROM INFORMATION_SCHEMA.APPLICABLE_ROLES" + statement = "%s" } -`, n, n) +`, n, n, q) } diff --git a/pkg/resources/view_test.go b/pkg/resources/view_test.go index 6aa0080bbc..fb36746959 100644 --- a/pkg/resources/view_test.go +++ b/pkg/resources/view_test.go @@ -42,6 +42,29 @@ func TestViewCreate(t *testing.T) { r.NoError(err) }) } +func TestViewCreateAmpersand(t *testing.T) { + r := require.New(t) + + in := map[string]interface{}{ + "name": "good_name", + "database": "test_db", + "comment": "great comment", + "statement": "SELECT * FROM test_db.PUBLIC.GREAT_TABLE WHERE account_id LIKE 'bob%'", + "is_secure": true, + } + d := schema.TestResourceDataRaw(t, resources.View().Schema, in) + r.NotNil(d) + + WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { + mock.ExpectExec( + `^CREATE SECURE VIEW "test_db"."PUBLIC"."good_name" COMMENT = 'great comment' AS SELECT \* FROM test_db.PUBLIC.GREAT_TABLE WHERE account_id LIKE 'bob%'$`, + ).WillReturnResult(sqlmock.NewResult(1, 1)) + + expectReadView(mock) + err := resources.CreateView(d, db) + r.NoError(err) + }) +} func expectReadView(mock sqlmock.Sqlmock) { rows := sqlmock.NewRows([]string{ diff --git a/pkg/resources/warehouse.go b/pkg/resources/warehouse.go index 5d0a04a82a..d37ff48486 100644 --- a/pkg/resources/warehouse.go +++ b/pkg/resources/warehouse.go @@ -2,50 +2,13 @@ package resources import ( "database/sql" - "log" "strings" - "time" "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "github.com/jmoiron/sqlx" ) -// warehouse is a go representation of a grant that can be used in conjunction -// with github.com/jmoiron/sqlx -type warehouse struct { - Name string `db:"name"` - State string `db:"state"` - Type string `db:"type"` - Size string `db:"size"` - MinClusterCount int64 `db:"min_cluster_count"` - MaxClusterCount int64 `db:"max_cluster_count"` - StartedClusters int64 `db:"started_clusters"` - Running int64 `db:"running"` - Queued int64 `db:"queued"` - IsDefault string `db:"is_default"` - IsCurrent string `db:"is_current"` - AutoSuspend int64 `db:"auto_suspend"` - AutoResume bool `db:"auto_resume"` - Available string `db:"available"` - Provisioning string `db:"provisioning"` - Quiescing string `db:"quiescing"` - Other string `db:"other"` - CreatedOn time.Time `db:"created_on"` - ResumedOn time.Time `db:"resumed_on"` - UpdatedOn time.Time `db:"updated_on"` - Owner string `db:"owner"` - Comment string `db:"comment"` - ResourceMonitor string `db:"resource_monitor"` - Actives int64 `db:"actives"` - Pendings int64 `db:"pendings"` - Failed int64 `db:"failed"` - Suspended int64 `db:"suspended"` - UUID string `db:"uuid"` - ScalingPolicy string `db:"scaling_policy"` -} - // warehouseCreateProperties are only available via the CREATE statement var warehouseCreateProperties = []string{"initially_suspended", "wait_for_provisioning", "statement_timeout_in_seconds"} @@ -165,16 +128,10 @@ func CreateWarehouse(data *schema.ResourceData, meta interface{}) error { // ReadWarehouse implements schema.ReadFunc func ReadWarehouse(data *schema.ResourceData, meta interface{}) error { db := meta.(*sql.DB) - sdb := sqlx.NewDb(db, "snowflake") - stmt := snowflake.Warehouse(data.Id()).Show() - log.Printf("[DEBUG] stmt %v\n", stmt) - row := sdb.QueryRowx(stmt) - - w := &warehouse{} - - err := row.StructScan(w) + row := snowflake.QueryRow(db, stmt) + w, err := snowflake.ScanWarehouse(row) if err != nil { return err } diff --git a/pkg/snowflake/database.go b/pkg/snowflake/database.go index 39efa9643a..ff40dd914a 100644 --- a/pkg/snowflake/database.go +++ b/pkg/snowflake/database.go @@ -1,7 +1,12 @@ package snowflake import ( + "database/sql" "fmt" + "log" + + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" ) // Database returns a pointer to a Builder for a database @@ -51,3 +56,38 @@ func DatabaseFromDatabase(name, database string) *DatabaseCloneBuilder { func (dsb *DatabaseCloneBuilder) Create() string { return fmt.Sprintf(`CREATE DATABASE "%v" CLONE "%v"`, dsb.name, dsb.database) } + +type database struct { + CreatedOn sql.NullString `db:"created_on"` + DBName sql.NullString `db:"name"` + IsDefault sql.NullString `db:"is_default"` + IsCurrent sql.NullString `db:"is_current"` + Origin sql.NullString `db:"origin"` + Owner sql.NullString `db:"owner"` + Comment sql.NullString `db:"comment"` + Options sql.NullString `db:"options"` + RetentionTime sql.NullString `db:"retention_time"` +} + +func ScanDatabase(row *sqlx.Row) (*database, error) { + d := &database{} + e := row.StructScan(d) + return d, e +} + +func ListDatabases(sdb *sqlx.DB) ([]database, error) { + stmt := "SHOW DATABASES" + rows, err := sdb.Queryx(stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + dbs := []database{} + err = sqlx.StructScan(rows, &dbs) + if err == sql.ErrNoRows { + log.Printf("[DEBUG] no databases found") + return nil, nil + } + return dbs, errors.Wrapf(err, "unable to scan row for %s", stmt) +} diff --git a/pkg/snowflake/database_test.go b/pkg/snowflake/database_test.go index e8c87edabb..5cb84add17 100644 --- a/pkg/snowflake/database_test.go +++ b/pkg/snowflake/database_test.go @@ -3,7 +3,9 @@ package snowflake_test import ( "testing" + sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -54,3 +56,15 @@ func TestDatabaseCreateFromDatabase(t *testing.T) { q := db.Create() r.Equal(`CREATE DATABASE "db1" CLONE "abc123"`, q) } + +func TestListDatabases(t *testing.T) { + r := require.New(t) + mockDB, mock, err := sqlmock.New() + r.NoError(err) + defer mockDB.Close() + sqlxDB := sqlx.NewDb(mockDB, "sqlmock") + rows := sqlmock.NewRows([]string{"created_on", "name", "is_default", "is_current", "origin", "owner", "comment", "options", "retention_time"}).AddRow("", "", "", "", "", "", "", "", "") + mock.ExpectQuery(`SHOW DATABASES`).WillReturnRows(rows) + _, err = snowflake.ListDatabases(sqlxDB) + r.NoError(err) +} diff --git a/pkg/snowflake/escaping.go b/pkg/snowflake/escaping.go index d6fe0ade17..5d4a93e0b0 100644 --- a/pkg/snowflake/escaping.go +++ b/pkg/snowflake/escaping.go @@ -9,3 +9,10 @@ func EscapeString(in string) string { out = strings.Replace(out, `'`, `\'`, -1) return out } + +// UnescapeString reverses EscapeString +func UnescapeString(in string) string { + out := strings.Replace(in, `\\`, `\`, -1) + out = strings.Replace(out, `\'`, `'`, -1) + return out +} diff --git a/pkg/snowflake/exec.go b/pkg/snowflake/exec.go new file mode 100644 index 0000000000..684fb02376 --- /dev/null +++ b/pkg/snowflake/exec.go @@ -0,0 +1,32 @@ +package snowflake + +import ( + "database/sql" + "log" + + "github.com/jmoiron/sqlx" +) + +func Exec(db *sql.DB, query string) error { + log.Print("[DEBUG] stmt ", query) + + _, err := db.Exec(query) + return err +} + +// QueryRow will run stmt against the db and return the row. We use +// [DB.Unsafe](https://godoc.org/github.com/jmoiron/sqlx#DB.Unsafe) so that we can scan to structs +// without worrying about newly introduced columns +func QueryRow(db *sql.DB, stmt string) *sqlx.Row { + log.Print("[DEBUG] stmt ", stmt) + sdb := sqlx.NewDb(db, "snowflake").Unsafe() + return sdb.QueryRowx(stmt) +} + +// Query will run stmt against the db and return the rows. We use +// [DB.Unsafe](https://godoc.org/github.com/jmoiron/sqlx#DB.Unsafe) so that we can scan to structs +// without worrying about newly introduced columns +func Query(db *sql.DB, stmt string) (*sqlx.Rows, error) { + sdb := sqlx.NewDb(db, "snowflake").Unsafe() + return sdb.Queryx(stmt) +} diff --git a/pkg/snowflake/managed_account.go b/pkg/snowflake/managed_account.go index 728cca6c97..e36acfa167 100644 --- a/pkg/snowflake/managed_account.go +++ b/pkg/snowflake/managed_account.go @@ -1,5 +1,11 @@ package snowflake +import ( + "database/sql" + + "github.com/jmoiron/sqlx" +) + // ManagedAccount returns a pointer to a Builder that abstracts the DDL // operations for a reader account. // @@ -15,3 +21,20 @@ func ManagedAccount(name string) *Builder { name: name, } } + +type managedAccount struct { + Name sql.NullString `db:"name"` + Cloud sql.NullString `db:"cloud"` + Region sql.NullString `db:"region"` + Locator sql.NullString `db:"locator"` + CreatedOn sql.NullString `db:"created_on"` + Url sql.NullString `db:"url"` + Comment sql.NullString `db:"comment"` + IsReader bool `db:"is_reader"` +} + +func ScanManagedAccount(row *sqlx.Row) (*managedAccount, error) { + a := &managedAccount{} + e := row.StructScan(a) + return a, e +} diff --git a/pkg/snowflake/pipe.go b/pkg/snowflake/pipe.go index 0f78b93b54..c1635b2dfa 100644 --- a/pkg/snowflake/pipe.go +++ b/pkg/snowflake/pipe.go @@ -3,6 +3,8 @@ package snowflake import ( "fmt" "strings" + + "github.com/jmoiron/sqlx" ) // PipeBuilder abstracts the creation of SQL queries for a Snowflake schema @@ -111,3 +113,20 @@ func (pb *PipeBuilder) Drop() string { func (pb *PipeBuilder) Show() string { return fmt.Sprintf(`SHOW PIPES LIKE '%v' IN DATABASE "%v"`, pb.name, pb.db) } + +type pipe struct { + Createdon string `db:"created_on"` + Name string `db:"name"` + DatabaseName string `db:"database_name"` + SchemaName string `db:"schema_name"` + Definition string `db:"definition"` + Owner string `db:"owner"` + NotificationChannel string `db:"notification_channel"` + Comment string `db:"comment"` +} + +func ScanPipe(row *sqlx.Row) (*pipe, error) { + p := &pipe{} + e := row.StructScan(p) + return p, e +} diff --git a/pkg/snowflake/resource_monitor.go b/pkg/snowflake/resource_monitor.go index f09537b992..21c0837140 100644 --- a/pkg/snowflake/resource_monitor.go +++ b/pkg/snowflake/resource_monitor.go @@ -1,8 +1,11 @@ package snowflake import ( + "database/sql" "fmt" "strings" + + "github.com/jmoiron/sqlx" ) // ResourceMonitorBuilder extends the generic builder to provide support for triggers @@ -113,3 +116,26 @@ func (rcb *ResourceMonitorCreateBuilder) Statement() string { return sb.String() } + +type resourceMonitor struct { + Name sql.NullString `db:"name"` + CreditQuota sql.NullFloat64 `db:"credit_quota"` + UsedCredits sql.NullString `db:"used_credits"` + RemainingCredits sql.NullString `db:"remaining_credits"` + Level sql.NullString `db:"level"` + Frequency sql.NullString `db:"frequency"` + StartTime sql.NullString `db:"start_time"` + EndTime sql.NullString `db:"end_time"` + NotifyAt sql.NullString `db:"notify_at"` + SuspendAt sql.NullString `db:"suspend_at"` + SuspendImmediatelyAt sql.NullString `db:"suspend_immediately_at"` + CreatedOn sql.NullString `db:"created_on"` + Owner sql.NullString `db:"owner"` + Comment sql.NullString `db:"comment"` +} + +func ScanResourceMonitor(row *sqlx.Row) (*resourceMonitor, error) { + rm := &resourceMonitor{} + err := row.StructScan(rm) + return rm, err +} diff --git a/pkg/snowflake/role.go b/pkg/snowflake/role.go index 8145a6c891..c661fb60cc 100644 --- a/pkg/snowflake/role.go +++ b/pkg/snowflake/role.go @@ -1,8 +1,25 @@ package snowflake +import ( + "database/sql" + + "github.com/jmoiron/sqlx" +) + func Role(name string) *Builder { return &Builder{ entityType: RoleType, name: name, } } + +type role struct { + Name sql.NullString `db:"name"` + Comment sql.NullString `db:"comment"` +} + +func ScanRole(row *sqlx.Row) (*role, error) { + r := &role{} + err := row.StructScan(r) + return r, err +} diff --git a/pkg/snowflake/schema.go b/pkg/snowflake/schema.go index 5f8ee1a9a2..203c4782e4 100644 --- a/pkg/snowflake/schema.go +++ b/pkg/snowflake/schema.go @@ -1,8 +1,11 @@ package snowflake import ( + "database/sql" "fmt" "strings" + + "github.com/jmoiron/sqlx" ) // SchemaBuilder abstracts the creation of SQL queries for a Snowflake schema @@ -172,3 +175,17 @@ func (sb *SchemaBuilder) Show() string { return q.String() } + +type schema struct { + Name sql.NullString `db:"name"` + DatabaseName sql.NullString `db:"database_name"` + Comment sql.NullString `db:"comment"` + Options sql.NullString `db:"options"` + RetentionTime sql.NullInt64 `db:"retention_time"` +} + +func ScanSchema(row *sqlx.Row) (*schema, error) { + r := &schema{} + err := row.StructScan(r) + return r, err +} diff --git a/pkg/snowflake/share.go b/pkg/snowflake/share.go index 15fa50e115..8846b43495 100644 --- a/pkg/snowflake/share.go +++ b/pkg/snowflake/share.go @@ -1,5 +1,11 @@ package snowflake +import ( + "database/sql" + + "github.com/jmoiron/sqlx" +) + // Share returns a pointer to a Builder that abstracts the DDL operations for a share. // // Supported DDL operations are: @@ -16,3 +22,15 @@ func Share(name string) *Builder { name: name, } } + +type share struct { + Name sql.NullString `db:"name"` + To sql.NullString `db:"to"` + Comment sql.NullString `db:"comment"` +} + +func ScanShare(row *sqlx.Row) (*share, error) { + r := &share{} + err := row.StructScan(r) + return r, err +} diff --git a/pkg/snowflake/stage.go b/pkg/snowflake/stage.go index ffb08e1d34..474c78c2f0 100644 --- a/pkg/snowflake/stage.go +++ b/pkg/snowflake/stage.go @@ -1,8 +1,11 @@ package snowflake import ( + "database/sql" "fmt" "strings" + + "github.com/jmoiron/sqlx" ) // StageBuilder abstracts the creation of SQL queries for a Snowflake stage @@ -190,3 +193,75 @@ func (sb *StageBuilder) Describe() string { func (sb *StageBuilder) Show() string { return fmt.Sprintf(`SHOW STAGES LIKE '%v' IN DATABASE "%v"`, sb.name, sb.db) } + +type stage struct { + Name *string `db:"name"` + DatabaseName *string `db:"database_name"` + SchemaName *string `db:"schema_name"` + Comment *string `db:"comment"` + StorageIntegration *string `db:"storage_integration"` +} + +func ScanStageShow(row *sqlx.Row) (*stage, error) { + r := &stage{} + err := row.StructScan(r) + return r, err +} + +type descStageResult struct { + Url string + AwsExternalID string + SnowflakeIamUser string + FileFormat string + CopyOptions string +} + +type descStageRow struct { + ParentProperty string `db:"parent_property"` + Property string `db:"property"` + PropertyValue string `db:"property_value"` + PropertyDefault string `db:"property_default"` +} + +func DescStage(db *sql.DB, query string) (*descStageResult, error) { + r := &descStageResult{} + var ff []string + var co []string + rows, err := Query(db, query) + if err != nil { + return r, err + } + defer rows.Close() + + for rows.Next() { + + row := &descStageRow{} + if err := rows.StructScan(row); err != nil { + return r, err + } + + switch row.Property { + case "URL": + r.Url = strings.Trim(row.PropertyValue, "[\"]") + case "AWS_EXTERNAL_ID": + r.AwsExternalID = row.PropertyValue + case "SNOWFLAKE_IAM_USER": + r.SnowflakeIamUser = row.PropertyValue + } + + switch row.ParentProperty { + case "STAGE_FILE_FORMAT": + if row.PropertyValue != row.PropertyDefault { + ff = append(ff, fmt.Sprintf("%s = %s", row.Property, row.PropertyValue)) + } + case "STAGE_COPY_OPTIONS": + if row.PropertyValue != row.PropertyDefault { + co = append(co, fmt.Sprintf("%s = %s", row.Property, row.PropertyValue)) + } + } + } + + r.FileFormat = strings.Join(ff, " ") + r.CopyOptions = strings.Join(co, " ") + return r, nil +} diff --git a/pkg/snowflake/storage_integration.go b/pkg/snowflake/storage_integration.go index d13a505bb7..7a98d06e0c 100644 --- a/pkg/snowflake/storage_integration.go +++ b/pkg/snowflake/storage_integration.go @@ -1,5 +1,11 @@ package snowflake +import ( + "database/sql" + + "github.com/jmoiron/sqlx" +) + // StorageIntegration returns a pointer to a Builder that abstracts the DDL operations for a storage integration. // // Supported DDL operations are: @@ -16,3 +22,17 @@ func StorageIntegration(name string) *Builder { name: name, } } + +type storageIntegration struct { + Name sql.NullString `db:"name"` + Category sql.NullString `db:"category"` + IntegrationType sql.NullString `db:"integration_type"` + CreatedOn sql.NullString `db:"created_on"` + Enabled sql.NullBool `db:"enabled"` +} + +func ScanStorageIntegration(row *sqlx.Row) (*storageIntegration, error) { + r := &storageIntegration{} + err := row.StructScan(r) + return r, err +} diff --git a/pkg/snowflake/task.go b/pkg/snowflake/task.go new file mode 100644 index 0000000000..59e7a2df2c --- /dev/null +++ b/pkg/snowflake/task.go @@ -0,0 +1,348 @@ +package snowflake + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "github.com/jmoiron/sqlx" +) + +// TaskBuilder abstracts the creation of sql queries for a snowflake task +type TaskBuilder struct { + name string + db string + schema string + warehouse string + schedule string + session_parameters map[string]interface{} + user_task_timeout_ms int + comment string + after string + when string + sql_statement string + disabled bool +} + +// GetFullName prepends db and schema to in parameter +func (tb *TaskBuilder) GetFullName(in string) string { + var n strings.Builder + + n.WriteString(fmt.Sprintf(`"%v"."%v"."%v"`, tb.db, tb.schema, in)) + + return n.String() +} + +// QualifiedName prepends the db and schema and escapes everything nicely +func (tb *TaskBuilder) QualifiedName() string { + return tb.GetFullName(tb.name) +} + +// Name returns the name of the task +func (tb *TaskBuilder) Name() string { + return tb.name +} + +// WithWarehouse adds a warehouse to the TaskBuilder +func (tb *TaskBuilder) WithWarehouse(s string) *TaskBuilder { + tb.warehouse = s + return tb +} + +// WithSchedule adds a schedule to the TaskBuilder +func (tb *TaskBuilder) WithSchedule(s string) *TaskBuilder { + tb.schedule = s + return tb +} + +// WithSessionParameters adds session parameters to the TaskBuilder +func (tb *TaskBuilder) WithSessionParameters(params map[string]interface{}) *TaskBuilder { + tb.session_parameters = params + return tb +} + +// WithComment adds a comment to the TaskBuilder +func (tb *TaskBuilder) WithComment(c string) *TaskBuilder { + tb.comment = c + return tb +} + +// WithTimeout adds a timeout to the TaskBuilder +func (tb *TaskBuilder) WithTimeout(t int) *TaskBuilder { + tb.user_task_timeout_ms = t + return tb +} + +// WithDependency adds an after task dependency to the TaskBuilder +func (tb *TaskBuilder) WithDependency(after string) *TaskBuilder { + tb.after = after + return tb +} + +// WithCondition adds a when condition to the TaskBuilder +func (tb *TaskBuilder) WithCondition(when string) *TaskBuilder { + tb.when = when + return tb +} + +// WithStatement adds a sql statement to the TaskBuilder +func (tb *TaskBuilder) WithStatement(sql string) *TaskBuilder { + tb.sql_statement = sql + return tb +} + +// Task returns a pointer to a Builder that abstracts the DDL operations for a task. +// +// Supported DDL operations are: +// - CREATE TASK +// - ALTER TASK +// - DROP TASK +// - DESCRIBE TASK +// +// [Snowflake Reference](https://docs.snowflake.com/en/user-guide/tasks-intro.html#task-ddl) +func Task(name, db, schema string) *TaskBuilder { + return &TaskBuilder{ + name: name, + db: db, + schema: schema, + disabled: false, // helper for when started root or standalone task gets supspended + } +} + +// Create returns the SQL that will create a new task +func (tb *TaskBuilder) Create() string { + q := strings.Builder{} + q.WriteString(`CREATE`) + + q.WriteString(fmt.Sprintf(` TASK %v`, tb.QualifiedName())) + q.WriteString(fmt.Sprintf(` WAREHOUSE = "%v"`, EscapeString(tb.warehouse))) + + if tb.schedule != "" { + q.WriteString(fmt.Sprintf(` SCHEDULE = '%v'`, EscapeString(tb.schedule))) + } + + if len(tb.session_parameters) > 0 { + sp := make([]string, 0) + sortedKeys := make([]string, 0) + for k := range tb.session_parameters { + sortedKeys = append(sortedKeys, k) + } + sort.Strings(sortedKeys) + + for _, k := range sortedKeys { + sp = append(sp, EscapeString(fmt.Sprintf(`%v = "%v"`, k, tb.session_parameters[k]))) + } + q.WriteString(fmt.Sprintf(` %v`, strings.Join(sp, ", "))) + } + + if tb.comment != "" { + q.WriteString(fmt.Sprintf(` COMMENT = '%v'`, EscapeString(tb.comment))) + } + + if tb.user_task_timeout_ms > 0 { + q.WriteString(fmt.Sprintf(` USER_TASK_TIMEOUT_MS = %v`, tb.user_task_timeout_ms)) + } + + if tb.after != "" { + q.WriteString(fmt.Sprintf(` AFTER %v`, tb.GetFullName(tb.after))) + } + + if tb.when != "" { + q.WriteString(fmt.Sprintf(` WHEN %v`, tb.when)) + } + + if tb.sql_statement != "" { + q.WriteString(fmt.Sprintf(` AS %v`, UnescapeString(tb.sql_statement))) + } + + return q.String() +} + +// ChangeWarehouse returns the sql that will change the warehouse for the task. +func (tb *TaskBuilder) ChangeWarehouse(newWh string) string { + return fmt.Sprintf(`ALTER TASK %v SET WAREHOUSE = "%v"`, tb.QualifiedName(), EscapeString(newWh)) +} + +// ChangeSchedule returns the sql that will change the schedule for the task. +func (tb *TaskBuilder) ChangeSchedule(newSchedule string) string { + return fmt.Sprintf(`ALTER TASK %v SET SCHEDULE = '%v'`, tb.QualifiedName(), EscapeString(newSchedule)) +} + +// RemoveSchedule returns the sql that will remove the schedule for the task. +func (tb *TaskBuilder) RemoveSchedule() string { + return fmt.Sprintf(`ALTER TASK %v UNSET SCHEDULE`, tb.QualifiedName()) +} + +// ChangeTimeout returns the sql that will change the user task timeout for the task. +func (tb *TaskBuilder) ChangeTimeout(newTimeout int) string { + return fmt.Sprintf(`ALTER TASK %v SET USER_TASK_TIMEOUT_MS = %v`, tb.QualifiedName(), newTimeout) +} + +// RemoveTimeout returns the sql that will remove the user task timeout for the task. +func (tb *TaskBuilder) RemoveTimeout() string { + return fmt.Sprintf(`ALTER TASK %v UNSET USER_TASK_TIMEOUT_MS`, tb.QualifiedName()) +} + +// ChangeComment returns the sql that will change the comment for the task. +func (tb *TaskBuilder) ChangeComment(newComment string) string { + return fmt.Sprintf(`ALTER TASK %v SET COMMENT = '%v'`, tb.QualifiedName(), EscapeString(newComment)) +} + +// RemoveComment returns the sql that will remove the comment for the task. +func (tb *TaskBuilder) RemoveComment() string { + return fmt.Sprintf(`ALTER TASK %v UNSET COMMENT`, tb.QualifiedName()) +} + +// AddDependency returns the sql that will add the after dependency for the task. +func (tb *TaskBuilder) AddDependency(after string) string { + return fmt.Sprintf(`ALTER TASK %v ADD AFTER %v`, tb.QualifiedName(), tb.GetFullName(after)) +} + +// RemoveDependency returns the sql that will remove the after dependency for the task. +func (tb *TaskBuilder) RemoveDependency(after string) string { + return fmt.Sprintf(`ALTER TASK %v REMOVE AFTER %v`, tb.QualifiedName(), tb.GetFullName(after)) +} + +// AddSessionParameters returns the sql that will remove the session parameters for the task +func (tb *TaskBuilder) AddSessionParameters(params map[string]interface{}) string { + p := make([]string, 0) + sortedKeys := make([]string, 0) + for k := range params { + sortedKeys = append(sortedKeys, k) + } + sort.Strings(sortedKeys) + + for _, k := range sortedKeys { + p = append(p, EscapeString(fmt.Sprintf(`%v = "%v"`, k, params[k]))) + } + + return fmt.Sprintf(`ALTER TASK %v SET %v`, tb.QualifiedName(), strings.Join(p, ", ")) +} + +// RemoveSessionParameters returns the sql that will remove the session parameters for the task +func (tb *TaskBuilder) RemoveSessionParameters(params map[string]interface{}) string { + sortedKeys := make([]string, 0) + for k := range params { + sortedKeys = append(sortedKeys, k) + } + sort.Strings(sortedKeys) + + return fmt.Sprintf(`ALTER TASK %v UNSET %v`, tb.QualifiedName(), strings.Join(sortedKeys, ", ")) +} + +// ChangeCondition returns the sql that will update the when condition for the task. +func (tb *TaskBuilder) ChangeCondition(newCondition string) string { + return fmt.Sprintf(`ALTER TASK %v MODIFY WHEN %v`, tb.QualifiedName(), newCondition) +} + +// ChangeSqlStatement returns the sql that will update the sql the task executes. +func (tb *TaskBuilder) ChangeSqlStatement(newStatement string) string { + return fmt.Sprintf(`ALTER TASK %v MODIFY AS %v`, tb.QualifiedName(), UnescapeString(newStatement)) +} + +// Suspend returns the sql that will suspend the task. +func (tb *TaskBuilder) Suspend() string { + return fmt.Sprintf(`ALTER TASK %v SUSPEND`, tb.QualifiedName()) +} + +// Resume returns the sql that will resume the task. +func (tb *TaskBuilder) Resume() string { + return fmt.Sprintf(`ALTER TASK %v RESUME`, tb.QualifiedName()) +} + +// Drop returns the sql that will remove the task. +func (tb *TaskBuilder) Drop() string { + return fmt.Sprintf(`DROP TASK %v`, tb.QualifiedName()) +} + +// Describe returns the sql that will describe a task. +func (tb *TaskBuilder) Describe() string { + return fmt.Sprintf(`DESCRIBE TASK %v`, tb.QualifiedName()) +} + +// Show returns the sql that will show a task. +func (tb *TaskBuilder) Show() string { + return fmt.Sprintf(`SHOW TASKS LIKE '%v' IN DATABASE "%v"`, EscapeString(tb.name), EscapeString(tb.db)) +} + +// ShowParameters returns the query to show the session parameters for the task +func (tb *TaskBuilder) ShowParameters() string { + return fmt.Sprintf(`SHOW PARAMETERS IN TASK %v`, tb.QualifiedName()) +} + +// SetDisabled disables the task builder +func (tb *TaskBuilder) SetDisabled() *TaskBuilder { + tb.disabled = true + return tb +} + +// IsDisabled returns if the task builder is disabled +func (tb *TaskBuilder) IsDisabled() bool { + return tb.disabled +} + +type task struct { + Id string `db:"id"` + CreatedOn string `db:"created_on"` + Name string `db:"name"` + DatabaseName string `db:"database_name"` + SchemaName string `db:"schema_name"` + Owner string `db:"owner"` + Comment *string `db:"comment"` + Warehouse string `db:"warehouse"` + Schedule *string `db:"schedule"` + Predecessors *string `db:"predecessors"` + State string `db:"state"` + Definition string `db:"definition"` + Condition *string `db:"condition"` +} + +func (t *task) IsEnabled() bool { + return strings.ToLower(t.State) == "started" +} + +func (t *task) GetPredecessorName() string { + if t.Predecessors == nil { + return "" + } + + pre := strings.Split(*t.Predecessors, ".") + name, err := strconv.Unquote(pre[len(pre)-1]) + if err != nil { + return pre[len(pre)-1] + } + return name +} + +// ScanTask turns a sql row into a task object +func ScanTask(row *sqlx.Row) (*task, error) { + t := &task{} + e := row.StructScan(t) + return t, e +} + +// taskParams struct to represent a row of parameters +type taskParams struct { + Key string `db:"key"` + Value string `db:"value"` + DefaultValue string `db:"default"` + Level string `db:"level"` + Description string `db:"description"` +} + +// ScanTaskParameters takes a database row and converts it to a task parameter pointer +func ScanTaskParameters(rows *sqlx.Rows) ([]*taskParams, error) { + t := []*taskParams{} + + for rows.Next() { + r := &taskParams{} + err := rows.StructScan(r) + if err != nil { + return nil, err + } + t = append(t, r) + + } + return t, nil +} diff --git a/pkg/snowflake/task_test.go b/pkg/snowflake/task_test.go new file mode 100644 index 0000000000..6a943b4c3a --- /dev/null +++ b/pkg/snowflake/task_test.go @@ -0,0 +1,153 @@ +package snowflake + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTaskCreate(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.QualifiedName(), `"test_db"."test_schema"."test_task"`) + + st.WithWarehouse("test_wh") + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh"`) + + st.WithSchedule("USING CRON 0 9-17 * * SUN America/Los_Angeles") + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles'`) + + st.WithSessionParameters(map[string]interface{}{"TIMESTAMP_INPUT_FORMAT": "YYYY-MM-DD HH24"}) + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles' TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24"`) + + st.WithComment("test comment") + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles' TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24" COMMENT = 'test comment'`) + + st.WithTimeout(12) + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles' TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24" COMMENT = 'test comment' USER_TASK_TIMEOUT_MS = 12`) + + st.WithDependency("other_task") + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles' TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24" COMMENT = 'test comment' USER_TASK_TIMEOUT_MS = 12 AFTER "test_db"."test_schema"."other_task"`) + + st.WithCondition("SYSTEM$STREAM_HAS_DATA('MYSTREAM')") + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles' TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24" COMMENT = 'test comment' USER_TASK_TIMEOUT_MS = 12 AFTER "test_db"."test_schema"."other_task" WHEN SYSTEM$STREAM_HAS_DATA('MYSTREAM')`) + + st.WithStatement("SELECT * FROM table WHERE column = 'name'") + r.Equal(st.Create(), `CREATE TASK "test_db"."test_schema"."test_task" WAREHOUSE = "test_wh" SCHEDULE = 'USING CRON 0 9-17 * * SUN America/Los_Angeles' TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24" COMMENT = 'test comment' USER_TASK_TIMEOUT_MS = 12 AFTER "test_db"."test_schema"."other_task" WHEN SYSTEM$STREAM_HAS_DATA('MYSTREAM') AS SELECT * FROM table WHERE column = 'name'`) +} + +func TestChangeWarehouse(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ChangeWarehouse("much_wh"), `ALTER TASK "test_db"."test_schema"."test_task" SET WAREHOUSE = "much_wh"`) +} + +func TestChangeSchedule(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ChangeSchedule("USING CRON 0 9-17 * * SUN America/New_York"), `ALTER TASK "test_db"."test_schema"."test_task" SET SCHEDULE = 'USING CRON 0 9-17 * * SUN America/New_York'`) +} + +func TestRemoveSchedule(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.RemoveSchedule(), `ALTER TASK "test_db"."test_schema"."test_task" UNSET SCHEDULE`) +} + +func TestChangeTimeout(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ChangeTimeout(100), `ALTER TASK "test_db"."test_schema"."test_task" SET USER_TASK_TIMEOUT_MS = 100`) +} + +func TestRemoveTimeout(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.RemoveTimeout(), `ALTER TASK "test_db"."test_schema"."test_task" UNSET USER_TASK_TIMEOUT_MS`) +} + +func TestChangeComment(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ChangeComment("much comment wow"), `ALTER TASK "test_db"."test_schema"."test_task" SET COMMENT = 'much comment wow'`) +} + +func TestRemoveComment(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.RemoveComment(), `ALTER TASK "test_db"."test_schema"."test_task" UNSET COMMENT`) +} + +func TestAddDependency(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.AddDependency("other_task"), `ALTER TASK "test_db"."test_schema"."test_task" ADD AFTER "test_db"."test_schema"."other_task"`) +} + +func TestRemoveDependency(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.RemoveDependency("first_me_task"), `ALTER TASK "test_db"."test_schema"."test_task" REMOVE AFTER "test_db"."test_schema"."first_me_task"`) +} + +func TestAddSessionParameters(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + params := map[string]interface{}{"TIMESTAMP_INPUT_FORMAT": "YYYY-MM-DD HH24", "CLIENT_TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_LTZ"} + r.Equal(st.AddSessionParameters(params), `ALTER TASK "test_db"."test_schema"."test_task" SET CLIENT_TIMESTAMP_TYPE_MAPPING = "TIMESTAMP_LTZ", TIMESTAMP_INPUT_FORMAT = "YYYY-MM-DD HH24"`) +} + +func TestRemoveSessionParameters(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + params := map[string]interface{}{"TIMESTAMP_INPUT_FORMAT": "YYYY-MM-DD HH24", "CLIENT_TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_LTZ"} + r.Equal(st.RemoveSessionParameters(params), `ALTER TASK "test_db"."test_schema"."test_task" UNSET CLIENT_TIMESTAMP_TYPE_MAPPING, TIMESTAMP_INPUT_FORMAT`) +} + +func TestChangeCondition(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ChangeCondition("TRUE = TRUE"), `ALTER TASK "test_db"."test_schema"."test_task" MODIFY WHEN TRUE = TRUE`) +} + +func TestChangeSqlStatement(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ChangeSqlStatement("SELECT * FROM table"), `ALTER TASK "test_db"."test_schema"."test_task" MODIFY AS SELECT * FROM table`) +} + +func TestSuspend(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.Suspend(), `ALTER TASK "test_db"."test_schema"."test_task" SUSPEND`) +} + +func TestResume(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.Resume(), `ALTER TASK "test_db"."test_schema"."test_task" RESUME`) +} + +func TestShowParameters(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.ShowParameters(), `SHOW PARAMETERS IN TASK "test_db"."test_schema"."test_task"`) +} + +func TestDrop(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.Drop(), `DROP TASK "test_db"."test_schema"."test_task"`) +} + +func TestDescribe(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.Describe(), `DESCRIBE TASK "test_db"."test_schema"."test_task"`) +} + +func TestShow(t *testing.T) { + r := require.New(t) + st := Task("test_task", "test_db", "test_schema") + r.Equal(st.Show(), `SHOW TASKS LIKE 'test_task' IN DATABASE "test_db"`) +} diff --git a/pkg/snowflake/user.go b/pkg/snowflake/user.go index 64b5c5af05..8d3f2c9e66 100644 --- a/pkg/snowflake/user.go +++ b/pkg/snowflake/user.go @@ -1,8 +1,31 @@ package snowflake +import ( + "database/sql" + + "github.com/jmoiron/sqlx" +) + func User(name string) *Builder { return &Builder{ entityType: UserType, name: name, } } + +type user struct { + Comment sql.NullString `db:"comment"` + DefaultNamespace sql.NullString `db:"default_namespace"` + DefaultRole sql.NullString `db:"default_role"` + DefaultWarehouse sql.NullString `db:"default_warehouse"` + Disabled bool `db:"disabled"` + HasRsaPublicKey bool `db:"has_rsa_public_key"` + LoginName sql.NullString `db:"login_name"` + Name sql.NullString `db:"name"` +} + +func ScanUser(row *sqlx.Row) (*user, error) { + r := &user{} + err := row.StructScan(r) + return r, err +} diff --git a/pkg/snowflake/view.go b/pkg/snowflake/view.go index d88f5941b5..7750d3e339 100644 --- a/pkg/snowflake/view.go +++ b/pkg/snowflake/view.go @@ -1,8 +1,11 @@ package snowflake import ( + "database/sql" "fmt" "strings" + + "github.com/jmoiron/sqlx" ) // ViewBuilder abstracts the creation of SQL queries for a Snowflake View @@ -146,3 +149,18 @@ func (vb *ViewBuilder) Show() string { func (vb *ViewBuilder) Drop() string { return fmt.Sprintf(`DROP VIEW %v`, vb.QualifiedName()) } + +type view struct { + Comment sql.NullString `db:"comment"` + IsSecure bool `db:"is_secure"` + Name sql.NullString `db:"name"` + SchemaName sql.NullString `db:"schema_name"` + Text sql.NullString `db:"text"` + DatabaseName sql.NullString `db:"database_name"` +} + +func ScanView(row *sqlx.Row) (*view, error) { + r := &view{} + err := row.StructScan(r) + return r, err +} diff --git a/pkg/snowflake/warehouse.go b/pkg/snowflake/warehouse.go index 0741536b1b..8ed466ab77 100644 --- a/pkg/snowflake/warehouse.go +++ b/pkg/snowflake/warehouse.go @@ -1,8 +1,54 @@ package snowflake +import ( + "time" + + "github.com/jmoiron/sqlx" +) + func Warehouse(name string) *Builder { return &Builder{ name: name, entityType: WarehouseType, } } + +// warehouse is a go representation of a grant that can be used in conjunction +// with github.com/jmoiron/sqlx +type warehouse struct { + Name string `db:"name"` + State string `db:"state"` + Type string `db:"type"` + Size string `db:"size"` + MinClusterCount int64 `db:"min_cluster_count"` + MaxClusterCount int64 `db:"max_cluster_count"` + StartedClusters int64 `db:"started_clusters"` + Running int64 `db:"running"` + Queued int64 `db:"queued"` + IsDefault string `db:"is_default"` + IsCurrent string `db:"is_current"` + AutoSuspend int64 `db:"auto_suspend"` + AutoResume bool `db:"auto_resume"` + Available string `db:"available"` + Provisioning string `db:"provisioning"` + Quiescing string `db:"quiescing"` + Other string `db:"other"` + CreatedOn time.Time `db:"created_on"` + ResumedOn time.Time `db:"resumed_on"` + UpdatedOn time.Time `db:"updated_on"` + Owner string `db:"owner"` + Comment string `db:"comment"` + ResourceMonitor string `db:"resource_monitor"` + Actives int64 `db:"actives"` + Pendings int64 `db:"pendings"` + Failed int64 `db:"failed"` + Suspended int64 `db:"suspended"` + UUID string `db:"uuid"` + ScalingPolicy string `db:"scaling_policy"` +} + +func ScanWarehouse(row *sqlx.Row) (*warehouse, error) { + w := &warehouse{} + err := row.StructScan(w) + return w, err +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 0f601b1fcf..f37d99d0c2 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,16 +1 @@ package version - -import ( - "github.com/chanzuckerberg/go-misc/ver" -) - -var ( - Version = "undefined" - GitSha = "undefined" - Release = "false" - Dirty = "true" -) - -func VersionString() (string, error) { - return ver.VersionString(Version, GitSha, Release, Dirty) -}