mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
add anonymous telemetry (#1401)
This commit is contained in:
parent
8e3b8f51c9
commit
11e7c3f9d6
141
cli/Gopkg.lock
generated
141
cli/Gopkg.lock
generated
@ -2,51 +2,66 @@
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:55388fd080150b9a072912f97b1f5891eb0b50df43401f8b75fb4273d3fec9fc"
|
||||
name = "github.com/Masterminds/semver"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c7af12943936e8c39859482e61f0574c2fd7fc75"
|
||||
version = "v1.4.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bf42be3cb1519bf8018dfd99720b1005ee028d947124cab3ccf965da59381df6"
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
|
||||
version = "v0.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:889290ee5c1f1888baa7caa2b4cdfa8a6abcfb86dd772fe6470ad7925cc44bff"
|
||||
name = "github.com/briandowns/spinner"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "48dbb65d7bd5c74ab50d53d04c949f20e3d14944"
|
||||
version = "1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:454adc7f974228ff789428b6dc098638c57a64aa0718f0bd61e53d3cd39d7a75"
|
||||
name = "github.com/chzyer/readline"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2972be24d48e78746da79ba8e24e8b488c9880de"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7cb4fdca4c251b3ef8027c90ea35f70c7b661a593b9eeae34753c65499098bb1"
|
||||
name = "github.com/cpuguy83/go-md2man"
|
||||
packages = ["md2man"]
|
||||
pruneopts = "UT"
|
||||
revision = "20f5889cbdc3c73dbd2862796665e7c465ade7d1"
|
||||
version = "v1.0.8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3cabbabc9e0e4aa7e12b882bdc213f41cf8bd2b2ce2a7b5e0aceaf8a6a78049b"
|
||||
name = "github.com/docker/distribution"
|
||||
packages = [
|
||||
"digest",
|
||||
"reference"
|
||||
"reference",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
|
||||
version = "v2.6.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c4c7064c2c67a0a00815918bae489dd62cd88d859d24c95115d69b00b3d33334"
|
||||
name = "github.com/docker/docker"
|
||||
packages = [
|
||||
"api/types",
|
||||
@ -64,92 +79,126 @@
|
||||
"api/types/versions",
|
||||
"api/types/volume",
|
||||
"client",
|
||||
"pkg/tlsconfig"
|
||||
"pkg/tlsconfig",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
|
||||
version = "v1.13.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b6b5c3e8da0fb8073cd2886ba249a40f4402b4391ca6eba905a142cceea97a12"
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = [
|
||||
"nat",
|
||||
"sockets",
|
||||
"tlsconfig"
|
||||
"tlsconfig",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
|
||||
name = "github.com/docker/go-units"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
|
||||
version = "v0.3.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f4f6279cb37479954644babd8f8ef00584ff9fa63555d2c6718c1c3517170202"
|
||||
name = "github.com/elazarl/go-bindata-assetfs"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2cd7915ab26ede7d95b8749e6b1f933f1c6d5398030684e6505940a10f31cfda"
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2b59aca2665ff804f6606c8829eaee133ddd3aefbc841014660d961b0034f888"
|
||||
name = "github.com/gin-contrib/cors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "cf4846e6a636a76237a28d9286f163c132e841bc"
|
||||
version = "v1.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:36fe9527deed01d2a317617e59304eb2c4ce9f8a24115bcc5c2e37b3aee5bae4"
|
||||
name = "github.com/gin-contrib/sse"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d178cf1d1da54559f7f89b0cf5d30763e03750bbf0692df2f45b1815b8b36462"
|
||||
name = "github.com/gin-contrib/static"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "73da7037e716e63aa2b0ffceb630dfa7be299086"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:1b45decf351430d074b2afaadf7dcb598755d58b185db5a52bbaa93b7cbbb85a"
|
||||
name = "github.com/gin-gonic/contrib"
|
||||
packages = ["renders/multitemplate"]
|
||||
pruneopts = "UT"
|
||||
revision = "39cfb9727134fef3120d2458fce5fab14265a46c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:489e108f21464371ebf9cb5c30b1eceb07c6dd772dff073919267493dd9d04ea"
|
||||
name = "github.com/gin-gonic/gin"
|
||||
packages = [
|
||||
".",
|
||||
"binding",
|
||||
"render"
|
||||
"render",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d459835d2b077e44f7c9b453505ee29881d5d12d"
|
||||
version = "v1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c594a691090b434d55c67f6cc8e326ef5ba49452abc059821bd5d4fd4cdef08c"
|
||||
name = "github.com/gofrs/uuid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7077aa61129615a0d7f45c49101cd011ab221c27"
|
||||
version = "v3.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6ba96a683441984156b05568b9d31dbc846d3336d21ac220fcc819a367dc1f65"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = "UT"
|
||||
revision = "5a0f697c9ed9d68fef0116532c6e05cfeae00e55"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a361611b8c8c75a1091f00027767f7779b29cb37c456a71b8f2604c88057ab40"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
@ -161,84 +210,107 @@
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token"
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e51f40f0c19b39c1825eadd07d5c0a98a2ad5942b166d9fc4f54750ce9a04810"
|
||||
name = "github.com/juju/ansiterm"
|
||||
packages = [
|
||||
".",
|
||||
"tabwriter"
|
||||
"tabwriter",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "720a0952cc2ac777afc295d9861263e2a4cf96a1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:37ce7d7d80531b227023331002c0d42b4b4b291a96798c82a049d03a54ba79e4"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"oid"
|
||||
"oid",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "90697d60dd844d5ef6ff15135d0203f65d2f53b8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:bb08c7bb1c7224636b1a00639f079ed4391eb822945f26db74b8d8ee3f14d991"
|
||||
name = "github.com/lunixbochs/vtclean"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2d01aacdc34a083dca635ba869909f5fc0cd4f41"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7"
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c2353362d570a7bfa228149c62842019201cfb71"
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e3294b38b5590a93f57da4a340158802222f3bb41377ebc796dbc6e2e586ee3a"
|
||||
name = "github.com/manifoldco/promptui"
|
||||
packages = [
|
||||
".",
|
||||
"list",
|
||||
"screenbuf"
|
||||
"screenbuf",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3dd80c00b7cb0bc779d1c204da6f3ae0fa6a4eee"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8eb17c2ec4df79193ae65b621cd1c0c4697db3bc317fe6afdc76d7f2746abd05"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e730597b38a4d56e2361e0b6236cb800e52c73cace2ff91396f4ff35792ddfa7"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7aefb397a53fc437c90f0fdb3e1419c751c5a3a165ced52325d5d797edf1aca6"
|
||||
name = "github.com/moul/http2curl"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d"
|
||||
|
||||
[[projects]]
|
||||
@ -250,109 +322,141 @@
|
||||
[[projects]]
|
||||
name = "github.com/parnurzeal/gorequest"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a578a48e8d6ca8b01a3b18314c43c6716bb5f5a3"
|
||||
version = "v0.2.15"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8bc629776d035c003c7814d4369521afe67fdb8efc4b5f66540d29343b98cf23"
|
||||
name = "github.com/russross/blackfriday"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "55d61fa8aa702f59229e6cff85793c22e580eaf5"
|
||||
version = "v1.5.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2cf43d017348f06dc503ed7e639717262208f79ddca52ce2f01776e71543b567"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = [
|
||||
".",
|
||||
"hooks/test"
|
||||
"hooks/test",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:39853e1ae46a02816e2419e1f590e00682b1a6b60bb988597cf2efb84314da45"
|
||||
name = "github.com/skratchdot/open-golang"
|
||||
packages = ["open"]
|
||||
pruneopts = "UT"
|
||||
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem"
|
||||
"mem",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e01b05ba901239c783dfe56450bcde607fc858908529868259c9a8765dc176d0"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = [
|
||||
".",
|
||||
"doc"
|
||||
"doc",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:59e7dceb53b4a1e57eb1eb0bf9951ff0c25912df7660004a789b62b4e8cdca3b"
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "UT"
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c268acaa4a4d94a467980e5e91452eb61c460145765293dc0aed48e5e9919cc6"
|
||||
name = "github.com/ugorji/go"
|
||||
packages = ["codec"]
|
||||
pruneopts = "UT"
|
||||
revision = "c88ee250d0221a57af388746f5cf03768c21d6e2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ssh/terminal"]
|
||||
pruneopts = "UT"
|
||||
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:aa91545b200c0c8e6cf39bb8eedc86d85d3ed0a208662ed964bef0433839fd3c"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
@ -360,20 +464,24 @@
|
||||
"idna",
|
||||
"internal/socks",
|
||||
"proxy",
|
||||
"publicsuffix"
|
||||
"publicsuffix",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c1fee1a2c739c7b220dc3de939f659dce271fa8d9de155cb8cfcfb99c941cbc3"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows"
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "63fc586f45fe72d95d5240a5d5eb95e6503907d3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
@ -389,20 +497,25 @@
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
"unicode/rangetable",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1b4724d3c8125f6044925f02b485b74bfec9905cbf579d95aafd1a6c8f8447d3"
|
||||
name = "gopkg.in/go-playground/validator.v8"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5f57d2222ad794d0dffb07e664ea05e2ee07d60c"
|
||||
version = "v8.18.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
|
@ -70,7 +70,7 @@ func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _assetsUnversionedConsoleHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\x5f\x6b\xfb\x36\x14\x7d\xff\x7d\x8a\x8b\xc6\xe8\xcb\x2c\xa5\x5d\x61\xc5\xb1\x03\x63\x30\x06\xdb\xa0\x0f\xeb\x5e\x8b\x2a\x5f\xdb\x37\x95\x25\x4f\x57\x49\xea\x95\x7c\xf7\xe1\x7f\x89\x9b\x0c\xc6\xca\x46\x5e\x74\xff\x48\xe7\x9e\x23\xe5\x38\xab\x63\x63\xc1\x6a\x57\xe5\x02\x5d\xb2\x63\xb1\xf9\x02\x90\xd5\xa8\x8b\x7e\x01\x90\x59\x72\xaf\x10\xd0\xe6\x82\x8c\x77\x02\x62\xd7\x62\x2e\xa8\xd1\x15\xaa\xd6\x55\x02\xea\x80\x65\x2e\xea\x18\x5b\x4e\x95\xe2\xe8\x83\xae\x50\x56\xde\x57\x16\x75\x4b\x2c\x8d\x6f\x54\xad\x79\x17\x74\x52\x05\xdd\xd6\x7f\xd8\x04\x5d\x45\x0e\x95\xf1\x8e\xbd\x45\xa5\x99\x31\xb2\x2a\xf5\xbe\xc7\x90\xc3\xb1\x6a\xc2\x67\x13\xa8\x8d\x63\x00\x07\x72\x85\x3f\xc8\xe7\x67\x74\x7b\xc8\xe1\x7d\xcc\x02\xe8\x96\x7e\xf2\x1c\x53\x78\x7f\x97\xd3\xfa\x78\xfc\x66\x51\x7d\xf4\x21\xa6\x20\xc6\x72\x1f\x1c\x8f\xe2\x54\x37\x96\x7e\xc7\xc0\xe4\xdd\x70\xc0\x39\x5c\x9c\x51\xe8\xa8\xbf\x6f\xe9\x29\xd8\xa1\xe7\x1c\x5e\xf7\x2c\xcf\xfa\x98\x5a\xce\x64\x0c\x32\xff\x8c\xdd\x38\xf3\x1c\x2d\x3a\x76\xc1\x3e\x06\x2c\xe9\x2d\x05\xa1\x16\xc3\x8e\x9a\xfd\xea\x0b\x4c\x41\x18\x4b\x62\xac\x1c\xd7\xa3\x5e\xea\x2c\x58\xa6\xe6\x7b\xcc\x5e\x7c\xd1\xcd\x82\xc6\xce\xe2\xb8\x96\x8d\x26\xf7\x83\x77\x11\x5d\x3c\x89\x59\x10\xb7\x56\x77\x29\xdc\x38\xef\xf0\x66\x3d\xa5\x7d\xab\x0d\xc5\x2e\x85\xd5\x9c\x89\x41\x3b\xa6\x38\x50\x9d\xaa\x20\xef\x56\x0c\x96\x1c\xea\x30\xb6\x1d\xaf\x80\x24\xd7\xfe\xf0\x37\x68\x2f\xd6\x9b\xd7\x6b\xb8\xdb\x4f\xc0\x65\x6a\x22\x39\x46\x05\xed\x81\x8a\x5c\x58\xaf\x0b\x72\x95\x98\x1e\xd3\x58\x30\x56\x33\xe7\xa2\xd5\x15\x26\x73\x03\x0c\xdb\xf3\x49\x59\x68\xc8\x25\x35\x52\x55\xc7\x14\x6e\x57\xab\x7d\x3d\x8f\x74\xa0\x22\xd6\x43\xee\xeb\xf5\x25\x9f\xd2\xe2\xdb\x9c\xd4\x96\x2a\x97\x50\xc4\x86\x53\x30\xe8\x22\x86\xb9\x54\x7a\x17\x93\x52\x37\x64\xbb\x14\x58\x3b\x4e\x18\x03\x95\x73\x79\xbb\xe3\x48\x65\x97\x98\x51\xbb\xcb\xdd\x27\x2a\xfd\xbd\xb6\xda\xcd\x6c\x2e\x19\x4c\x38\x4c\x7f\x62\x0a\x77\xd8\xac\x4f\xf9\x46\x87\x8a\x5c\x12\x7d\x9b\x42\xf2\xed\xb2\x62\xbc\xf5\x21\x85\xaf\x1e\xee\xfb\xdf\x39\xbf\xc0\xfc\x65\xd4\x4b\x4a\x39\x2b\xaa\xfa\x29\x4e\xfa\xaa\x82\xf6\xd3\xab\x5b\x2c\xe7\xeb\x98\x38\x89\x79\xe8\xc5\x1b\x11\x9b\xe5\x86\xb3\x05\x0d\xac\xb8\x46\x8c\x97\xbe\x63\x0a\xb7\x65\x69\xac\xdf\x15\xa5\xd5\x01\x07\xd7\xd1\x5b\xfd\xa6\x2c\xbd\xb0\x1a\xe8\xeb\x03\xb2\x6f\x50\xdd\xcb\xef\xe4\x4a\x19\xfe\x98\x96\x0d\x39\x69\x98\x85\xfa\x17\xb0\x9f\xb2\xbb\xfe\x0f\x3f\x38\xde\xc9\x16\x54\xcf\x7d\x00\x07\x53\xeb\xc0\x18\x73\xf1\xf4\xdb\x8f\xc9\x83\xf8\x68\x83\xc0\xc1\xfc\xf7\xe0\x7b\x74\x85\x0f\x72\x7b\x8d\xbe\x59\xda\xc9\xff\x3c\xc5\x20\xc1\x3f\xcd\x90\xa9\xd1\xc9\x32\xd5\x7f\xb8\x36\x5f\xfe\x0a\x00\x00\xff\xff\xdf\x70\xc8\xd7\xc0\x06\x00\x00")
|
||||
var _assetsUnversionedConsoleHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\x5d\xab\xe3\x36\x10\x7d\xdf\x5f\x21\x54\xca\xbe\xd4\x52\x76\xbb\xd0\xc5\xd7\xbe\x50\x5a\x4a\x4b\x5b\xd8\x87\x4d\x5f\x97\x89\x3c\xb6\x27\x2b\x4b\xae\x46\x49\xae\x7b\xf1\x7f\x2f\xfe\x4a\x9c\xa4\x50\x7a\xe9\x92\x17\xcd\x99\xf1\x9c\x39\x23\x71\x92\xd5\xb1\xb1\xc2\x82\xab\x72\x89\x2e\x39\xb0\x7c\x7c\x25\x44\x56\x23\x14\xc3\x41\x88\xcc\x92\xfb\x2c\x02\xda\x5c\x92\xf1\x4e\x8a\xd8\xb5\x98\x4b\x6a\xa0\x42\xdd\xba\x4a\x8a\x3a\x60\x99\xcb\x3a\xc6\x96\x53\xad\x39\xfa\x00\x15\xaa\xca\xfb\xca\x22\xb4\xc4\xca\xf8\x46\xd7\xc0\x87\x00\x49\x15\xa0\xad\xff\xb4\x09\xba\x8a\x1c\x6a\xe3\x1d\x7b\x8b\x1a\x98\x31\xb2\x2e\xe1\x38\x70\xa8\xb1\xad\x9e\xf9\xd9\x04\x6a\xe3\x14\x88\x13\xb9\xc2\x9f\xd4\xa7\x4f\xe8\x8e\x22\x17\xcf\x13\x2a\x04\xb4\xf4\xb3\xe7\x98\x8a\xe7\x67\x35\x9f\xfb\xfe\x9b\x55\xf6\x83\x0f\x31\x15\x72\x4a\x0f\x41\xdf\xcb\x73\xde\x58\xfa\x03\x03\x93\x77\x63\x83\x4b\xb8\xea\x51\x40\x84\xef\x5b\xda\x06\x3b\xd6\x5c\xc2\xfb\x9a\x75\xaf\x6b\x68\x3d\x93\x31\xc8\xfc\x2b\x76\xd3\xcc\x4b\xb4\xaa\x38\x04\xfb\x21\x60\x49\x4f\xa9\x90\x7a\x35\xec\xb4\xb3\xdf\x7d\x81\xa9\x90\xc6\xd2\x95\x8e\xed\xf6\x97\x1f\x17\x11\xc3\x79\xd5\x0f\x1d\xec\x2c\x7e\x44\x8b\x0d\xc6\x30\xf1\xde\x60\x7d\x3f\x15\xf7\x0f\xd3\xee\xf5\x65\xf9\x99\x5e\xde\x44\xb6\xf3\x45\xb7\x5c\x4e\xec\x2c\x4e\x67\xd5\x00\xb9\x1f\xbc\x8b\xe8\xe2\xf9\x62\x0a\xe2\xd6\x42\x97\x8a\xd7\xce\x3b\x7c\xfd\x30\xc3\xbe\x05\x43\xb1\x4b\xc5\x66\x41\x62\x00\xc7\x14\xc7\xb5\xcd\x59\xa1\xde\x6e\x58\x58\x72\x08\x61\x2a\xeb\xef\x88\x14\xd7\xfe\xf4\x0f\x6c\x3b\xeb\xcd\xe7\x7b\xba\x37\x2f\xa0\xcb\xf4\x2c\x72\x8a\x0a\x3a\x0a\x2a\x72\x69\x3d\x14\xe4\x2a\x39\x3f\xcc\x29\x61\x2c\x30\xe7\xb2\x85\x0a\x93\xa5\x40\x8c\x9f\xe7\x72\xae\x6b\xc8\x25\x35\x52\x55\xc7\x54\xbc\xd9\x6c\x8e\xf5\x32\xd2\x89\x8a\x58\x8f\xd8\xd7\x0f\xb7\x7a\x4a\x8b\x4f\x0b\x08\x96\x2a\x97\x50\xc4\x86\x53\x61\xd0\x45\x0c\x4b\xaa\xf4\x2e\x26\x25\x34\x64\xbb\x54\x30\x38\x4e\x18\x03\x95\x4b\x7a\x7f\xe0\x48\x65\x97\x98\x69\x77\xb7\x5f\x9f\xa5\x0c\xf7\xda\x82\x5b\xd4\xdc\x2a\x98\x79\x98\xfe\xc2\x54\xbc\xc5\xe6\xe1\x8c\x37\x10\x2a\x72\x49\xf4\x6d\x2a\x92\x6f\xd7\x19\xe3\xad\x0f\xa9\xf8\xea\xfd\xbb\xe1\x77\xc1\x57\x9c\xbf\x4d\xfb\x52\x4a\x2d\x1b\xd5\xc3\x14\xe7\xfd\xea\x82\x8e\xf3\xab\x5b\x1d\x97\xeb\x98\x35\xc9\x65\xe8\xd5\x1b\x91\x8f\xeb\x0f\x2e\x76\x36\xaa\xe2\x1a\x31\xde\x7a\x98\x29\xdc\x9e\x95\xb1\xfe\x50\x94\x16\x02\x8e\x0e\x06\x7b\x78\xd2\x96\x76\xac\x47\xf9\x70\x42\xf6\x0d\xea\x77\xea\x3b\xb5\xd1\x86\xaf\x61\xd5\x90\x53\x86\x59\xea\xff\x40\xfb\x22\xeb\x1c\xcc\x63\x74\xcf\xb3\xc5\xe8\x41\xfb\x48\x2e\x4c\x0d\x81\x31\xe6\x72\xfb\xf1\xa7\xe4\xbd\xbc\xb6\x54\xc1\xc1\xfc\xff\xe4\x47\x74\x85\x0f\x6a\x7f\xcf\xfe\xb8\xb6\x93\x2f\x3c\xc5\xb8\x82\x7f\x9b\x21\xd3\x93\x93\x65\x7a\xf8\x13\x7c\x7c\xf5\x77\x00\x00\x00\xff\xff\xe7\x5e\x19\x48\x0c\x07\x00\x00")
|
||||
|
||||
func assetsUnversionedConsoleHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@ -85,12 +85,12 @@ func assetsUnversionedConsoleHtml() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "assets/unversioned/console.html", size: 1728, mode: os.FileMode(436), modTime: time.Unix(1531822928, 0)}
|
||||
info := bindataFileInfo{name: "assets/unversioned/console.html", size: 1804, mode: os.FileMode(436), modTime: time.Unix(1547699579, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _assetsV10AlphaConsoleHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\x5f\x6b\xfb\x36\x14\x7d\xff\x7d\x8a\x8b\xc6\xe8\xcb\x2c\xa5\x5d\x61\xc5\xb1\x03\x63\x30\x06\xdb\xa0\x0f\xeb\x5e\x8b\x2a\x5f\xdb\x37\x95\x25\x4f\x57\x49\xea\x95\x7c\xf7\xe1\x7f\x89\x9b\x0c\xc6\xca\x46\x5e\x74\xff\x48\xe7\x9e\x23\xe5\x38\xab\x63\x63\xc1\x6a\x57\xe5\x02\x5d\xb2\x63\xb1\xf9\x02\x90\xd5\xa8\x8b\x7e\x01\x90\x59\x72\xaf\x10\xd0\xe6\x82\x8c\x77\x02\x62\xd7\x62\x2e\xa8\xd1\x15\xaa\xd6\x55\x02\xea\x80\x65\x2e\xea\x18\x5b\x4e\x95\xe2\xe8\x83\xae\x50\x56\xde\x57\x16\x75\x4b\x2c\x8d\x6f\x54\xad\x79\x17\x74\x52\x05\xdd\xd6\x7f\xd8\x04\x5d\x45\x0e\x95\xf1\x8e\xbd\x45\xa5\x99\x31\xb2\x2a\xf5\xbe\xc7\x90\xc3\xb1\x6a\xc2\x67\x13\xa8\x8d\x63\x00\x07\x72\x85\x3f\xc8\xe7\x67\x74\x7b\xc8\xe1\x7d\xcc\x02\xe8\x96\x7e\xf2\x1c\x53\x78\x7f\x97\xd3\xfa\x78\xfc\x66\x51\x7d\xf4\x21\xa6\x20\xc6\x72\x1f\x1c\x8f\xe2\x54\x37\x96\x7e\xc7\xc0\xe4\xdd\x70\xc0\x39\x5c\x9c\x51\xe8\xa8\xbf\x6f\xe9\x29\xd8\xa1\xe7\x1c\x5e\xf7\x2c\xcf\xfa\x98\x5a\xce\x64\x0c\x32\xff\x8c\xdd\x38\xf3\x1c\x2d\x3a\x76\xc1\x3e\x06\x2c\xe9\x2d\x05\xa1\x16\xc3\x8e\x9a\xfd\xea\x0b\x4c\x41\x18\x4b\x62\xac\x1c\xd7\xa3\x5e\xea\x2c\x58\xa6\xe6\x7b\xcc\x5e\x7c\xd1\xcd\x82\xc6\xce\xe2\xb8\x96\x8d\x26\xf7\x83\x77\x11\x5d\x3c\x89\x59\x10\xb7\x56\x77\x29\xdc\x38\xef\xf0\x66\x3d\xa5\x7d\xab\x0d\xc5\x2e\x85\xd5\x9c\x89\x41\x3b\xa6\x38\x50\x9d\xaa\x20\xef\x56\x0c\x96\x1c\xea\x30\xb6\x1d\xaf\x80\x24\xd7\xfe\xf0\x37\x68\x2f\xd6\x9b\xd7\x6b\xb8\xdb\x4f\xc0\x65\x6a\x22\x39\x46\x05\xed\x81\x8a\x5c\x58\xaf\x0b\x72\x95\x98\x1e\xd3\x58\x30\x56\x33\xe7\xa2\xd5\x15\x26\x73\x03\x0c\xdb\xf3\x49\x59\x68\xc8\x25\x35\x52\x55\xc7\x14\x6e\x57\xab\x7d\x3d\x8f\x74\xa0\x22\xd6\x43\xee\xeb\xf5\x25\x9f\xd2\xe2\xdb\x9c\xd4\x96\x2a\x97\x50\xc4\x86\x53\x30\xe8\x22\x86\xb9\x54\x7a\x17\x93\x52\x37\x64\xbb\x14\x58\x3b\x4e\x18\x03\x95\x73\x79\xbb\xe3\x48\x65\x97\x98\x51\xbb\xcb\xdd\x27\x2a\xfd\xbd\xb6\xda\xcd\x6c\x2e\x19\x4c\x38\x4c\x7f\x62\x0a\x77\xd8\xac\x4f\xf9\x46\x87\x8a\x5c\x12\x7d\x9b\x42\xf2\xed\xb2\x62\xbc\xf5\x21\x85\xaf\x1e\xee\xfb\xdf\x39\xbf\xc0\xfc\x65\xd4\x4b\x4a\x39\x2b\xaa\xfa\x29\x4e\xfa\xaa\x82\xf6\xd3\xab\x5b\x2c\xe7\xeb\x98\x38\x89\x79\xe8\xc5\x1b\x11\x9b\xe5\x86\xb3\x05\x0d\xac\xb8\x46\x8c\x97\xbe\x63\x0a\xb7\x65\x69\xac\xdf\x15\xa5\xd5\x01\x07\xd7\xd1\x5b\xfd\xa6\x2c\xbd\xb0\x1a\xe8\xeb\x03\xb2\x6f\x50\xdd\xcb\xef\xe4\x4a\x19\xfe\x98\x96\x0d\x39\x69\x98\x85\xfa\x17\xb0\x9f\xb2\xbb\xfe\x0f\x3f\x38\xde\xc9\x16\x54\xcf\x7d\x00\x07\x53\xeb\xc0\x18\x73\xf1\xf4\xdb\x8f\xc9\x83\xf8\x68\x83\xc0\xc1\xfc\xf7\xe0\x7b\x74\x85\x0f\x72\x7b\x8d\xbe\x59\xda\xc9\xff\x3c\xc5\x20\xc1\x3f\xcd\x90\xa9\xd1\xc9\x32\xd5\x7f\xb8\x36\x5f\xfe\x0a\x00\x00\xff\xff\xdf\x70\xc8\xd7\xc0\x06\x00\x00")
|
||||
var _assetsV10AlphaConsoleHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\x5d\xab\xe3\x36\x10\x7d\xdf\x5f\x21\x54\xca\xbe\xd4\x52\x76\xbb\xd0\xc5\xd7\xbe\x50\x5a\x4a\x4b\x5b\xd8\x87\x4d\x5f\x97\x89\x3c\xb6\x27\x2b\x4b\xae\x46\x49\xae\x7b\xf1\x7f\x2f\xfe\x4a\x9c\xa4\x50\x7a\xe9\x92\x17\xcd\x99\xf1\x9c\x39\x23\x71\x92\xd5\xb1\xb1\xc2\x82\xab\x72\x89\x2e\x39\xb0\x7c\x7c\x25\x44\x56\x23\x14\xc3\x41\x88\xcc\x92\xfb\x2c\x02\xda\x5c\x92\xf1\x4e\x8a\xd8\xb5\x98\x4b\x6a\xa0\x42\xdd\xba\x4a\x8a\x3a\x60\x99\xcb\x3a\xc6\x96\x53\xad\x39\xfa\x00\x15\xaa\xca\xfb\xca\x22\xb4\xc4\xca\xf8\x46\xd7\xc0\x87\x00\x49\x15\xa0\xad\xff\xb4\x09\xba\x8a\x1c\x6a\xe3\x1d\x7b\x8b\x1a\x98\x31\xb2\x2e\xe1\x38\x70\xa8\xb1\xad\x9e\xf9\xd9\x04\x6a\xe3\x14\x88\x13\xb9\xc2\x9f\xd4\xa7\x4f\xe8\x8e\x22\x17\xcf\x13\x2a\x04\xb4\xf4\xb3\xe7\x98\x8a\xe7\x67\x35\x9f\xfb\xfe\x9b\x55\xf6\x83\x0f\x31\x15\x72\x4a\x0f\x41\xdf\xcb\x73\xde\x58\xfa\x03\x03\x93\x77\x63\x83\x4b\xb8\xea\x51\x40\x84\xef\x5b\xda\x06\x3b\xd6\x5c\xc2\xfb\x9a\x75\xaf\x6b\x68\x3d\x93\x31\xc8\xfc\x2b\x76\xd3\xcc\x4b\xb4\xaa\x38\x04\xfb\x21\x60\x49\x4f\xa9\x90\x7a\x35\xec\xb4\xb3\xdf\x7d\x81\xa9\x90\xc6\xd2\x95\x8e\xed\xf6\x97\x1f\x17\x11\xc3\x79\xd5\x0f\x1d\xec\x2c\x7e\x44\x8b\x0d\xc6\x30\xf1\xde\x60\x7d\x3f\x15\xf7\x0f\xd3\xee\xf5\x65\xf9\x99\x5e\xde\x44\xb6\xf3\x45\xb7\x5c\x4e\xec\x2c\x4e\x67\xd5\x00\xb9\x1f\xbc\x8b\xe8\xe2\xf9\x62\x0a\xe2\xd6\x42\x97\x8a\xd7\xce\x3b\x7c\xfd\x30\xc3\xbe\x05\x43\xb1\x4b\xc5\x66\x41\x62\x00\xc7\x14\xc7\xb5\xcd\x59\xa1\xde\x6e\x58\x58\x72\x08\x61\x2a\xeb\xef\x88\x14\xd7\xfe\xf4\x0f\x6c\x3b\xeb\xcd\xe7\x7b\xba\x37\x2f\xa0\xcb\xf4\x2c\x72\x8a\x0a\x3a\x0a\x2a\x72\x69\x3d\x14\xe4\x2a\x39\x3f\xcc\x29\x61\x2c\x30\xe7\xb2\x85\x0a\x93\xa5\x40\x8c\x9f\xe7\x72\xae\x6b\xc8\x25\x35\x52\x55\xc7\x54\xbc\xd9\x6c\x8e\xf5\x32\xd2\x89\x8a\x58\x8f\xd8\xd7\x0f\xb7\x7a\x4a\x8b\x4f\x0b\x08\x96\x2a\x97\x50\xc4\x86\x53\x61\xd0\x45\x0c\x4b\xaa\xf4\x2e\x26\x25\x34\x64\xbb\x54\x30\x38\x4e\x18\x03\x95\x4b\x7a\x7f\xe0\x48\x65\x97\x98\x69\x77\xb7\x5f\x9f\xa5\x0c\xf7\xda\x82\x5b\xd4\xdc\x2a\x98\x79\x98\xfe\xc2\x54\xbc\xc5\xe6\xe1\x8c\x37\x10\x2a\x72\x49\xf4\x6d\x2a\x92\x6f\xd7\x19\xe3\xad\x0f\xa9\xf8\xea\xfd\xbb\xe1\x77\xc1\x57\x9c\xbf\x4d\xfb\x52\x4a\x2d\x1b\xd5\xc3\x14\xe7\xfd\xea\x82\x8e\xf3\xab\x5b\x1d\x97\xeb\x98\x35\xc9\x65\xe8\xd5\x1b\x91\x8f\xeb\x0f\x2e\x76\x36\xaa\xe2\x1a\x31\xde\x7a\x98\x29\xdc\x9e\x95\xb1\xfe\x50\x94\x16\x02\x8e\x0e\x06\x7b\x78\xd2\x96\x76\xac\x47\xf9\x70\x42\xf6\x0d\xea\x77\xea\x3b\xb5\xd1\x86\xaf\x61\xd5\x90\x53\x86\x59\xea\xff\x40\xfb\x22\xeb\x1c\xcc\x63\x74\xcf\xb3\xc5\xe8\x41\xfb\x48\x2e\x4c\x0d\x81\x31\xe6\x72\xfb\xf1\xa7\xe4\xbd\xbc\xb6\x54\xc1\xc1\xfc\xff\xe4\x47\x74\x85\x0f\x6a\x7f\xcf\xfe\xb8\xb6\x93\x2f\x3c\xc5\xb8\x82\x7f\x9b\x21\xd3\x93\x93\x65\x7a\xf8\x13\x7c\x7c\xf5\x77\x00\x00\x00\xff\xff\xe7\x5e\x19\x48\x0c\x07\x00\x00")
|
||||
|
||||
func assetsV10AlphaConsoleHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@ -105,12 +105,12 @@ func assetsV10AlphaConsoleHtml() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "assets/v1.0-alpha/console.html", size: 1728, mode: os.FileMode(436), modTime: time.Unix(1531338351, 0)}
|
||||
info := bindataFileInfo{name: "assets/v1.0-alpha/console.html", size: 1804, mode: os.FileMode(436), modTime: time.Unix(1547699579, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _assetsV10ConsoleHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x55\xdf\x8f\xdc\x34\x10\x7e\xbf\xbf\x62\x64\x84\xfa\x42\xec\x6d\xa9\x44\x95\xdb\x9c\x84\x40\x08\x09\x90\x2a\x41\x79\xad\xe6\x9c\x49\x32\x57\xc7\x0e\x1e\xef\xee\x2d\xab\xfc\xef\x28\xbf\x76\x73\x77\x40\xe1\xa0\xda\x17\xcf\x2f\xcf\x7c\xdf\x78\xbf\x6c\x9b\xd4\x3a\x70\xe8\xeb\x42\x91\xcf\x76\xa2\x6e\xae\x00\xb6\x0d\x61\x39\x1c\x00\xb6\x8e\xfd\x07\x88\xe4\x0a\xc5\x36\x78\x05\xe9\xd8\x51\xa1\xb8\xc5\x9a\x4c\xe7\x6b\x05\x4d\xa4\xaa\x50\x4d\x4a\x9d\xe4\xc6\x48\x0a\x11\x6b\xd2\x75\x08\xb5\x23\xec\x58\xb4\x0d\xad\x69\x50\x76\x11\xb3\x3a\x62\xd7\xfc\xe6\x32\xf2\x35\x7b\x32\x36\x78\x09\x8e\x0c\x8a\x50\x12\x53\xe1\x7e\xe8\xa1\xc7\x6b\xcd\xdc\x5f\x6c\xe4\x2e\x4d\x06\x1c\xd8\x97\xe1\xa0\xdf\xbf\x27\xbf\x87\x02\x4e\x93\x17\x00\x3b\xfe\x3e\x48\xca\xe1\x74\xd2\xf3\xb9\xef\xbf\x58\x45\xdf\x86\x98\x72\x50\x53\x78\x30\xfa\x5e\x9d\xe3\xd6\xf1\xaf\x14\x85\x83\x1f\x2f\xb8\x98\xab\x3b\x4a\x4c\xf8\x75\xc7\xef\xa2\x1b\x73\x2e\xe6\xd3\x9c\xf5\x5d\x0f\x5d\xeb\x99\xac\x25\x91\x1f\xe8\x38\xcd\xbc\x58\xab\x8c\x5d\x74\x6f\x23\x55\x7c\x9f\x83\x32\xab\x61\x27\xce\x7e\x0a\x25\xe5\xa0\xac\x63\x35\x45\xfa\xeb\x89\x2f\x73\x21\x6c\x6b\x96\x3d\x6e\x6f\x43\x79\x5c\x08\x4d\x47\x47\xd3\x59\xb7\xc8\xfe\x9b\xe0\x13\xf9\x74\x26\xb3\x64\xe9\x1c\x1e\x73\x78\xe1\x83\xa7\x17\xd7\xb3\x3b\x74\x68\x39\x1d\x73\xd8\x2c\x9e\x14\xd1\x0b\xa7\x11\xea\x1c\x05\xfd\x6a\x23\xe0\xd8\x13\xc6\x29\xad\x7f\xd2\x48\x4b\x13\x0e\x7f\xd2\xed\xd6\x05\xfb\xe1\x69\xbb\x97\xcf\x68\xb7\x35\x33\xc8\xc9\x2a\x79\x0f\x5c\x16\xca\x05\x2c\xd9\xd7\x6a\x7e\x4c\x53\xc0\x3a\x14\x29\x54\x87\x35\x65\x4b\x02\x8c\xe5\xc5\xcc\x2c\xb4\xec\xb3\x86\xb8\x6e\x52\x0e\x2f\x37\x9b\x7d\xb3\x8c\x74\xe0\x32\x35\xa3\xef\xf3\xeb\xc7\x78\x2a\x47\xf7\x8b\x13\x1d\xd7\x3e\xe3\x44\xad\xe4\x60\xc9\x27\x8a\x4b\xa8\x0a\x3e\x65\x15\xb6\xec\x8e\x39\x08\x7a\xc9\x84\x22\x57\x4b\xf8\x6e\x27\x89\xab\x63\x66\x27\xee\x1e\x57\x9f\xa1\x0c\x7b\xed\xd0\x2f\x68\x1e\x23\x98\xfb\x08\xff\x4e\x39\xbc\xa2\xf6\xfa\xec\x6f\x31\xd6\xec\xb3\x14\xba\x1c\xb2\x2f\xd7\x11\x1b\x5c\x88\x39\x7c\xf6\xe6\xf5\xf0\xbb\xf8\x57\x3d\x7f\x9c\xf8\xd2\x5a\x2f\x8c\x9a\x61\x8a\x33\xbf\xa6\xe4\xfd\xfc\xea\x56\xc7\x65\x1d\x33\x26\xb5\x0c\xbd\x7a\x23\xea\x66\x5d\x70\x91\xa0\x11\x95\x34\x44\xe9\xb1\xee\xd8\xd2\xdf\x89\xb6\x2e\xec\xca\xca\x61\xa4\x51\x75\xf0\x0e\xef\x8d\xe3\x5b\x31\x23\x7c\x3c\x90\x84\x96\xcc\x6b\xfd\x95\xde\x18\x2b\x0f\xdd\xba\x65\xaf\xad\x88\x32\xf3\xbb\x39\x9d\x80\x2b\x18\xe4\xe0\xe7\x84\x89\xed\xb7\x1c\xa1\xef\xaf\x3e\x3e\x93\x91\x31\xdf\x0c\x80\xc6\x1b\xc1\x36\x18\x85\x52\xa1\xde\xfd\xf2\x5d\xf6\x46\x3d\xd4\x36\x90\x68\x2f\x45\x7b\xf2\x65\x88\xfa\xee\x69\xd5\xcd\xfa\xbf\xfd\x17\xd5\x63\xcb\xbf\xaf\x5d\xb0\x91\x13\xfa\x67\x78\xfe\x93\xb6\x0f\xea\x36\xca\xfb\x59\x03\xff\x1d\x2f\xff\x77\xf3\xe7\xf1\xfb\x49\x28\xf8\xf8\x9e\x86\x35\xf9\x72\xde\xd2\xd6\x4c\x2a\xbe\x35\xc3\x47\xfb\xe6\xea\x8f\x00\x00\x00\xff\xff\x65\x0e\x41\xb9\xbc\x07\x00\x00")
|
||||
var _assetsV10ConsoleHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x56\x5f\x8f\xe3\x34\x10\x7f\xdf\x4f\x31\x32\x42\xf7\x42\xec\xde\x71\x12\xa7\x6c\xbb\x12\xe2\x84\x40\x80\x74\x12\xb7\xbc\x9e\x66\x9d\x49\x32\x7b\x8e\x1d\x3c\x6e\xbb\xa5\xca\x77\x47\xf9\xd7\x66\xbb\xc0\xc1\x02\xea\x8b\xe7\xff\xfc\x7e\xe3\x8c\xbb\xae\x53\xe3\xc0\xa1\xaf\x36\x8a\x7c\xb6\x15\x75\x73\x05\xb0\xae\x09\x8b\xfe\x00\xb0\x76\xec\x3f\x42\x24\xb7\x51\x6c\x83\x57\x90\x0e\x2d\x6d\x14\x37\x58\x91\x69\x7d\xa5\xa0\x8e\x54\x6e\x54\x9d\x52\x2b\xb9\x31\x92\x42\xc4\x8a\x74\x15\x42\xe5\x08\x5b\x16\x6d\x43\x63\x6a\x94\x6d\xc4\xac\x8a\xd8\xd6\xbf\xba\x8c\x7c\xc5\x9e\x8c\x0d\x5e\x82\x23\x83\x22\x94\xc4\x94\xb8\xeb\x6b\xe8\x21\xad\x99\xea\x8b\x8d\xdc\xa6\x51\x80\x3d\xfb\x22\xec\xf5\x87\x0f\xe4\x77\xb0\x81\xe3\xa8\x05\xc0\x96\xbf\x0b\x92\x72\x38\x1e\xf5\x74\xee\xba\x2f\x16\xd6\x77\x21\xa6\x1c\xd4\x68\xee\x85\xae\x53\x27\xbb\x75\xfc\x0b\x45\xe1\xe0\x87\x04\x67\x71\x91\xa3\xc0\x84\x5f\xb7\x7c\x1b\xdd\xe0\x73\x16\x9f\xfa\x2c\x73\x3d\x56\x2d\x7b\xb2\x96\x44\x7e\xa0\xc3\xd8\xf3\x2c\x2d\x3c\xb6\xd1\xbd\x8b\x54\xf2\x43\x0e\xca\x2c\x9a\x1d\x39\xfb\x29\x14\x94\x83\xb2\x8e\x1f\xe1\xb8\xbd\xfd\xfe\xed\x0c\xa2\x3f\x2f\xf2\x91\xc7\x3b\x47\xef\xc9\x51\x43\x29\x8e\x75\x2f\x74\x5d\x37\x3a\x77\xd7\x23\xf7\xe6\x4c\xfe\xda\xcc\x77\x62\x7d\x17\x8a\xc3\x3c\x9c\x74\x70\x34\x9e\x75\x83\xec\xbf\x09\x3e\x91\x4f\xa7\xc1\x14\x2c\xad\xc3\x43\x0e\x2f\x7c\xf0\xf4\xe2\x7a\x52\x87\x16\x2d\xa7\x43\x0e\xab\x59\x93\x22\x7a\xe1\x34\xd0\x36\x59\x41\xbf\x5a\x09\x38\xf6\x84\x71\x74\xeb\x9e\x14\xd2\x52\x87\xfd\x1f\x54\xbb\x73\xc1\x7e\x7c\x5a\xee\xe5\x33\xca\xad\xcd\x04\x72\x94\x0a\xde\x01\x17\x1b\xe5\x02\x16\xec\x2b\x35\x5d\xcc\xd1\x60\x1d\x8a\x6c\x54\x8b\x15\x65\xb3\x03\x0c\xe1\x1b\x35\xf9\x35\xec\xb3\x9a\xb8\xaa\x53\x0e\x2f\x57\xab\x5d\x3d\xb7\xb4\xe7\x22\xd5\x83\xee\xf3\xeb\x4b\x3c\xa5\xa3\x87\x59\x89\x8e\x2b\x9f\x71\xa2\x46\x72\xb0\xe4\x13\xc5\xd9\x54\x06\x9f\xb2\x12\x1b\x76\x87\x1c\x04\xbd\x64\x42\x91\xcb\xd9\x7c\xbf\x95\xc4\xe5\x21\xb3\x23\x77\x97\xd1\x27\x28\xfd\x5c\x5b\xf4\x33\x9a\x4b\x04\x53\x1d\xe1\xdf\x28\x87\x57\xd4\x5c\x9f\xf4\x0d\xc6\x8a\x7d\x96\x42\x9b\x43\xf6\xe5\xd2\x62\x83\x0b\x31\x87\xcf\xde\xbc\xee\x7f\x67\xfd\xa2\xe6\x8f\x23\x5f\x5a\xeb\x99\x51\xd3\x77\x71\xe2\xd7\x14\xbc\x9b\x6e\xdd\xe2\x38\x8f\x63\xc2\xa4\xe6\xa6\x17\x77\x44\xdd\x2c\x03\xce\xeb\x6c\x40\x25\x35\x51\xba\xdc\x61\xb6\xf0\xf7\xa2\xad\x0b\xdb\xa2\x74\x18\x69\xd8\x60\x78\x8f\x0f\xc6\xf1\x9d\x98\x01\x3e\xee\x49\x42\x43\xe6\xb5\xfe\x4a\xaf\x8c\x95\xc7\x6a\xdd\xb0\xd7\x56\x44\x99\xe9\xde\x1c\x8f\xc0\x25\xf4\x5f\xe5\xcf\x09\x13\xdb\xb7\x1c\xa1\xeb\xae\x3e\xdd\x93\x91\xc1\xdf\xf4\x80\x86\x8c\x60\x6b\x8c\x42\x69\xa3\x6e\xdf\x7f\x9b\xbd\x51\x8f\xf7\x24\x48\xb4\xe7\xa0\x1d\xf9\x22\x44\x7d\xff\x34\xea\x66\xf9\x6d\xff\x49\xf4\x50\xf2\xaf\x63\x67\x6c\xe4\x84\xfe\x1e\x9e\x7f\xf5\x4e\xf4\x9b\x72\x78\x2a\x4e\xfb\xf4\x9f\xf1\xf2\x5f\x17\x7f\x1e\xbf\xff\x0b\x05\x9f\x9e\x53\x3f\x26\x5f\x4c\x53\x5a\x9b\x71\x8b\xaf\x4d\xff\x07\xe0\xe6\xea\xf7\x00\x00\x00\xff\xff\xe7\xc6\x9b\x76\x08\x08\x00\x00")
|
||||
|
||||
func assetsV10ConsoleHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
@ -125,7 +125,7 @@ func assetsV10ConsoleHtml() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "assets/v1.0/console.html", size: 1980, mode: os.FileMode(436), modTime: time.Unix(1531907113, 0)}
|
||||
info := bindataFileInfo{name: "assets/v1.0/console.html", size: 2056, mode: os.FileMode(436), modTime: time.Unix(1547699579, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -10,7 +10,9 @@
|
||||
dataApiVersion: {{.dataApiVersion}},
|
||||
accessKey: {{.accessKey}},
|
||||
urlPrefix: "/",
|
||||
consoleMode: "cli"
|
||||
consoleMode: "cli",
|
||||
cliUUID: {{.cliUUID}},
|
||||
enableTelemetry: {{.enableTelemetry}}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
@ -10,7 +10,9 @@
|
||||
dataApiVersion: {{.dataApiVersion}},
|
||||
accessKey: {{.accessKey}},
|
||||
urlPrefix: "/",
|
||||
consoleMode: "cli"
|
||||
consoleMode: "cli",
|
||||
cliUUID: {{.cliUUID}},
|
||||
enableTelemetry: {{.enableTelemetry}}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
@ -10,7 +10,9 @@
|
||||
dataApiVersion: {{.dataApiVersion}},
|
||||
accessKey: {{.accessKey}},
|
||||
urlPrefix: "/",
|
||||
consoleMode: "cli"
|
||||
consoleMode: "cli",
|
||||
cliUUID: {{.cliUUID}},
|
||||
enableTelemetry: {{.enableTelemetry}}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
204
cli/cli.go
204
cli/cli.go
@ -8,6 +8,8 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -15,7 +17,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hasura/graphql-engine/cli/telemetry"
|
||||
"github.com/hasura/graphql-engine/cli/util"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/hasura/graphql-engine/cli/version"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
@ -37,11 +43,19 @@ const (
|
||||
// Other constants used in the package
|
||||
const (
|
||||
// Name of the global configuration directory
|
||||
GLOBAL_CONFIG_DIR_NAME = ".hasura-graphql"
|
||||
GLOBAL_CONFIG_DIR_NAME = ".hasura"
|
||||
// Name of the global configuration file
|
||||
GLOBAL_CONFIG_FILE_NAME = "config.json"
|
||||
)
|
||||
|
||||
// String constants
|
||||
const (
|
||||
StrTelemetryNotice = `Help us improve Hasura! The cli collects anonymized usage stats which
|
||||
allow us to keep improving Hasura at warp speed. To opt-out or read more,
|
||||
visit https://docs.hasura.io/1.0/graphql/manual/guides/telemetry.html
|
||||
`
|
||||
)
|
||||
|
||||
// HasuraGraphQLConfig has the config values required to contact the server.
|
||||
type HasuraGraphQLConfig struct {
|
||||
// Endpoint for the GraphQL Engine
|
||||
@ -62,6 +76,15 @@ func (hgc *HasuraGraphQLConfig) ParseEndpoint() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GlobalConfig is the configuration object stored in the GlobalConfigFile.
|
||||
type GlobalConfig struct {
|
||||
// UUID used for telemetry, generated on first run.
|
||||
UUID string `json:"uuid"`
|
||||
|
||||
// Indicate if telemetry is enabled or not
|
||||
EnableTelemetry bool `json:"enable_telemetry"`
|
||||
}
|
||||
|
||||
// ExecutionContext contains various contextual information required by the cli
|
||||
// at various points of it's execution. Values are filled in by the
|
||||
// initializers and passed on to each command. Commands can also fill in values
|
||||
@ -71,6 +94,12 @@ type ExecutionContext struct {
|
||||
// correctly render example strings etc.
|
||||
CMDName string
|
||||
|
||||
// ID is a unique ID for this Execution
|
||||
ID string
|
||||
|
||||
// ServerUUID is the unique ID for the server this execution is contacting.
|
||||
ServerUUID string
|
||||
|
||||
// Spinner is the global spinner object used to show progress across the cli.
|
||||
Spinner *spinner.Spinner
|
||||
// Logger is the global logger object to print logs.
|
||||
@ -96,6 +125,9 @@ type ExecutionContext struct {
|
||||
// stored.
|
||||
GlobalConfigFile string
|
||||
|
||||
// GlobalConfig holds all the configuration options.
|
||||
GlobalConfig *GlobalConfig
|
||||
|
||||
// IsStableRelease indicates if the CLI release is stable or not.
|
||||
IsStableRelease bool
|
||||
// Version indicates the version object
|
||||
@ -106,6 +138,17 @@ type ExecutionContext struct {
|
||||
|
||||
// LogLevel indicates the logrus default logging level
|
||||
LogLevel string
|
||||
|
||||
// Telemetry collects the telemetry data throughout the execution
|
||||
Telemetry *telemetry.Data
|
||||
}
|
||||
|
||||
// NewExecutionContext returns a new instance of execution context
|
||||
func NewExecutionContext() *ExecutionContext {
|
||||
ec := &ExecutionContext{}
|
||||
ec.Telemetry = telemetry.BuildEvent()
|
||||
ec.Telemetry.Version = version.BuildVersion
|
||||
return ec
|
||||
}
|
||||
|
||||
// Prepare as the name suggests, prepares the ExecutionContext ec by
|
||||
@ -128,14 +171,37 @@ func (ec *ExecutionContext) Prepare() error {
|
||||
// populate version
|
||||
ec.setVersion()
|
||||
|
||||
// setup global config directory
|
||||
err := ec.setupGlobalConfigDir()
|
||||
// setup global config
|
||||
err := ec.setupGlobalConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setting up config directory failed")
|
||||
// TODO(shahidhk): should this be a failure?
|
||||
return errors.Wrap(err, "setting up global config directory failed")
|
||||
}
|
||||
|
||||
// read global config
|
||||
err = ec.readGlobalConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading global config failed")
|
||||
}
|
||||
|
||||
// initialize a blank config
|
||||
ec.Config = &HasuraGraphQLConfig{}
|
||||
if ec.Config == nil {
|
||||
ec.Config = &HasuraGraphQLConfig{}
|
||||
}
|
||||
|
||||
// generate an execution id
|
||||
if ec.ID == "" {
|
||||
id := "00000000-0000-0000-0000-000000000000"
|
||||
u, err := uuid.NewV4()
|
||||
if err == nil {
|
||||
id = u.String()
|
||||
} else {
|
||||
ec.Logger.Debugf("generating uuid for execution ID failed, %v", err)
|
||||
}
|
||||
ec.ID = id
|
||||
ec.Logger.Debugf("execution id: %v", ec.ID)
|
||||
}
|
||||
ec.Telemetry.ExecutionID = ec.ID
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -171,7 +237,17 @@ func (ec *ExecutionContext) Validate() error {
|
||||
ec.Logger.Debug("graphql engine access_key: ", ec.Config.AccessKey)
|
||||
|
||||
// get version from the server and match with the cli version
|
||||
return ec.checkServerVersion()
|
||||
err = ec.checkServerVersion()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "version check")
|
||||
}
|
||||
|
||||
state := util.GetServerState(ec.Config.Endpoint, ec.Config.AccessKey, ec.Version.ServerSemver, ec.Logger)
|
||||
ec.ServerUUID = state.UUID
|
||||
ec.Telemetry.ServerUUID = ec.ServerUUID
|
||||
ec.Logger.Debugf("server: uuid: %s", ec.ServerUUID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ec *ExecutionContext) checkServerVersion() error {
|
||||
@ -180,6 +256,7 @@ func (ec *ExecutionContext) checkServerVersion() error {
|
||||
return errors.Wrap(err, "failed to get version from server")
|
||||
}
|
||||
ec.Version.SetServerVersion(v)
|
||||
ec.Telemetry.ServerVersion = ec.Version.GetServerVersion()
|
||||
isCompatible, reason := ec.Version.CheckCLIServerCompatibility()
|
||||
ec.Logger.Debugf("versions: cli: [%s] server: [%s]", ec.Version.GetCLIVersion(), ec.Version.GetServerVersion())
|
||||
ec.Logger.Debugf("compatibility check: [%v] %v", isCompatible, reason)
|
||||
@ -189,27 +266,56 @@ func (ec *ExecutionContext) checkServerVersion() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// readGlobalConfig reads the configuration from global config file env vars,
|
||||
// through viper.
|
||||
func (ec *ExecutionContext) readGlobalConfig() error {
|
||||
// need to get existing viper because https://github.com/spf13/viper/issues/233
|
||||
v := viper.New()
|
||||
v.SetEnvPrefix("HASURA_GRAPHQL")
|
||||
v.AutomaticEnv()
|
||||
v.SetConfigName("config")
|
||||
v.AddConfigPath(ec.GlobalConfigDir)
|
||||
err := v.ReadInConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannor read global config from file/env")
|
||||
}
|
||||
if ec.GlobalConfig == nil {
|
||||
ec.Logger.Debugf("global config is not pre-set, reading from current env")
|
||||
ec.GlobalConfig = &GlobalConfig{
|
||||
UUID: v.GetString("uuid"),
|
||||
EnableTelemetry: v.GetBool("enable_telemetry"),
|
||||
}
|
||||
} else {
|
||||
ec.Logger.Debugf("global config is pre-set to %#v", ec.GlobalConfig)
|
||||
}
|
||||
ec.Logger.Debugf("global config: uuid: %v", ec.GlobalConfig.UUID)
|
||||
ec.Logger.Debugf("global config: enableTelemetry: %v", ec.GlobalConfig.EnableTelemetry)
|
||||
// set if telemetry can be beamed or not
|
||||
ec.Telemetry.CanBeam = ec.GlobalConfig.EnableTelemetry
|
||||
ec.Telemetry.UUID = ec.GlobalConfig.UUID
|
||||
return nil
|
||||
}
|
||||
|
||||
// readConfig reads the configuration from config file, flags and env vars,
|
||||
// through viper.
|
||||
func (ec *ExecutionContext) readConfig() error {
|
||||
// need to get existing viper because https://github.com/spf13/viper/issues/233
|
||||
v := ec.Viper
|
||||
v.SetDefault("endpoint", "http://localhost:8080")
|
||||
v.SetDefault("access_key", "")
|
||||
v.SetEnvPrefix("HASURA_GRAPHQL")
|
||||
v.AutomaticEnv()
|
||||
v.SetConfigName("config")
|
||||
v.SetDefault("endpoint", "http://localhost:8080")
|
||||
v.SetDefault("access_key", "")
|
||||
v.AddConfigPath(ec.ExecutionDirectory)
|
||||
err := v.ReadInConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannor read config file")
|
||||
return errors.Wrap(err, "cannor read config from file/env")
|
||||
}
|
||||
ec.Config = &HasuraGraphQLConfig{
|
||||
Endpoint: v.GetString("endpoint"),
|
||||
AccessKey: v.GetString("access_key"),
|
||||
}
|
||||
err = ec.Config.ParseEndpoint()
|
||||
return err
|
||||
return ec.Config.ParseEndpoint()
|
||||
}
|
||||
|
||||
// setupSpinner creates a default spinner if the context does not already have
|
||||
@ -249,24 +355,90 @@ func (ec *ExecutionContext) setupLogger() {
|
||||
}
|
||||
ec.Logger.SetLevel(level)
|
||||
}
|
||||
|
||||
// set the logger for telemetry
|
||||
if ec.Telemetry.Logger == nil {
|
||||
ec.Telemetry.Logger = ec.Logger
|
||||
}
|
||||
}
|
||||
|
||||
// setupGlobalConfigDir ensures that global config directory exists and the
|
||||
// paths are correctly set.
|
||||
func (ec *ExecutionContext) setupGlobalConfigDir() error {
|
||||
// setupGlobConfig ensures that global config directory and file exists and
|
||||
// reads it into the GlobalConfig object.
|
||||
func (ec *ExecutionContext) setupGlobalConfig() error {
|
||||
if len(ec.GlobalConfigDir) == 0 {
|
||||
ec.Logger.Debug("global config directory is not pre-set, defaulting")
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot get home directory")
|
||||
}
|
||||
globalConfigDir := filepath.Join(home, GLOBAL_CONFIG_DIR_NAME)
|
||||
ec.GlobalConfigDir = globalConfigDir
|
||||
ec.Logger.Debugf("global config directory set as '%s'", ec.GlobalConfigDir)
|
||||
}
|
||||
err := os.MkdirAll(ec.GlobalConfigDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot create config directory")
|
||||
return errors.Wrap(err, "cannot create global config directory")
|
||||
}
|
||||
if len(ec.GlobalConfigFile) == 0 {
|
||||
ec.GlobalConfigFile = filepath.Join(ec.GlobalConfigDir, GLOBAL_CONFIG_FILE_NAME)
|
||||
ec.Logger.Debugf("global config file set as '%s'", ec.GlobalConfigFile)
|
||||
}
|
||||
_, err = os.Stat(ec.GlobalConfigFile)
|
||||
if os.IsNotExist(err) {
|
||||
// file does not exist, teat as first run and create it
|
||||
ec.Logger.Debug("global config file does not exist, this could be the first run, creating it...")
|
||||
u, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate uuid")
|
||||
}
|
||||
gc := GlobalConfig{
|
||||
UUID: u.String(),
|
||||
EnableTelemetry: true,
|
||||
}
|
||||
data, err := json.MarshalIndent(gc, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot marshal json for config file")
|
||||
}
|
||||
err = ioutil.WriteFile(ec.GlobalConfigFile, data, 0644)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing global config file failed")
|
||||
}
|
||||
ec.Logger.Debugf("global config file written at '%s' with content '%v'", ec.GlobalConfigFile, string(data))
|
||||
// also show a notice about telemetry
|
||||
ec.Logger.Info(StrTelemetryNotice)
|
||||
} else if os.IsExist(err) || err == nil {
|
||||
// file exists, verify contents
|
||||
ec.Logger.Debug("global config file exisits, verifying contents")
|
||||
data, err := ioutil.ReadFile(ec.GlobalConfigFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading global config file failed")
|
||||
}
|
||||
var gc GlobalConfig
|
||||
err = json.Unmarshal(data, &gc)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "global config file not a valid json")
|
||||
}
|
||||
_, err = uuid.FromString(gc.UUID)
|
||||
if err != nil {
|
||||
ec.Logger.Debugf("invalid uuid '%s' in global config: %v", gc.UUID, err)
|
||||
// create a new UUID
|
||||
ec.Logger.Debug("global config file exists, but uuid is invalid, creating a new one...")
|
||||
u, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate uuid")
|
||||
}
|
||||
gc.UUID = u.String()
|
||||
data, err := json.Marshal(gc)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot marshal json for config file")
|
||||
}
|
||||
err = ioutil.WriteFile(ec.GlobalConfigFile, data, 0644)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing global config file failed")
|
||||
}
|
||||
ec.Logger.Debugf("global config file written at '%s' with content '%v'", ec.GlobalConfigFile, string(data))
|
||||
}
|
||||
}
|
||||
ec.GlobalConfigFile = filepath.Join(ec.GlobalConfigDir, GLOBAL_CONFIG_FILE_NAME)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,9 @@ func init() {
|
||||
|
||||
func TestPrepare(t *testing.T) {
|
||||
logger, _ := test.NewNullLogger()
|
||||
ec := &cli.ExecutionContext{
|
||||
Logger: logger,
|
||||
Spinner: spinner.New(spinner.CharSets[7], 100*time.Millisecond),
|
||||
}
|
||||
ec := cli.NewExecutionContext()
|
||||
ec.Logger = logger
|
||||
ec.Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond)
|
||||
ec.Spinner.Writer = &fake.FakeWriter{}
|
||||
err := ec.Prepare()
|
||||
if err != nil {
|
||||
@ -47,16 +46,15 @@ func TestPrepare(t *testing.T) {
|
||||
t.Fatalf("global config file: expected $HOME/%s/%s, got %s", cli.GLOBAL_CONFIG_DIR_NAME, cli.GLOBAL_CONFIG_FILE_NAME, ec.GlobalConfigFile)
|
||||
}
|
||||
if ec.Config == nil {
|
||||
t.Fatal("nil HasuraGraphQLConfig")
|
||||
t.Fatal("got empty Config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
logger, _ := test.NewNullLogger()
|
||||
ec := &cli.ExecutionContext{
|
||||
Logger: logger,
|
||||
Spinner: spinner.New(spinner.CharSets[7], 100*time.Millisecond),
|
||||
}
|
||||
ec := cli.NewExecutionContext()
|
||||
ec.Logger = logger
|
||||
ec.Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond)
|
||||
ec.Spinner.Writer = &fake.FakeWriter{}
|
||||
ec.ExecutionDirectory = filepath.Join(os.TempDir(), "hasura-gql-tests-"+strconv.Itoa(rand.Intn(1000)))
|
||||
ec.Viper = viper.New()
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewConsoleCmd returns the console command
|
||||
func NewConsoleCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
v := viper.New()
|
||||
opts := &consoleOptions{
|
||||
@ -100,13 +101,15 @@ func (o *consoleOptions) run() error {
|
||||
o.EC.Logger.Debugf("rendering console template [%s] with assets [%s]", consoleTemplateVersion, consoleAssetsVersion)
|
||||
|
||||
consoleRouter, err := serveConsole(consoleTemplateVersion, o.StaticDir, gin.H{
|
||||
"apiHost": "http://" + o.Address,
|
||||
"apiPort": o.APIPort,
|
||||
"cliVersion": o.EC.Version.GetCLIVersion(),
|
||||
"dataApiUrl": o.EC.Config.ParsedEndpoint.String(),
|
||||
"dataApiVersion": "",
|
||||
"accessKey": o.EC.Config.AccessKey,
|
||||
"assetsVersion": consoleAssetsVersion,
|
||||
"apiHost": "http://" + o.Address,
|
||||
"apiPort": o.APIPort,
|
||||
"cliVersion": o.EC.Version.GetCLIVersion(),
|
||||
"dataApiUrl": o.EC.Config.ParsedEndpoint.String(),
|
||||
"dataApiVersion": "",
|
||||
"accessKey": o.EC.Config.AccessKey,
|
||||
"assetsVersion": consoleAssetsVersion,
|
||||
"enableTelemetry": o.EC.GlobalConfig.EnableTelemetry,
|
||||
"cliUUID": o.EC.GlobalConfig.UUID,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error serving console")
|
||||
@ -147,6 +150,8 @@ func (o *consoleOptions) run() error {
|
||||
o.EC.Spinner.Stop()
|
||||
log.Infof("console running at: %s", consoleURL)
|
||||
|
||||
o.EC.Telemetry.Beam()
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
@ -13,23 +13,29 @@ import (
|
||||
|
||||
func TestConsoleCmd(t *testing.T) {
|
||||
logger, _ := test.NewNullLogger()
|
||||
ec := cli.NewExecutionContext()
|
||||
ec.Telemetry.Command = "TEST"
|
||||
ec.Logger = logger
|
||||
ec.Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond)
|
||||
ec.Config = &cli.HasuraGraphQLConfig{
|
||||
Endpoint: "http://localhost:8080",
|
||||
AccessKey: "",
|
||||
}
|
||||
ec.Version = version.New()
|
||||
err := ec.Prepare()
|
||||
if err != nil {
|
||||
t.Fatalf("prepare failed: %v", err)
|
||||
}
|
||||
|
||||
opts := &consoleOptions{
|
||||
EC: &cli.ExecutionContext{
|
||||
Logger: logger,
|
||||
Spinner: spinner.New(spinner.CharSets[7], 100*time.Millisecond),
|
||||
Config: &cli.HasuraGraphQLConfig{
|
||||
Endpoint: "http://localhost:8080",
|
||||
AccessKey: "",
|
||||
},
|
||||
Version: version.New(),
|
||||
},
|
||||
EC: ec,
|
||||
APIPort: "9693",
|
||||
ConsolePort: "9695",
|
||||
Address: "localhost",
|
||||
DontOpenBrowser: true,
|
||||
}
|
||||
opts.EC.Spinner.Writer = &fake.FakeWriter{}
|
||||
err := opts.EC.Config.ParseEndpoint()
|
||||
err = opts.EC.Config.ParseEndpoint()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -7,8 +7,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewDocsCmd returns the docs command
|
||||
func NewDocsCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
var docType, docDirectory string
|
||||
docsCmd := &cobra.Command{
|
||||
@ -16,7 +18,8 @@ func NewDocsCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
Short: "Generate CLI docs in various formats",
|
||||
Hidden: true,
|
||||
SilenceUsage: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
ec.Viper = viper.New()
|
||||
return ec.Prepare()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -38,6 +39,7 @@ func NewInitCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
# See https://docs.hasura.io/1.0/graphql/manual/migrations/index.html for more details`,
|
||||
SilenceUsage: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
ec.Viper = viper.New()
|
||||
return ec.Prepare()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -20,29 +20,20 @@ func init() {
|
||||
|
||||
func TestInitCmd(t *testing.T) {
|
||||
logger, _ := test.NewNullLogger()
|
||||
ec := cli.NewExecutionContext()
|
||||
ec.Logger = logger
|
||||
ec.Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond)
|
||||
tt := []struct {
|
||||
name string
|
||||
opts *initOptions
|
||||
err error
|
||||
}{
|
||||
{"only-init-dir", &initOptions{
|
||||
EC: &cli.ExecutionContext{
|
||||
Logger: logger,
|
||||
Spinner: spinner.New(spinner.CharSets[7], 100*time.Millisecond),
|
||||
},
|
||||
EC: ec,
|
||||
Endpoint: "",
|
||||
AccessKey: "",
|
||||
InitDir: filepath.Join(os.TempDir(), "hasura-cli-test-"+strconv.Itoa(rand.Intn(1000))),
|
||||
}, nil},
|
||||
{"with-endpoint-flag", &initOptions{
|
||||
EC: &cli.ExecutionContext{
|
||||
Logger: logger,
|
||||
Spinner: spinner.New(spinner.CharSets[7], 100*time.Millisecond),
|
||||
},
|
||||
Endpoint: "https://localhost:8080",
|
||||
AccessKey: "",
|
||||
InitDir: filepath.Join(os.TempDir(), "hasura-cli-test-"+strconv.Itoa(rand.Intn(1000))),
|
||||
}, nil},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
|
@ -7,16 +7,22 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ec is the Execution Context for the current run.
|
||||
var ec *cli.ExecutionContext
|
||||
|
||||
// rootCmd is the main "hasura" command
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "hasura",
|
||||
Short: "Hasura GraphQL Engine command line tool",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
ec.Telemetry.Command = cmd.CommandPath()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ec := &cli.ExecutionContext{}
|
||||
ec = cli.NewExecutionContext()
|
||||
rootCmd.AddCommand(
|
||||
NewInitCmd(ec),
|
||||
NewConsoleCmd(ec),
|
||||
@ -32,5 +38,13 @@ func init() {
|
||||
|
||||
// Execute executes the command and returns the error
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
ec.Telemetry.IsError = true
|
||||
}
|
||||
ec.Telemetry.Beam()
|
||||
if ec.Spinner != nil {
|
||||
ec.Spinner.Stop()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ func NewVersionCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
Short: "Print the CLI version",
|
||||
SilenceUsage: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
ec.Viper = viper.New()
|
||||
return ec.Prepare()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ec.Logger.WithField("version", ec.Version.GetCLIVersion()).Info("hasura cli")
|
||||
ec.Viper = viper.New()
|
||||
err := ec.Validate()
|
||||
if err == nil {
|
||||
ec.Logger.
|
||||
|
117
cli/get.sh
Executable file
117
cli/get.sh
Executable file
@ -0,0 +1,117 @@
|
||||
# adapted from https://github.com/openfaas/faas-cli/blob/master/get.sh
|
||||
version=$(curl -s -H 'Content-Type: text/plain' https://releases.hasura.io/graphql-engine?agent=cli-get.sh)
|
||||
if [ ! $version ]; then
|
||||
echo "Failed while attempting to install hasura graphql-engine cli. Please manually install:"
|
||||
echo ""
|
||||
echo "1. Open your web browser and go to https://github.com/hasura/graphql-engine/releases"
|
||||
echo "2. Download the cli from latest release for your platform. Call it 'hasura'."
|
||||
echo "3. chmod +x ./hasura"
|
||||
echo "4. mv ./hasura /usr/local/bin"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hasCli() {
|
||||
|
||||
has=$(which hasura)
|
||||
|
||||
if [ "$?" = "0" ]; then
|
||||
echo
|
||||
echo "You already have the hasura cli!"
|
||||
export n=1
|
||||
echo "Overwriting in $n seconds.. Press Control+C to cancel."
|
||||
echo
|
||||
sleep $n
|
||||
fi
|
||||
|
||||
hasCurl=$(which curl)
|
||||
if [ "$?" = "1" ]; then
|
||||
echo "You need curl to use this script."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
getPackage() {
|
||||
uname=$(uname)
|
||||
userid=$(id -u)
|
||||
|
||||
suffix=""
|
||||
case $uname in
|
||||
"Darwin")
|
||||
suffix="-darwin-amd64"
|
||||
;;
|
||||
"Linux")
|
||||
arch=$(uname -m)
|
||||
echo $arch
|
||||
case $arch in
|
||||
"amd64" | "x86_64")
|
||||
suffix="-linux-amd4"
|
||||
;;
|
||||
esac
|
||||
case $arch in
|
||||
"aarch64")
|
||||
suffix="-linux-arm64"
|
||||
;;
|
||||
esac
|
||||
case $arch in
|
||||
"armv6l" | "armv7l")
|
||||
suffix="-linux-armhf"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
targetFile="/tmp/cli-hasura$suffix"
|
||||
|
||||
if [ "$userid" != "0" ]; then
|
||||
targetFile="$(pwd)/cli-hasura$suffix"
|
||||
fi
|
||||
|
||||
if [ -e $targetFile ]; then
|
||||
rm $targetFile
|
||||
fi
|
||||
|
||||
url=https://github.com/hasura/graphql-engine/releases/download/$version/cli-hasura$suffix
|
||||
echo "Downloading package $url as $targetFile"
|
||||
|
||||
curl -sSL $url --output $targetFile
|
||||
|
||||
if [ "$?" = "0" ]; then
|
||||
|
||||
chmod +x $targetFile
|
||||
|
||||
echo "Download complete."
|
||||
|
||||
if [ "$userid" != "0" ]; then
|
||||
|
||||
echo
|
||||
echo "========================================================="
|
||||
echo "== As the script was run as a non-root user the =="
|
||||
echo "== following command may need to be run manually =="
|
||||
echo "========================================================="
|
||||
echo
|
||||
echo " sudo cp cli-hasura$suffix /usr/local/bin/hasura"
|
||||
echo
|
||||
|
||||
else
|
||||
|
||||
echo
|
||||
echo "Running as root - Attempting to move hasura cli to /usr/local/bin"
|
||||
|
||||
mv $targetFile /usr/local/bin/hasura
|
||||
|
||||
if [ "$?" = "0" ]; then
|
||||
echo "New version of hasura cli installed to /usr/local/bin"
|
||||
fi
|
||||
|
||||
if [ -e $targetFile ]; then
|
||||
rm $targetFile
|
||||
fi
|
||||
|
||||
hasura version
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
hasCli
|
||||
getPackage
|
120
cli/telemetry/telemetry.go
Normal file
120
cli/telemetry/telemetry.go
Normal file
@ -0,0 +1,120 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hasura/graphql-engine/cli/version"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Waiter waits for telemetry ops to complete, if required
|
||||
var Waiter sync.WaitGroup
|
||||
|
||||
// Endpoint is where telemetry data is sent.
|
||||
const Endpoint = "https://telemetry.hasura.io/v1/http"
|
||||
|
||||
// Topic is the name under which telemetry is sent.
|
||||
var Topic = "cli_test"
|
||||
|
||||
func init() {
|
||||
var v = version.New()
|
||||
if v.CLISemver != nil {
|
||||
Topic = "cli"
|
||||
}
|
||||
}
|
||||
|
||||
type requestPayload struct {
|
||||
Topic string `json:"topic"`
|
||||
Data `json:"data"`
|
||||
}
|
||||
|
||||
// Data holds all info collected and transmitted
|
||||
type Data struct {
|
||||
// UUID used for telemetry, generated on first run.
|
||||
UUID string `json:"uuid"`
|
||||
|
||||
// UUID obtained from server.
|
||||
ServerUUID string `json:"server_uuid"`
|
||||
|
||||
// Unique id for the current execution.
|
||||
ExecutionID string `json:"execution_id"`
|
||||
|
||||
// OS platform and architecture.
|
||||
OSPlatform string `json:"os_platform"`
|
||||
OSArch string `json:"os_arch"`
|
||||
|
||||
// Current cli version.
|
||||
Version string `json:"version"`
|
||||
|
||||
// Current Server version.
|
||||
ServerVersion string `json:"server_version"`
|
||||
|
||||
// Command being executed.
|
||||
Command string `json:"command"`
|
||||
|
||||
// Indicates whether the execution resulted in an error or not.
|
||||
IsError bool `json:"is_error"`
|
||||
|
||||
// Any additional payload information.
|
||||
Payload map[string]interface{} `json:"payload"`
|
||||
|
||||
// Additional objects - mandatory
|
||||
Logger *logrus.Logger `json:"-"`
|
||||
|
||||
// IsBeamed indicates if this data is already beamed or not.
|
||||
IsBeamed bool `json:"-"`
|
||||
|
||||
// CanBeam indicates if data can be beamed or not, e.g. disabled telemetry.
|
||||
CanBeam bool `json:"-"`
|
||||
}
|
||||
|
||||
// BuildEvent returns a Data object which represent a telemetry event
|
||||
func BuildEvent() *Data {
|
||||
return &Data{
|
||||
OSPlatform: runtime.GOOS,
|
||||
OSArch: runtime.GOARCH,
|
||||
CanBeam: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Beam the telemetry data
|
||||
func (d *Data) Beam() {
|
||||
// to be on the safe side, create a new logger if a logger
|
||||
// is not passed
|
||||
if d.Logger == nil {
|
||||
d.Logger = logrus.New()
|
||||
}
|
||||
if !d.CanBeam {
|
||||
d.Logger.Debugf("telemetry: disabled, not beaming any data")
|
||||
return
|
||||
}
|
||||
if !d.IsBeamed {
|
||||
beam(d, d.Logger)
|
||||
} else {
|
||||
d.Logger.Debugf("telemetry: data already beamed")
|
||||
}
|
||||
}
|
||||
|
||||
func beam(d *Data, log *logrus.Logger) {
|
||||
d.IsBeamed = true
|
||||
p := requestPayload{
|
||||
Topic: Topic,
|
||||
Data: *d,
|
||||
}
|
||||
tick := time.Now()
|
||||
_, _, err := gorequest.New().
|
||||
Post(Endpoint).
|
||||
Timeout(2 * time.Second).
|
||||
Send(p).
|
||||
End()
|
||||
if err != nil {
|
||||
log.Debugf("telemetry: beaming payload failed: %v", err)
|
||||
} else {
|
||||
tock := time.Now()
|
||||
delta := tock.Sub(tick)
|
||||
log.WithField("isError", d.IsError).WithField("time", delta.String()).Debug("telemetry: beamed")
|
||||
}
|
||||
}
|
16
cli/telemetry/telemetry_test.go
Normal file
16
cli/telemetry/telemetry_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package telemetry_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hasura/graphql-engine/cli/telemetry"
|
||||
"github.com/hasura/graphql-engine/cli/version"
|
||||
)
|
||||
|
||||
func TestBeam(t *testing.T) {
|
||||
tm := telemetry.BuildEvent()
|
||||
tm.Version = version.BuildVersion
|
||||
tm.Command = "TEST"
|
||||
tm.CanBeam = true
|
||||
tm.Beam()
|
||||
}
|
58
cli/util/server.go
Normal file
58
cli/util/server.go
Normal file
@ -0,0 +1,58 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
)
|
||||
|
||||
// ServerState is the state of Hasura stored on the server.
|
||||
type ServerState struct {
|
||||
UUID string
|
||||
CLIState map[string]interface{}
|
||||
}
|
||||
|
||||
type hdbVersion struct {
|
||||
UUID string `json:"hasura_uuid"`
|
||||
CLIState map[string]interface{} `json:"cli_state"`
|
||||
}
|
||||
|
||||
// GetServerState queries a server for the state.
|
||||
func GetServerState(endpoint, accessKey string, serverVersion *semver.Version, log *logrus.Logger) *ServerState {
|
||||
state := &ServerState{
|
||||
UUID: "00000000-0000-0000-0000-000000000000",
|
||||
}
|
||||
payload := `{
|
||||
"type": "select",
|
||||
"args": {
|
||||
"table": {
|
||||
"schema": "hdb_catalog",
|
||||
"name": "hdb_version"
|
||||
},
|
||||
"columns": [
|
||||
"hasura_uuid",
|
||||
"cli_state"
|
||||
]
|
||||
}
|
||||
}`
|
||||
req := gorequest.New()
|
||||
req = req.Post(endpoint + "/v1/query").Send(payload)
|
||||
req.Set("X-Hasura-Access-Key", accessKey)
|
||||
|
||||
var r []hdbVersion
|
||||
_, _, errs := req.EndStruct(&r)
|
||||
if len(errs) != 0 {
|
||||
log.Debugf("server state: errors: %v", errs)
|
||||
return state
|
||||
}
|
||||
|
||||
if len(r) != 1 {
|
||||
log.Debugf("invalid response: %v", r)
|
||||
return state
|
||||
}
|
||||
|
||||
state.UUID = r[0].UUID
|
||||
state.CLIState = r[0].CLIState
|
||||
return state
|
||||
}
|
@ -6,12 +6,17 @@ const (
|
||||
noServerVersion = "server with no version treated as pre-release build"
|
||||
noCLIVersion = "cli version is empty, indicates a broken build"
|
||||
untaggedCLI = "untagged cli build can work with tagged server build"
|
||||
devCLI = "dev version of cli, compatible with all servers"
|
||||
)
|
||||
|
||||
// CheckCLIServerCompatibility compares server and cli for compatibility,
|
||||
// subject to certain conditions. compatible boolean is returned along with
|
||||
// a message which states the reason for the result.
|
||||
func (v *Version) CheckCLIServerCompatibility() (compatible bool, reason string) {
|
||||
// mark dev builds as compatible
|
||||
if v.CLI == "dev" {
|
||||
return true, devCLI
|
||||
}
|
||||
// empty cli version
|
||||
if v.CLI == "" {
|
||||
return false, noCLIVersion
|
||||
|
@ -17,6 +17,7 @@ const Endpoints = {
|
||||
hasuractlMigrate: `${hasuractlUrl}/apis/migrate`,
|
||||
hasuractlMetadata: `${hasuractlUrl}/apis/metadata`,
|
||||
hasuractlMigrateSettings: `${hasuractlUrl}/apis/migrate/settings`,
|
||||
telemetryServer: 'wss://telemetry.hasura.io/v1/ws',
|
||||
};
|
||||
|
||||
const globalCookiePolicy = 'omit';
|
||||
|
@ -32,6 +32,9 @@ const globals = {
|
||||
? 'server'
|
||||
: window.__env.consoleMode,
|
||||
urlPrefix: checkExtraSlashes(window.__env.urlPrefix),
|
||||
enableTelemetry: window.__env.enableTelemetry,
|
||||
telemetryTopic:
|
||||
window.__env.nodeEnv !== 'development' ? 'console' : 'console_test',
|
||||
};
|
||||
|
||||
// set defaults
|
||||
|
@ -19,13 +19,97 @@ import getRoutes from './routes';
|
||||
|
||||
import reducer from './reducer';
|
||||
import globals from './Globals';
|
||||
import Endpoints from './Endpoints';
|
||||
import { filterEventsBlockList, sanitiseUrl } from './telemetryFilter';
|
||||
|
||||
const analyticsUrl = Endpoints.telemetryServer;
|
||||
let analyticsConnection;
|
||||
const { consoleMode, enableTelemetry, cliUUID } = window.__env;
|
||||
const telemetryEnabled =
|
||||
enableTelemetry !== undefined && enableTelemetry === true;
|
||||
if (telemetryEnabled) {
|
||||
try {
|
||||
analyticsConnection = new WebSocket(analyticsUrl);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const onError = error => {
|
||||
console.log('WebSocket Error for Events' + error);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
try {
|
||||
analyticsConnection = new WebSocket(analyticsUrl);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
analyticsConnection.onclose = onClose();
|
||||
analyticsConnection.onerror = onError();
|
||||
};
|
||||
|
||||
function analyticsLogger({ getState }) {
|
||||
return next => action => {
|
||||
// Call the next dispatch method in the middleware chain.
|
||||
const returnValue = next(action);
|
||||
// check if analytics tracking is enabled
|
||||
if (telemetryEnabled) {
|
||||
const serverVersion = getState().main.serverVersion;
|
||||
const actionType = action.type;
|
||||
const url = sanitiseUrl(window.location.pathname);
|
||||
const reqBody = {
|
||||
server_version: serverVersion,
|
||||
event_type: actionType,
|
||||
url,
|
||||
console_mode: consoleMode,
|
||||
cli_uuid: cliUUID,
|
||||
server_uuid: getState().telemetry.hasura_uuid,
|
||||
};
|
||||
|
||||
let isLocationType = false;
|
||||
if (actionType === '@@router/LOCATION_CHANGE') {
|
||||
isLocationType = true;
|
||||
}
|
||||
// filter events
|
||||
if (!filterEventsBlockList.includes(actionType)) {
|
||||
if (
|
||||
analyticsConnection &&
|
||||
analyticsConnection.readyState === analyticsConnection.OPEN
|
||||
) {
|
||||
// When the connection is open, send data to the server
|
||||
if (isLocationType) {
|
||||
// capture page views
|
||||
const payload = action.payload;
|
||||
reqBody.url = sanitiseUrl(payload.pathname);
|
||||
}
|
||||
analyticsConnection.send(
|
||||
JSON.stringify({ data: reqBody, topic: globals.telemetryTopic })
|
||||
); // Send the data
|
||||
// check for possible error events and store more data?
|
||||
} else {
|
||||
// retry websocket connection
|
||||
// analyticsConnection = new WebSocket(analyticsUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This will likely be the action itself, unless
|
||||
// a middleware further in chain changed it.
|
||||
return returnValue;
|
||||
};
|
||||
}
|
||||
|
||||
// Create the store
|
||||
let _finalCreateStore;
|
||||
|
||||
if (__DEVELOPMENT__) {
|
||||
const tools = [
|
||||
applyMiddleware(thunk, routerMiddleware(browserHistory), createLogger()),
|
||||
applyMiddleware(
|
||||
thunk,
|
||||
routerMiddleware(browserHistory),
|
||||
createLogger(),
|
||||
analyticsLogger
|
||||
),
|
||||
require('redux-devtools').persistState(
|
||||
window.location.href.match(/[?&]debug_session=([^&]+)\b/)
|
||||
),
|
||||
@ -36,7 +120,7 @@ if (__DEVELOPMENT__) {
|
||||
_finalCreateStore = compose(...tools)(createStore);
|
||||
} else {
|
||||
_finalCreateStore = compose(
|
||||
applyMiddleware(thunk, routerMiddleware(browserHistory))
|
||||
applyMiddleware(thunk, routerMiddleware(browserHistory), analyticsLogger)
|
||||
)(createStore);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ import { NOTIF_EXPANDED } from './Actions';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'brace/mode/json';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import { telemetryNotificationShown } from '../../telemetry/Actions';
|
||||
import { showTelemetryNotification } from '../../telemetry/Notifications';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
@ -36,6 +38,8 @@ class App extends Component {
|
||||
connectionFailed,
|
||||
isNotifExpanded,
|
||||
notifMsg,
|
||||
telemetry,
|
||||
dispatch,
|
||||
} = this.props;
|
||||
|
||||
if (requestError && error) {
|
||||
@ -77,6 +81,13 @@ class App extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (telemetry.console_opts) {
|
||||
if (!telemetry.console_opts.telemetryNotificationShown) {
|
||||
dispatch(showTelemetryNotification());
|
||||
dispatch(telemetryNotificationShown());
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div>
|
||||
@ -153,6 +164,7 @@ const mapStateToProps = state => {
|
||||
return {
|
||||
...state.progressBar,
|
||||
notifications: state.notifications,
|
||||
telemetry: state.telemetry,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -7,6 +7,7 @@ import * as tooltip from './Tooltips';
|
||||
import 'react-toggle/style.css';
|
||||
import Spinner from '../Common/Spinner/Spinner';
|
||||
import { loadServerVersion, checkServerUpdates } from './Actions';
|
||||
import { loadConsoleOpts } from '../../telemetry/Actions.js';
|
||||
import './NotificationOverrides.css';
|
||||
import semverCheck from '../../helpers/semver';
|
||||
|
||||
@ -35,6 +36,7 @@ class Main extends React.Component {
|
||||
.querySelector('body')
|
||||
.addEventListener('click', this.handleBodyClick);
|
||||
dispatch(loadServerVersion()).then(() => {
|
||||
dispatch(loadConsoleOpts());
|
||||
dispatch(checkServerUpdates()).then(() => {
|
||||
let isUpdateAvailable = false;
|
||||
try {
|
||||
@ -50,9 +52,12 @@ class Main extends React.Component {
|
||||
);
|
||||
if (isClosedBefore === 'true') {
|
||||
isUpdateAvailable = false;
|
||||
this.setState({ showBannerNotification: false });
|
||||
this.setState({ ...this.state, showBannerNotification: false });
|
||||
} else {
|
||||
this.setState({ showBannerNotification: isUpdateAvailable });
|
||||
this.setState({
|
||||
...this.state,
|
||||
showBannerNotification: isUpdateAvailable,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@ -73,7 +78,7 @@ class Main extends React.Component {
|
||||
checkEventsTab() {
|
||||
const showEvents = semverCheck('eventsTab', this.props.serverVersion);
|
||||
if (showEvents) {
|
||||
this.setState({ showEvents: true });
|
||||
this.setState({ ...this.state, showEvents: true });
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -107,7 +112,7 @@ class Main extends React.Component {
|
||||
latestServerVersion + '_BANNER_NOTIFICATION_CLOSED',
|
||||
'true'
|
||||
);
|
||||
this.setState({ showBannerNotification: false });
|
||||
this.setState({ ...this.state, showBannerNotification: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -1116,3 +1116,10 @@
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
.telemetryNotification {
|
||||
background-color: red;
|
||||
padding: 10px;
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: 0px;
|
||||
}
|
@ -9,6 +9,7 @@ const defaultState = {
|
||||
loginError: false,
|
||||
serverVersion: null,
|
||||
latestServerVersion: null,
|
||||
telemetryEnabled: true,
|
||||
};
|
||||
|
||||
export default defaultState;
|
||||
|
@ -47,7 +47,8 @@ export default class Html extends Component {
|
||||
isAccessKeySet: ${process.env.IS_ACCESS_KEY_SET},
|
||||
consoleMode: '${process.env.CONSOLE_MODE}',
|
||||
nodeEnv: '${process.env.NODE_ENV}',
|
||||
urlPrefix: '${process.env.URL_PREFIX}'
|
||||
urlPrefix: '${process.env.URL_PREFIX}',
|
||||
enableTelemetry: ${process.env.ENABLE_TELEMETRY}
|
||||
};`,
|
||||
}}
|
||||
/>
|
||||
|
@ -6,6 +6,7 @@ import { customResolverReducer } from './components/Services/CustomResolver';
|
||||
import mainReducer from './components/Main/Actions';
|
||||
import apiExplorerReducer from 'components/ApiExplorer/Actions';
|
||||
import progressBarReducer from 'components/App/Actions';
|
||||
import telemetryReducer from './telemetry/Actions';
|
||||
|
||||
import { reducer as notifications } from 'react-notification-system-redux';
|
||||
|
||||
@ -17,6 +18,7 @@ const reducer = combineReducers({
|
||||
main: mainReducer,
|
||||
routing: routerReducer,
|
||||
customResolverData: customResolverReducer,
|
||||
telemetry: telemetryReducer,
|
||||
notifications,
|
||||
});
|
||||
|
||||
|
122
console/src/telemetry/Actions.js
Normal file
122
console/src/telemetry/Actions.js
Normal file
@ -0,0 +1,122 @@
|
||||
import Endpoints, { globalCookiePolicy } from '../Endpoints';
|
||||
import requestAction from '../utils/requestAction';
|
||||
import dataHeaders from '../components/Services/Data/Common/Headers';
|
||||
import defaultTelemetryState from './State';
|
||||
|
||||
const SET_CONSOLE_OPTS = 'Telemetry/SET_CONSOLE_OPTS';
|
||||
const SET_NOTIFICATION_SHOWN = 'Telemetry/SET_NOTIFICATION_SHOWN';
|
||||
const SET_HASURA_UUID = 'Telemetry/SET_HASURA_UUID';
|
||||
const SET_TELEMETRY_DISABLED = 'Telemetr/SET_TELEMETRY_DISABLED';
|
||||
|
||||
const telemetryNotificationShown = () => dispatch => {
|
||||
dispatch({ type: SET_NOTIFICATION_SHOWN });
|
||||
};
|
||||
|
||||
const setNotificationShownInDB = () => (dispatch, getState) => {
|
||||
const url = Endpoints.getSchema;
|
||||
const uuid = getState().telemetry.hasura_uuid;
|
||||
const options = {
|
||||
credentials: globalCookiePolicy,
|
||||
method: 'POST',
|
||||
headers: dataHeaders(getState),
|
||||
body: JSON.stringify({
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `update hdb_catalog.hdb_version set console_state = console_state || jsonb_build_object('telemetryNotificationShown', true) where hasura_uuid='${uuid}';`,
|
||||
},
|
||||
}),
|
||||
};
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
data => {
|
||||
console.log(
|
||||
'Updated telemetry notification status in db' + JSON.stringify(data)
|
||||
);
|
||||
},
|
||||
error => {
|
||||
console.error(
|
||||
'Failed to update telemetry notification status in db' +
|
||||
JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const loadConsoleOpts = () => {
|
||||
return (dispatch, getState) => {
|
||||
if (window.__env.enableTelemetry === undefined) {
|
||||
return dispatch({ type: SET_TELEMETRY_DISABLED });
|
||||
}
|
||||
const url = Endpoints.getSchema;
|
||||
const options = {
|
||||
credentials: globalCookiePolicy,
|
||||
method: 'POST',
|
||||
headers: dataHeaders(getState),
|
||||
body: JSON.stringify({
|
||||
type: 'select',
|
||||
args: {
|
||||
table: {
|
||||
name: 'hdb_version',
|
||||
schema: 'hdb_catalog',
|
||||
},
|
||||
columns: ['hasura_uuid', 'console_state'],
|
||||
},
|
||||
}),
|
||||
};
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
data => {
|
||||
if (data.length !== 0) {
|
||||
dispatch({
|
||||
type: SET_HASURA_UUID,
|
||||
data: data[0].hasura_uuid,
|
||||
});
|
||||
dispatch({
|
||||
type: SET_CONSOLE_OPTS,
|
||||
data: data[0].console_state,
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.error(
|
||||
'Failed to load telemetry misc options' + JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const telemetryReducer = (state = defaultTelemetryState, action) => {
|
||||
switch (action.type) {
|
||||
case SET_CONSOLE_OPTS:
|
||||
return {
|
||||
...state,
|
||||
console_opts: {
|
||||
...action.data,
|
||||
telemetryNotificationShown: action.data.telemetryNotificationShown
|
||||
? true
|
||||
: false,
|
||||
},
|
||||
};
|
||||
case SET_NOTIFICATION_SHOWN:
|
||||
return {
|
||||
...state,
|
||||
console_opts: {
|
||||
...state.console_opts,
|
||||
telemetryNotificationShown: true,
|
||||
},
|
||||
};
|
||||
case SET_HASURA_UUID:
|
||||
return {
|
||||
...state,
|
||||
hasura_uuid: action.data,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default telemetryReducer;
|
||||
export {
|
||||
loadConsoleOpts,
|
||||
telemetryNotificationShown,
|
||||
setNotificationShownInDB,
|
||||
};
|
40
console/src/telemetry/Notifications.js
Normal file
40
console/src/telemetry/Notifications.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import Notifications from 'react-notification-system-redux';
|
||||
import { setNotificationShownInDB } from './Actions';
|
||||
|
||||
const onRemove = () => {
|
||||
return dispatch => {
|
||||
dispatch(setNotificationShownInDB());
|
||||
};
|
||||
};
|
||||
|
||||
const showTelemetryNotification = () => {
|
||||
return dispatch => {
|
||||
dispatch(
|
||||
Notifications.show({
|
||||
position: 'tr',
|
||||
autoDismiss: 10,
|
||||
level: 'info',
|
||||
title: 'Telemetry',
|
||||
children: (
|
||||
<div>
|
||||
Help us improve Hasura! The console collects anonymized usage stats
|
||||
which allows us to keep improving Hasura at warp speed.
|
||||
<a
|
||||
href="https://docs.hasura.io/1.0/graphql/manual/guides/telemetry.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{' '}
|
||||
Click here
|
||||
</a>{' '}
|
||||
to read more or to opt-out.
|
||||
</div>
|
||||
),
|
||||
onRemove: () => dispatch(onRemove()),
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export { showTelemetryNotification };
|
6
console/src/telemetry/State.js
Normal file
6
console/src/telemetry/State.js
Normal file
@ -0,0 +1,6 @@
|
||||
const defaultTelemetryState = {
|
||||
console_opts: null,
|
||||
hasura_uuid: null,
|
||||
};
|
||||
|
||||
export default defaultTelemetryState;
|
61
console/src/telemetryFilter.js
Normal file
61
console/src/telemetryFilter.js
Normal file
@ -0,0 +1,61 @@
|
||||
import globals from './Globals';
|
||||
|
||||
const filterEventsBlockList = [
|
||||
'App/ONGOING_REQUEST',
|
||||
'App/DONE_REQUEST',
|
||||
'App/FAILED_REQUEST',
|
||||
'App/ERROR_REQUEST',
|
||||
];
|
||||
|
||||
const filterPayloadAllowList = [];
|
||||
|
||||
const DATA_PATH = '/data';
|
||||
const API_EXPLORER_PATH = '/api-explorer';
|
||||
const REMOTE_SCHEMAS_PATH = '/remote-schemas';
|
||||
const EVENTS_PATH = '/events';
|
||||
|
||||
const dataHandler = path => {
|
||||
return (
|
||||
DATA_PATH +
|
||||
path
|
||||
.replace(/\/schema\/(\w+)(\/)?/, '/schema/SCHEMA_NAME$2')
|
||||
.replace(/(\/schema\/.*)\/tables\/(\w*)(\/.*)?/, '$1/tables/TABLE_NAME$3')
|
||||
);
|
||||
};
|
||||
|
||||
const apiExplorerHandler = () => {
|
||||
return API_EXPLORER_PATH;
|
||||
};
|
||||
|
||||
const remoteSchemasHandler = path => {
|
||||
return (
|
||||
REMOTE_SCHEMAS_PATH +
|
||||
path.replace(/(\/manage\/).*(\/\w+.*)$/, '$1REMOTE_SCHEMA_NAME$2')
|
||||
);
|
||||
};
|
||||
|
||||
const eventsHandler = path => {
|
||||
return (
|
||||
EVENTS_PATH +
|
||||
path.replace(/(\/manage\/triggers\/).*(\/\w+.*)$/, '$1TRIGGER_NAME$2')
|
||||
);
|
||||
};
|
||||
|
||||
const sanitiseUrl = path => {
|
||||
path = path.replace(new RegExp(globals.urlPrefix, 'g'), '');
|
||||
if (path.indexOf(DATA_PATH) === 0) {
|
||||
return dataHandler(path.slice(DATA_PATH.length));
|
||||
}
|
||||
if (path.indexOf(API_EXPLORER_PATH) === 0) {
|
||||
return apiExplorerHandler(path.slice(API_EXPLORER_PATH.length));
|
||||
}
|
||||
if (path.indexOf(REMOTE_SCHEMAS_PATH) === 0) {
|
||||
return remoteSchemasHandler(path.slice(REMOTE_SCHEMAS_PATH.length));
|
||||
}
|
||||
if (path.indexOf(EVENTS_PATH) === 0) {
|
||||
return eventsHandler(path.slice(EVENTS_PATH.length));
|
||||
}
|
||||
return '/';
|
||||
};
|
||||
|
||||
export { filterEventsBlockList, filterPayloadAllowList, sanitiseUrl };
|
@ -81,6 +81,8 @@ For ``serve`` subcommand these are the flags available
|
||||
|
||||
--use-prepared-statements Use prepared statements for SQL queries (default: true)
|
||||
|
||||
--enable-telemetry Enable anonymous telemetry (default: true)
|
||||
|
||||
|
||||
Default environment variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -147,5 +149,7 @@ These are the environment variables which are available:
|
||||
HASURA_GRAPHQL_ENABLE_CONSOLE Enable API console. It is served at
|
||||
'/' and '/console'
|
||||
|
||||
HASURA_GRAPHQL_ENABLE_TELEMETRY Enable anonymous telemetry (default: true)
|
||||
|
||||
HASURA_GRAPHQL_USE_PREPARED_STATEMENTS Use prepared statements for SQL queries
|
||||
(default: true)
|
||||
|
@ -50,6 +50,10 @@ Postgres Auditing
|
||||
|
||||
- :doc:`Auditing tables <auditing-tables>`
|
||||
|
||||
Telemetry
|
||||
---------
|
||||
|
||||
- :doc:`Guide on telemetry and instructions to opt-out <telemetry>`
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
@ -61,3 +65,4 @@ Postgres Auditing
|
||||
Integration/migration tutorials <integrations/index>
|
||||
Integrating with monitoring frameworks <monitoring/index>
|
||||
Auditing tables <auditing-tables>
|
||||
Telemetry <telemetry>
|
||||
|
148
docs/graphql/manual/guides/telemetry.rst
Normal file
148
docs/graphql/manual/guides/telemetry.rst
Normal file
@ -0,0 +1,148 @@
|
||||
.. _telemetry:
|
||||
|
||||
Telemetry Guide/FAQ
|
||||
===================
|
||||
|
||||
.. contents:: Table of contents
|
||||
:backlinks: none
|
||||
:depth: 1
|
||||
:local:
|
||||
|
||||
The Hasura GraphQL Engine collects anonymous telemetry data that helps the
|
||||
Hasura team in understanding how the product is being used and in deciding
|
||||
what to focus on next.
|
||||
|
||||
The data collected is minimal and, since there is no *sign-in* associated with
|
||||
the GraphQL Engine, it **cannot be used to uniquely identify any user**.
|
||||
Furthermore, data collected is strictly statistical in nature and
|
||||
**no proprietary information is collected** (*please see the next section*).
|
||||
|
||||
As a growing community, we greatly appreciate the telemetry data users
|
||||
send to us, as it is very valuable in making GraphQL Engine a better product
|
||||
for everyone. If you are worried about privacy, you can choose to disable
|
||||
sending telemetry as described :ref:`here <telemetry_optout>`.
|
||||
|
||||
.. note::
|
||||
|
||||
Access to collected data is strictly limited to the Hasura team and not shared with 3rd parties.
|
||||
|
||||
What data are collected?
|
||||
------------------------
|
||||
|
||||
Server
|
||||
~~~~~~
|
||||
|
||||
The server periodically sends the number of tables, views, relationships,
|
||||
permission rules, event triggers and remote schemas tracked by GraphQL Engine,
|
||||
along with randomly generated UUID per database and per instance. The
|
||||
server version is also sent.
|
||||
|
||||
Here is a sample row from the telemetry database:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": 12,
|
||||
"timestamp": "2019-01-21T19:43:33.63838+00:00",
|
||||
"db_uid": "dddff371-dab2-450f-9969-235bca66dab1",
|
||||
"instance_uid": "6799360d-a431-40c5-9f68-24592a9f07df",
|
||||
"version": "v1.0.0-alpha35",
|
||||
"metrics": {
|
||||
"views": 1,
|
||||
"tables": 2,
|
||||
"permissions": {
|
||||
"roles": 1,
|
||||
"delete": 2,
|
||||
"insert": 1,
|
||||
"select": 2,
|
||||
"update": 2
|
||||
},
|
||||
"relationships": {
|
||||
"auto": 2,
|
||||
"manual": 0
|
||||
},
|
||||
"event_triggers": 0,
|
||||
"remote_schemas": 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Console
|
||||
~~~~~~~
|
||||
|
||||
The console is a React-Redux UI. Redux action names along with anonymized
|
||||
route names are sent without any identifiable information or payload. Console
|
||||
also records the UUID of the server/CLI that it is connected to.
|
||||
|
||||
Here is a sample:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": 902,
|
||||
"timestamp": "2019-01-21T10:00:23.849202+00:00",
|
||||
"url": "/data/schema/SCHEMA_NAME/tables/TABLE_NAME/modify",
|
||||
"event_type": "ModifyTable/RESET",
|
||||
"console_mode": "server",
|
||||
"server_uuid": "79485a57-fca5-40f3-a31b-78c0d211314b",
|
||||
"server_version": "v1.0.0-alpha35",
|
||||
"cli_uuid": null
|
||||
}
|
||||
|
||||
|
||||
CLI
|
||||
~~~
|
||||
|
||||
The CLI collects each execution event, along with a randomly generated UUID.
|
||||
The execution event contains the command name, timestamp and whether the
|
||||
execution resulted in an error or not. **Error messages, arguments and flags
|
||||
are not recorded**. CLI also collects the server version and UUID that it
|
||||
is talking to. The operating system platform and architecture is also
|
||||
noted along with the CLI version.
|
||||
|
||||
Sample data:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": 115,
|
||||
"timestamp": "2019-01-21T11:36:07.86783+00:00",
|
||||
"uuid": "e462ce20-42dd-40fd-9549-edfb92f80455",
|
||||
"execution_id": "ddfa9c33-0693-457d-9026-c7f456c43322",
|
||||
"version": "v0.4.27",
|
||||
"command": "hasura version",
|
||||
"is_error": false,
|
||||
"os_platform": "linux",
|
||||
"os_arch": "amd64",
|
||||
"server_uuid": "a4d66fb2-f88d-457b-8db1-ea7a0b57921d",
|
||||
"server_version": "v1.0.0-alpha35",
|
||||
"payload": null
|
||||
}
|
||||
|
||||
Where is the data sent?
|
||||
-----------------------
|
||||
|
||||
The data is sent to Hasura's servers addressed by ``telemetry.hasura.io``.
|
||||
|
||||
.. _telemetry_optout:
|
||||
|
||||
How do I turn off telemetry (opt-out)?
|
||||
--------------------------------------
|
||||
|
||||
You can turn off telemetry on the server and on the console hosted by server
|
||||
by setting the following environment variable on the server or by using
|
||||
the flag ``--enable-telemetry=false``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HASURA_GRAPHQL_ENABLE_TELEMETRY=false
|
||||
|
||||
In order to turn off telemetry on CLI and on the console served by CLI,
|
||||
you can set the same environment varibale on the machine running CLI.
|
||||
You can also set ``"enable_telemetry": false`` in the JSON file created
|
||||
by the CLI at ``~/.hasura/.config.json`` to perisist the setting.
|
||||
|
||||
Privacy Policy
|
||||
--------------
|
||||
|
||||
You can check out our privacy policy `here <https://hasura.io/legal/hasura-privacy-policy>`_.
|
@ -148,6 +148,7 @@ library
|
||||
, Hasura.Server.Utils
|
||||
, Hasura.Server.Version
|
||||
, Hasura.Server.CheckUpdates
|
||||
, Hasura.Server.Telemetry
|
||||
, Hasura.RQL.Types
|
||||
, Hasura.RQL.Instances
|
||||
, Hasura.RQL.Types.SchemaCache
|
||||
|
@ -31,6 +31,7 @@ import Hasura.Server.Auth
|
||||
import Hasura.Server.CheckUpdates (checkForUpdates)
|
||||
import Hasura.Server.Init
|
||||
import Hasura.Server.Query (peelRun)
|
||||
import Hasura.Server.Telemetry
|
||||
import Hasura.Server.Version (currentVersion)
|
||||
|
||||
import qualified Database.PG.Query as Q
|
||||
@ -71,6 +72,7 @@ parseHGECommand =
|
||||
<*> parseUnAuthRole
|
||||
<*> parseCorsConfig
|
||||
<*> parseEnableConsole
|
||||
<*> parseEnableTelemetry
|
||||
|
||||
parseArgs :: IO HGEOptions
|
||||
parseArgs = do
|
||||
@ -100,8 +102,8 @@ main = do
|
||||
loggerCtx <- mkLoggerCtx $ defaultLoggerSettings True
|
||||
let logger = mkLogger loggerCtx
|
||||
case hgeCmd of
|
||||
HCServe so@(ServeOptions port host cp isoL mAccessKey mAuthHook
|
||||
mJwtSecret mUnAuthRole corsCfg enableConsole) -> do
|
||||
HCServe so@(ServeOptions port host cp isoL mAccessKey mAuthHook mJwtSecret
|
||||
mUnAuthRole corsCfg enableConsole enableTelemetry) -> do
|
||||
-- log serve options
|
||||
unLogger logger $ serveOptsToLog so
|
||||
hloggerCtx <- mkLoggerCtx $ defaultLoggerSettings False
|
||||
@ -114,22 +116,19 @@ main = do
|
||||
ci <- procConnInfo rci
|
||||
-- log postgres connection info
|
||||
unLogger logger $ connInfoToLog ci
|
||||
|
||||
-- safe init catalog
|
||||
initialise logger ci httpManager
|
||||
-- migrate catalog if necessary
|
||||
migrate logger ci httpManager
|
||||
initRes <- initialise logger ci httpManager
|
||||
|
||||
-- prepare event triggers data
|
||||
prepareEvents logger ci
|
||||
|
||||
pool <- Q.initPGPool ci cp
|
||||
(app, cacheRef) <- mkWaiApp isoL loggerCtx pool httpManager
|
||||
am corsCfg enableConsole
|
||||
am corsCfg enableConsole enableTelemetry
|
||||
|
||||
let warpSettings = Warp.setPort port $ Warp.setHost host Warp.defaultSettings
|
||||
|
||||
-- start a background thread to check for updates
|
||||
void $ C.forkIO $ checkForUpdates loggerCtx httpManager
|
||||
|
||||
maxEvThrds <- getFromEnv defaultMaxEventThreads "HASURA_GRAPHQL_EVENTS_HTTP_POOL_SIZE"
|
||||
evFetchMilliSec <- getFromEnv defaultFetchIntervalMilliSec "HASURA_GRAPHQL_EVENTS_FETCH_INTERVAL"
|
||||
logEnvHeaders <- getFromEnv False "LOG_HEADERS_FROM_ENV"
|
||||
@ -141,6 +140,14 @@ main = do
|
||||
mkGenericStrLog "event_triggers" "starting workers"
|
||||
void $ C.forkIO $ processEventQueue hloggerCtx logEnvHeaders httpSession pool cacheRef eventEngineCtx
|
||||
|
||||
-- start a background thread to check for updates
|
||||
void $ C.forkIO $ checkForUpdates loggerCtx httpManager
|
||||
|
||||
-- start a background thread for telemetry
|
||||
when enableTelemetry $ do
|
||||
unLogger logger $ mkGenericStrLog "telemetry" telemetryNotice
|
||||
void $ C.forkIO $ runTelemetry logger httpManager cacheRef initRes
|
||||
|
||||
unLogger logger $
|
||||
mkGenericStrLog "server" "starting API server"
|
||||
Warp.runSettings warpSettings app
|
||||
@ -163,6 +170,7 @@ main = do
|
||||
|
||||
HCVersion -> putStrLn $ "Hasura GraphQL Engine: " ++ T.unpack currentVersion
|
||||
where
|
||||
|
||||
runTx :: Q.ConnInfo -> Q.TxE QErr a -> IO (Either QErr a)
|
||||
runTx ci tx = do
|
||||
pool <- getMinimalPool ci
|
||||
@ -184,19 +192,29 @@ main = do
|
||||
|
||||
initialise (Logger logger) ci httpMgr = do
|
||||
currentTime <- getCurrentTime
|
||||
res <- runAsAdmin ci httpMgr $ initCatalogSafe currentTime
|
||||
either printErrJExit (logger . mkGenericStrLog "db_init") res
|
||||
|
||||
migrate (Logger logger) ci httpMgr = do
|
||||
currentTime <- getCurrentTime
|
||||
res <- runAsAdmin ci httpMgr $ migrateCatalog currentTime
|
||||
either printErrJExit (logger . mkGenericStrLog "db_migrate") res
|
||||
-- initialise the catalog
|
||||
initRes <- runAsAdmin ci httpMgr $ initCatalogSafe currentTime
|
||||
either printErrJExit (logger . mkGenericStrLog "db_init") initRes
|
||||
|
||||
-- migrate catalog if necessary
|
||||
migRes <- runAsAdmin ci httpMgr $ migrateCatalog currentTime
|
||||
either printErrJExit (logger . mkGenericStrLog "db_migrate") migRes
|
||||
|
||||
-- generate and retrieve uuids
|
||||
getUniqIds ci
|
||||
|
||||
prepareEvents (Logger logger) ci = do
|
||||
logger $ mkGenericStrLog "event_triggers" "preparing data"
|
||||
res <- runTx ci unlockAllEvents
|
||||
either printErrJExit return res
|
||||
|
||||
getUniqIds ci = do
|
||||
eDbId <- runTx ci getDbId
|
||||
dbId <- either printErrJExit return eDbId
|
||||
fp <- liftIO generateFingerprint
|
||||
return (dbId, fp)
|
||||
|
||||
getFromEnv :: (Read a) => a -> String -> IO a
|
||||
getFromEnv defaults env = do
|
||||
mEnv <- lookupEnv env
|
||||
@ -208,3 +226,10 @@ main = do
|
||||
|
||||
cleanSuccess =
|
||||
putStrLn "successfully cleaned graphql-engine related data"
|
||||
|
||||
|
||||
telemetryNotice :: String
|
||||
telemetryNotice =
|
||||
"Help us improve Hasura! The graphql-engine server collects anonymized "
|
||||
<> "usage stats which allows us to keep improving Hasura at warp speed. "
|
||||
<> "To read more or opt-out, visit https://docs.hasura.io/1.0/graphql/manual/guides/telemetry.html"
|
||||
|
@ -24,7 +24,7 @@ import qualified Database.PG.Query as Q
|
||||
import qualified Database.PG.Query.Connection as Q
|
||||
|
||||
curCatalogVer :: T.Text
|
||||
curCatalogVer = "8"
|
||||
curCatalogVer = "9"
|
||||
|
||||
initCatalogSafe
|
||||
:: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m)
|
||||
@ -102,7 +102,8 @@ initCatalogStrict createSchema initTime = do
|
||||
|
||||
addVersion modTime = liftTx $ Q.catchE defaultTxErrorHandler $
|
||||
Q.unitQ [Q.sql|
|
||||
INSERT INTO "hdb_catalog"."hdb_version" VALUES ($1, $2)
|
||||
INSERT INTO "hdb_catalog"."hdb_version"
|
||||
(version, upgraded_on) VALUES ($1, $2)
|
||||
|] (curCatalogVer, modTime) False
|
||||
|
||||
isExtAvailable :: T.Text -> Q.Tx Bool
|
||||
@ -178,6 +179,17 @@ setAsSystemDefinedFor8 =
|
||||
AND table_name = 'hdb_function_agg';
|
||||
|]
|
||||
|
||||
setAsSystemDefinedFor9 :: (MonadTx m) => m ()
|
||||
setAsSystemDefinedFor9 =
|
||||
liftTx $ Q.catchE defaultTxErrorHandler $
|
||||
Q.multiQ [Q.sql|
|
||||
UPDATE hdb_catalog.hdb_table
|
||||
SET is_system_defined = 'true'
|
||||
WHERE table_schema = 'hdb_catalog'
|
||||
AND table_name = 'hdb_version';
|
||||
|]
|
||||
|
||||
|
||||
cleanCatalog :: (MonadTx m) => m ()
|
||||
cleanCatalog = liftTx $ Q.catchE defaultTxErrorHandler $ do
|
||||
-- This is where the generated views and triggers are stored
|
||||
@ -293,6 +305,21 @@ from7To8 = do
|
||||
migrateMetadataFrom7 =
|
||||
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_7_to_8.yaml" :: Q (TExp RQLQuery)))
|
||||
|
||||
-- alter hdb_version table and track it (telemetry changes)
|
||||
from8To9
|
||||
:: (MonadTx m, HasHttpManager m, CacheRWM m, UserInfoM m, MonadIO m)
|
||||
=> m ()
|
||||
from8To9 = do
|
||||
Q.Discard () <- liftTx $ Q.multiQE defaultTxErrorHandler
|
||||
$(Q.sqlFromFile "src-rsr/migrate_from_8_to_9.sql")
|
||||
void $ runQueryM migrateMetadataFrom8
|
||||
-- set as system defined
|
||||
setAsSystemDefinedFor9
|
||||
where
|
||||
migrateMetadataFrom8 =
|
||||
$(unTypeQ (Y.decodeFile "src-rsr/migrate_metadata_from_8_to_9.yaml" :: Q (TExp RQLQuery)))
|
||||
|
||||
|
||||
migrateCatalog
|
||||
:: (MonadTx m, CacheRWM m, MonadIO m, UserInfoM m, HasHttpManager m)
|
||||
=> UTCTime -> m String
|
||||
@ -308,12 +335,17 @@ migrateCatalog migrationTime = do
|
||||
| preVer == "5" -> from5ToCurrent
|
||||
| preVer == "6" -> from6ToCurrent
|
||||
| preVer == "7" -> from7ToCurrent
|
||||
| preVer == "8" -> from8ToCurrent
|
||||
| otherwise -> throw400 NotSupported $
|
||||
"unsupported version : " <> preVer
|
||||
where
|
||||
from8ToCurrent = do
|
||||
from8To9
|
||||
postMigrate
|
||||
|
||||
from7ToCurrent = do
|
||||
from7To8
|
||||
postMigrate
|
||||
from8ToCurrent
|
||||
|
||||
from6ToCurrent = do
|
||||
from6To7
|
||||
|
@ -55,9 +55,12 @@ import Hasura.SQL.Types
|
||||
consoleTmplt :: M.Template
|
||||
consoleTmplt = $(M.embedSingleTemplate "src-rsr/console.html")
|
||||
|
||||
boolToText :: Bool -> T.Text
|
||||
boolToText = bool "false" "true"
|
||||
|
||||
isAccessKeySet :: AuthMode -> T.Text
|
||||
isAccessKeySet AMNoAuth = "false"
|
||||
isAccessKeySet _ = "true"
|
||||
isAccessKeySet AMNoAuth = boolToText False
|
||||
isAccessKeySet _ = boolToText True
|
||||
|
||||
#ifdef LocalConsole
|
||||
consoleAssetsLoc :: Text
|
||||
@ -68,14 +71,15 @@ consoleAssetsLoc =
|
||||
"https://storage.googleapis.com/hasura-graphql-engine/console/" <> consoleVersion
|
||||
#endif
|
||||
|
||||
mkConsoleHTML :: T.Text -> AuthMode -> Either String T.Text
|
||||
mkConsoleHTML path authMode =
|
||||
mkConsoleHTML :: T.Text -> AuthMode -> Bool -> Either String T.Text
|
||||
mkConsoleHTML path authMode enableTelemetry =
|
||||
bool (Left errMsg) (Right res) $ null errs
|
||||
where
|
||||
(errs, res) = M.checkedSubstitute consoleTmplt $
|
||||
object [ "consoleAssetsLoc" .= consoleAssetsLoc
|
||||
, "isAccessKeySet" .= isAccessKeySet authMode
|
||||
, "consolePath" .= consolePath
|
||||
, "enableTelemetry" .= boolToText enableTelemetry
|
||||
]
|
||||
consolePath = case path of
|
||||
"" -> "/console"
|
||||
@ -284,8 +288,9 @@ mkWaiApp
|
||||
-> AuthMode
|
||||
-> CorsConfig
|
||||
-> Bool
|
||||
-> Bool
|
||||
-> IO (Wai.Application, IORef SchemaCache)
|
||||
mkWaiApp isoLevel loggerCtx pool httpManager mode corsCfg enableConsole = do
|
||||
mkWaiApp isoLevel loggerCtx pool httpManager mode corsCfg enableConsole enableTelemetry = do
|
||||
cacheRef <- do
|
||||
pgResp <- runExceptT $
|
||||
peelRun emptySchemaCache adminUserInfo httpManager pool Q.Serializable $ do
|
||||
@ -300,7 +305,7 @@ mkWaiApp isoLevel loggerCtx pool httpManager mode corsCfg enableConsole = do
|
||||
cacheLock mode httpManager
|
||||
|
||||
spockApp <- spockAsApp $ spockT id $
|
||||
httpApp corsCfg serverCtx enableConsole
|
||||
httpApp corsCfg serverCtx enableConsole enableTelemetry
|
||||
|
||||
let runTx tx = runExceptT $ runLazyTx pool isoLevel tx
|
||||
|
||||
@ -308,8 +313,8 @@ mkWaiApp isoLevel loggerCtx pool httpManager mode corsCfg enableConsole = do
|
||||
let wsServerApp = WS.createWSServerApp mode wsServerEnv
|
||||
return (WS.websocketsOr WS.defaultConnectionOptions wsServerApp spockApp, cacheRef)
|
||||
|
||||
httpApp :: CorsConfig -> ServerCtx -> Bool -> SpockT IO ()
|
||||
httpApp corsCfg serverCtx enableConsole = do
|
||||
httpApp :: CorsConfig -> ServerCtx -> Bool -> Bool -> SpockT IO ()
|
||||
httpApp corsCfg serverCtx enableConsole enableTelemetry = do
|
||||
-- cors middleware
|
||||
unless (ccDisabled corsCfg) $
|
||||
middleware $ corsMiddleware (mkDefaultCorsPolicy $ ccDomain corsCfg)
|
||||
@ -381,7 +386,7 @@ httpApp corsCfg serverCtx enableConsole = do
|
||||
get root $ redirect "console"
|
||||
get ("console" <//> wildcard) $ \path ->
|
||||
either (raiseGenericApiError . err500 Unexpected . T.pack) html $
|
||||
mkConsoleHTML path $ scAuthMode serverCtx
|
||||
mkConsoleHTML path (scAuthMode serverCtx) enableTelemetry
|
||||
|
||||
#ifdef LocalConsole
|
||||
get "static/main.js" $ do
|
||||
|
@ -6,10 +6,9 @@ import Options.Applicative
|
||||
import System.Exit (exitFailure)
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.String as DataString
|
||||
import qualified Data.Text as T
|
||||
import qualified Hasura.Logging as L
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as PP
|
||||
import qualified Data.String as DataString
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.DDL.Utils
|
||||
import Hasura.RQL.Types (RoleName (..))
|
||||
@ -17,6 +16,7 @@ import Hasura.Server.Auth
|
||||
import Hasura.Server.Logging
|
||||
import Hasura.Server.Utils
|
||||
import Network.Wai.Handler.Warp
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as PP
|
||||
|
||||
|
||||
initErrExit :: (Show e) => e -> IO a
|
||||
@ -38,16 +38,17 @@ type RawAuthHook = AuthHookG (Maybe T.Text) (Maybe AuthHookType)
|
||||
|
||||
data RawServeOptions
|
||||
= RawServeOptions
|
||||
{ rsoPort :: !(Maybe Int)
|
||||
, rsoHost :: !(Maybe HostPreference)
|
||||
, rsoConnParams :: !RawConnParams
|
||||
, rsoTxIso :: !(Maybe Q.TxIsolation)
|
||||
, rsoAccessKey :: !(Maybe AccessKey)
|
||||
, rsoAuthHook :: !RawAuthHook
|
||||
, rsoJwtSecret :: !(Maybe Text)
|
||||
, rsoUnAuthRole :: !(Maybe RoleName)
|
||||
, rsoCorsConfig :: !RawCorsConfig
|
||||
, rsoEnableConsole :: !Bool
|
||||
{ rsoPort :: !(Maybe Int)
|
||||
, rsoHost :: !(Maybe HostPreference)
|
||||
, rsoConnParams :: !RawConnParams
|
||||
, rsoTxIso :: !(Maybe Q.TxIsolation)
|
||||
, rsoAccessKey :: !(Maybe AccessKey)
|
||||
, rsoAuthHook :: !RawAuthHook
|
||||
, rsoJwtSecret :: !(Maybe Text)
|
||||
, rsoUnAuthRole :: !(Maybe RoleName)
|
||||
, rsoCorsConfig :: !RawCorsConfig
|
||||
, rsoEnableConsole :: !Bool
|
||||
, rsoEnableTelemetry :: !(Maybe Bool)
|
||||
} deriving (Show, Eq)
|
||||
|
||||
data CorsConfigG a
|
||||
@ -61,16 +62,17 @@ type CorsConfig = CorsConfigG T.Text
|
||||
|
||||
data ServeOptions
|
||||
= ServeOptions
|
||||
{ soPort :: !Int
|
||||
, soHost :: !HostPreference
|
||||
, soConnParams :: !Q.ConnParams
|
||||
, soTxIso :: !Q.TxIsolation
|
||||
, soAccessKey :: !(Maybe AccessKey)
|
||||
, soAuthHook :: !(Maybe AuthHook)
|
||||
, soJwtSecret :: !(Maybe Text)
|
||||
, soUnAuthRole :: !(Maybe RoleName)
|
||||
, soCorsConfig :: !CorsConfig
|
||||
, soEnableConsole :: !Bool
|
||||
{ soPort :: !Int
|
||||
, soHost :: !HostPreference
|
||||
, soConnParams :: !Q.ConnParams
|
||||
, soTxIso :: !Q.TxIsolation
|
||||
, soAccessKey :: !(Maybe AccessKey)
|
||||
, soAuthHook :: !(Maybe AuthHook)
|
||||
, soJwtSecret :: !(Maybe Text)
|
||||
, soUnAuthRole :: !(Maybe RoleName)
|
||||
, soCorsConfig :: !CorsConfig
|
||||
, soEnableConsole :: !Bool
|
||||
, soEnableTelemetry :: !Bool
|
||||
} deriving (Show, Eq)
|
||||
|
||||
data RawConnInfo =
|
||||
@ -218,13 +220,16 @@ mkServeOptions rso = do
|
||||
withEnv (rsoTxIso rso) (fst txIsoEnv)
|
||||
accKey <- withEnv (rsoAccessKey rso) $ fst accessKeyEnv
|
||||
authHook <- mkAuthHook $ rsoAuthHook rso
|
||||
jwtSecr <- withEnv (rsoJwtSecret rso) $ fst jwtSecretEnv
|
||||
jwtSecret <- withEnv (rsoJwtSecret rso) $ fst jwtSecretEnv
|
||||
unAuthRole <- withEnv (rsoUnAuthRole rso) $ fst unAuthRoleEnv
|
||||
corsCfg <- mkCorsConfig $ rsoCorsConfig rso
|
||||
enableConsole <- withEnvBool (rsoEnableConsole rso) $
|
||||
fst enableConsoleEnv
|
||||
return $ ServeOptions port host connParams txIso accKey authHook
|
||||
jwtSecr unAuthRole corsCfg enableConsole
|
||||
enableTelemetry <- fromMaybe True <$>
|
||||
withEnv (rsoEnableTelemetry rso) (fst enableTelemetryEnv)
|
||||
|
||||
return $ ServeOptions port host connParams txIso accKey authHook jwtSecret
|
||||
unAuthRole corsCfg enableConsole enableTelemetry
|
||||
where
|
||||
mkConnParams (RawConnParams s c i p) = do
|
||||
stripes <- fromMaybe 1 <$> withEnv s (fst pgStripesEnv)
|
||||
@ -260,7 +265,7 @@ mkEnvVarDoc envVars =
|
||||
PP.indent 2 (PP.vsep $ map mkEnvVarLine envVars)
|
||||
where
|
||||
mkEnvVarLine (var, desc) =
|
||||
(PP.fillBreak 30 (PP.text var) PP.<+> prettifyDesc desc) <> PP.hardline
|
||||
(PP.fillBreak 40 (PP.text var) PP.<+> prettifyDesc desc) <> PP.hardline
|
||||
prettifyDesc = PP.align . PP.fillSep . map PP.text . words
|
||||
|
||||
mainCmdFooter :: PP.Doc
|
||||
@ -316,6 +321,9 @@ serveCmdFooter =
|
||||
, "graphql-engine --database-url <database-url> serve --access-key <secretaccesskey>"
|
||||
<> " --auth-hook https://mywebhook.com/post --auth-hook-mode POST"
|
||||
]
|
||||
, [ "# Start GraphQL Engine with telemetry enabled/disabled"
|
||||
, "graphql-engine --database-url <database-url> serve --enable-telemetry true|false"
|
||||
]
|
||||
]
|
||||
|
||||
envVarDoc = mkEnvVarDoc $ envVars <> eventEnvs
|
||||
@ -323,6 +331,7 @@ serveCmdFooter =
|
||||
[ servePortEnv, serveHostEnv, pgStripesEnv, pgConnsEnv, pgTimeoutEnv
|
||||
, txIsoEnv, accessKeyEnv, authHookEnv , authHookModeEnv
|
||||
, jwtSecretEnv , unAuthRoleEnv, corsDomainEnv , enableConsoleEnv
|
||||
, enableTelemetryEnv
|
||||
]
|
||||
|
||||
eventEnvs =
|
||||
@ -418,6 +427,13 @@ enableConsoleEnv =
|
||||
, "Enable API Console"
|
||||
)
|
||||
|
||||
enableTelemetryEnv :: (String, String)
|
||||
enableTelemetryEnv =
|
||||
( "HASURA_GRAPHQL_ENABLE_TELEMETRY"
|
||||
-- TODO: better description
|
||||
, "Enable anonymous telemetry (default: true)"
|
||||
)
|
||||
|
||||
parseRawConnInfo :: Parser RawConnInfo
|
||||
parseRawConnInfo =
|
||||
RawConnInfo <$> host <*> port <*> user <*> password
|
||||
@ -611,6 +627,13 @@ parseEnableConsole =
|
||||
help (snd enableConsoleEnv)
|
||||
)
|
||||
|
||||
parseEnableTelemetry :: Parser (Maybe Bool)
|
||||
parseEnableTelemetry = optional $
|
||||
option (eitherReader parseStrAsBool)
|
||||
( long "enable-telemetry" <>
|
||||
help (snd enableTelemetryEnv)
|
||||
)
|
||||
|
||||
-- Init logging related
|
||||
connInfoToLog :: Q.ConnInfo -> StartupLog
|
||||
connInfoToLog (Q.ConnInfo host port user _ db _) =
|
||||
@ -634,6 +657,7 @@ serveOptsToLog so =
|
||||
, "cors_domain" J..= (ccDomain . soCorsConfig) so
|
||||
, "cors_disabled" J..= (ccDisabled . soCorsConfig) so
|
||||
, "enable_console" J..= soEnableConsole so
|
||||
, "enable_telemetry" J..= soEnableTelemetry so
|
||||
, "use_prepared_statements" J..= (Q.cpAllowPrepare . soConnParams) so
|
||||
]
|
||||
|
||||
|
223
server/src-lib/Hasura/Server/Telemetry.hs
Normal file
223
server/src-lib/Hasura/Server/Telemetry.hs
Normal file
@ -0,0 +1,223 @@
|
||||
{-|
|
||||
Send anonymized metrics to the telemetry server regarding usage of various
|
||||
features of Hasura.
|
||||
-}
|
||||
|
||||
module Hasura.Server.Telemetry
|
||||
( runTelemetry
|
||||
, getDbId
|
||||
, generateFingerprint
|
||||
, mkTelemetryLog
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Exception (try)
|
||||
import Control.Lens
|
||||
import Data.IORef
|
||||
import Data.List
|
||||
|
||||
import Hasura.HTTP
|
||||
import Hasura.Logging
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.Server.Version
|
||||
|
||||
import qualified Control.Concurrent as C
|
||||
import qualified Data.Aeson as A
|
||||
import qualified Data.Aeson.Casing as A
|
||||
import qualified Data.Aeson.TH as A
|
||||
import qualified Data.ByteString.Lazy as BL
|
||||
import qualified Data.HashMap.Strict as Map
|
||||
import qualified Data.String.Conversions as CS
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.UUID as UUID
|
||||
import qualified Data.UUID.V4 as UUID
|
||||
import qualified Database.PG.Query as Q
|
||||
import qualified Network.HTTP.Client as HTTP
|
||||
import qualified Network.HTTP.Types as HTTP
|
||||
import qualified Network.Wreq as Wreq
|
||||
|
||||
|
||||
data RelationshipMetric
|
||||
= RelationshipMetric
|
||||
{ _rmManual :: !Int
|
||||
, _rmAuto :: !Int
|
||||
} deriving (Show, Eq)
|
||||
$(A.deriveJSON (A.aesonDrop 3 A.snakeCase) ''RelationshipMetric)
|
||||
|
||||
data PermissionMetric
|
||||
= PermissionMetric
|
||||
{ _pmSelect :: !Int
|
||||
, _pmInsert :: !Int
|
||||
, _pmUpdate :: !Int
|
||||
, _pmDelete :: !Int
|
||||
, _pmRoles :: !Int
|
||||
} deriving (Show, Eq)
|
||||
$(A.deriveJSON (A.aesonDrop 3 A.snakeCase) ''PermissionMetric)
|
||||
|
||||
data Metrics
|
||||
= Metrics
|
||||
{ _mtTables :: !Int
|
||||
, _mtViews :: !Int
|
||||
, _mtRelationships :: !RelationshipMetric
|
||||
, _mtPermissions :: !PermissionMetric
|
||||
, _mtEventTriggers :: !Int
|
||||
, _mtRemoteSchemas :: !Int
|
||||
} deriving (Show, Eq)
|
||||
$(A.deriveJSON (A.aesonDrop 3 A.snakeCase) ''Metrics)
|
||||
|
||||
data HasuraTelemetry
|
||||
= HasuraTelemetry
|
||||
{ _htDbUid :: !Text
|
||||
, _htInstanceUid :: !Text
|
||||
, _htVersion :: !Text
|
||||
, _htMetrics :: !Metrics
|
||||
} deriving (Show, Eq)
|
||||
$(A.deriveJSON (A.aesonDrop 3 A.snakeCase) ''HasuraTelemetry)
|
||||
|
||||
data TelemetryPayload
|
||||
= TelemetryPayload
|
||||
{ _tpTopic :: !Text
|
||||
, _tpData :: !HasuraTelemetry
|
||||
} deriving (Show, Eq)
|
||||
$(A.deriveJSON (A.aesonDrop 3 A.snakeCase) ''TelemetryPayload)
|
||||
|
||||
telemetryUrl :: Text
|
||||
telemetryUrl = "https://telemetry.hasura.io/v1/http"
|
||||
|
||||
mkPayload :: Text -> Text -> Text -> Metrics -> TelemetryPayload
|
||||
mkPayload dbId instanceId version metrics =
|
||||
TelemetryPayload topic $ HasuraTelemetry dbId instanceId version metrics
|
||||
where topic = bool "server" "server_test" isDevVersion
|
||||
|
||||
runTelemetry
|
||||
:: Logger
|
||||
-> HTTP.Manager
|
||||
-> IORef SchemaCache
|
||||
-> (Text, Text)
|
||||
-> IO ()
|
||||
runTelemetry (Logger logger) manager cacheRef (dbId, instanceId) = do
|
||||
let options = wreqOptions manager []
|
||||
forever $ do
|
||||
schemaCache <- readIORef cacheRef
|
||||
let metrics = computeMetrics schemaCache
|
||||
payload = A.encode $ mkPayload dbId instanceId currentVersion metrics
|
||||
logger $ debugLBS $ "metrics_info: " <> payload
|
||||
resp <- try $ Wreq.postWith options (T.unpack telemetryUrl) payload
|
||||
either logHttpEx handleHttpResp resp
|
||||
C.threadDelay aDay
|
||||
|
||||
where
|
||||
logHttpEx :: HTTP.HttpException -> IO ()
|
||||
logHttpEx ex = do
|
||||
let httpErr = Just $ mkHttpError telemetryUrl Nothing (Just $ HttpException ex)
|
||||
logger $ mkTelemetryLog "http_exception" "http exception occurred" httpErr
|
||||
|
||||
handleHttpResp resp = do
|
||||
let statusCode = resp ^. Wreq.responseStatus . Wreq.statusCode
|
||||
logger $ debugLBS $ "http_success: " <> resp ^. Wreq.responseBody
|
||||
when (statusCode /= 200) $ do
|
||||
let httpErr = Just $ mkHttpError telemetryUrl (Just resp) Nothing
|
||||
logger $ mkTelemetryLog "http_error" "failed to post telemetry" httpErr
|
||||
|
||||
aDay = 86400 * 1000 * 1000
|
||||
|
||||
computeMetrics :: SchemaCache -> Metrics
|
||||
computeMetrics sc =
|
||||
let nTables = Map.size $ Map.filter (isNothing . tiViewInfo) usrTbls
|
||||
nViews = Map.size $ Map.filter (isJust . tiViewInfo) usrTbls
|
||||
allRels = join $ Map.elems $ Map.map relsOfTbl usrTbls
|
||||
(manualRels, autoRels) = partition riIsManual allRels
|
||||
relMetrics = RelationshipMetric (length manualRels) (length autoRels)
|
||||
rolePerms = join $ Map.elems $ Map.map permsOfTbl usrTbls
|
||||
nRoles = length $ nub $ fst <$> rolePerms
|
||||
allPerms = snd <$> rolePerms
|
||||
insPerms = calcPerms _permIns allPerms
|
||||
selPerms = calcPerms _permSel allPerms
|
||||
updPerms = calcPerms _permUpd allPerms
|
||||
delPerms = calcPerms _permDel allPerms
|
||||
permMetrics =
|
||||
PermissionMetric selPerms insPerms updPerms delPerms nRoles
|
||||
evtTriggers = Map.size $ Map.filter (not . Map.null)
|
||||
$ Map.map tiEventTriggerInfoMap usrTbls
|
||||
rmSchemas = Map.size $ scRemoteResolvers sc
|
||||
|
||||
in Metrics nTables nViews relMetrics permMetrics evtTriggers rmSchemas
|
||||
|
||||
where
|
||||
usrTbls = Map.filter (not . tiSystemDefined) $ scTables sc
|
||||
|
||||
calcPerms :: (RolePermInfo -> Maybe a) -> [RolePermInfo] -> Int
|
||||
calcPerms fn perms = length $ catMaybes $ map fn perms
|
||||
|
||||
relsOfTbl :: TableInfo -> [RelInfo]
|
||||
relsOfTbl = rights . Map.elems . Map.map fieldInfoToEither . tiFieldInfoMap
|
||||
|
||||
permsOfTbl :: TableInfo -> [(RoleName, RolePermInfo)]
|
||||
permsOfTbl = Map.toList . tiRolePermInfoMap
|
||||
|
||||
|
||||
generateFingerprint :: IO Text
|
||||
generateFingerprint = UUID.toText <$> UUID.nextRandom
|
||||
|
||||
getDbId :: Q.TxE QErr Text
|
||||
getDbId =
|
||||
(runIdentity . Q.getRow) <$>
|
||||
Q.withQE defaultTxErrorHandler
|
||||
[Q.sql|
|
||||
SELECT (hasura_uuid :: text) FROM hdb_catalog.hdb_version
|
||||
|] () False
|
||||
|
||||
|
||||
-- | Logging related
|
||||
|
||||
data TelemetryLog
|
||||
= TelemetryLog
|
||||
{ _tlLogLevel :: !LogLevel
|
||||
, _tlType :: !Text
|
||||
, _tlMessage :: !Text
|
||||
, _tlHttpError :: !(Maybe TelemetryHttpError)
|
||||
} deriving (Show)
|
||||
|
||||
data TelemetryHttpError
|
||||
= TelemetryHttpError
|
||||
{ tlheStatus :: !(Maybe HTTP.Status)
|
||||
, tlheUrl :: !T.Text
|
||||
, tlheHttpException :: !(Maybe HttpException)
|
||||
, tlheResponse :: !(Maybe T.Text)
|
||||
} deriving (Show)
|
||||
|
||||
instance A.ToJSON TelemetryLog where
|
||||
toJSON tl =
|
||||
A.object [ "type" A..= _tlType tl
|
||||
, "message" A..= _tlMessage tl
|
||||
, "http_error" A..= (A.toJSON <$> _tlHttpError tl)
|
||||
]
|
||||
|
||||
instance A.ToJSON TelemetryHttpError where
|
||||
toJSON tlhe =
|
||||
A.object [ "status_code" A..= (HTTP.statusCode <$> tlheStatus tlhe)
|
||||
, "url" A..= tlheUrl tlhe
|
||||
, "response" A..= tlheResponse tlhe
|
||||
, "http_exception" A..= (A.toJSON <$> tlheHttpException tlhe)
|
||||
]
|
||||
|
||||
|
||||
instance ToEngineLog TelemetryLog where
|
||||
toEngineLog tl = (_tlLogLevel tl, "telemetry-log", A.toJSON tl)
|
||||
|
||||
mkHttpError
|
||||
:: Text
|
||||
-> Maybe (Wreq.Response BL.ByteString)
|
||||
-> Maybe HttpException
|
||||
-> TelemetryHttpError
|
||||
mkHttpError url mResp httpEx =
|
||||
case mResp of
|
||||
Nothing -> TelemetryHttpError Nothing url httpEx Nothing
|
||||
Just resp ->
|
||||
let status = resp ^. Wreq.responseStatus
|
||||
body = CS.cs $ resp ^. Wreq.responseBody
|
||||
in TelemetryHttpError (Just status) url httpEx (Just body)
|
||||
|
||||
mkTelemetryLog :: Text -> Text -> Maybe TelemetryHttpError -> TelemetryLog
|
||||
mkTelemetryLog = TelemetryLog LevelInfo
|
@ -1,6 +1,7 @@
|
||||
module Hasura.Server.Version
|
||||
( currentVersion
|
||||
, consoleVersion
|
||||
, isDevVersion
|
||||
)
|
||||
where
|
||||
|
||||
@ -28,3 +29,7 @@ mkVersion ver = T.pack $ "v" ++ show major ++ "." ++ show minor
|
||||
|
||||
currentVersion :: T.Text
|
||||
currentVersion = version
|
||||
|
||||
isDevVersion :: Bool
|
||||
isDevVersion = either (const True) (const False) $
|
||||
V.fromText $ T.dropWhile (== 'v') version
|
||||
|
@ -6,7 +6,8 @@
|
||||
consoleMode: "server",
|
||||
urlPrefix: "/console",
|
||||
consolePath: "{{consolePath}}",
|
||||
isAccessKeySet: {{isAccessKeySet}}
|
||||
isAccessKeySet: {{isAccessKeySet}},
|
||||
enableTelemetry: {{enableTelemetry}}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
@ -260,6 +260,11 @@ args:
|
||||
schema: hdb_catalog
|
||||
name: remote_schemas
|
||||
|
||||
- type: track_table
|
||||
args:
|
||||
schema: hdb_catalog
|
||||
name: hdb_version
|
||||
|
||||
- type: create_object_relationship
|
||||
args:
|
||||
name: return_table_info
|
||||
|
@ -1,6 +1,9 @@
|
||||
CREATE TABLE hdb_catalog.hdb_version (
|
||||
hasura_uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
version TEXT NOT NULL,
|
||||
upgraded_on TIMESTAMPTZ NOT NULL
|
||||
upgraded_on TIMESTAMPTZ NOT NULL,
|
||||
cli_state JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
console_state JSONB NOT NULL DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX hdb_version_one_row
|
||||
|
5
server/src-rsr/migrate_from_8_to_9.sql
Normal file
5
server/src-rsr/migrate_from_8_to_9.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE hdb_catalog.hdb_version
|
||||
ADD COLUMN hasura_uuid UUID DEFAULT gen_random_uuid(),
|
||||
ADD COLUMN cli_state JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN console_state JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD CONSTRAINT hasura_uuid_pkey PRIMARY KEY (hasura_uuid);
|
4
server/src-rsr/migrate_metadata_from_8_to_9.yaml
Normal file
4
server/src-rsr/migrate_metadata_from_8_to_9.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
type: track_table
|
||||
args:
|
||||
schema: hdb_catalog
|
||||
name: hdb_version
|
@ -241,5 +241,5 @@ def stop_server(server):
|
||||
server.server_close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
s = create_server()
|
||||
s = create_server(host='0.0.0.0')
|
||||
s.serve_forever()
|
||||
|
Loading…
Reference in New Issue
Block a user