diff --git a/.github/workflows/run-integ-tests.yaml b/.github/workflows/run-integ-tests.yaml index bf040c86c..e31a4797e 100644 --- a/.github/workflows/run-integ-tests.yaml +++ b/.github/workflows/run-integ-tests.yaml @@ -15,11 +15,6 @@ on: description: comma-delimited list of dirs within test-app-repo to run (if empty, runs all) required: false type: string - klotho-login: - description: email address to log into Klotho - required: true - type: string - default: klotho-engineering@klo.dev region: description: the AWS region to deploy to, other than redis tests required: false @@ -46,10 +41,6 @@ on: description: comma-delimited list of dirs within test-app-repo required: false type: string - klotho-login: - description: email address to log into Klotho - required: true - type: string region: description: the AWS region to deploy to required: false @@ -166,9 +157,7 @@ jobs: run: | curl -fsSL http://srv.klo.dev/update/latest/linux/amd64 -o "$RUNNER_TEMP/klotho-old" chmod +x "$RUNNER_TEMP/klotho-old" - "$RUNNER_TEMP/klotho-old" --login "$KLOTHO_LOGIN" - env: - KLOTHO_LOGIN: ${{ inputs.klotho-login }} + "$RUNNER_TEMP/klotho-old" --login 'klotho-engineering@klo.dev' - name: download klotho uses: actions/download-artifact@v3 with: @@ -184,9 +173,6 @@ jobs: - name: install klotho run: | chmod +x $RUNNER_TEMP/klotho - klotho --login $KLOTHO_LOGIN - env: - KLOTHO_LOGIN: ${{ inputs.klotho-login }} - name: typescript compilation if: steps.get_language.outputs.language == 'ts' working-directory: ${{ matrix.app_to_test }} @@ -205,6 +191,8 @@ jobs: else klotho . --app $STACK_NAME -p aws fi + env: + KLOTHO_ID_TOKEN: ${{ secrets.KLOTHO_CREDS_ID_TOKEN }} - name: pulumi npm install working-directory: ${{ matrix.app_to_test }} run: | @@ -282,6 +270,8 @@ jobs: else klotho . --app $STACK_NAME -p aws fi + env: + KLOTHO_ID_TOKEN: ${{ secrets.KLOTHO_CREDS_ID_TOKEN }} - name: pulumi npm install (upgrade path) if: matrix.mode == 'upgrade' working-directory: ${{ matrix.app_to_test }} diff --git a/README.md b/README.md index 581f06258..c4d4e2aa5 100644 --- a/README.md +++ b/README.md @@ -214,3 +214,4 @@ These providers are not yet supported but are in design and development * to run integration tests against a branch, navigate to the [run-integ-tests.yaml](https://github.com/klothoplatform/klotho/actions/workflows/run-integ-tests.yaml) action and click the "run workflow ▾" button. Select your branch, optionally fill in or change any of the parameters, and then click the "run workflow" button. * For security reasons, only authorized members of the team may do this. You can run integration tests on your own fork, providing your own AWS and Pulumi credentials. * Note that the nightly integration tests are [a different workflow](https://github.com/klothoplatform/klotho/actions/workflows/nightly-integ-tests.yaml). Authorized members of the team can manually kick off a run of that workflow, but it doesn't take any inputs. The nightly integration tests workflow simply invokes the run-integ-tests.yaml workflow, so they effectively do the same thing. + * The tests use a Klothoh login token that's stored as a GH Action secret within the `integ-test` environment. The login credentials are in BitWarden, under the "GitHub CI/CD login" entry. diff --git a/cmd/klotho/main.go b/cmd/klotho/main.go index c114910c4..9042cd21d 100644 --- a/cmd/klotho/main.go +++ b/cmd/klotho/main.go @@ -1,16 +1,35 @@ package main import ( + "github.com/klothoplatform/klotho/pkg/auth" "github.com/klothoplatform/klotho/pkg/cli" + "github.com/spf13/pflag" ) func main() { + authRequirement := LocalAuth(false) km := cli.KlothoMain{ DefaultUpdateStream: "open:latest", Version: Version, PluginSetup: func(psb *cli.PluginSetBuilder) error { return psb.AddAll() }, + Authorizer: &authRequirement, } + km.Main() } + +// LocalAuth is an auth.Authorizer that requires login unless its value is true. +type LocalAuth bool + +func (local *LocalAuth) SetUpCliFlags(flags *pflag.FlagSet) { + flags.BoolVar((*bool)(local), "local", bool(*local), "If provided, runs Klotho with a local login (that is, not requiring an authenticated login)") +} + +func (local *LocalAuth) Authorize() (*auth.KlothoClaims, error) { + if !*local { + return auth.Authorize() + } + return nil, nil +} diff --git a/go.mod b/go.mod index 65dab23ac..cd163dae6 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,12 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/fatih/color v1.13.0 github.com/gojek/heimdall/v7 v7.0.2 + github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/uuid v1.3.0 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54 github.com/pborman/ansi v1.0.0 - github.com/pelletier/go-toml/v2 v2.0.0-beta.6 + github.com/pelletier/go-toml/v2 v2.0.6 github.com/pkg/errors v0.9.1 github.com/smacker/go-tree-sitter v0.0.0-20220209044044-0d3022e933c3 github.com/spf13/cobra v1.6.1 @@ -29,6 +30,13 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require ( + github.com/gomodule/redigo v2.0.0+incompatible // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + golang.org/x/oauth2 v0.4.0 // indirect +) + replace github.com/smacker/go-tree-sitter => github.com/klothoplatform/go-tree-sitter v0.1.1 require ( @@ -85,7 +93,7 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -102,6 +110,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -122,7 +131,6 @@ require ( go.uber.org/multierr v1.7.0 // indirect golang.org/x/crypto v0.5.0 // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect diff --git a/go.sum b/go.sum index 500852d24..fb1cc40d3 100644 --- a/go.sum +++ b/go.sum @@ -259,6 +259,8 @@ github.com/gojek/heimdall/v7 v7.0.2 h1:+YutGXZ8oEWbCJIwjRnkKmoTl+Oxt1Urs3hc/FR0s github.com/gojek/heimdall/v7 v7.0.2/go.mod h1:Z43HtMid7ysSjmsedPTXAki6jcdcNVnjn5pmsTyiMic= github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf h1:5xRGbUdOmZKoDXkGx5evVLehuCMpuO1hl701bEQqXOM= github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -293,7 +295,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 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= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= @@ -436,8 +439,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -473,16 +477,18 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -560,11 +566,13 @@ github.com/pborman/ansi v1.0.0 h1:OqjHMhvlSuCCV5JT07yqPuJPQzQl+WXsiZ14gZsqOrQ= github.com/pborman/ansi v1.0.0/go.mod h1:SgWzwMAx1X/Ez7i90VqF8LRiQtx52pWDiQP+x3iGnzw= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE= -github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -613,6 +621,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU= github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y= @@ -671,7 +681,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -933,6 +942,7 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -941,6 +951,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/analytics/client.go b/pkg/analytics/client.go index 1eba3e730..c5c1d417e 100644 --- a/pkg/analytics/client.go +++ b/pkg/analytics/client.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "github.com/klothoplatform/klotho/pkg/auth" "github.com/google/uuid" "github.com/klothoplatform/klotho/pkg/core" @@ -36,38 +37,29 @@ var ( var datadogLogLevel = "_logLevel" var datadogStatus = "status" -func NewClient(properties map[string]interface{}) (*Client, error) { - result, err := getTrackingFileContents(analyticsFile) - if err != nil { - return nil, err - } - user := RetrieveUser(result) - if user == nil { - return nil, errors.New("required user info not set") - } - - err = user.RegisterUser() - if err != nil { - return nil, err - } +func NewClient(properties map[string]interface{}) *Client { + local := GetOrCreateAnalyticsFile() client := &Client{ Properties: properties, } - if user.Email != "" { - client.UserId = user.Email - client.Properties["validated"] = user.Validated - if user.Id != "" { - client.Properties["localId"] = user.Id - } - } else { - client.UserId = user.Id - } + + // These will get validated in AttachAuthorizations + client.UserId = local.Id + client.Properties["validated"] = false + + client.Properties["localId"] = local.Id if runUuid, err := uuid.NewRandom(); err == nil { client.Properties["runId"] = runUuid.String() } - return client, nil + return client +} + +func (t *Client) AttachAuthorizations(claims *auth.KlothoClaims) { + t.Properties["localId"] = t.UserId + t.UserId = claims.Email + t.Properties["validated"] = claims.EmailVerified } func (t *Client) Info(event string) { diff --git a/pkg/analytics/tracking_utils.go b/pkg/analytics/tracking_utils.go index fb4e03033..41c06d8fe 100644 --- a/pkg/analytics/tracking_utils.go +++ b/pkg/analytics/tracking_utils.go @@ -5,10 +5,8 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/klothoplatform/klotho/pkg/cli_config" "math" "net/http" - "os" "time" "github.com/klothoplatform/klotho/pkg/core" @@ -17,8 +15,7 @@ import ( var kloServerUrl = "http://srv.klo.dev" type AnalyticsFile struct { - Email string - Id string + Id string } func SendTrackingToServer(bundle *Client) error { @@ -72,20 +69,3 @@ func CompressFiles(input *core.InputFiles) ([]byte, error) { return buf.Bytes(), err } - -func getTrackingFileContents(file string) (AnalyticsFile, error) { - configPath, err := cli_config.KlothoConfigPath(file) - result := AnalyticsFile{} - - if err != nil { - return result, err - } - - content, err := os.ReadFile(configPath) - if err != nil { - return result, err - } - err = json.Unmarshal(content, &result) - - return result, err -} diff --git a/pkg/analytics/user.go b/pkg/analytics/user.go index e81a5b062..75c73ec22 100644 --- a/pkg/analytics/user.go +++ b/pkg/analytics/user.go @@ -1,18 +1,11 @@ package analytics import ( - "bytes" "encoding/json" - "errors" - "fmt" - "github.com/klothoplatform/klotho/pkg/cli_config" - "net/http" - "net/mail" - "os" - - "github.com/fatih/color" "github.com/google/uuid" + "github.com/klothoplatform/klotho/pkg/cli_config" "go.uber.org/zap" + "os" ) type User struct { @@ -30,184 +23,49 @@ type Validated struct { // located in ~/.klotho/ var analyticsFile = "analytics.json" -func CreateUser(email string) error { - +func GetOrCreateAnalyticsFile() AnalyticsFile { // Check if the analytics file exists. If it does, try retrieving the user. // If it doesn't or we error because the data is invalid, it's fine. // We will create the new user and override the invalid or non-existent file - result, err := getTrackingFileContents(analyticsFile) - var existUser *User - if err == nil { - existUser = RetrieveUser(result) - } - - user := User{} - if email == "local" { - // login local will wipe an existing set email, but we want to preserve any set uuid - if existUser != nil { - user.Id = existUser.Id - } else { - user.Id = uuid.New().String() - } - printLocalLoginMessage() - } else { - addr, err := mail.ParseAddress(email) - if err != nil { - return err - } - - if existUser == nil { - user.Email = addr.Address - if err := user.SendUserEmailValidation(); err != nil { - return err - } - printEmailLoginMessage(user.Email) - } else { - // preserve the uuid if it was set before - user.Id = existUser.Id - user.Email = addr.Address - validated := false - // Determine if the address provided is new or the same and if we need to do any validation - if existUser.Email == addr.Address { - validated = existUser.Validated - } else { - if v, err := user.CheckUserEmailValidation(); err != nil { - zap.L().Warn("Failed to validate email with server") - } else { - user.Validated = v.Validated - } - validated = user.Validated - } - - if validated { - printEmailLoginMessage(user.Email) - } else { - if err := user.SendUserEmailValidation(); err != nil { - return err - } - printEmailLoginMessage(user.Email) - } - } - } - - configPath, err := cli_config.KlothoConfigPath(analyticsFile) - if err != nil { - return err - } - return user.writeConfig(configPath) -} - -func RetrieveUser(result AnalyticsFile) *User { - user := User{} - - if result.Email != "" { - user.Email = result.Email - if v, err := user.CheckUserEmailValidation(); err != nil { - zap.L().Warn("Failed to validate email with server") - } else { - user.Validated = v.Validated - } - } - if result.Id != "" { - user.Id = result.Id - } - if (User{} == user) { - return nil - } - return &user -} - -func (u *User) CheckUserEmailValidation() (*Validated, error) { - postBody, err := json.Marshal(u) - if err != nil { - return nil, err - } - data := bytes.NewBuffer(postBody) - resp, err := http.Post(fmt.Sprintf("%v/user/check-validation", kloServerUrl), "application/json", data) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, errors.New("failed to check user validation") + localLogin, err := getTrackingFileContents(analyticsFile) + if err == nil { + return localLogin } + login := AnalyticsFile{Id: uuid.New().String()} - defer resp.Body.Close() - - validated := Validated{} - - err = json.NewDecoder(resp.Body).Decode(&validated) - + // Try to write the file, but don't let any errors stop us + err = writeTrackingFileContents(analyticsFile, AnalyticsFile{Id: login.Id}) if err != nil { - return nil, err + zap.L().Debug("Couldn't write local analytics state", zap.Error(err)) } - - return &validated, nil + return login } - -func (u *User) SendUserEmailValidation() error { - postBody, _ := json.Marshal(u) - data := bytes.NewBuffer(postBody) - resp, err := http.Post(fmt.Sprintf("%v/user/send-validation", kloServerUrl), "application/json", data) +func getTrackingFileContents(file string) (AnalyticsFile, error) { + configPath, err := cli_config.KlothoConfigPath(file) + result := AnalyticsFile{} if err != nil { - return err + return result, err } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return errors.New("failed to send user validation email") - } - - return nil -} - -func (user *User) writeConfig(configPath string) error { - content, err := json.Marshal(user) + content, err := os.ReadFile(configPath) if err != nil { - return err + return result, err } + err = json.Unmarshal(content, &result) - return os.WriteFile(configPath, content, 0660) + return result, err } -func (u *User) RegisterUser() error { - - postBody, err := json.Marshal(u) +func writeTrackingFileContents(file string, contents AnalyticsFile) error { + configPath, err := cli_config.KlothoConfigPath(file) if err != nil { return err } - - data := bytes.NewBuffer(postBody) - resp, err := http.Post(fmt.Sprintf("%v/analytics/user", kloServerUrl), "application/json", data) + loginJson, err := json.Marshal(contents) if err != nil { return err } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("non 200 status code: %v", resp.StatusCode) - } - - return nil -} - -func printLocalLoginMessage() { - color.New(color.FgHiGreen).Println("Success: Logged in as local user") - color.New(color.FgYellow).Println( - "If you would like to \n", - " \u2022 Receive support with klotho issues\n", - " \u2022 Help shape the future of the product\n", - " \u2022 Access features like the developer console", - ) - color.New(color.FgHiBlue).Println( - "run:\n", - " $ klotho --login ", - ) -} - -func printEmailLoginMessage(email string) { - color.New(color.FgHiGreen).Printf("Success: Logged in as %s\n\n", email) + return os.WriteFile(configPath, loginJson, 0660) } diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 000000000..25fb5eedd --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,275 @@ +package auth + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "github.com/klothoplatform/klotho/pkg/closenicely" + "github.com/pkg/errors" + "io" + "log" + "net/http" + "net/url" + "os" + "path" + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/klothoplatform/klotho/pkg/cli_config" + "github.com/pkg/browser" + "go.uber.org/zap" +) + +var ( + authUrlBase = cli_config.EnvVar("KLOTHO_AUTH_BASE").GetOr(`http://klotho-auth-service-alb-e22c092-466389525.us-east-1.elb.amazonaws.com`) + pemUrl = cli_config.EnvVar("KLOTHO_AUTH_PEM").GetOr(`https://klotho.us.auth0.com/pem`) + ErrNoCredentialsFile = errors.New("no local credentials file") + ErrEmailUnverified = errors.New("login email hasn't been verified") +) + +type LoginResponse struct { + Url string + State string +} + +type Auth0Authorizer struct{} + +func (s Auth0Authorizer) Authorize() (*KlothoClaims, error) { + return Authorize() +} + +func Login(onError func(error) error) error { + state, err := CallLoginEndpoint() + if err != nil { + return onError(err) + } + err = CallGetTokenEndpoint(state) + if err != nil { + return onError(err) + } + return nil +} + +func CallLoginEndpoint() (string, error) { + res, err := http.Get(authUrlBase + "/login") + if err != nil { + return "", err + } + defer closenicely.OrDebug(res.Body) + if res.StatusCode < 200 || res.StatusCode >= 300 { + return "", errors.Errorf(`received %v from auth server`, res.StatusCode) + } + body, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + result := LoginResponse{} + err = json.Unmarshal(body, &result) + if err != nil { + return "", err + } + err = browser.OpenURL(result.Url) + if err != nil { + return "", err + } + return result.State, nil +} + +func CallGetTokenEndpoint(state string) error { + values := map[string]string{"state": state} + jsonData, err := json.Marshal(values) + if err != nil { + log.Fatal(err) + } + res, err := http.Post(authUrlBase+"/logintoken", "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + return err + } + defer closenicely.OrDebug(res.Body) + if res.StatusCode != 200 { + return fmt.Errorf("recieved invalid status code %d", res.StatusCode) + } + body, err := io.ReadAll(res.Body) + if err != nil { + return err + } + err = WriteIDToken(string(body)) + if err != nil { + return err + } + return nil +} + +func CallLogoutEndpoint() error { + res, err := http.Get(authUrlBase + "/logout") + if err != nil { + return errors.Wrap(err, "couldn't invoke logout URL") + } + defer closenicely.OrDebug(res.Body) + body, err := io.ReadAll(res.Body) + if err != nil { + return errors.Wrap(err, "couldn't read logout redirect URL") + } + err = browser.OpenURL(string(body)) + if err != nil { + zap.S().Debug("couldn't open logout URL: %s", string(body)) + zap.L().Warn("couldn't open logout URL. If this persists, run with --verbose to see it. Will still clear local credentials.") + } + + configPath, err := cli_config.KlothoConfigPath("credentials.json") + if err != nil { + return err + } + if _, err := os.Stat(configPath); err == nil { + err = os.Remove(configPath) + if err != nil { + return err + } + } + return nil +} + +func CallRefreshToken(token string) error { + values := map[string]string{"refresh_token": token} + jsonData, err := json.Marshal(values) + if err != nil { + return err + } + res, err := http.Post(authUrlBase+"/refresh", "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + return err + } + defer closenicely.OrDebug(res.Body) + body, err := io.ReadAll(res.Body) + if err != nil { + return err + } + err = WriteIDToken(string(body)) + if err != nil { + return err + } + return nil +} + +type KlothoClaims struct { + ProEnabled bool + ProTier int + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Name string `json:"name"` + jwt.RegisteredClaims +} + +func Authorize() (*KlothoClaims, error) { + return authorize(false) +} + +func authorize(tokenRefreshed bool) (*KlothoClaims, error) { + creds, claims, err := getClaims() + if err != nil { + return nil, err + } + + if !claims.EmailVerified { + if tokenRefreshed { + return claims, ErrEmailUnverified + } + err := CallRefreshToken(creds.RefreshToken) + if err != nil { + return claims, err + } + claims, err = authorize(true) + if err != nil { + return claims, err + } + } else if !claims.ProEnabled { + return claims, fmt.Errorf("user %s is not authorized to use KlothoPro", claims.Email) + } else if claims.ExpiresAt.Before(time.Now()) { + if tokenRefreshed { + return claims, fmt.Errorf("user %s, does not have a valid token", claims.Email) + } + err := CallRefreshToken(creds.RefreshToken) + if err != nil { + return claims, err + } + claims, err = authorize(true) + if err != nil { + return claims, err + } + } + return claims, nil +} + +func getClaims() (*Credentials, *KlothoClaims, error) { + creds, err := GetIDToken() + if err != nil { + return nil, nil, err + } + token, err := jwt.ParseWithClaims(creds.IdToken, &KlothoClaims{}, func(token *jwt.Token) (interface{}, error) { + return getPem() + }) + if err != nil { + return nil, nil, err + } + if claims, ok := token.Claims.(*KlothoClaims); ok { + return creds, claims, nil + } else { + return nil, nil, err + } +} + +func getPem() (*rsa.PublicKey, error) { + var authServerPemCacheFile = path.Join("pem", url.PathEscape(pemUrl)) + + writePemCache := false + // Try to read the PEM from local cache + configPath, err := cli_config.KlothoConfigPath(authServerPemCacheFile) + if err != nil { + return nil, err + } + bs, err := os.ReadFile(configPath) + // Couldn't read it from cache, so (a) try to fetch it from URL and (b) mark down that we should write it on success + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + zap.L().Debug("Couldn't read PEM cache file. Will download it.", zap.Error(err)) + } + pemResp, err := http.Get(pemUrl) + if err != nil { + return nil, err + } + defer closenicely.OrDebug(pemResp.Body) + bs, err = io.ReadAll(pemResp.Body) + if err != nil { + return nil, err + } + writePemCache = true + } + // okay, we have the PEM bytes. Try to decode them into a PublicKey. + block, _ := pem.Decode(bs) + if block == nil { + return nil, errors.New("Couldn't parse PEM certificate") + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + pub, ok := cert.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, errors.New("Couldn't parse PEM certificate block") + } + // Finally, if we'd fetched the PEM bytes from URL, save them now. + if writePemCache { + configPath, err := cli_config.KlothoConfigPath(authServerPemCacheFile) + if err == nil { + _ = os.MkdirAll(path.Dir(configPath), 0777) + err = os.WriteFile(configPath, bs, 0644) + } + if err != nil { + zap.L().Debug("Couldn't write PEM to local cache", zap.Error(err)) + } + } + return pub, nil +} diff --git a/pkg/auth/credentials.go b/pkg/auth/credentials.go new file mode 100644 index 000000000..08477cd7f --- /dev/null +++ b/pkg/auth/credentials.go @@ -0,0 +1,59 @@ +package auth + +import ( + "encoding/json" + "github.com/pkg/errors" + "os" + + "github.com/klothoplatform/klotho/pkg/cli_config" +) + +type Credentials struct { + IdToken string + RefreshToken string +} + +func WriteIDToken(token string) error { + configPath, err := cli_config.KlothoConfigPath("credentials.json") + if err != nil { + return err + } + err = cli_config.CreateKlothoConfigPath() + if err != nil { + return err + } + err = os.WriteFile(configPath, []byte(token), 0644) + if err != nil { + return err + } + return nil +} + +func GetIDToken() (*Credentials, error) { + idToken := os.Getenv("KLOTHO_ID_TOKEN") + if idToken != "" { + return &Credentials{ + IdToken: idToken, + }, nil + } + + configPath, err := cli_config.KlothoConfigPath("credentials.json") + result := Credentials{} + + if err != nil { + return &result, err + } + + content, err := os.ReadFile(configPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + err = ErrNoCredentialsFile + } + return nil, err + } + if len(content) > 0 { + err = json.Unmarshal(content, &result) + } + + return &result, err +} diff --git a/pkg/cli/klothomain.go b/pkg/cli/klothomain.go index 3adc3bf95..d52357b3c 100644 --- a/pkg/cli/klothomain.go +++ b/pkg/cli/klothomain.go @@ -2,9 +2,12 @@ package cli import ( "fmt" + "github.com/klothoplatform/klotho/pkg/closenicely" + "github.com/spf13/pflag" "os" "regexp" + "github.com/klothoplatform/klotho/pkg/auth" "github.com/klothoplatform/klotho/pkg/cli_config" "github.com/klothoplatform/klotho/pkg/updater" @@ -28,6 +31,20 @@ type KlothoMain struct { Version string VersionQualifier string PluginSetup func(*PluginSetBuilder) error + // Authorizer is an optional authorizer override. If this also conforms to FlagsProvider, those flags will be added. + Authorizer Authorizer +} +type Authorizer interface { + + // Authorize tries to authorize the user. The KlothoClaims it returns may be nil, even if the authentication + // succeeds. Conversely, if the KlothoClaims is non-nil, it is valid even if the error is also non-nil; you can use + // those claims provisionally (and specifically, in analytics) even if the error is non-nil, indicating failed + // authentication. + Authorize() (*auth.KlothoClaims, error) +} + +type FlagsProvider interface { + SetUpCliFlags(flags *pflag.FlagSet) } var cfg struct { @@ -45,8 +62,9 @@ var cfg struct { uploadSource bool update bool cfgFormat string - login string setOption map[string]string + login bool + logout bool } const defaultDisableLogo = false @@ -69,6 +87,9 @@ const ( ) func (km KlothoMain) Main() { + if km.Authorizer == nil { + km.Authorizer = auth.Auth0Authorizer{} + } var root = &cobra.Command{ Use: "klotho [path to source]", @@ -91,8 +112,14 @@ func (km KlothoMain) Main() { flags.BoolVar(&cfg.internalDebug, "internalDebug", false, "Enable debugging for compiler") flags.BoolVar(&cfg.version, "version", false, "Print the version") flags.BoolVar(&cfg.update, "update", false, "update the cli to the latest version") - flags.StringVar(&cfg.login, "login", "", "Login to Klotho with email. For anonymous login, use 'local'") flags.StringToStringVar(&cfg.setOption, "set-option", nil, "Sets a CLI option") + flags.BoolVar(&cfg.login, "login", false, "Login to Klotho with email.") + flags.BoolVar(&cfg.logout, "logout", false, "Logout of current klotho account.") + + if authFlags, hasFlags := km.Authorizer.(FlagsProvider); hasFlags { + authFlags.SetUpCliFlags(flags) + } + _ = flags.MarkHidden("internalDebug") err := root.Execute() @@ -169,7 +196,6 @@ func (km KlothoMain) run(cmd *cobra.Command, args []string) (err error) { // supports color if !color.NoColor && showLogo { color.New(color.FgHiGreen).Println(Logo) - fmt.Println() } // create config directory if necessary, must run @@ -178,31 +204,42 @@ func (km KlothoMain) run(cmd *cobra.Command, args []string) (err error) { zap.S().Warnf("failed to create .klotho directory: %v", err) } - // Set up user if login is specified - if cfg.login != "" { - if err := analytics.CreateUser(cfg.login); err != nil { - return errors.Wrapf(err, "could not configure user '%s'", cfg.login) - } - return nil - } - - // Set up analytics - analyticsClient, err := analytics.NewClient(map[string]interface{}{ + // Set up analytics, and hook them up to the logs + analyticsClient := analytics.NewClient(map[string]interface{}{ "version": km.Version, "strict": cfg.strict, "edition": km.DefaultUpdateStream, }) - if err != nil { - return errors.New(fmt.Sprintf("Issue retrieving user info: %s. \nYou may need to run: klotho --login ", err)) - } - z, err := setupLogger(analyticsClient) if err != nil { return err } - defer z.Sync() // nolint:errcheck + defer closenicely.FuncOrDebug(z.Sync) zap.ReplaceGlobals(z) + // Set up user if login is specified + if cfg.login { + err := auth.Login(func(err error) error { + zap.L().Warn(`Couldn't log in. You may be able to continue using klotho without logging in for now, but this may break in the future. Please contact us if this continues.`) + // Set an empty token. This will mean that the user doesn't get prompted to log in. The login token is still + // invalid, but it'll fail-open (at least for now). + _ = auth.WriteIDToken("") + return nil + }) + if err != nil { + return err + } + return nil + } + // Set up user if login is specified + if cfg.logout { + err := auth.CallLogoutEndpoint() + if err != nil { + return err + } + return nil + } + errHandler := ErrorHandler{ InternalDebug: cfg.internalDebug, Verbose: cfg.verbose, @@ -265,6 +302,30 @@ func (km KlothoMain) run(cmd *cobra.Command, args []string) (err error) { return nil } + // Needs to go after the --version and --update checks + claims, err := km.Authorizer.Authorize() + if claims != nil { + analyticsClient.AttachAuthorizations(claims) + } + if err != nil { + if errors.Is(err, auth.ErrNoCredentialsFile) { + return errors.New(`Failed to get credentials for user. Please run "klotho --login"`) + } + if errors.Is(err, auth.ErrEmailUnverified) { + zap.L().Warn( + `You have not verified your email. You may continue using klotho for now, but this may break in the future. Please check your email to complete registration.`, + zap.Error(err), + logging.SendEntryMessage) + } else { + // Fail-open. See also the error handler at auth.Login(...) above (you should change that to not write the + // empty token, if this fail-open ever changes). + zap.L().Warn( + `Not logged in. You may be able to continue using klotho without logging in for now, but this may break in the future. Please contact us if this continues.`, + zap.Error(err), + logging.SendEntryMessage) + } + } + appCfg, err := readConfig(args) if err != nil { return errors.Wrapf(err, "could not read config '%s'", cfg.config) diff --git a/pkg/cli_config/envvar.go b/pkg/cli_config/envvar.go new file mode 100644 index 000000000..07dd3af51 --- /dev/null +++ b/pkg/cli_config/envvar.go @@ -0,0 +1,19 @@ +package cli_config + +import "os" + +// EnvVar represents an environment variable, specified by its key name. +// wrapper around os.Getenv. This string's value is the env var key. Use GetOr to get its value, or a +// default if the value isn't set. +type EnvVar string + +// GetOr uses os.Getenv to get the env var specified by the target EnvVar. If that env var's value is unset or empty, +// it returns the defaultValue. +func (s EnvVar) GetOr(defaultValue string) string { + value := os.Getenv(string(s)) + if value == "" { + return defaultValue + } else { + return value + } +} diff --git a/pkg/closenicely/closeutil.go b/pkg/closenicely/closeutil.go new file mode 100644 index 000000000..0a1bd8564 --- /dev/null +++ b/pkg/closenicely/closeutil.go @@ -0,0 +1,16 @@ +package closenicely + +import ( + "go.uber.org/zap" + "io" +) + +func OrDebug(closer io.Closer) { + FuncOrDebug(closer.Close) +} + +func FuncOrDebug(closer func() error) { + if err := closer(); err != nil { + zap.L().Debug("Failed to close resource", zap.Error(err)) + } +} diff --git a/pkg/logging/console.go b/pkg/logging/console.go index 94dfaa6ab..ab02a518c 100644 --- a/pkg/logging/console.go +++ b/pkg/logging/console.go @@ -146,6 +146,9 @@ func (enc *ConsoleEncoder) EncodeEntry(ent zapcore.Entry, fieldList []zapcore.Fi case postLogMessage: postMessage = v.Message continue + case error: + // hacky workaround to #195: just don't print errors, since they can be long strings of multi-line trace + continue } if fieldCount > 0 { fields.AppendString(", ") diff --git a/pkg/logging/fields.go b/pkg/logging/fields.go index 41efe3cfb..a2fd079c2 100644 --- a/pkg/logging/fields.go +++ b/pkg/logging/fields.go @@ -10,7 +10,7 @@ import ( "go.uber.org/zap/zapcore" ) -var EntryMessageField = "entryMessage" +const EntryMessageField = "entryMessage" type fileField struct { f core.File @@ -75,7 +75,7 @@ func (field entryMessage) MarshalLogObject(enc zapcore.ObjectEncoder) error { } // SendEntryMessage adds the entryMessage field to the logger in order to bypass sanitization and allow for the raw message to be logged. -var SendEntryMessage = zap.Object("entryMessage", entryMessage{}) +var SendEntryMessage = zap.Object(EntryMessageField, entryMessage{}) // DescribeKlothoFields is intended for unit testing expected log lines. //