From d428c056940369da774e80099c6ffdef9240d8fa Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 1 Apr 2024 15:02:50 -0500 Subject: [PATCH 01/74] chore: move log output message before logs begin streaming (#12836) --- cli/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/server.go b/cli/server.go index f2178d470a..9894e0c1f2 100644 --- a/cli/server.go +++ b/cli/server.go @@ -792,6 +792,9 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. return err } + // This should be output before the logs start streaming. + cliui.Infof(inv.Stdout, "\n==> Logs will stream in below (press ctrl+c to gracefully exit):") + if vals.Telemetry.Enable { gitAuth := make([]telemetry.GitAuth, 0) // TODO: @@ -1025,8 +1028,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. } }() - cliui.Infof(inv.Stdout, "\n==> Logs will stream in below (press ctrl+c to gracefully exit):") - // Updates the systemd status from activating to activated. _, err = daemon.SdNotify(false, daemon.SdNotifyReady) if err != nil { From 2a30194ed71cfbfce9522cf7fbda7f0ba53a3366 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 1 Apr 2024 15:31:26 -0500 Subject: [PATCH 02/74] chore: use validator fork to fix 10ms of init time (#12837) @ammario inspired me --- go.mod | 3 +++ go.sum | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1a43916705..0e290bb3f4 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module github.com/coder/coder/v2 go 1.21.4 +// Required until https://github.com/go-playground/validator/pull/1246 is merged. +replace github.com/go-playground/validator/v10 => github.com/kylecarbs/validator/v10 v10.0.0-20240401200135-cf222ac94618 + // Required until https://github.com/hashicorp/terraform-config-inspect/pull/74 is merged. replace github.com/hashicorp/terraform-config-inspect => github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88 diff --git a/go.sum b/go.sum index 05dc5fa961..46b5f9951c 100644 --- a/go.sum +++ b/go.sum @@ -378,18 +378,12 @@ github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicb github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= @@ -639,6 +633,8 @@ github.com/kylecarbs/spinner v1.18.2-0.20220329160715-20702b5af89e h1:OP0ZMFeZkU github.com/kylecarbs/spinner v1.18.2-0.20220329160715-20702b5af89e/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88 h1:tvG/qs5c4worwGyGnbbb4i/dYYLjpFwDMqcIT3awAf8= github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs= +github.com/kylecarbs/validator/v10 v10.0.0-20240401200135-cf222ac94618 h1:OqN1NhVKYipmiKmuyfnCl+SfaRED1by9V+69jdHdT4o= +github.com/kylecarbs/validator/v10 v10.0.0-20240401200135-cf222ac94618/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -646,7 +642,6 @@ github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -1020,6 +1015,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= @@ -1059,6 +1055,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1114,6 +1112,7 @@ golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepC golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1124,6 +1123,7 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= @@ -1136,6 +1136,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From b47fb4178377270f5e6954489676ce1005230879 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 20:48:52 +0000 Subject: [PATCH 03/74] chore: bump github.com/bramvdbogaerde/go-scp from 1.3.0 to 1.4.0 (#12825) Bumps [github.com/bramvdbogaerde/go-scp](https://github.com/bramvdbogaerde/go-scp) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/bramvdbogaerde/go-scp/releases) - [Commits](https://github.com/bramvdbogaerde/go-scp/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: github.com/bramvdbogaerde/go-scp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e290bb3f4..e526eaf88d 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,7 @@ require ( github.com/awalterschulze/gographviz v2.0.3+incompatible github.com/aws/smithy-go v1.20.1 github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 - github.com/bramvdbogaerde/go-scp v1.3.0 + github.com/bramvdbogaerde/go-scp v1.4.0 github.com/briandowns/spinner v1.18.1 github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 github.com/cenkalti/backoff/v4 v4.3.0 diff --git a/go.sum b/go.sum index 46b5f9951c..cad5f13b3b 100644 --- a/go.sum +++ b/go.sum @@ -160,8 +160,8 @@ github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2 github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= -github.com/bramvdbogaerde/go-scp v1.3.0 h1:2EDcH4fQr6ylcVV4p9HLI0Lcr6JuHZyR+sBOPukXSAA= -github.com/bramvdbogaerde/go-scp v1.3.0/go.mod h1:27lDUlS44PyAtY6BGtu2a8ksStHLNaJraqw65UmRA8c= +github.com/bramvdbogaerde/go-scp v1.4.0 h1:jKMwpwCbcX1KyvDbm/PDJuXcMuNVlLGi0Q0reuzjyKY= +github.com/bramvdbogaerde/go-scp v1.4.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= From f5a70500d2c565db3d0ddb080af4db74850f5139 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 1 Apr 2024 16:04:39 -0500 Subject: [PATCH 04/74] chore: update tailscale for to lazily load hostinfo (#12840) Speeds up `init()` by ~10ms --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e526eaf88d..1258b5177f 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ replace github.com/dlclark/regexp2 => github.com/dlclark/regexp2 v1.7.0 // There are a few minor changes we make to Tailscale that we're slowly upstreaming. Compare here: // https://github.com/tailscale/tailscale/compare/main...coder:tailscale:main -replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240312053019-86ba201e56df +replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240401202854-d329bbdb530d // Fixes a race-condition in coder/wgtunnel. // Upstream PR: https://github.com/WireGuard/wireguard-go/pull/85 diff --git a/go.sum b/go.sum index cad5f13b3b..99774545b8 100644 --- a/go.sum +++ b/go.sum @@ -221,8 +221,8 @@ github.com/coder/serpent v0.7.0 h1:zGpD2GlF3lKIVkMjNGKbkip88qzd5r/TRcc30X/SrT0= github.com/coder/serpent v0.7.0/go.mod h1:REkJ5ZFHQUWFTPLExhXYZ1CaHFjxvGNRlLXLdsI08YA= github.com/coder/ssh v0.0.0-20231128192721-70855dedb788 h1:YoUSJ19E8AtuUFVYBpXuOD6a/zVP3rcxezNsoDseTUw= github.com/coder/ssh v0.0.0-20231128192721-70855dedb788/go.mod h1:aGQbuCLyhRLMzZF067xc84Lh7JDs1FKwCmF1Crl9dxQ= -github.com/coder/tailscale v1.1.1-0.20240312053019-86ba201e56df h1:ZipFsPxJXSgTPUemHK4IFYydacrwtEmYJc4/XisybTw= -github.com/coder/tailscale v1.1.1-0.20240312053019-86ba201e56df/go.mod h1:L8tPrwSi31RAMEMV8rjb0vYTGs7rXt8rAHbqY/p41j4= +github.com/coder/tailscale v1.1.1-0.20240401202854-d329bbdb530d h1:IMvBC1GrCIiZFxpOYRQacZtdjnmsdWNAMilPz+kvdG4= +github.com/coder/tailscale v1.1.1-0.20240401202854-d329bbdb530d/go.mod h1:L8tPrwSi31RAMEMV8rjb0vYTGs7rXt8rAHbqY/p41j4= github.com/coder/terraform-provider-coder v0.19.0 h1:mmUXSXcar1h2wgwoHIUwdEKy9Kw0GW7fLO4Vzzf+4R4= github.com/coder/terraform-provider-coder v0.19.0/go.mod h1:pACHRoXSHBGyY696mLeQ1hR/Ag1G2wFk5bw0mT5Zp2g= github.com/coder/wgtunnel v0.1.13-0.20231127054351-578bfff9b92a h1:KhR9LUVllMZ+e9lhubZ1HNrtJDgH5YLoTvpKwmrGag4= From 114830de26824c6fcb05c37e85e4718a0714d1f2 Mon Sep 17 00:00:00 2001 From: Toshiki Shimomura Date: Tue, 2 Apr 2024 12:36:35 +0900 Subject: [PATCH 05/74] Fix coder-logstream-kube typo in deployment-logs.md (#12845) --- docs/platforms/kubernetes/deployment-logs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/platforms/kubernetes/deployment-logs.md b/docs/platforms/kubernetes/deployment-logs.md index 14d8abd170..184362cc14 100644 --- a/docs/platforms/kubernetes/deployment-logs.md +++ b/docs/platforms/kubernetes/deployment-logs.md @@ -33,7 +33,7 @@ serviceAccount: ## Installation -Install the `coder-kubestream-logs` helm chart on the cluster where the +Install the `coder-logstream-kube` helm chart on the cluster where the deployment is running. ```shell From 79fb8e43c5ddbac951430f30352d0b22ce36c04a Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Tue, 2 Apr 2024 09:57:36 +0200 Subject: [PATCH 06/74] feat: expose workspace statuses (with details) as a prometheus metric (#12762) Implements #12462 --- cli/server.go | 2 +- cli/server_test.go | 6 - coderd/database/dbmem/dbmem.go | 10 + coderd/database/modelqueries.go | 1 + coderd/database/queries.sql.go | 64 ++-- coderd/database/queries/workspaces.sql | 8 +- coderd/prometheusmetrics/prometheusmetrics.go | 85 ++++- .../prometheusmetrics_test.go | 360 +++++++++++++----- 8 files changed, 375 insertions(+), 161 deletions(-) diff --git a/cli/server.go b/cli/server.go index 9894e0c1f2..d278dbea35 100644 --- a/cli/server.go +++ b/cli/server.go @@ -209,7 +209,7 @@ func enablePrometheus( } afterCtx(ctx, closeUsersFunc) - closeWorkspacesFunc, err := prometheusmetrics.Workspaces(ctx, options.PrometheusRegistry, options.Database, 0) + closeWorkspacesFunc, err := prometheusmetrics.Workspaces(ctx, options.Logger.Named("workspaces_metrics"), options.PrometheusRegistry, options.Database, 0) if err != nil { return nil, xerrors.Errorf("register workspaces prometheus metric: %w", err) } diff --git a/cli/server_test.go b/cli/server_test.go index 7842f9e62b..065131fd97 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -973,7 +973,6 @@ func TestServer(t *testing.T) { scanner := bufio.NewScanner(res.Body) hasActiveUsers := false - hasWorkspaces := false for scanner.Scan() { // This metric is manually registered to be tracked in the server. That's // why we test it's tracked here. @@ -981,10 +980,6 @@ func TestServer(t *testing.T) { hasActiveUsers = true continue } - if strings.HasPrefix(scanner.Text(), "coderd_api_workspace_latest_build_total") { - hasWorkspaces = true - continue - } if strings.HasPrefix(scanner.Text(), "coderd_db_query_latencies_seconds") { t.Fatal("db metrics should not be tracked when --prometheus-collect-db-metrics is not enabled") } @@ -992,7 +987,6 @@ func TestServer(t *testing.T) { } require.NoError(t, scanner.Err()) require.True(t, hasActiveUsers) - require.True(t, hasWorkspaces) }) t.Run("DBMetricsEnabled", func(t *testing.T) { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index ef112da121..8bb8559be7 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -404,6 +404,16 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac break } } + + if pj, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID); err == nil { + wr.LatestBuildStatus = pj.JobStatus + } + + wr.LatestBuildTransition = build.Transition + } + + if u, err := q.getUserByIDNoLock(w.OwnerID); err == nil { + wr.Username = u.Username } rows = append(rows, wr) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 40c953375d..ca38505b28 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -266,6 +266,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa &i.LatestBuildCanceledAt, &i.LatestBuildError, &i.LatestBuildTransition, + &i.LatestBuildStatus, &i.Count, ); err != nil { return nil, err diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 12d0658989..b3216fc2d8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -12280,7 +12280,8 @@ SELECT latest_build.completed_at as latest_build_completed_at, latest_build.canceled_at as latest_build_canceled_at, latest_build.error as latest_build_error, - latest_build.transition as latest_build_transition + latest_build.transition as latest_build_transition, + latest_build.job_status as latest_build_status FROM workspaces JOIN @@ -12302,7 +12303,7 @@ LEFT JOIN LATERAL ( provisioner_jobs.job_status FROM workspace_builds - LEFT JOIN + JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id @@ -12507,7 +12508,7 @@ WHERE -- @authorize_filter ), filtered_workspaces_order AS ( SELECT - fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.template_name, fw.template_version_id, fw.template_version_name, fw.username, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition + fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.template_name, fw.template_version_id, fw.template_version_name, fw.username, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status FROM filtered_workspaces fw ORDER BY @@ -12528,7 +12529,7 @@ WHERE $19 ), filtered_workspaces_order_with_summary AS ( SELECT - fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.template_name, fwo.template_version_id, fwo.template_version_name, fwo.username, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition + fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.template_name, fwo.template_version_id, fwo.template_version_name, fwo.username, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status FROM filtered_workspaces_order fwo -- Return a technical summary row with total count of workspaces. @@ -12558,7 +12559,8 @@ WHERE '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_completed_at, '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_canceled_at, '', -- latest_build_error - 'start'::workspace_transition -- latest_build_transition + 'start'::workspace_transition, -- latest_build_transition + 'unknown'::provisioner_job_status -- latest_build_status WHERE $21 :: boolean = true ), total_count AS ( @@ -12568,7 +12570,7 @@ WHERE filtered_workspaces ) SELECT - fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.template_name, fwos.template_version_id, fwos.template_version_name, fwos.username, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, + fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.template_name, fwos.template_version_id, fwos.template_version_name, fwos.username, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, tc.count FROM filtered_workspaces_order_with_summary fwos @@ -12601,30 +12603,31 @@ type GetWorkspacesParams struct { } type GetWorkspacesRow struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` - AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` - Ttl sql.NullInt64 `db:"ttl" json:"ttl"` - LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` - DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` - DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` - AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` - Favorite bool `db:"favorite" json:"favorite"` - TemplateName string `db:"template_name" json:"template_name"` - TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` - TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` - Username string `db:"username" json:"username"` - LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` - LatestBuildCanceledAt sql.NullTime `db:"latest_build_canceled_at" json:"latest_build_canceled_at"` - LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` - LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` - Count int64 `db:"count" json:"count"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` + AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` + Ttl sql.NullInt64 `db:"ttl" json:"ttl"` + LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` + DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` + DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` + AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` + Favorite bool `db:"favorite" json:"favorite"` + TemplateName string `db:"template_name" json:"template_name"` + TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` + TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` + Username string `db:"username" json:"username"` + LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` + LatestBuildCanceledAt sql.NullTime `db:"latest_build_canceled_at" json:"latest_build_canceled_at"` + LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` + LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` + LatestBuildStatus ProvisionerJobStatus `db:"latest_build_status" json:"latest_build_status"` + Count int64 `db:"count" json:"count"` } // build_params is used to filter by build parameters if present. @@ -12685,6 +12688,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) &i.LatestBuildCanceledAt, &i.LatestBuildError, &i.LatestBuildTransition, + &i.LatestBuildStatus, &i.Count, ); err != nil { return nil, err diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 767280634f..616e83a2ba 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -96,7 +96,8 @@ SELECT latest_build.completed_at as latest_build_completed_at, latest_build.canceled_at as latest_build_canceled_at, latest_build.error as latest_build_error, - latest_build.transition as latest_build_transition + latest_build.transition as latest_build_transition, + latest_build.job_status as latest_build_status FROM workspaces JOIN @@ -118,7 +119,7 @@ LEFT JOIN LATERAL ( provisioner_jobs.job_status FROM workspace_builds - LEFT JOIN + JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id @@ -374,7 +375,8 @@ WHERE '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_completed_at, '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_canceled_at, '', -- latest_build_error - 'start'::workspace_transition -- latest_build_transition + 'start'::workspace_transition, -- latest_build_transition + 'unknown'::provisioner_job_status -- latest_build_status WHERE @with_summary :: boolean = true ), total_count AS ( diff --git a/coderd/prometheusmetrics/prometheusmetrics.go b/coderd/prometheusmetrics/prometheusmetrics.go index b2c4b46677..4d3f1d1a04 100644 --- a/coderd/prometheusmetrics/prometheusmetrics.go +++ b/coderd/prometheusmetrics/prometheusmetrics.go @@ -24,10 +24,12 @@ import ( "github.com/coder/coder/v2/tailnet" ) +const defaultRefreshRate = time.Minute + // ActiveUsers tracks the number of users that have authenticated within the past hour. func ActiveUsers(ctx context.Context, registerer prometheus.Registerer, db database.Store, duration time.Duration) (func(), error) { if duration == 0 { - duration = 5 * time.Minute + duration = defaultRefreshRate } gauge := prometheus.NewGauge(prometheus.GaugeOpts{ @@ -72,36 +74,42 @@ func ActiveUsers(ctx context.Context, registerer prometheus.Registerer, db datab } // Workspaces tracks the total number of workspaces with labels on status. -func Workspaces(ctx context.Context, registerer prometheus.Registerer, db database.Store, duration time.Duration) (func(), error) { +func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.Registerer, db database.Store, duration time.Duration) (func(), error) { if duration == 0 { - duration = 5 * time.Minute + duration = defaultRefreshRate } - gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + workspaceLatestBuildTotals := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "coderd", Subsystem: "api", Name: "workspace_latest_build_total", - Help: "The latest workspace builds with a status.", + Help: "The current number of workspace builds by status.", }, []string{"status"}) - err := registerer.Register(gauge) - if err != nil { + if err := registerer.Register(workspaceLatestBuildTotals); err != nil { + return nil, err + } + + workspaceLatestBuildStatuses := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Name: "workspace_latest_build_status", + Help: "The current workspace statuses by template, transition, and owner.", + }, []string{"status", "template_name", "template_version", "workspace_owner", "workspace_transition"}) + if err := registerer.Register(workspaceLatestBuildStatuses); err != nil { return nil, err } - // This exists so the prometheus metric exports immediately when set. - // It helps with tests so they don't have to wait for a tick. - gauge.WithLabelValues("pending").Set(0) ctx, cancelFunc := context.WithCancel(ctx) done := make(chan struct{}) - // Use time.Nanosecond to force an initial tick. It will be reset to the - // correct duration after executing once. - ticker := time.NewTicker(time.Nanosecond) - doTick := func() { - defer ticker.Reset(duration) - + updateWorkspaceTotals := func() { builds, err := db.GetLatestWorkspaceBuilds(ctx) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // clear all series if there are no database entries + workspaceLatestBuildTotals.Reset() + } + + logger.Warn(ctx, "failed to load latest workspace builds", slog.Error(err)) return } jobIDs := make([]uuid.UUID, 0, len(builds)) @@ -110,16 +118,53 @@ func Workspaces(ctx context.Context, registerer prometheus.Registerer, db databa } jobs, err := db.GetProvisionerJobsByIDs(ctx, jobIDs) if err != nil { + ids := make([]string, 0, len(jobIDs)) + for _, id := range jobIDs { + ids = append(ids, id.String()) + } + + logger.Warn(ctx, "failed to load provisioner jobs", slog.F("ids", ids), slog.Error(err)) return } - gauge.Reset() + workspaceLatestBuildTotals.Reset() for _, job := range jobs { status := codersdk.ProvisionerJobStatus(job.JobStatus) - gauge.WithLabelValues(string(status)).Add(1) + workspaceLatestBuildTotals.WithLabelValues(string(status)).Add(1) } } + updateWorkspaceStatuses := func() { + ws, err := db.GetWorkspaces(ctx, database.GetWorkspacesParams{ + Deleted: false, + WithSummary: false, + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // clear all series if there are no database entries + workspaceLatestBuildStatuses.Reset() + } + + logger.Warn(ctx, "failed to load active workspaces", slog.Error(err)) + return + } + + workspaceLatestBuildStatuses.Reset() + for _, w := range ws { + workspaceLatestBuildStatuses.WithLabelValues(string(w.LatestBuildStatus), w.TemplateName, w.TemplateVersionName.String, w.Username, string(w.LatestBuildTransition)).Add(1) + } + } + + // Use time.Nanosecond to force an initial tick. It will be reset to the + // correct duration after executing once. + ticker := time.NewTicker(time.Nanosecond) + doTick := func() { + defer ticker.Reset(duration) + + updateWorkspaceTotals() + updateWorkspaceStatuses() + } + go func() { defer close(done) defer ticker.Stop() @@ -141,7 +186,7 @@ func Workspaces(ctx context.Context, registerer prometheus.Registerer, db databa // Agents tracks the total number of workspaces with labels on status. func Agents(ctx context.Context, logger slog.Logger, registerer prometheus.Registerer, db database.Store, coordinator *atomic.Pointer[tailnet.Coordinator], derpMapFn func() *tailcfg.DERPMap, agentInactiveDisconnectTimeout, duration time.Duration) (func(), error) { if duration == 0 { - duration = 1 * time.Minute + duration = defaultRefreshRate } agentsGauge := NewCachedGaugeVec(prometheus.NewGaugeVec(prometheus.GaugeOpts{ @@ -330,7 +375,7 @@ func Agents(ctx context.Context, logger slog.Logger, registerer prometheus.Regis func AgentStats(ctx context.Context, logger slog.Logger, registerer prometheus.Registerer, db database.Store, initialCreateAfter time.Time, duration time.Duration, aggregateByLabels []string) (func(), error) { if duration == 0 { - duration = 1 * time.Minute + duration = defaultRefreshRate } if len(aggregateByLabels) == 0 { diff --git a/coderd/prometheusmetrics/prometheusmetrics_test.go b/coderd/prometheusmetrics/prometheusmetrics_test.go index 32e97f84c3..0ca7884cfb 100644 --- a/coderd/prometheusmetrics/prometheusmetrics_test.go +++ b/coderd/prometheusmetrics/prometheusmetrics_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/coder/coder/v2/cryptorand" "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" @@ -110,89 +111,9 @@ func TestActiveUsers(t *testing.T) { } } -func TestWorkspaces(t *testing.T) { +func TestWorkspaceLatestBuildTotals(t *testing.T) { t.Parallel() - insertRunning := func(db database.Store) database.ProvisionerJob { - job, err := db.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{ - ID: uuid.New(), - CreatedAt: dbtime.Now(), - UpdatedAt: dbtime.Now(), - Provisioner: database.ProvisionerTypeEcho, - StorageMethod: database.ProvisionerStorageMethodFile, - Type: database.ProvisionerJobTypeWorkspaceBuild, - }) - require.NoError(t, err) - err = db.InsertWorkspaceBuild(context.Background(), database.InsertWorkspaceBuildParams{ - ID: uuid.New(), - WorkspaceID: uuid.New(), - JobID: job.ID, - BuildNumber: 1, - Transition: database.WorkspaceTransitionStart, - Reason: database.BuildReasonInitiator, - }) - require.NoError(t, err) - // This marks the job as started. - _, err = db.AcquireProvisionerJob(context.Background(), database.AcquireProvisionerJobParams{ - OrganizationID: job.OrganizationID, - StartedAt: sql.NullTime{ - Time: dbtime.Now(), - Valid: true, - }, - Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, - }) - require.NoError(t, err) - return job - } - - insertCanceled := func(db database.Store) { - job := insertRunning(db) - err := db.UpdateProvisionerJobWithCancelByID(context.Background(), database.UpdateProvisionerJobWithCancelByIDParams{ - ID: job.ID, - CanceledAt: sql.NullTime{ - Time: dbtime.Now(), - Valid: true, - }, - }) - require.NoError(t, err) - err = db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ - ID: job.ID, - CompletedAt: sql.NullTime{ - Time: dbtime.Now(), - Valid: true, - }, - }) - require.NoError(t, err) - } - - insertFailed := func(db database.Store) { - job := insertRunning(db) - err := db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ - ID: job.ID, - CompletedAt: sql.NullTime{ - Time: dbtime.Now(), - Valid: true, - }, - Error: sql.NullString{ - String: "failed", - Valid: true, - }, - }) - require.NoError(t, err) - } - - insertSuccess := func(db database.Store) { - job := insertRunning(db) - err := db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ - ID: job.ID, - CompletedAt: sql.NullTime{ - Time: dbtime.Now(), - Valid: true, - }, - }) - require.NoError(t, err) - } - for _, tc := range []struct { Name string Database func() database.Store @@ -208,13 +129,13 @@ func TestWorkspaces(t *testing.T) { Name: "Multiple", Database: func() database.Store { db := dbmem.New() - insertCanceled(db) - insertFailed(db) - insertFailed(db) - insertSuccess(db) - insertSuccess(db) - insertSuccess(db) - insertRunning(db) + insertCanceled(t, db) + insertFailed(t, db) + insertFailed(t, db) + insertSuccess(t, db) + insertSuccess(t, db) + insertSuccess(t, db) + insertRunning(t, db) return db }, Total: 7, @@ -229,29 +150,32 @@ func TestWorkspaces(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { t.Parallel() registry := prometheus.NewRegistry() - closeFunc, err := prometheusmetrics.Workspaces(context.Background(), registry, tc.Database(), time.Millisecond) + closeFunc, err := prometheusmetrics.Workspaces(context.Background(), slogtest.Make(t, nil).Leveled(slog.LevelWarn), registry, tc.Database(), testutil.IntervalFast) require.NoError(t, err) t.Cleanup(closeFunc) require.Eventually(t, func() bool { metrics, err := registry.Gather() assert.NoError(t, err) - if len(metrics) < 1 { - return false - } sum := 0 - for _, metric := range metrics[0].Metric { - count, ok := tc.Status[codersdk.ProvisionerJobStatus(metric.Label[0].GetValue())] - if metric.Gauge.GetValue() == 0 { + for _, m := range metrics { + if m.GetName() != "coderd_api_workspace_latest_build_total" { continue } - if !ok { - t.Fail() + + for _, metric := range m.Metric { + count, ok := tc.Status[codersdk.ProvisionerJobStatus(metric.Label[0].GetValue())] + if metric.Gauge.GetValue() == 0 { + continue + } + if !ok { + t.Fail() + } + if metric.Gauge.GetValue() != float64(count) { + return false + } + sum += int(metric.Gauge.GetValue()) } - if metric.Gauge.GetValue() != float64(count) { - return false - } - sum += int(metric.Gauge.GetValue()) } t.Logf("sum %d == total %d", sum, tc.Total) return sum == tc.Total @@ -260,6 +184,90 @@ func TestWorkspaces(t *testing.T) { } } +func TestWorkspaceLatestBuildStatuses(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + Name string + Database func() database.Store + ExpectedWorkspaces int + ExpectedStatuses map[codersdk.ProvisionerJobStatus]int + }{{ + Name: "None", + Database: func() database.Store { + return dbmem.New() + }, + ExpectedWorkspaces: 0, + }, { + Name: "Multiple", + Database: func() database.Store { + db := dbmem.New() + insertTemplates(t, db) + insertCanceled(t, db) + insertFailed(t, db) + insertFailed(t, db) + insertSuccess(t, db) + insertSuccess(t, db) + insertSuccess(t, db) + insertRunning(t, db) + return db + }, + ExpectedWorkspaces: 7, + ExpectedStatuses: map[codersdk.ProvisionerJobStatus]int{ + codersdk.ProvisionerJobCanceled: 1, + codersdk.ProvisionerJobFailed: 2, + codersdk.ProvisionerJobSucceeded: 3, + codersdk.ProvisionerJobRunning: 1, + }, + }} { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + registry := prometheus.NewRegistry() + closeFunc, err := prometheusmetrics.Workspaces(context.Background(), slogtest.Make(t, nil), registry, tc.Database(), testutil.IntervalFast) + require.NoError(t, err) + t.Cleanup(closeFunc) + + require.Eventually(t, func() bool { + metrics, err := registry.Gather() + assert.NoError(t, err) + + stMap := map[codersdk.ProvisionerJobStatus]int{} + for _, m := range metrics { + if m.GetName() != "coderd_workspace_latest_build_status" { + continue + } + + for _, metric := range m.Metric { + for _, l := range metric.Label { + if l == nil { + continue + } + + if l.GetName() == "status" { + status := codersdk.ProvisionerJobStatus(l.GetValue()) + stMap[status] += int(metric.Gauge.GetValue()) + } + } + } + } + + stSum := 0 + for st, count := range stMap { + if tc.ExpectedStatuses[st] != count { + return false + } + + stSum += count + } + + t.Logf("status series = %d, expected == %d", stSum, tc.ExpectedWorkspaces) + return stSum == tc.ExpectedWorkspaces + }, testutil.WaitShort, testutil.IntervalFast) + }) + } +} + func TestAgents(t *testing.T) { t.Parallel() @@ -601,3 +609,153 @@ func prepareWorkspaceAndAgent(t *testing.T, client *codersdk.Client, user coders agentClient.SetSessionToken(authToken) return agentClient } + +var ( + templateA = uuid.New() + templateVersionA = uuid.New() + templateB = uuid.New() + templateVersionB = uuid.New() +) + +func insertTemplates(t *testing.T, db database.Store) { + require.NoError(t, db.InsertTemplate(context.Background(), database.InsertTemplateParams{ + ID: templateA, + Name: "template-a", + Provisioner: database.ProvisionerTypeTerraform, + MaxPortSharingLevel: database.AppSharingLevelAuthenticated, + })) + + require.NoError(t, db.InsertTemplateVersion(context.Background(), database.InsertTemplateVersionParams{ + ID: templateVersionA, + TemplateID: uuid.NullUUID{UUID: templateA}, + Name: "version-1a", + })) + + require.NoError(t, db.InsertTemplate(context.Background(), database.InsertTemplateParams{ + ID: templateB, + Name: "template-b", + Provisioner: database.ProvisionerTypeTerraform, + MaxPortSharingLevel: database.AppSharingLevelAuthenticated, + })) + + require.NoError(t, db.InsertTemplateVersion(context.Background(), database.InsertTemplateVersionParams{ + ID: templateVersionB, + TemplateID: uuid.NullUUID{UUID: templateB}, + Name: "version-1b", + })) +} + +func insertUser(t *testing.T, db database.Store) database.User { + username, err := cryptorand.String(8) + require.NoError(t, err) + + user, err := db.InsertUser(context.Background(), database.InsertUserParams{ + ID: uuid.New(), + Username: username, + LoginType: database.LoginTypeNone, + }) + require.NoError(t, err) + + return user +} + +func insertRunning(t *testing.T, db database.Store) database.ProvisionerJob { + var template, templateVersion uuid.UUID + rnd, err := cryptorand.Intn(10) + require.NoError(t, err) + if rnd > 5 { + template = templateB + templateVersion = templateVersionB + } else { + template = templateA + templateVersion = templateVersionA + } + + workspace, err := db.InsertWorkspace(context.Background(), database.InsertWorkspaceParams{ + ID: uuid.New(), + OwnerID: insertUser(t, db).ID, + Name: uuid.NewString(), + TemplateID: template, + AutomaticUpdates: database.AutomaticUpdatesNever, + }) + require.NoError(t, err) + + job, err := db.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{ + ID: uuid.New(), + CreatedAt: dbtime.Now(), + UpdatedAt: dbtime.Now(), + Provisioner: database.ProvisionerTypeEcho, + StorageMethod: database.ProvisionerStorageMethodFile, + Type: database.ProvisionerJobTypeWorkspaceBuild, + }) + require.NoError(t, err) + err = db.InsertWorkspaceBuild(context.Background(), database.InsertWorkspaceBuildParams{ + ID: uuid.New(), + WorkspaceID: workspace.ID, + JobID: job.ID, + BuildNumber: 1, + Transition: database.WorkspaceTransitionStart, + Reason: database.BuildReasonInitiator, + TemplateVersionID: templateVersion, + }) + require.NoError(t, err) + // This marks the job as started. + _, err = db.AcquireProvisionerJob(context.Background(), database.AcquireProvisionerJobParams{ + OrganizationID: job.OrganizationID, + StartedAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + Types: []database.ProvisionerType{database.ProvisionerTypeEcho}, + }) + require.NoError(t, err) + return job +} + +func insertCanceled(t *testing.T, db database.Store) { + job := insertRunning(t, db) + err := db.UpdateProvisionerJobWithCancelByID(context.Background(), database.UpdateProvisionerJobWithCancelByIDParams{ + ID: job.ID, + CanceledAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + }) + require.NoError(t, err) + err = db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: job.ID, + CompletedAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + }) + require.NoError(t, err) +} + +func insertFailed(t *testing.T, db database.Store) { + job := insertRunning(t, db) + err := db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: job.ID, + CompletedAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + Error: sql.NullString{ + String: "failed", + Valid: true, + }, + }) + require.NoError(t, err) +} + +func insertSuccess(t *testing.T, db database.Store) { + job := insertRunning(t, db) + err := db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: job.ID, + CompletedAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + }) + require.NoError(t, err) +} From 94e82f966235d21c05962a265ca867cfe0b1e27e Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 2 Apr 2024 09:14:38 -0500 Subject: [PATCH 07/74] chore: use fork of chroma to remove unused inits (#12842) * chore: use fork of chroma to remove unused inits This seems fine to do since compilation errors would occur if it were actually in use. Everything seems fine here. * Update validator --- go.mod | 8 +++++++- go.sum | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1258b5177f..7452a05b00 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,14 @@ module github.com/coder/coder/v2 go 1.21.4 +// Required until a v3 of chroma is created to lazily initialize all XML files. +// None of our dependencies seem to use the registries anyways, so this +// should be fine... +// See: https://github.com/kylecarbs/chroma/commit/9e036e0631f38ef60de5ee8eec7a42e9cb7da423 +replace github.com/alecthomas/chroma/v2 => github.com/kylecarbs/chroma/v2 v2.0.0-20240401211003-9e036e0631f3 + // Required until https://github.com/go-playground/validator/pull/1246 is merged. -replace github.com/go-playground/validator/v10 => github.com/kylecarbs/validator/v10 v10.0.0-20240401200135-cf222ac94618 +replace github.com/go-playground/validator/v10 => github.com/kylecarbs/validator/v10 v10.0.0-20240401214733-cebbc77c0ece // Required until https://github.com/hashicorp/terraform-config-inspect/pull/74 is merged. replace github.com/hashicorp/terraform-config-inspect => github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88 diff --git a/go.sum b/go.sum index 99774545b8..0d5a0eaade 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,6 @@ github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= -github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= @@ -627,14 +625,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylecarbs/chroma/v2 v2.0.0-20240401211003-9e036e0631f3 h1:Z9/bo5PSeMutpdiKYNt/TTSfGM1Ll0naj3QzYX9VxTc= +github.com/kylecarbs/chroma/v2 v2.0.0-20240401211003-9e036e0631f3/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= github.com/kylecarbs/opencensus-go v0.23.1-0.20220307014935-4d0325a68f8b h1:1Y1X6aR78kMEQE1iCjQodB3lA7VO4jB88Wf8ZrzXSsA= github.com/kylecarbs/opencensus-go v0.23.1-0.20220307014935-4d0325a68f8b/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= github.com/kylecarbs/spinner v1.18.2-0.20220329160715-20702b5af89e h1:OP0ZMFeZkUnOzTFRfpuK3m7Kp4fNvC6qN+exwj7aI4M= github.com/kylecarbs/spinner v1.18.2-0.20220329160715-20702b5af89e/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88 h1:tvG/qs5c4worwGyGnbbb4i/dYYLjpFwDMqcIT3awAf8= github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs= -github.com/kylecarbs/validator/v10 v10.0.0-20240401200135-cf222ac94618 h1:OqN1NhVKYipmiKmuyfnCl+SfaRED1by9V+69jdHdT4o= -github.com/kylecarbs/validator/v10 v10.0.0-20240401200135-cf222ac94618/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/kylecarbs/validator/v10 v10.0.0-20240401214733-cebbc77c0ece h1:FDpneVFUZzTpR6HrrHZhfD09gKB2gGKfCmKkquh/Trk= +github.com/kylecarbs/validator/v10 v10.0.0-20240401214733-cebbc77c0ece/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= From 51374331233cb6ed99d3427829f32ea850889b46 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 2 Apr 2024 10:02:30 -0500 Subject: [PATCH 08/74] chore: add validation errors to the cli output (#12814) * chore: add validation errors to the cli output --- cli/root.go | 15 ++++++++++++++- cli/testdata/coder_exp_example-error_api.golden | 4 +++- .../coder_exp_example-error_multi-error.golden | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cli/root.go b/cli/root.go index 83367169df..d9407cf217 100644 --- a/cli/root.go +++ b/cli/root.go @@ -1084,10 +1084,23 @@ func formatCoderSDKError(from string, err *codersdk.Error, opts *formatOpts) str _, _ = str.WriteString("\n") } + // The main error message _, _ = str.WriteString(pretty.Sprint(headLineStyle(), err.Message)) + + // Validation errors. + if len(err.Validations) > 0 { + _, _ = str.WriteString("\n") + _, _ = str.WriteString(pretty.Sprint(tailLineStyle(), fmt.Sprintf("%d validation error(s) found", len(err.Validations)))) + for _, e := range err.Validations { + _, _ = str.WriteString("\n\t") + _, _ = str.WriteString(pretty.Sprint(cliui.DefaultStyles.Field, e.Field)) + _, _ = str.WriteString(pretty.Sprintf(cliui.DefaultStyles.Warn, ": %s", e.Detail)) + } + } + if err.Helper != "" { _, _ = str.WriteString("\n") - _, _ = str.WriteString(pretty.Sprint(tailLineStyle(), err.Helper)) + _, _ = str.WriteString(pretty.Sprintf(tailLineStyle(), "Suggestion: %s", err.Helper)) } // By default we do not show the Detail with the helper. if opts.Verbose || (err.Helper == "" && err.Detail != "") { diff --git a/cli/testdata/coder_exp_example-error_api.golden b/cli/testdata/coder_exp_example-error_api.golden index e15b60abbd..a0a8455447 100644 --- a/cli/testdata/coder_exp_example-error_api.golden +++ b/cli/testdata/coder_exp_example-error_api.golden @@ -1,3 +1,5 @@ Encountered an error running "coder exp example-error api", see "coder exp example-error api --help" for more information error: Top level sdk error message. -Have you tried turning it off and on again? +1 validation error(s) found + region : magic dust is not available in your region +Suggestion: Have you tried turning it off and on again? diff --git a/cli/testdata/coder_exp_example-error_multi-error.golden b/cli/testdata/coder_exp_example-error_multi-error.golden index 73a32afd80..2b89275dff 100644 --- a/cli/testdata/coder_exp_example-error_multi-error.golden +++ b/cli/testdata/coder_exp_example-error_multi-error.golden @@ -4,4 +4,6 @@ error: 3 errors encountered: Trace=[wrapped: ]) 2. second error: function decided not to work, and it never will 3. Trace=[wrapped api error: ] Top level sdk error message. + 1 validation error(s) found + region : magic dust is not available in your region magic dust unavailable, please try again later From b5b5c37d03296abbaf4a65434da9bf43ce0fef46 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 2 Apr 2024 10:11:24 -0500 Subject: [PATCH 09/74] docs: describe mutually exclusive create workspace template fields (#12834) * docs: describe mutually exclusive create workspace template fields Ideally we could do this in the OpenAPI spec, but there is no first class "mutually exclusive" feature in OpenAPI. So in lieu of something more complex, or changing our struct/validation, a description comment should suffice. * chore: Add description to code sample as well --- coderd/apidoc/docs.go | 2 ++ coderd/apidoc/swagger.json | 2 ++ coderd/workspaces.go | 4 ++++ codersdk/organizations.go | 3 +++ docs/api/schemas.md | 2 ++ docs/api/workspaces.md | 5 +++++ 6 files changed, 18 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index a082996d32..7bfd521b09 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -2211,6 +2211,7 @@ const docTemplate = `{ "CoderSessionToken": [] } ], + "description": "Create a new workspace using a template. The request must\nspecify either the Template ID or the Template Version ID,\nnot both. If the Template ID is specified, the active version\nof the template will be used.", "consumes": [ "application/json" ], @@ -9045,6 +9046,7 @@ const docTemplate = `{ } }, "codersdk.CreateWorkspaceRequest": { + "description": "CreateWorkspaceRequest provides options for creating a new workspace. Only one of TemplateID or TemplateVersionID can be specified, not both. If TemplateID is specified, the active version of the template will be used.", "type": "object", "required": [ "name" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a559938463..c4dabcacaf 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -1932,6 +1932,7 @@ "CoderSessionToken": [] } ], + "description": "Create a new workspace using a template. The request must\nspecify either the Template ID or the Template Version ID,\nnot both. If the Template ID is specified, the active version\nof the template will be used.", "consumes": ["application/json"], "produces": ["application/json"], "tags": ["Workspaces"], @@ -8052,6 +8053,7 @@ } }, "codersdk.CreateWorkspaceRequest": { + "description": "CreateWorkspaceRequest provides options for creating a new workspace. Only one of TemplateID or TemplateVersionID can be specified, not both. If TemplateID is specified, the active version of the template will be used.", "type": "object", "required": ["name"], "properties": { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index f29d44d6d7..d3456fab00 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -332,6 +332,10 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) // Create a new workspace for the currently authenticated user. // // @Summary Create user workspace by organization +// @Description Create a new workspace using a template. The request must +// @Description specify either the Template ID or the Template Version ID, +// @Description not both. If the Template ID is specified, the active version +// @Description of the template will be used. // @ID create-user-workspace-by-organization // @Security CoderSessionToken // @Accept json diff --git a/codersdk/organizations.go b/codersdk/organizations.go index a6a1b927ca..cb353dff27 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -138,6 +138,9 @@ type CreateTemplateRequest struct { // CreateWorkspaceRequest provides options for creating a new workspace. // Either TemplateID or TemplateVersionID must be specified. They cannot both be present. +// @Description CreateWorkspaceRequest provides options for creating a new workspace. +// @Description Only one of TemplateID or TemplateVersionID can be specified, not both. +// @Description If TemplateID is specified, the active version of the template will be used. type CreateWorkspaceRequest struct { // TemplateID specifies which template should be used for creating the workspace. TemplateID uuid.UUID `json:"template_id,omitempty" validate:"required_without=TemplateVersionID,excluded_with=TemplateVersionID" format:"uuid"` diff --git a/docs/api/schemas.md b/docs/api/schemas.md index f46e02a636..f0b5646fea 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1646,6 +1646,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in } ``` +CreateWorkspaceRequest provides options for creating a new workspace. Only one of TemplateID or TemplateVersionID can be specified, not both. If TemplateID is specified, the active version of the template will be used. + ### Properties | Name | Type | Required | Restrictions | Description | diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index c16dd970a5..886f8401f7 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -14,6 +14,11 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member `POST /organizations/{organization}/members/{user}/workspaces` +Create a new workspace using a template. The request must +specify either the Template ID or the Template Version ID, +not both. If the Template ID is specified, the active version +of the template will be used. + > Body parameter ```json From 7698cfda7201bdbbb12876b9ad47ce6582612ff3 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 2 Apr 2024 10:19:54 -0500 Subject: [PATCH 10/74] chore: remove unnecessary extraction library (#12847) This was allocating ~256KB on init. --- cli/templatepull.go | 4 ++-- cli/templatepull_test.go | 11 +++-------- go.mod | 4 ---- go.sum | 10 ---------- provisionersdk/archive.go | 4 ++++ 5 files changed, 9 insertions(+), 24 deletions(-) diff --git a/cli/templatepull.go b/cli/templatepull.go index 0d0c46f687..7f9317be37 100644 --- a/cli/templatepull.go +++ b/cli/templatepull.go @@ -7,11 +7,11 @@ import ( "path/filepath" "sort" - "github.com/codeclysm/extract/v3" "golang.org/x/xerrors" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/provisionersdk" "github.com/coder/serpent" ) @@ -161,7 +161,7 @@ func (r *RootCmd) templatePull() *serpent.Command { } _, _ = fmt.Fprintf(inv.Stderr, "Extracting template to %q\n", dest) - err = extract.Tar(ctx, bytes.NewReader(raw), dest, nil) + err = provisionersdk.Untar(dest, bytes.NewReader(raw)) return err }, } diff --git a/cli/templatepull_test.go b/cli/templatepull_test.go index 1b1d51b0cc..da981f6ad6 100644 --- a/cli/templatepull_test.go +++ b/cli/templatepull_test.go @@ -3,7 +3,6 @@ package cli_test import ( "archive/tar" "bytes" - "context" "crypto/sha256" "encoding/hex" "os" @@ -11,7 +10,6 @@ import ( "strings" "testing" - "github.com/codeclysm/extract/v3" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -20,6 +18,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/provisioner/echo" + "github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/pty/ptytest" ) @@ -310,9 +309,7 @@ func TestTemplatePull_ToDir(t *testing.T) { _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, updatedVersion.ID) coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, updatedVersion.ID) - ctx := context.Background() - - err = extract.Tar(ctx, bytes.NewReader(expected), expectedDest, nil) + err = provisionersdk.Untar(expectedDest, bytes.NewReader(expected)) require.NoError(t, err) ents, _ := os.ReadDir(actualDest) @@ -387,9 +384,7 @@ func TestTemplatePull_FolderConflict(t *testing.T) { ) require.NoError(t, err) - ctx := context.Background() - - err = extract.Tar(ctx, bytes.NewReader(expected), expectedDest, nil) + err = provisionersdk.Untar(expectedDest, bytes.NewReader(expected)) require.NoError(t, err) inv, root := clitest.New(t, "templates", "pull", template.Name, conflictDest) diff --git a/go.mod b/go.mod index 7452a05b00..cd3c83bba8 100644 --- a/go.mod +++ b/go.mod @@ -101,7 +101,6 @@ require ( github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 github.com/chromedp/chromedp v0.9.2 github.com/cli/safeexec v1.0.1 - github.com/codeclysm/extract/v3 v3.1.1 github.com/coder/flog v1.1.0 github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 github.com/coder/retry v1.5.1 @@ -328,7 +327,6 @@ require ( github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect - github.com/h2non/filetype v1.1.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect @@ -349,7 +347,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect github.com/jsimonetti/rtnetlink v1.3.5 // indirect - github.com/juju/errors v1.0.0 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -406,7 +403,6 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tinylib/msgp v1.1.8 // indirect github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect - github.com/ulikunitz/xz v0.5.11 // indirect github.com/vishvananda/netlink v1.2.1-beta.2 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect diff --git a/go.sum b/go.sum index 0d5a0eaade..3f620bd14a 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,6 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/arduino/go-paths-helper v1.2.0 h1:qDW93PR5IZUN/jzO4rCtexiwF8P4OIcOmcSgAYLZfY4= -github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M= @@ -199,8 +197,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/codeclysm/extract/v3 v3.1.1 h1:iHZtdEAwSTqPrd+1n4jfhr1qBhUWtHlMTjT90+fJVXg= -github.com/codeclysm/extract/v3 v3.1.1/go.mod h1:ZJi80UG2JtfHqJI+lgJSCACttZi++dHxfWuPaMhlOfQ= github.com/coder/flog v1.1.0 h1:kbAes1ai8fIS5OeV+QAnKBQE22ty1jRF/mcAwHpLBa4= github.com/coder/flog v1.1.0/go.mod h1:UQlQvrkJBvnRGo69Le8E24Tcl5SJleAAR7gYEHzAmdQ= github.com/coder/glog v1.0.1-0.20220322161911-7365fe7f2cd1 h1:UqBrPWSYvRI2s5RtOul20JukUEpu4ip9u7biBL+ntgk= @@ -502,8 +498,6 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= -github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= -github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hairyhenderson/go-codeowners v0.4.0 h1:Wx/tRXb07sCyHeC8mXfio710Iu35uAy5KYiBdLHdv4Q= github.com/hairyhenderson/go-codeowners v0.4.0/go.mod h1:iJgZeCt+W/GzXo5uchFCqvVHZY2T4TAIpvuVlKVkLxc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -597,8 +591,6 @@ github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQpho github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM= -github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8= github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -904,8 +896,6 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unrolled/secure v1.14.0 h1:u9vJTU/pR4Bny0ntLUMxdfLtmIRGvQf2sEFuA0TG9AE= github.com/unrolled/secure v1.14.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/provisionersdk/archive.go b/provisionersdk/archive.go index 4051698fa4..410315c18a 100644 --- a/provisionersdk/archive.go +++ b/provisionersdk/archive.go @@ -171,6 +171,10 @@ func Untar(directory string, r io.Reader) error { } } case tar.TypeReg: + err := os.MkdirAll(filepath.Dir(target), os.FileMode(header.Mode)|os.ModeDir|100) + if err != nil { + return err + } file, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) if err != nil { return err From f705f9a5eb9e99bfa441dbd306e6cda6855f9302 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Tue, 2 Apr 2024 11:29:22 -0600 Subject: [PATCH 11/74] test: ensure `RequireActiveVersion` is actually set when testing with AGPL store (#12843) --- coderd/autobuild/lifecycle_executor_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index d8c1a4d8a8..54ceb53254 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -17,6 +17,7 @@ import ( "github.com/coder/coder/v2/coderd/autobuild" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/schedule/cron" "github.com/coder/coder/v2/coderd/util/ptr" @@ -849,14 +850,17 @@ func TestExecutorRequireActiveVersion(t *testing.T) { ticker = make(chan time.Time) statCh = make(chan autobuild.Stats) - ownerClient = coderdtest.New(t, &coderdtest.Options{ + ownerClient, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ AutobuildTicker: ticker, IncludeProvisionerDaemon: true, AutobuildStats: statCh, TemplateScheduleStore: schedule.NewAGPLTemplateScheduleStore(), }) ) + ctx := testutil.Context(t, testutil.WaitShort) owner := coderdtest.CreateFirstUser(t, ownerClient) + me, err := ownerClient.User(ctx, codersdk.Me) + require.NoError(t, err) // Create an active and inactive template version. We'll // build a regular member's workspace using a non-active @@ -864,10 +868,14 @@ func TestExecutorRequireActiveVersion(t *testing.T) { // since there is no enterprise license. activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID) - template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, activeVersion.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.RequireActiveVersion = true - ctr.VersionID = activeVersion.ID + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, activeVersion.ID) + //nolint We need to set this in the database directly, because the API will return an error + // letting you know that this feature requires an enterprise license. + err = db.UpdateTemplateAccessControlByID(dbauthz.As(ctx, coderdtest.AuthzUserSubject(me, owner.OrganizationID)), database.UpdateTemplateAccessControlByIDParams{ + ID: template.ID, + RequireActiveVersion: true, }) + require.NoError(t, err) inactiveVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { ctvr.TemplateID = template.ID }) From 1dd840d1495aab695f2d0eb2b36265bc83f08d82 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Tue, 2 Apr 2024 11:29:43 -0600 Subject: [PATCH 12/74] test: add an e2e test for removing a group (#12844) --- site/e2e/tests/updateTemplate.spec.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/site/e2e/tests/updateTemplate.spec.ts b/site/e2e/tests/updateTemplate.spec.ts index 1159b9903f..261e8bbca7 100644 --- a/site/e2e/tests/updateTemplate.spec.ts +++ b/site/e2e/tests/updateTemplate.spec.ts @@ -37,9 +37,14 @@ test("add and remove a group", async ({ page }) => { // Select the group from the list and add it await page.getByText(groupName).click(); await page.getByText("Add member").click(); - await expect( - page.locator(".MuiTable-root").getByText(groupName), - ).toBeVisible(); + const row = page.locator(".MuiTableRow-root", { hasText: groupName }); + await expect(row).toBeVisible(); + + // Now remove the group + await row.getByLabel("More options").click(); + await page.getByText("Delete").click(); + await expect(page.getByText("Group removed successfully!")).toBeVisible(); + await expect(row).not.toBeVisible(); }); test("require latest version", async ({ page }) => { From 41914256b37dee337bf185b785c7a193df871d60 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 2 Apr 2024 16:53:36 -0500 Subject: [PATCH 13/74] chore: update terraform version in `install.sh` (#12856) --- install.sh | 2 +- provisioner/terraform/install.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index bdcf3af007..0cd1344413 100755 --- a/install.sh +++ b/install.sh @@ -224,7 +224,7 @@ EOF } main() { - TERRAFORM_VERSION="1.3.4" + TERRAFORM_VERSION="1.6.6" if [ "${TRACE-}" ]; then set -x diff --git a/provisioner/terraform/install.go b/provisioner/terraform/install.go index 24e4001419..e50c3d9af9 100644 --- a/provisioner/terraform/install.go +++ b/provisioner/terraform/install.go @@ -19,6 +19,7 @@ var ( // TerraformVersion is the version of Terraform used internally // when Terraform is not available on the system. // NOTE: Keep this in sync with the version in scripts/Dockerfile.base. + // NOTE: Keep this in sync with the version in install.sh. TerraformVersion = version.Must(version.NewVersion("1.6.6")) minTerraformVersion = version.Must(version.NewVersion("1.1.0")) From caa49ea6a1b76e63e71e262255be3d97a8546e7f Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Tue, 2 Apr 2024 16:06:31 -0600 Subject: [PATCH 14/74] chore: stabilize light theme (#12855) --- site/src/modules/dashboard/Navbar/NavbarView.stories.tsx | 6 ++++++ site/src/modules/dashboard/Navbar/NavbarView.tsx | 5 +++-- .../UserSettingsPage/AppearancePage/AppearanceForm.tsx | 1 - 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index fac1f344ce..cf5522c562 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -25,3 +25,9 @@ export const ForMember: Story = { canViewAllUsers: false, }, }; + +export const CustomLogo: Story = { + args: { + logo_url: "/icon/github.svg", + }, +}; diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index 2ad38dcf9f..558706eee6 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -13,6 +13,7 @@ import { type FC, type ReactNode, useRef, useState } from "react"; import { NavLink, useLocation, useNavigate } from "react-router-dom"; import type * as TypesGen from "api/typesGenerated"; import { Abbr } from "components/Abbr/Abbr"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { displayError } from "components/GlobalSnackbar/utils"; import { CoderIcon } from "components/Icons/CoderIcon"; import { Latency } from "components/Latency/Latency"; @@ -150,7 +151,7 @@ export const NavbarView: FC = ({
{logo_url ? ( - Custom Logo + ) : ( )} @@ -167,7 +168,7 @@ export const NavbarView: FC = ({ {logo_url ? ( - Custom Logo + ) : ( )} diff --git a/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx b/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx index ae0d15358e..6b34ba19b9 100644 --- a/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx +++ b/site/src/pages/UserSettingsPage/AppearancePage/AppearanceForm.tsx @@ -57,7 +57,6 @@ export const AppearanceForm: FC = ({ /> onChangeTheme("light")} From ac8d1c6696eabb45bc005098732c32b9cf8d3e57 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 3 Apr 2024 12:45:01 +0200 Subject: [PATCH 15/74] docs: describe single region and multi-region deployments (#12779) --- docs/about/architecture.md | 128 ++++++++++++++++++---- docs/images/architecture-multi-region.png | Bin 182888 -> 176765 bytes 2 files changed, 109 insertions(+), 19 deletions(-) diff --git a/docs/about/architecture.md b/docs/about/architecture.md index 027bd0ff5b..b180caf66d 100644 --- a/docs/about/architecture.md +++ b/docs/about/architecture.md @@ -1,39 +1,38 @@ # Architecture -This document provides a high level overview of Coder's architecture. +The Coder deployment model is flexible and offers various components that +platform administrators can deploy and scale depending on their use case. This +page describes possible deployments, challenges, and risks associated with them. -## Single region architecture +Learn more about our [Reference Architectures](../admin/architectures/index.md) +and platform scaling capabilities. -![Architecture Diagram](../images/architecture-single-region.png) +## Primary components -## Multi-region architecture +### coderd -![Architecture Diagram](../images/architecture-multi-region.png) - -## coderd - -coderd is the service created by running `coder server`. It is a thin API that -connects workspaces, provisioners and users. coderd stores its state in Postgres -and is the only service that communicates with Postgres. +_coderd_ is the service created by running `coder server`. It is a thin API that +connects workspaces, provisioners and users. _coderd_ stores its state in +Postgres and is the only service that communicates with Postgres. It offers: - Dashboard (UI) - HTTP API - Dev URLs (HTTP reverse proxy to workspaces) -- Workspace Web Applications (e.g easily access code-server) +- Workspace Web Applications (e.g for easy access to `code-server`) - Agent registration -## provisionerd +### provisionerd -provisionerd is the execution context for infrastructure modifying providers. At -the moment, the only provider is Terraform (running `terraform`). +_provisionerd_ is the execution context for infrastructure modifying providers. +At the moment, the only provider is Terraform (running `terraform`). By default, the Coder server runs multiple provisioner daemons. [External provisioners](../admin/provisioners.md) can be added for security or scalability purposes. -## Agents +### Agents An agent is the Coder service that runs within a user's remote workspace. It provides a consistent interface for coderd and clients to communicate with @@ -50,9 +49,9 @@ Templates are responsible for [creating and running agents](../templates/index.md#coder-agent) within workspaces. -## Service Bundling +### Service Bundling -While coderd and Postgres can be orchestrated independently, our default +While _coderd_ and Postgres can be orchestrated independently, our default installation paths bundle them all together into one system service. It's perfectly fine to run a production deployment this way, but there are certain situations that necessitate decomposition: @@ -61,7 +60,7 @@ situations that necessitate decomposition: - Achieving greater availability and efficiency (horizontally scale individual services) -## Workspaces +### Workspaces At the highest level, a workspace is a set of cloud resources. These resources can be VMs, Kubernetes clusters, storage buckets, or whatever else Terraform @@ -72,3 +71,94 @@ while those that don't are called _peripheral resources_. Each resource may also be _persistent_ or _ephemeral_ depending on whether they're destroyed on workspace stop. + +## Deployment models + +### Single region architecture + +![Architecture Diagram](../images/architecture-single-region.png) + +#### Components + +This architecture consists of a single load balancer, several _coderd_ replicas, +and _Coder workspaces_ deployed in the same region. + +##### Workload resources + +- Deploy at least one _coderd_ replica per availability zone with _coderd_ + instances and provisioners. High availability is recommended but not essential + for small deployments. +- Single replica deployment is a special case that can address a + tiny/small/proof-of-concept installation on a single virtual machine. If you + are serving more than 100 users/workspaces, you should add more replicas. + +**Coder workspace** + +- For small deployments consider a lightweight workspace runtime like the + [Sysbox](https://github.com/nestybox/sysbox) container runtime. Learn more how + to enable + [docker-in-docker using Sysbox](https://asciinema.org/a/kkTmOxl8DhEZiM2fLZNFlYzbo?speed=2). + +**HA Database** + +- Monitor node status and resource utilization metrics. +- Implement robust backup and disaster recovery strategies to protect against + data loss. + +##### Workload supporting resources + +**Load balancer** + +- Distributes and load balances traffic from agents and clients to _Coder + Server_ replicas across availability zones. +- Layer 7 load balancing. The load balancer can decrypt SSL traffic, and + re-encrypt using an internal certificate. +- Session persistence (sticky sessions) can be disabled as _coderd_ instances + are stateless. +- WebSocket and long-lived connections must be supported. + +**Single sign-on** + +- Integrate with existing Single Sign-On (SSO) solutions used within the + organization via the supported OAuth 2.0 or OpenID Connect standards. +- Learn more about [Authentication in Coder](../admin/auth.md). + +### Multi-region architecture + +![Architecture Diagram](../images/architecture-multi-region.png) + +#### Components + +This architecture is for globally distributed developer teams using Coder +workspaces on daily basis. It features a single load balancer with regionally +deployed _Workspace Proxies_, several _coderd_ replicas, and _Coder workspaces_ +provisioned in different regions. + +Note: The _multi-region architecture_ assumes the same deployment principles as +the _single region architecture_, but it extends them to multi region deployment +with workspace proxies. Proxies are deployed in regions closest to developers to +offer the fastest developer experience. + +##### Workload resources + +**Workspace proxy** + +- Workspace proxy offers developers the option to establish a fast relay + connection when accessing their workspace via SSH, a workspace application, or + port forwarding. +- Dashboard connections, API calls (e.g. _list workspaces_) are not served over + proxies. +- Proxies do not establish connections to the database. +- Proxy instances do not share authentication tokens between one another. + +##### Workload supporting resources + +**Proxy load balancer** + +- Distributes and load balances workspace relay traffic in a single region + across availability zones. +- Layer 7 load balancing. The load balancer can decrypt SSL traffic, and + re-encrypt using internal certificate. +- Session persistence (sticky sessions) can be disabled as _coderd_ instances + are stateless. +- WebSocket and long-lived connections must be supported. diff --git a/docs/images/architecture-multi-region.png b/docs/images/architecture-multi-region.png index 503c8e0fe96b8fba52ad8c7742460cbc97d3f788..d76141e837b96b2a358c6922ecb6e89604546061 100644 GIT binary patch delta 116433 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

)7^t|kqgg`)A`K( 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 CgaSMO delta 123631 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 z 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 tG(j<5ukfM7%D Date: Wed, 3 Apr 2024 09:42:03 -0500 Subject: [PATCH 16/74] feat: support adjusting child proc oom scores (#12655) --- agent/agent.go | 173 ++++++++++++++++++++++---- agent/agent_test.go | 84 ++++++++++++- agent/agentproc/agentproctest/proc.go | 10 +- agent/agentproc/proc_unix.go | 23 +++- agent/agentproc/syscaller.go | 7 +- agent/stats_internal_test.go | 59 +++++++++ cli/agent.go | 3 + 7 files changed, 320 insertions(+), 39 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 0cb2aa2aca..abbe9c8ea4 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -62,7 +62,10 @@ const ( // EnvProcPrioMgmt determines whether we attempt to manage // process CPU and OOM Killer priority. -const EnvProcPrioMgmt = "CODER_PROC_PRIO_MGMT" +const ( + EnvProcPrioMgmt = "CODER_PROC_PRIO_MGMT" + EnvProcOOMScore = "CODER_PROC_OOM_SCORE" +) type Options struct { Filesystem afero.Fs @@ -1575,10 +1578,31 @@ func (a *agent) manageProcessPriorityUntilGracefulShutdown() { a.processManagementTick = ticker.C } + oomScore := unsetOOMScore + if scoreStr, ok := a.environmentVariables[EnvProcOOMScore]; ok { + score, err := strconv.Atoi(strings.TrimSpace(scoreStr)) + if err == nil && score >= -1000 && score <= 1000 { + oomScore = score + } else { + a.logger.Error(ctx, "invalid oom score", + slog.F("min_value", -1000), + slog.F("max_value", 1000), + slog.F("value", scoreStr), + ) + } + } + + debouncer := &logDebouncer{ + logger: a.logger, + messages: map[string]time.Time{}, + interval: time.Minute, + } + for { - procs, err := a.manageProcessPriority(ctx) + procs, err := a.manageProcessPriority(ctx, debouncer, oomScore) + // Avoid spamming the logs too often. if err != nil { - a.logger.Error(ctx, "manage process priority", + debouncer.Error(ctx, "manage process priority", slog.Error(err), ) } @@ -1594,27 +1618,34 @@ func (a *agent) manageProcessPriorityUntilGracefulShutdown() { } } -func (a *agent) manageProcessPriority(ctx context.Context) ([]*agentproc.Process, error) { +// unsetOOMScore is set to an invalid OOM score to imply an unset value. +const unsetOOMScore = 1001 + +func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebouncer, oomScore int) ([]*agentproc.Process, error) { const ( niceness = 10 ) + // We fetch the agent score each time because it's possible someone updates the + // value after it is started. + agentScore, err := a.getAgentOOMScore() + if err != nil { + agentScore = unsetOOMScore + } + if oomScore == unsetOOMScore && agentScore != unsetOOMScore { + // If the child score has not been explicitly specified we should + // set it to a score relative to the agent score. + oomScore = childOOMScore(agentScore) + } + procs, err := agentproc.List(a.filesystem, a.syscaller) if err != nil { return nil, xerrors.Errorf("list: %w", err) } - var ( - modProcs = []*agentproc.Process{} - logger slog.Logger - ) + modProcs := []*agentproc.Process{} for _, proc := range procs { - logger = a.logger.With( - slog.F("cmd", proc.Cmd()), - slog.F("pid", proc.PID), - ) - containsFn := func(e string) bool { contains := strings.Contains(proc.Cmd(), e) return contains @@ -1622,14 +1653,16 @@ func (a *agent) manageProcessPriority(ctx context.Context) ([]*agentproc.Process // If the process is prioritized we should adjust // it's oom_score_adj and avoid lowering its niceness. - if slices.ContainsFunc[[]string, string](prioritizedProcs, containsFn) { + if slices.ContainsFunc(prioritizedProcs, containsFn) { continue } - score, err := proc.Niceness(a.syscaller) - if err != nil { - logger.Warn(ctx, "unable to get proc niceness", - slog.Error(err), + score, niceErr := proc.Niceness(a.syscaller) + if niceErr != nil && !xerrors.Is(niceErr, os.ErrPermission) { + debouncer.Warn(ctx, "unable to get proc niceness", + slog.F("cmd", proc.Cmd()), + slog.F("pid", proc.PID), + slog.Error(niceErr), ) continue } @@ -1643,15 +1676,31 @@ func (a *agent) manageProcessPriority(ctx context.Context) ([]*agentproc.Process continue } - err = proc.SetNiceness(a.syscaller, niceness) - if err != nil { - logger.Warn(ctx, "unable to set proc niceness", - slog.F("niceness", niceness), - slog.Error(err), - ) - continue + if niceErr == nil { + err := proc.SetNiceness(a.syscaller, niceness) + if err != nil && !xerrors.Is(err, os.ErrPermission) { + debouncer.Warn(ctx, "unable to set proc niceness", + slog.F("cmd", proc.Cmd()), + slog.F("pid", proc.PID), + slog.F("niceness", niceness), + slog.Error(err), + ) + } } + // If the oom score is valid and it's not already set and isn't a custom value set by another process then it's ok to update it. + if oomScore != unsetOOMScore && oomScore != proc.OOMScoreAdj && !isCustomOOMScore(agentScore, proc) { + oomScoreStr := strconv.Itoa(oomScore) + err := afero.WriteFile(a.filesystem, fmt.Sprintf("/proc/%d/oom_score_adj", proc.PID), []byte(oomScoreStr), 0o644) + if err != nil && !xerrors.Is(err, os.ErrPermission) { + debouncer.Warn(ctx, "unable to set oom_score_adj", + slog.F("cmd", proc.Cmd()), + slog.F("pid", proc.PID), + slog.F("score", oomScoreStr), + slog.Error(err), + ) + } + } modProcs = append(modProcs, proc) } return modProcs, nil @@ -2005,3 +2054,77 @@ func PrometheusMetricsHandler(prometheusRegistry *prometheus.Registry, logger sl } }) } + +// childOOMScore returns the oom_score_adj for a child process. It is based +// on the oom_score_adj of the agent process. +func childOOMScore(agentScore int) int { + // If the agent has a negative oom_score_adj, we set the child to 0 + // so it's treated like every other process. + if agentScore < 0 { + return 0 + } + + // If the agent is already almost at the maximum then set it to the max. + if agentScore >= 998 { + return 1000 + } + + // If the agent oom_score_adj is >=0, we set the child to slightly + // less than the maximum. If users want a different score they set it + // directly. + return 998 +} + +func (a *agent) getAgentOOMScore() (int, error) { + scoreStr, err := afero.ReadFile(a.filesystem, "/proc/self/oom_score_adj") + if err != nil { + return 0, xerrors.Errorf("read file: %w", err) + } + + score, err := strconv.Atoi(strings.TrimSpace(string(scoreStr))) + if err != nil { + return 0, xerrors.Errorf("parse int: %w", err) + } + + return score, nil +} + +// isCustomOOMScore checks to see if the oom_score_adj is not a value that would +// originate from an agent-spawned process. +func isCustomOOMScore(agentScore int, process *agentproc.Process) bool { + score := process.OOMScoreAdj + return agentScore != score && score != 1000 && score != 0 && score != 998 +} + +// logDebouncer skips writing a log for a particular message if +// it's been emitted within the given interval duration. +// It's a shoddy implementation used in one spot that should be replaced at +// some point. +type logDebouncer struct { + logger slog.Logger + messages map[string]time.Time + interval time.Duration +} + +func (l *logDebouncer) Warn(ctx context.Context, msg string, fields ...any) { + l.log(ctx, slog.LevelWarn, msg, fields...) +} + +func (l *logDebouncer) Error(ctx context.Context, msg string, fields ...any) { + l.log(ctx, slog.LevelError, msg, fields...) +} + +func (l *logDebouncer) log(ctx context.Context, level slog.Level, msg string, fields ...any) { + // This (bad) implementation assumes you wouldn't reuse the same msg + // for different levels. + if last, ok := l.messages[msg]; ok && time.Since(last) < l.interval { + return + } + switch level { + case slog.LevelWarn: + l.logger.Warn(ctx, msg, fields...) + case slog.LevelError: + l.logger.Error(ctx, msg, fields...) + } + l.messages[msg] = time.Now() +} diff --git a/agent/agent_test.go b/agent/agent_test.go index 2813d45125..45ebf7b709 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -2529,11 +2529,11 @@ func TestAgent_ManageProcessPriority(t *testing.T) { logger = slog.Make(sloghuman.Sink(io.Discard)) ) + requireFileWrite(t, fs, "/proc/self/oom_score_adj", "-500") + // Create some processes. for i := 0; i < 4; i++ { - // Create a prioritized process. This process should - // have it's oom_score_adj set to -500 and its nice - // score should be untouched. + // Create a prioritized process. var proc agentproc.Process if i == 0 { proc = agentproctest.GenerateProcess(t, fs, @@ -2551,8 +2551,8 @@ func TestAgent_ManageProcessPriority(t *testing.T) { }, ) - syscaller.EXPECT().SetPriority(proc.PID, 10).Return(nil) syscaller.EXPECT().GetPriority(proc.PID).Return(20, nil) + syscaller.EXPECT().SetPriority(proc.PID, 10).Return(nil) } syscaller.EXPECT(). Kill(proc.PID, syscall.Signal(0)). @@ -2571,6 +2571,9 @@ func TestAgent_ManageProcessPriority(t *testing.T) { }) actualProcs := <-modProcs require.Len(t, actualProcs, len(expectedProcs)-1) + for _, proc := range actualProcs { + requireFileEquals(t, fs, fmt.Sprintf("/proc/%d/oom_score_adj", proc.PID), "0") + } }) t.Run("IgnoreCustomNice", func(t *testing.T) { @@ -2589,8 +2592,11 @@ func TestAgent_ManageProcessPriority(t *testing.T) { logger = slog.Make(sloghuman.Sink(io.Discard)) ) + err := afero.WriteFile(fs, "/proc/self/oom_score_adj", []byte("0"), 0o644) + require.NoError(t, err) + // Create some processes. - for i := 0; i < 2; i++ { + for i := 0; i < 3; i++ { proc := agentproctest.GenerateProcess(t, fs) syscaller.EXPECT(). Kill(proc.PID, syscall.Signal(0)). @@ -2618,7 +2624,59 @@ func TestAgent_ManageProcessPriority(t *testing.T) { }) actualProcs := <-modProcs // We should ignore the process with a custom nice score. - require.Len(t, actualProcs, 1) + require.Len(t, actualProcs, 2) + for _, proc := range actualProcs { + _, ok := expectedProcs[proc.PID] + require.True(t, ok) + requireFileEquals(t, fs, fmt.Sprintf("/proc/%d/oom_score_adj", proc.PID), "998") + } + }) + + t.Run("CustomOOMScore", func(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "linux" { + t.Skip("Skipping non-linux environment") + } + + var ( + fs = afero.NewMemMapFs() + ticker = make(chan time.Time) + syscaller = agentproctest.NewMockSyscaller(gomock.NewController(t)) + modProcs = make(chan []*agentproc.Process) + logger = slog.Make(sloghuman.Sink(io.Discard)) + ) + + err := afero.WriteFile(fs, "/proc/self/oom_score_adj", []byte("0"), 0o644) + require.NoError(t, err) + + // Create some processes. + for i := 0; i < 3; i++ { + proc := agentproctest.GenerateProcess(t, fs) + syscaller.EXPECT(). + Kill(proc.PID, syscall.Signal(0)). + Return(nil) + syscaller.EXPECT().GetPriority(proc.PID).Return(20, nil) + syscaller.EXPECT().SetPriority(proc.PID, 10).Return(nil) + } + + _, _, _, _, _ = setupAgent(t, agentsdk.Manifest{}, 0, func(c *agenttest.Client, o *agent.Options) { + o.Syscaller = syscaller + o.ModifiedProcesses = modProcs + o.EnvironmentVariables = map[string]string{ + agent.EnvProcPrioMgmt: "1", + agent.EnvProcOOMScore: "-567", + } + o.Filesystem = fs + o.Logger = logger + o.ProcessManagementTick = ticker + }) + actualProcs := <-modProcs + // We should ignore the process with a custom nice score. + require.Len(t, actualProcs, 3) + for _, proc := range actualProcs { + requireFileEquals(t, fs, fmt.Sprintf("/proc/%d/oom_score_adj", proc.PID), "-567") + } }) t.Run("DisabledByDefault", func(t *testing.T) { @@ -2739,3 +2797,17 @@ func requireEcho(t *testing.T, conn net.Conn) { require.NoError(t, err) require.Equal(t, "test", string(b)) } + +func requireFileWrite(t testing.TB, fs afero.Fs, fp, data string) { + t.Helper() + err := afero.WriteFile(fs, fp, []byte(data), 0o600) + require.NoError(t, err) +} + +func requireFileEquals(t testing.TB, fs afero.Fs, fp, expect string) { + t.Helper() + actual, err := afero.ReadFile(fs, fp) + require.NoError(t, err) + + require.Equal(t, expect, string(actual)) +} diff --git a/agent/agentproc/agentproctest/proc.go b/agent/agentproc/agentproctest/proc.go index c36e04ec1c..4fa1c698b5 100644 --- a/agent/agentproc/agentproctest/proc.go +++ b/agent/agentproc/agentproctest/proc.go @@ -2,6 +2,7 @@ package agentproctest import ( "fmt" + "strconv" "testing" "github.com/spf13/afero" @@ -29,8 +30,9 @@ func GenerateProcess(t *testing.T, fs afero.Fs, muts ...func(*agentproc.Process) cmdline := fmt.Sprintf("%s\x00%s\x00%s", arg1, arg2, arg3) process := agentproc.Process{ - CmdLine: cmdline, - PID: int32(pid), + CmdLine: cmdline, + PID: int32(pid), + OOMScoreAdj: 0, } for _, mut := range muts { @@ -45,5 +47,9 @@ func GenerateProcess(t *testing.T, fs afero.Fs, muts ...func(*agentproc.Process) err = afero.WriteFile(fs, fmt.Sprintf("%s/cmdline", process.Dir), []byte(process.CmdLine), 0o444) require.NoError(t, err) + score := strconv.Itoa(process.OOMScoreAdj) + err = afero.WriteFile(fs, fmt.Sprintf("%s/oom_score_adj", process.Dir), []byte(score), 0o444) + require.NoError(t, err) + return process } diff --git a/agent/agentproc/proc_unix.go b/agent/agentproc/proc_unix.go index f52caed52e..2eeb7d5a22 100644 --- a/agent/agentproc/proc_unix.go +++ b/agent/agentproc/proc_unix.go @@ -5,6 +5,7 @@ package agentproc import ( "errors" + "os" "path/filepath" "strconv" "strings" @@ -50,10 +51,26 @@ func List(fs afero.Fs, syscaller Syscaller) ([]*Process, error) { } return nil, xerrors.Errorf("read cmdline: %w", err) } + + oomScore, err := afero.ReadFile(fs, filepath.Join(defaultProcDir, entry, "oom_score_adj")) + if err != nil { + if xerrors.Is(err, os.ErrPermission) { + continue + } + + return nil, xerrors.Errorf("read oom_score_adj: %w", err) + } + + oom, err := strconv.Atoi(strings.TrimSpace(string(oomScore))) + if err != nil { + return nil, xerrors.Errorf("convert oom score: %w", err) + } + processes = append(processes, &Process{ - PID: int32(pid), - CmdLine: string(cmdline), - Dir: filepath.Join(defaultProcDir, entry), + PID: int32(pid), + CmdLine: string(cmdline), + Dir: filepath.Join(defaultProcDir, entry), + OOMScoreAdj: oom, }) } diff --git a/agent/agentproc/syscaller.go b/agent/agentproc/syscaller.go index 25dc6cfd54..fba3bf32ce 100644 --- a/agent/agentproc/syscaller.go +++ b/agent/agentproc/syscaller.go @@ -14,7 +14,8 @@ type Syscaller interface { const defaultProcDir = "/proc" type Process struct { - Dir string - CmdLine string - PID int32 + Dir string + CmdLine string + PID int32 + OOMScoreAdj int } diff --git a/agent/stats_internal_test.go b/agent/stats_internal_test.go index bfd6a3436d..57b21a655a 100644 --- a/agent/stats_internal_test.go +++ b/agent/stats_internal_test.go @@ -1,7 +1,10 @@ package agent import ( + "bytes" "context" + "encoding/json" + "io" "net/netip" "sync" "testing" @@ -14,6 +17,7 @@ import ( "tailscale.com/types/netlogtype" "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogjson" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/testutil" @@ -210,3 +214,58 @@ func newFakeStatsDest() *fakeStatsDest { resps: make(chan *proto.UpdateStatsResponse), } } + +func Test_logDebouncer(t *testing.T) { + t.Parallel() + + var ( + buf bytes.Buffer + logger = slog.Make(slogjson.Sink(&buf)) + ctx = context.Background() + ) + + debouncer := &logDebouncer{ + logger: logger, + messages: map[string]time.Time{}, + interval: time.Minute, + } + + fields := map[string]interface{}{ + "field_1": float64(1), + "field_2": "2", + } + + debouncer.Error(ctx, "my message", "field_1", 1, "field_2", "2") + debouncer.Warn(ctx, "another message", "field_1", 1, "field_2", "2") + // Shouldn't log this. + debouncer.Warn(ctx, "another message", "field_1", 1, "field_2", "2") + + require.Len(t, debouncer.messages, 2) + + type entry struct { + Msg string `json:"msg"` + Level string `json:"level"` + Fields map[string]interface{} `json:"fields"` + } + + assertLog := func(msg string, level string, fields map[string]interface{}) { + line, err := buf.ReadString('\n') + require.NoError(t, err) + + var e entry + err = json.Unmarshal([]byte(line), &e) + require.NoError(t, err) + require.Equal(t, msg, e.Msg) + require.Equal(t, level, e.Level) + require.Equal(t, fields, e.Fields) + } + assertLog("my message", "ERROR", fields) + assertLog("another message", "WARN", fields) + + debouncer.messages["another message"] = time.Now().Add(-2 * time.Minute) + debouncer.Warn(ctx, "another message", "field_1", 1, "field_2", "2") + assertLog("another message", "WARN", fields) + // Assert nothing else was written. + _, err := buf.ReadString('\n') + require.ErrorIs(t, err, io.EOF) +} diff --git a/cli/agent.go b/cli/agent.go index aaef3805e6..1f91f1c98b 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -283,6 +283,9 @@ func (r *RootCmd) workspaceAgent() *serpent.Command { if v, ok := os.LookupEnv(agent.EnvProcPrioMgmt); ok { environmentVariables[agent.EnvProcPrioMgmt] = v } + if v, ok := os.LookupEnv(agent.EnvProcOOMScore); ok { + environmentVariables[agent.EnvProcOOMScore] = v + } agnt := agent.New(agent.Options{ Client: client, From 65f8d18ce578f3efc55ae761464e0dc110ab729c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 3 Apr 2024 20:26:48 +0300 Subject: [PATCH 17/74] feat(install.sh): add support for `--mainline` (default) and `--stable` (#12858) Fixes #12461 --- install.sh | 77 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/install.sh b/install.sh index 0cd1344413..3df67fa789 100755 --- a/install.sh +++ b/install.sh @@ -26,18 +26,21 @@ The remote host must have internet access. ${not_curl_usage-} Usage: - $arg0 [--dry-run] [--version X.X.X] [--edge] [--method detect] \ + ${arg0} [--dry-run] [--mainline | --stable | --version X.X.X] [--method detect] \ [--prefix ~/.local] [--rsh ssh] [user@host] --dry-run Echo the commands for the install process without running them. + --mainline + Install the latest mainline version (default). + + --stable + Install the latest stable version instead of the latest mainline version. + --version X.X.X Install a specific version instead of the latest. - --edge - Install the latest edge version instead of the latest stable version. - --method [detect | standalone] Choose the installation method. Defaults to detect. - detect detects the system package manager and tries to use it. @@ -88,16 +91,25 @@ The installer will cache all downloaded assets into ~/.cache/coder EOF } -echo_latest_version() { - if [ "${EDGE-}" ]; then - version="$(curl -fsSL https://api.github.com/repos/coder/coder/releases | awk 'match($0,/.*"html_url": "(.*\/releases\/tag\/.*)".*/)' | head -n 1 | awk -F '"' '{print $4}')" - else - # https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c#gistcomment-2758860 - version="$(curl -fsSLI -o /dev/null -w "%{url_effective}" https://github.com/coder/coder/releases/latest)" - fi - version="${version#https://github.com/coder/coder/releases/tag/}" - version="${version#v}" - echo "$version" +echo_latest_stable_version() { + # https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c#gistcomment-2758860 + version="$(curl -fsSLI -o /dev/null -w "%{url_effective}" https://github.com/coder/coder/releases/latest)" + version="${version#https://github.com/coder/coder/releases/tag/v}" + echo "${version}" +} + +echo_latest_mainline_version() { + # Fetch the releases from the GitHub API, sort by version number, + # and take the first result. Note that we're sorting by space- + # separated numbers and without utilizing the sort -V flag for the + # best compatibility. + curl -fsSL https://api.github.com/repos/coder/coder/releases | + awk -F'"' '/"tag_name"/ {print $4}' | + tr -d v | + tr . ' ' | + sort -k1,1nr -k2,2nr -k3,3nr | + head -n1 | + tr ' ' . } echo_standalone_postinstall() { @@ -106,9 +118,18 @@ echo_standalone_postinstall() { return fi + channel=mainline + advisory="To install our stable release (v${STABLE_VERSION}), use the --stable flag. " + if [ "${MAINLINE}" = 0 ]; then + channel=stable + advisory="" + fi + cath < Date: Wed, 3 Apr 2024 13:17:11 -0500 Subject: [PATCH 18/74] chore: enforce unique linked_ids (#12815) * chore: enforce unique linked_ids Duplicate linked_ids make no sense. 2 users cannot share the same source user from a provider --- coderd/database/dump.sql | 2 ++ .../000205_unique_linked_id.down.sql | 1 + .../migrations/000205_unique_linked_id.up.sql | 21 +++++++++++++++++++ .../fixtures/000048_userdelete.up.sql | 15 +++++++++++++ coderd/database/unique_constraint.go | 1 + 5 files changed, 40 insertions(+) create mode 100644 coderd/database/migrations/000205_unique_linked_id.down.sql create mode 100644 coderd/database/migrations/000205_unique_linked_id.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 55872db31d..830f8a1825 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1644,6 +1644,8 @@ COMMENT ON INDEX template_usage_stats_start_time_template_id_user_id_idx IS 'Ind CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); +CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text); + CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); diff --git a/coderd/database/migrations/000205_unique_linked_id.down.sql b/coderd/database/migrations/000205_unique_linked_id.down.sql new file mode 100644 index 0000000000..81e7d14fc1 --- /dev/null +++ b/coderd/database/migrations/000205_unique_linked_id.down.sql @@ -0,0 +1 @@ +DROP INDEX user_links_linked_id_login_type_idx; diff --git a/coderd/database/migrations/000205_unique_linked_id.up.sql b/coderd/database/migrations/000205_unique_linked_id.up.sql new file mode 100644 index 0000000000..da3ff6126a --- /dev/null +++ b/coderd/database/migrations/000205_unique_linked_id.up.sql @@ -0,0 +1,21 @@ +-- Remove the linked_id if two user_links share the same value. +-- This will affect the user if they attempt to change their settings on +-- the oauth/oidc provider. However, if two users exist with the same +-- linked_value, there is no way to determine correctly which user should +-- be updated. Since the linked_id is empty, this value will be linked +-- by email. +UPDATE ONLY user_links AS out +SET + linked_id = + CASE WHEN ( + -- When the count of linked_id is greater than 1, set the linked_id to empty + SELECT + COUNT(*) + FROM + user_links inn + WHERE + out.linked_id = inn.linked_id AND out.login_type = inn.login_type + ) > 1 THEN '' ELSE out.linked_id END; + +-- Enforce unique linked_id constraint on non-empty linked_id +CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id != ''); diff --git a/coderd/database/migrations/testdata/fixtures/000048_userdelete.up.sql b/coderd/database/migrations/testdata/fixtures/000048_userdelete.up.sql index 0fb1d0efd4..c4f8b2e909 100644 --- a/coderd/database/migrations/testdata/fixtures/000048_userdelete.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000048_userdelete.up.sql @@ -17,3 +17,18 @@ INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token -- This has happened on a production database. INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) VALUES('fc1511ef-4fcf-4a3b-98a1-8df64160e35a', 'oidc', 'foo', ''); + + +-- Lastly, make 2 other users who have the same user link. +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) +VALUES ('580ed397-727d-4aaf-950a-51f89f556c24', 'dup_link_a@coder.com', 'dupe_a', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; +INSERT INTO public.organization_members VALUES ('580ed397-727d-4aaf-950a-51f89f556c24', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) +VALUES('580ed397-727d-4aaf-950a-51f89f556c24', 'github', '500', ''); + + +INSERT INTO public.users(id, email, username, hashed_password, created_at, updated_at, status, rbac_roles, deleted) +VALUES ('c813366b-2fde-45ae-920c-101c3ad6a1e1', 'dup_link_b@coder.com', 'dupe_b', '\x', '2022-11-02 13:05:21.445455+02', '2022-11-02 13:05:21.445455+02', 'active', '{}', false) ON CONFLICT DO NOTHING; +INSERT INTO public.organization_members VALUES ('c813366b-2fde-45ae-920c-101c3ad6a1e1', 'bb640d07-ca8a-4869-b6bc-ae61ebb2fda1', '2022-11-02 13:05:21.447595+02', '2022-11-02 13:05:21.447595+02', '{}') ON CONFLICT DO NOTHING; +INSERT INTO public.user_links(user_id, login_type, linked_id, oauth_access_token) +VALUES('c813366b-2fde-45ae-920c-101c3ad6a1e1', 'github', '500', ''); diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index 52de0a50f6..9db8af72c8 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -82,6 +82,7 @@ const ( UniqueOrganizationsSingleDefaultOrg UniqueConstraint = "organizations_single_default_org" // CREATE UNIQUE INDEX organizations_single_default_org ON organizations USING btree (is_default) WHERE (is_default = true); UniqueTemplateUsageStatsStartTimeTemplateIDUserIDIndex UniqueConstraint = "template_usage_stats_start_time_template_id_user_id_idx" // CREATE UNIQUE INDEX template_usage_stats_start_time_template_id_user_id_idx ON template_usage_stats USING btree (start_time, template_id, user_id); UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); + UniqueUserLinksLinkedIDLoginTypeIndex UniqueConstraint = "user_links_linked_id_login_type_idx" // CREATE UNIQUE INDEX user_links_linked_id_login_type_idx ON user_links USING btree (linked_id, login_type) WHERE (linked_id <> ''::text); UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); UniqueWorkspaceProxiesLowerNameIndex UniqueConstraint = "workspace_proxies_lower_name_idx" // CREATE UNIQUE INDEX workspace_proxies_lower_name_idx ON workspace_proxies USING btree (lower(name)) WHERE (deleted = false); From cb6fea61df5e81c9b7556772a4503aca81b83a1a Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 3 Apr 2024 13:20:26 -0500 Subject: [PATCH 19/74] chore: upgrade go to `1.21.9` (#12861) --- .github/actions/setup-go/action.yaml | 2 +- .github/workflows/ci.yaml | 2 +- dogfood/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-go/action.yaml b/.github/actions/setup-go/action.yaml index d4c67cb7dd..819bc27e0e 100644 --- a/.github/actions/setup-go/action.yaml +++ b/.github/actions/setup-go/action.yaml @@ -4,7 +4,7 @@ description: | inputs: version: description: "The Go version to use." - default: "1.21.5" + default: "1.21.9" runs: using: "composite" steps: diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 084d4c5983..c3541a2493 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -228,7 +228,7 @@ jobs: with: # This doesn't need caching. It's super fast anyways! cache: false - go-version: 1.21.5 + go-version: 1.21.9 - name: Install shfmt run: go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0 diff --git a/dogfood/Dockerfile b/dogfood/Dockerfile index c2899a48c0..4daaa0a636 100644 --- a/dogfood/Dockerfile +++ b/dogfood/Dockerfile @@ -8,7 +8,7 @@ FROM ubuntu:jammy AS go RUN apt-get update && apt-get install --yes curl gcc # Install Go manually, so that we can control the version -ARG GO_VERSION=1.21.5 +ARG GO_VERSION=1.21.9 RUN mkdir --parents /usr/local/go # Boring Go is needed to build FIPS-compliant binaries. From d9211b66931b038a74769caf18cd27b95fb1165f Mon Sep 17 00:00:00 2001 From: Stephen Kirby <58410745+stirby@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:44:28 -0500 Subject: [PATCH 20/74] chore(docs): replace FAQ twisties with h3s (#12859) * replace FAQ twisties with h3s * make fmt --- docs/faqs.md | 103 +++++++++++---------------------------------------- 1 file changed, 21 insertions(+), 82 deletions(-) diff --git a/docs/faqs.md b/docs/faqs.md index 7b09bb516a..66c0e98a76 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -4,8 +4,7 @@ Frequently asked questions on Coder OSS and Enterprise deployments. These FAQs come from our community and enterprise customers, feel free to [contribute to this page](https://github.com/coder/coder/edit/main/docs/faqs.md). -

- How do I add an enterprise license? +### How do I add an enterprise license? Visit https://coder.com/trial or contact [sales@coder.com](mailto:sales@coder.com?subject=License) to get a v2 enterprise @@ -32,10 +31,7 @@ If the license is in a file: coder licenses add -f ``` -
- -
- I'm experiencing networking issues, so want to disable Tailscale, STUN, Direct connections and force use of websockets +### I'm experiencing networking issues, so want to disable Tailscale, STUN, Direct connections and force use of websockets The primary developer use case is a local IDE connecting over SSH to a Coder workspace. @@ -62,19 +58,13 @@ troubleshooting. | [`CODER_DERP_SERVER_STUN_ADDRESSES`](https://coder.com/docs/v2/latest/cli/server#--derp-server-stun-addresses) | `"disable"` | Disables STUN | | [`CODER_DERP_FORCE_WEBSOCKETS`](https://coder.com/docs/v2/latest/cli/server#--derp-force-websockets) | `true` | Forces websockets over Tailscale DERP | -
- -
- How do I configure NGINX as the reverse proxy in front of Coder? +### How do I configure NGINX as the reverse proxy in front of Coder? [This doc](https://github.com/coder/coder/tree/main/examples/web-server/nginx#configure-nginx) in our repo explains in detail how to configure NGINX with Coder so that our Tailscale Wireguard networking functions properly. -
- -
- How do I hide some of the default icons in a workspace like VS Code Desktop, Terminal, SSH, Ports? +### How do I hide some of the default icons in a workspace like VS Code Desktop, Terminal, SSH, Ports? The visibility of Coder apps is configurable in the template. To change the default (shows all), add this block inside the @@ -93,10 +83,7 @@ of a template and configure as needed: This example will hide all built-in coder_app icons except the web terminal. -
- -
- I want to allow code-server to be accessible by other users in my deployment. +### I want to allow code-server to be accessible by other users in my deployment. > It is **not** recommended to share a web IDE, but if required, the following > deployment environment variable settings are required. @@ -126,10 +113,7 @@ resource "coder_app" "code-server" { } ``` -
- -
- I installed Coder and created a workspace but the icons do not load. +### I installed Coder and created a workspace but the icons do not load. An important concept to understand is that Coder creates workspaces which have an agent that must be able to reach the `coder server`. @@ -153,10 +137,7 @@ coder server --access-url http://localhost:3000 --address 0.0.0.0:3000 > Even `coder server` which creates a reverse proxy, will let you use > http://localhost to access Coder from a browser. -
- -
- I updated a template, and an existing workspace based on that template fails to start. +### I updated a template, and an existing workspace based on that template fails to start. When updating a template, be aware of potential issues with input variables. For example, if a template prompts users to choose options like a @@ -176,10 +157,7 @@ potentially saving the workspace from a failed status. coder update --always-prompt ``` -
- -
- I'm running coder on a VM with systemd but latest release installed isn't showing up. +### I'm running coder on a VM with systemd but latest release installed isn't showing up. Take, for example, a Coder deployment on a VM with a 2 shared vCPU systemd service. In this scenario, it's necessary to reload the daemon and then restart @@ -194,10 +172,7 @@ sudo systemctl daemon-reload sudo systemctl restart coder.service ``` -
- -
- I'm using the built-in Postgres database and forgot admin email I set up. +### I'm using the built-in Postgres database and forgot admin email I set up. 1. Run the `coder server` command below to retrieve the `psql` connection URL which includes the database user and password. @@ -210,10 +185,7 @@ coder server postgres-builtin-url psql "postgres://coder@localhost:53737/coder?sslmode=disable&password=I2S...pTk" ``` -
- -
- How to find out Coder's latest Terraform provider version? +### How to find out Coder's latest Terraform provider version? [Coder is on the HashiCorp's Terraform registry](https://registry.terraform.io/providers/coder/coder/latest). Check this frequently to make sure you are on the latest version. @@ -222,10 +194,7 @@ Sometimes, the version may change and `resource` configurations will either become deprecated or new ones will be added when you get warnings or errors creating and pushing templates. -
- -
- How can I set up TLS for my deployment and not create a signed certificate? +### How can I set up TLS for my deployment and not create a signed certificate? Caddy is an easy-to-configure reverse proxy that also automatically creates certificates from Let's Encrypt. @@ -250,10 +219,7 @@ coder.example.com { } ``` -
- -
- I'm using Caddy as my reverse proxy in front of Coder. How do I set up a wildcard domain for port forwarding? +### I'm using Caddy as my reverse proxy in front of Coder. How do I set up a wildcard domain for port forwarding? Caddy requires your DNS provider's credentials to create wildcard certificates. This involves building the Caddy binary @@ -283,10 +249,7 @@ The updated Caddyfile configuration will look like this: } ``` -
- -
- Can I use local or remote Terraform Modules in Coder templates? +### Can I use local or remote Terraform Modules in Coder templates? One way is to reference a Terraform module from a GitHub repo to avoid duplication and then just extend it or pass template-specific @@ -328,10 +291,8 @@ References: - [Public Github Issue 6117](https://github.com/coder/coder/issues/6117) - [Public Github Issue 5677](https://github.com/coder/coder/issues/5677) - [Coder docs: Templates/Change Management](https://coder.com/docs/v2/latest/templates/change-management) -
-
- Can I run Coder in an air-gapped or offline mode? (no Internet)? +### Can I run Coder in an air-gapped or offline mode? (no Internet)? Yes, Coder can be deployed in air-gapped or offline mode. https://coder.com/docs/v2/latest/install/offline @@ -345,10 +306,7 @@ defaults to Google's STUN servers, so you can either create your STUN server in your network or disable and force all traffic through the control plane's DERP proxy. -
- -
- Create a randomized computer_name for an Azure VM +### Create a randomized computer_name for an Azure VM Azure VMs have a 15 character limit for the `computer_name` which can lead to duplicate name errors. @@ -363,10 +321,7 @@ locals { } ``` -
- -
- Do you have example JetBrains Gateway templates? +### Do you have example JetBrains Gateway templates? In August 2023, JetBrains certified the Coder plugin signifying enhanced stability and reliability. @@ -387,10 +342,8 @@ open the IDE. - [IntelliJ IDEA](https://github.com/sharkymark/v2-templates/tree/main/pod-idea) - [IntelliJ IDEA with Icon](https://github.com/sharkymark/v2-templates/tree/main/pod-idea-icon) -
-
- What options do I have for adding VS Code extensions into code-server, VS Code Desktop or Microsoft's Code Server? +### What options do I have for adding VS Code extensions into code-server, VS Code Desktop or Microsoft's Code Server? Coder has an open-source project called [`code-marketplace`](https://github.com/coder/code-marketplace) which is a @@ -416,10 +369,7 @@ https://github.com/sharkymark/v2-templates/blob/main/vs-code-server/main.tf > Note: these are example templates with no SLAs on them and are not guaranteed > for long-term support. -
- -
- I want to run Docker for my workspaces but not install Docker Desktop. +### I want to run Docker for my workspaces but not install Docker Desktop. [Colima](https://github.com/abiosoft/colima) is a Docker Desktop alternative. @@ -454,10 +404,7 @@ Colima will show the path to the docker socket so we have a [community template](https://github.com/sharkymark/v2-templates/tree/main/docker-code-server) that prompts the Coder admin to enter the docker socket as a Terraform variable. -
- -
- How to make a `coder_app` optional? +### How to make a `coder_app` optional? An example use case is the user should decide if they want a browser-based IDE like code-server when creating the workspace. @@ -515,10 +462,7 @@ resource "coder_app" "code-server" { } ``` -
- -
- Why am I getting this "remote host doesn't meet VS Code Server's prerequisites" error when opening up VSCode remote in a Linux environment? +### Why am I getting this "remote host doesn't meet VS Code Server's prerequisites" error when opening up VSCode remote in a Linux environment? ![VS Code Server prerequisite](https://github.com/coder/coder/assets/10648092/150c5996-18b1-4fae-afd0-be2b386a3239) @@ -529,10 +473,7 @@ image or supported OS for the VS Code Server. For more information on OS prerequisites for Linux, please look at the VSCode docs. https://code.visualstudio.com/docs/remote/linux#_local-linux-prerequisites -
- -
- How can I resolve disconnects when connected to Coder via JetBrains Gateway? +### How can I resolve disconnects when connected to Coder via JetBrains Gateway? If your JetBrains IDE is disconnected for a long period of time due to a network change (for example turning off a VPN), you may find that the IDE will not @@ -560,5 +501,3 @@ Note that the JetBrains Gateway configuration blocks for each host in your SSH config file will be overwritten by the JetBrains Gateway client when it re-authenticates to your Coder deployment so you must add the above config as a separate block and not add it to any existing ones. - -
From bf19e3469f917e7aa54b78f7e1ca8beba244108b Mon Sep 17 00:00:00 2001 From: Stephen Kirby <58410745+stirby@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:44:57 -0500 Subject: [PATCH 21/74] added 'JFrog' in front of XRay in guide (#12860) --- docs/guides/xray-integration.md | 6 +++--- docs/manifest.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/xray-integration.md b/docs/guides/xray-integration.md index 90d449853e..76c29e3343 100644 --- a/docs/guides/xray-integration.md +++ b/docs/guides/xray-integration.md @@ -19,7 +19,7 @@ using Coder's [JFrog Xray Integration](github.com/coder/coder-xray). - A self-hosted JFrog Platform instance. - Kubernetes workspaces running on Coder. -## Deploying the Coder Xray Integration +## Deploying the Coder - JFrog Xray Integration 1. Create a JFrog Platform [Access Token](https://jfrog.com/help/r/jfrog-platform-administration-documentation/access-tokens) @@ -37,7 +37,7 @@ kubectl create secret generic coder-token --from-literal=coder-token='' kubectl create secret generic jfrog-token --from-literal=user='' --from-literal=token='' ``` -4. Deploy the Coder Xray integration. +4. Deploy the Coder - JFrog Xray integration. ```bash helm repo add coder-xray https://helm.coder.com/coder-xray @@ -69,4 +69,4 @@ image = "//:" > use it in the `imagePullSecrets` field of the kubernetes pod. See this > [guide](./image-pull-secret.md) for more information. -![Coder Xray Integration](../images/guides/xray-integration/example.png) +![JFrog Xray Integration](../images/guides/xray-integration/example.png) diff --git a/docs/manifest.json b/docs/manifest.json index 65a5175f20..6620160b0f 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1101,7 +1101,7 @@ "path": "./guides/azure-federation.md" }, { - "title": "Scanning Coder Workspaces with Xray", + "title": "Scanning Coder Workspaces with JFrog Xray", "description": "Integrate Coder with JFrog Xray", "path": "./guides/xray-integration.md" } From a7234f61a13b93e27787175a4197c06dc3153a5c Mon Sep 17 00:00:00 2001 From: Stephen Kirby <58410745+stirby@users.noreply.github.com> Date: Wed, 3 Apr 2024 16:43:49 -0500 Subject: [PATCH 22/74] chore: update change log to v2.10.0 and install docs for release channels (#12863) * 2.10.0 changelog * updated install docs for mainline/stable releases * make fmt * cpp icon -> C++ * added disclaimer on MAX_TTL, support bundle info * 'release schedule' * lowercase mainline * Agent OOM protection info * minor tweak --- docs/changelogs/images/support-bundle.png | Bin 0 -> 192176 bytes docs/changelogs/v2.10.0.md | 130 ++++++++++++++++++++++ docs/install/index.md | 3 + docs/install/kubernetes.md | 23 +++- docs/install/releases.md | 56 ++++++++++ 5 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 docs/changelogs/images/support-bundle.png create mode 100644 docs/changelogs/v2.10.0.md create mode 100644 docs/install/releases.md diff --git a/docs/changelogs/images/support-bundle.png b/docs/changelogs/images/support-bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..c4046edcd6c88cc30885fd67d169200b3d405e37 GIT binary patch literal 192176 zcmd43bzIb4yEh65NQfW^DAG!o2uOn{hzJfS9Yc55&>>1AAkr;Jch?ZoNJ` z^kX{J%95_R8a!6h8Wb-wQ zULqp1Jv}!Z9dnbAVrW>cRZ&XMHMG^E&*gWTL$p89W|Yx~_t+=UpNt);!aqu{TD{ts zY|ub*XKNE?toL@>om!sTG;YF7%A{VNK}d|J%FyLMtId?v{-&Cj2nnO-#O>!hRVo=< z?0ISYR)$!I8Z07C++#Z|kdvSh(eJ{?94eMGp79 z#U{I$^gb9oAba}x1@+U5;K!ro<

)_pYdz{vWT`2=EPTvV|y8 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

&J1k|HG?(%t>6 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 literal 0 HcmV?d00001 From bc9ea61eb45a06f54a2cae1be7277a98ce87f4de Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 4 Apr 2024 17:39:07 -0600 Subject: [PATCH 25/74] ci: disable enterprise e2e tests temporarily (#12874) --- .github/workflows/ci.yaml | 7 ------ site/e2e/playwright.config.ts | 35 ++++++++++++++++++--------- site/e2e/tests/updateTemplate.spec.ts | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c3541a2493..b255a0e442 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -478,13 +478,6 @@ jobs: DEBUG: pw:api working-directory: site - # Run all of the tests with an enterprise license - - run: pnpm playwright:test --forbid-only --workers 1 - env: - DEBUG: pw:api - CODER_E2E_ENTERPRISE_LICENSE: ${{ secrets.CODER_E2E_ENTERPRISE_LICENSE }} - working-directory: site - - name: Upload Playwright Failed Tests if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index df38e57243..d345de379d 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -1,6 +1,12 @@ import { defineConfig } from "@playwright/test"; import * as path from "path"; -import { coderMain, coderPort, coderdPProfPort, gitAuth } from "./constants"; +import { + coderMain, + coderPort, + coderdPProfPort, + enterpriseLicense, + gitAuth, +} from "./constants"; export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT; @@ -43,17 +49,22 @@ export default defineConfig({ }, webServer: { url: `http://localhost:${coderPort}/api/v2/deployment/config`, - command: - `go run -tags embed ${coderMain} server ` + - `--global-config $(mktemp -d -t e2e-XXXXXXXXXX) ` + - `--access-url=http://localhost:${coderPort} ` + - `--http-address=localhost:${coderPort} ` + - `--in-memory --telemetry=false ` + - `--dangerous-disable-rate-limits ` + - `--provisioner-daemons 10 ` + - `--provisioner-daemons-echo ` + - `--web-terminal-renderer=dom ` + - `--pprof-enable`, + command: [ + `go run -tags embed ${coderMain} server`, + "--global-config $(mktemp -d -t e2e-XXXXXXXXXX)", + `--access-url=http://localhost:${coderPort}`, + `--http-address=localhost:${coderPort}`, + // Adding an enterprise license causes issues with pgcoord when running with `--in-memory`. + !enterpriseLicense && "--in-memory", + "--telemetry=false", + "--dangerous-disable-rate-limits", + "--provisioner-daemons 10", + "--provisioner-daemons-echo", + "--web-terminal-renderer=dom", + "--pprof-enable", + ] + .filter(Boolean) + .join(" "), env: { ...process.env, diff --git a/site/e2e/tests/updateTemplate.spec.ts b/site/e2e/tests/updateTemplate.spec.ts index 261e8bbca7..95182ca19e 100644 --- a/site/e2e/tests/updateTemplate.spec.ts +++ b/site/e2e/tests/updateTemplate.spec.ts @@ -42,7 +42,7 @@ test("add and remove a group", async ({ page }) => { // Now remove the group await row.getByLabel("More options").click(); - await page.getByText("Delete").click(); + await page.getByText("Remove").click(); await expect(page.getByText("Group removed successfully!")).toBeVisible(); await expect(row).not.toBeVisible(); }); From 3fbcdb0ddc9f53c2d4d0661661dee5c69877de1b Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Thu, 4 Apr 2024 21:56:28 -0300 Subject: [PATCH 26/74] chore(site): add e2e tests for groups (#12866) --- site/e2e/api.ts | 45 +++++++++++++++++++ site/e2e/helpers.ts | 7 --- site/e2e/tests/groups/addMembers.spec.ts | 34 ++++++++++++++ .../groups/addUsersToDefaultGroup.spec.ts | 32 +++++++++++++ site/e2e/tests/groups/createGroup.spec.ts | 30 +++++++++++++ .../tests/groups/navigateToGroupPage.spec.ts | 23 ++++++++++ site/e2e/tests/groups/removeGroup.spec.ts | 26 +++++++++++ site/e2e/tests/groups/removeMember.spec.ts | 36 +++++++++++++++ site/e2e/tests/users/removeUser.spec.ts | 20 +++------ site/src/api/api.ts | 1 - site/src/pages/GroupsPage/GroupPage.tsx | 1 + site/src/pages/UsersPage/UsersLayout.tsx | 11 +++-- 12 files changed, 240 insertions(+), 26 deletions(-) create mode 100644 site/e2e/api.ts create mode 100644 site/e2e/tests/groups/addMembers.spec.ts create mode 100644 site/e2e/tests/groups/addUsersToDefaultGroup.spec.ts create mode 100644 site/e2e/tests/groups/createGroup.spec.ts create mode 100644 site/e2e/tests/groups/navigateToGroupPage.spec.ts create mode 100644 site/e2e/tests/groups/removeGroup.spec.ts create mode 100644 site/e2e/tests/groups/removeMember.spec.ts diff --git a/site/e2e/api.ts b/site/e2e/api.ts new file mode 100644 index 0000000000..88f8666475 --- /dev/null +++ b/site/e2e/api.ts @@ -0,0 +1,45 @@ +import type { Page } from "@playwright/test"; +import * as API from "api/api"; +import { coderPort } from "./constants"; +import { findSessionToken, randomName } from "./helpers"; + +let currentOrgId: string; + +export const setupApiCalls = async (page: Page) => { + const token = await findSessionToken(page); + API.setSessionToken(token); + API.setHost(`http://127.0.0.1:${coderPort}`); +}; + +export const getCurrentOrgId = async (): Promise => { + if (currentOrgId) { + return currentOrgId; + } + const currentUser = await API.getAuthenticatedUser(); + currentOrgId = currentUser.organization_ids[0]; + return currentOrgId; +}; + +export const createUser = async (orgId: string) => { + const name = randomName(); + const user = await API.createUser({ + email: `${name}@coder.com`, + username: name, + password: "s3cure&password!", + login_type: "password", + disable_login: false, + organization_id: orgId, + }); + return user; +}; + +export const createGroup = async (orgId: string) => { + const name = randomName(); + const group = await API.createGroup(orgId, { + name, + display_name: `Display ${name}`, + avatar_url: "/emojis/1f60d.png", + quota_allowance: 0, + }); + return group; +}; diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 84b1b911c9..a1fb47816f 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -7,7 +7,6 @@ import capitalize from "lodash/capitalize"; import path from "path"; import * as ssh from "ssh2"; import { Duplex } from "stream"; -import * as API from "api/api"; import type { WorkspaceBuildParameter, UpdateTemplateMeta, @@ -826,9 +825,3 @@ export async function openTerminalWindow( return terminal; } - -export const setupApiCalls = async (page: Page) => { - const token = await findSessionToken(page); - API.setSessionToken(token); - API.setHost(`http://127.0.0.1:${coderPort}`); -}; diff --git a/site/e2e/tests/groups/addMembers.spec.ts b/site/e2e/tests/groups/addMembers.spec.ts new file mode 100644 index 0000000000..f9532733d8 --- /dev/null +++ b/site/e2e/tests/groups/addMembers.spec.ts @@ -0,0 +1,34 @@ +import { test, expect } from "@playwright/test"; +import { + createGroup, + createUser, + getCurrentOrgId, + setupApiCalls, +} from "../../api"; +import { requiresEnterpriseLicense } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("add members", async ({ page, baseURL }) => { + requiresEnterpriseLicense(); + await setupApiCalls(page); + const orgId = await getCurrentOrgId(); + const group = await createGroup(orgId); + const numberOfMembers = 3; + const users = await Promise.all( + Array.from({ length: numberOfMembers }, () => createUser(orgId)), + ); + + await page.goto(`${baseURL}/groups/${group.id}`, { + waitUntil: "domcontentloaded", + }); + await expect(page).toHaveTitle(`${group.display_name} - Coder`); + + for (const user of users) { + await page.getByPlaceholder("User email or username").fill(user.username); + await page.getByRole("option", { name: user.email }).click(); + await page.getByRole("button", { name: "Add user" }).click(); + await expect(page.getByRole("row", { name: user.username })).toBeVisible(); + } +}); diff --git a/site/e2e/tests/groups/addUsersToDefaultGroup.spec.ts b/site/e2e/tests/groups/addUsersToDefaultGroup.spec.ts new file mode 100644 index 0000000000..b5767026c0 --- /dev/null +++ b/site/e2e/tests/groups/addUsersToDefaultGroup.spec.ts @@ -0,0 +1,32 @@ +import { test, expect } from "@playwright/test"; +import { createUser, getCurrentOrgId, setupApiCalls } from "../../api"; +import { requiresEnterpriseLicense } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +const DEFAULT_GROUP_NAME = "Everyone"; + +test(`Every user should be automatically added to the default '${DEFAULT_GROUP_NAME}' group upon creation`, async ({ + page, + baseURL, +}) => { + requiresEnterpriseLicense(); + await setupApiCalls(page); + const orgId = await getCurrentOrgId(); + const numberOfMembers = 3; + const users = await Promise.all( + Array.from({ length: numberOfMembers }, () => createUser(orgId)), + ); + + await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" }); + await expect(page).toHaveTitle("Groups - Coder"); + + const groupRow = page.getByRole("row", { name: DEFAULT_GROUP_NAME }); + await groupRow.click(); + await expect(page).toHaveTitle(`${DEFAULT_GROUP_NAME} - Coder`); + + for (const user of users) { + await expect(page.getByRole("row", { name: user.username })).toBeVisible(); + } +}); diff --git a/site/e2e/tests/groups/createGroup.spec.ts b/site/e2e/tests/groups/createGroup.spec.ts new file mode 100644 index 0000000000..9542f4ea13 --- /dev/null +++ b/site/e2e/tests/groups/createGroup.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from "@playwright/test"; +import { randomName, requiresEnterpriseLicense } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("create group", async ({ page, baseURL }) => { + requiresEnterpriseLicense(); + await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" }); + await expect(page).toHaveTitle("Groups - Coder"); + + await page.getByText("Create group").click(); + await expect(page).toHaveTitle("Create Group - Coder"); + + const name = randomName(); + const groupValues = { + name: name, + displayName: `Display Name for ${name}`, + avatarURL: "/emojis/1f60d.png", + }; + + await page.getByLabel("Name", { exact: true }).fill(groupValues.name); + await page.getByLabel("Display Name").fill(groupValues.displayName); + await page.getByLabel("Avatar URL").fill(groupValues.avatarURL); + await page.getByRole("button", { name: "Submit" }).click(); + + await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`); + await expect(page.getByText(groupValues.displayName)).toBeVisible(); + await expect(page.getByText("No members yet")).toBeVisible(); +}); diff --git a/site/e2e/tests/groups/navigateToGroupPage.spec.ts b/site/e2e/tests/groups/navigateToGroupPage.spec.ts new file mode 100644 index 0000000000..44e2224df7 --- /dev/null +++ b/site/e2e/tests/groups/navigateToGroupPage.spec.ts @@ -0,0 +1,23 @@ +import { test, expect } from "@playwright/test"; +import { createGroup, getCurrentOrgId, setupApiCalls } from "../../api"; +import { requiresEnterpriseLicense } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("navigate to group page", async ({ page, baseURL }) => { + requiresEnterpriseLicense(); + await setupApiCalls(page); + const orgId = await getCurrentOrgId(); + const group = await createGroup(orgId); + + await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" }); + await expect(page).toHaveTitle("Users - Coder"); + + await page.getByRole("link", { name: "Groups" }).click(); + await expect(page).toHaveTitle("Groups - Coder"); + + const groupRow = page.getByRole("row", { name: group.display_name }); + await groupRow.click(); + await expect(page).toHaveTitle(`${group.display_name} - Coder`); +}); diff --git a/site/e2e/tests/groups/removeGroup.spec.ts b/site/e2e/tests/groups/removeGroup.spec.ts new file mode 100644 index 0000000000..9011ecbb71 --- /dev/null +++ b/site/e2e/tests/groups/removeGroup.spec.ts @@ -0,0 +1,26 @@ +import { test, expect } from "@playwright/test"; +import { createGroup, getCurrentOrgId, setupApiCalls } from "../../api"; +import { requiresEnterpriseLicense } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("remove group", async ({ page, baseURL }) => { + requiresEnterpriseLicense(); + await setupApiCalls(page); + const orgId = await getCurrentOrgId(); + const group = await createGroup(orgId); + + await page.goto(`${baseURL}/groups/${group.id}`, { + waitUntil: "domcontentloaded", + }); + await expect(page).toHaveTitle(`${group.display_name} - Coder`); + + await page.getByRole("button", { name: "Delete" }).click(); + const dialog = page.getByTestId("dialog"); + await dialog.getByLabel("Name of the group to delete").fill(group.name); + await dialog.getByRole("button", { name: "Delete" }).click(); + await expect(page.getByText("Group deleted successfully.")).toBeVisible(); + + await expect(page).toHaveTitle("Groups - Coder"); +}); diff --git a/site/e2e/tests/groups/removeMember.spec.ts b/site/e2e/tests/groups/removeMember.spec.ts new file mode 100644 index 0000000000..716c86af84 --- /dev/null +++ b/site/e2e/tests/groups/removeMember.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from "@playwright/test"; +import * as API from "api/api"; +import { + createGroup, + createUser, + getCurrentOrgId, + setupApiCalls, +} from "../../api"; +import { requiresEnterpriseLicense } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("remove member", async ({ page, baseURL }) => { + requiresEnterpriseLicense(); + await setupApiCalls(page); + const orgId = await getCurrentOrgId(); + const [group, member] = await Promise.all([ + createGroup(orgId), + createUser(orgId), + ]); + await API.addMember(group.id, member.id); + + await page.goto(`${baseURL}/groups/${group.id}`, { + waitUntil: "domcontentloaded", + }); + await expect(page).toHaveTitle(`${group.display_name} - Coder`); + + const userRow = page.getByRole("row", { name: member.username }); + await userRow.getByRole("button", { name: "More options" }).click(); + + const menu = page.locator("#more-options"); + await menu.getByText("Remove").click({ timeout: 1_000 }); + + await expect(page.getByText("Member removed successfully.")).toBeVisible(); +}); diff --git a/site/e2e/tests/users/removeUser.spec.ts b/site/e2e/tests/users/removeUser.spec.ts index c6e60c25e6..cd09d13611 100644 --- a/site/e2e/tests/users/removeUser.spec.ts +++ b/site/e2e/tests/users/removeUser.spec.ts @@ -1,29 +1,21 @@ import { test, expect } from "@playwright/test"; -import * as API from "api/api"; -import { randomName, setupApiCalls } from "../../helpers"; +import { createUser, getCurrentOrgId, setupApiCalls } from "../../api"; import { beforeCoderTest } from "../../hooks"; test.beforeEach(async ({ page }) => await beforeCoderTest(page)); test("remove user", async ({ page, baseURL }) => { await setupApiCalls(page); - const currentUser = await API.getAuthenticatedUser(); - const name = randomName(); - const user = await API.createUser({ - email: `${name}@coder.com`, - username: name, - password: "s3cure&password!", - login_type: "password", - disable_login: false, - organization_id: currentUser.organization_ids[0], - }); + const orgId = await getCurrentOrgId(); + const user = await createUser(orgId); await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" }); await expect(page).toHaveTitle("Users - Coder"); - const userRow = page.locator("tr", { hasText: user.email }); + const userRow = page.getByRole("row", { name: user.email }); await userRow.getByRole("button", { name: "More options" }).click(); - await userRow.getByText("Delete", { exact: false }).click(); + const menu = page.locator("#more-options"); + await menu.getByText("Delete").click(); const dialog = page.getByTestId("dialog"); await dialog.getByLabel("Name of the user to delete").fill(user.username); diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 760f860ebe..12c2a63b2c 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1147,7 +1147,6 @@ export const patchGroup = async ( export const addMember = async (groupId: string, userId: string) => { return patchGroup(groupId, { name: "", - display_name: "", add_users: [userId], remove_users: [], }); diff --git a/site/src/pages/GroupsPage/GroupPage.tsx b/site/src/pages/GroupsPage/GroupPage.tsx index f1f3a7bd24..01e8dc250b 100644 --- a/site/src/pages/GroupsPage/GroupPage.tsx +++ b/site/src/pages/GroupsPage/GroupPage.tsx @@ -197,6 +197,7 @@ export const GroupPage: FC = () => { onConfirm={async () => { try { await deleteGroupMutation.mutateAsync(groupId); + displaySuccess("Group deleted successfully."); navigate("/groups"); } catch (error) { displayError(getErrorMessage(error, "Failed to delete group.")); diff --git a/site/src/pages/UsersPage/UsersLayout.tsx b/site/src/pages/UsersPage/UsersLayout.tsx index dc39ae33ac..bb85cae1b0 100644 --- a/site/src/pages/UsersPage/UsersLayout.tsx +++ b/site/src/pages/UsersPage/UsersLayout.tsx @@ -1,7 +1,6 @@ import GroupAdd from "@mui/icons-material/GroupAddOutlined"; import PersonAdd from "@mui/icons-material/PersonAddOutlined"; import Button from "@mui/material/Button"; -import Link from "@mui/material/Link"; import { type FC, Suspense } from "react"; import { Link as RouterLink, @@ -43,9 +42,13 @@ export const UsersLayout: FC = () => { )} {canCreateGroup && isTemplateRBACEnabled && ( - - - + )} } From 61e5721caa838dee14d59a9ad17dcabfcae5ab5e Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Fri, 5 Apr 2024 03:14:49 -0700 Subject: [PATCH 27/74] fix(install.sh): use `--version` when provided (#12873) --- install.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index 3df67fa789..57b1fc1454 100755 --- a/install.sh +++ b/install.sh @@ -390,10 +390,12 @@ main() { STANDALONE_INSTALL_PREFIX=${STANDALONE_INSTALL_PREFIX:-/usr/local} STANDALONE_BINARY_NAME=${STANDALONE_BINARY_NAME:-coder} STABLE_VERSION=$(echo_latest_stable_version) - if [ "${MAINLINE}" = 0 ]; then - VERSION=${STABLE_VERSION} - else - VERSION=$(echo_latest_mainline_version) + if [ -z "${VERSION}" ]; then + if [ "${MAINLINE}" = 0 ]; then + VERSION=${STABLE_VERSION} + else + VERSION=$(echo_latest_mainline_version) + fi fi distro_name From c243210ae5d6f58d11601262cf6f5ba85bd9b454 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 5 Apr 2024 15:55:11 +0300 Subject: [PATCH 28/74] fix(install.sh): change post-install advisory when installing specific version (#12878) --- install.sh | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/install.sh b/install.sh index 57b1fc1454..cdb0bed420 100755 --- a/install.sh +++ b/install.sh @@ -118,16 +118,19 @@ echo_standalone_postinstall() { return fi - channel=mainline + channel= advisory="To install our stable release (v${STABLE_VERSION}), use the --stable flag. " - if [ "${MAINLINE}" = 0 ]; then - channel=stable + if [ "${STABLE}" = 1 ]; then + channel="stable " advisory="" fi + if [ "${MAINLINE}" = 1 ]; then + channel="mainline " + fi cath < Date: Fri, 5 Apr 2024 15:30:49 +0200 Subject: [PATCH 29/74] docs: describe devcontainers as deployment model (#12877) --- docs/about/architecture.md | 51 +++++++++++++++++++++ docs/images/architecture-devcontainers.png | Bin 0 -> 79416 bytes docs/templates/devcontainers.md | 14 +++--- 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 docs/images/architecture-devcontainers.png diff --git a/docs/about/architecture.md b/docs/about/architecture.md index 943071276e..15af701c9a 100644 --- a/docs/about/architecture.md +++ b/docs/about/architecture.md @@ -268,3 +268,54 @@ Coder on Kubernetes. [Microsoft Entra ID Sign-On](https://learn.microsoft.com/en-us/entra/identity/app-proxy/) - For GCP: [Google Cloud Identity Platform](https://cloud.google.com/architecture/identity/single-sign-on) + +### Dev Container + +Note: _Dev containers_ are at early stage and considered experimental at the +moment. + +This architecture enhances a Coder workspace with a +[development container](https://containers.dev/) setup built using the +[envbuilder](https://github.com/coder/envbuilder) project. Workspace users have +the flexibility to extend generic, base developer environments with custom, +project-oriented [features](https://containers.dev/features) without requiring +platform administrators to push altered Docker images. + +Learn more about +[Dev containers support](https://coder.com/docs/v2/latest/templates/devcontainers) +in Coder. + +![Architecture Diagram](../images/architecture-devcontainers.png) + +#### Components + +The deployment model includes: + +- _Workspace_ built using Coder template with _envbuilder_ enabled to set up the + developer environment accordingly to the dev container spec. +- _Container Registry_ for Docker images used by _envbuilder_, maintained by + Coder platform engineers or developer productivity engineers. + +Since this model is strictly focused on workspace nodes, it does not affect the +setup of regional infrastructure. It can be deployed alongside other deployment +models, in multiple regions, or across various cloud platforms. + +##### Workload resources + +**Workspace** + +- Docker and Kubernetes based templates are supported. +- The `docker_container` resource uses `ghcr.io/coder/envbuilder` as the base + image. + +_Envbuilder_ checks out the base Docker image from the container registry and +installs selected features as specified in the `devcontainer.json` on top. +Eventually, it starts the container with the developer environment. + +##### Workload supporting resources + +**Container Registry (optional)** + +- Workspace nodes need access to the Container Registry to check out images. To + shorten the provisioning time, it is recommended to deploy registry mirrors in + the same region as the workspace nodes. diff --git a/docs/images/architecture-devcontainers.png b/docs/images/architecture-devcontainers.png new file mode 100644 index 0000000000000000000000000000000000000000..c61ad77085812bdfbeee54f0c29adedeb5d78acd GIT binary patch literal 79416 zcmeEv2_Tef`!^!dinK3EBwMrDS}kLrvF{0Ej4`%hjBTWd6m5hMT9l=Qv}+-uO^Z^} zBHBv|t)k+)?q_C7o!;}l@BcgRdC&QOU+2`!v)%V~U)S&YUDtI#_w!7$jg{$0=}FQe zA|fNr&B%5lB4Tpz*Lc`a*u&(8Uxg3RFgsI2k+Q2(+C@Y{+PKCp+@L65Rv<$}iD<ark4IKQh<$%X4 z5e@KK@DGlpi6au+_~+A@j9@{7R5p(l7)Vnhm}qI@peYq9jm`?@vcvq8h=#Cj9?WF~ z!YAy8f35A|9~bzG$9dwlJay*4SA&p{K!!8J+mZ#+m}n7oG>O`cCcXZBu#=Qo_~NF9m3%6WwP1KKZ5sj5_25>6+pJ*h|H*5scKx*ttdoG6^Ow$lFz!GRa)SnlL^9*$j40m-2 zGj$2n4s-JPxjkX4K~bh0T8JN&?StvpCrTg^L6^vv(WjKoDe|I z53Qpe_PDbrhZ>S-H#;xzmo!8T!z|{IJW>akODW<8m1sr>d1aKPqup&UF@3zwp4M(6g^koNgHNr4I zMx=u$g@95(>%!ykjfu`P0;=G^36X40Kp19Y0>5Ez*vmf!KC!M~P*`umy+|3~J0|>F zph1RbfqNSSvX}@KTsA`0p8*QekD$N{K{5yx5bHD%D$oE21B0OZmji}?|9$|*Pj+GW z0Fw%QTi~Dca8ATG|NPNzY!26t&4e%%NMR$JU@q>@;Bun{L4g*|WrL{(697O>a1ak+ z&M=Hi;|O;kS7!tZ0}6i&eAGqIFt8ja+7&i%nvhSxnXpL)ppp^q@VAijqAk8T&|qK( zZ2W>M1-ru7;T(*s;H+PI6JB6Okdq174%XbSPsG&w`!4)T0^%?NXH5lPy3VH6a0%^$I#8HAI6PG-X1 zF-rT!vA-`j5%RM?mH-9Od=TCI#bEv4fG0A|k9j5ESik)j#hGumBjEllcxq`9h__+$mVzOW2gVw*v_}|e>Sxv;RGBm z1d}kV4+`VD-`R0+6xSA9DFoVoKfA+|1S1<92{1e1)?K`CYz0@oWs?dOMRTj$N{|tjd#L^^vqYC&U$ZQ1mB?+xI z$i8?T0c8xft`G-*A?pgH{DBXC%F&=G#by;)L0BDZC`^wjG;cfDzVg%UtRtV09gUX*7EL?HhNu*k?r z&9De2hY`Z&Bg`bs52|Q_2~h`_8%jzOsP8y5Up6NQo%bh!<~J;$t*1E{LwW+o8Uz|) zZ8sP*!hL@s%m}i&K|cH!%Xb)2EH298u|yrdyQ1&>Bp$ZD=kRC)`S7=d{uik~;m^$d z1-$Z4bAQYNzn|vv^Q`|$ZiUx{nN{6ESO-EGoLlJV!5xKvV0!omd31=Hy1IGj>aO; z>5)_xiDX4~hBt5?2Yg z9P!Xkv^n`7^iBQ;{rQuE%!z(}){z0abfTS>x2Yr3$1E_?%>kx{O`S+SMtc5qB9&#s za`k7J8tWL@Yw>)@hB|IRfnnZcwm+3;PH-hS;;o4|VLRb;qHPp|;1=R-8mVnga0_zt z=jvIotVpg7VHP$>uV#T^ZVrR`H8sJ}j12v~O-*>nE^vJ$=pn$y-olE?b0ksx14vfU z2AD3af~Z_87AYF^8cyXI^C%9+(G-7bB-P#^isH}2TN&Y^tfEO;RI)46%7}!g@=U1K z4zA%=JSx$gY|I0l^Q_1g{`~8hQE(lB;%^*fZG?*kn?+F_TtT<=a4UbN7RA9FPw_Vg zI~kzsa8#aO7{$RgQm`)?Y#L7WcY?Oe!!g@|{oq)nRWvT#%E26GPDa-oL|Yl5J^%+s z20SZ!OeR0T2FUNo6n;{T3?j|R227BFV1;CeCK0U3eqkVw7RVQEWp4mu2Algcqg;7R z=+Bh|o)l^2Z{bgM zutnP-vjLoE4(9<*qj5-9Eh`qt%0jXlb1{984G7+3V}dnyEtLz`fgY$)R00+3!vx>K zfi3~7{!EYo^x_aePvU1@K2> zE_`Z%FF`xzaE&8@0y;o?VzA63zG2)UD$>=(j%{vg73S(<#RD7y=8c(vOXQydT)LwE zBduX<2htf=|*xkH8=HSf2#wiwUxT?x9cA7vcfXBj5x+4VYkCo;C7uf4J5k z*;;@#vMU$a1#Aobm`7RL<51hkP6(SMWJ9nG!U^b*6a_rMM4!+P6JZ?e!vwjas60Ag z3fd9)1RRT|@_i{9+Cz8){X;t-yOqBy@)3cI<{+aZvKed}YoUI@kH9tv>jt1}N7xTz zMPouZjwS&&S%raI;9s^(z)lq47t@Iq59x#+4R%DnNdSDHeu49lJprd=CU`+N5%=9iVRq#C49Cy^+tuxlv$m_yn$G;b6Zl4&e>? z4(Lwc$7q|5jRD34=Th;2TXYQB1h5BOgXw`ugmC~~Y!TPst;miDH#m?FYzX>y0DpEs z__qaK!hvl_z`rmS;0?qBFb42#1L%i}&c#uXyue|=9dO(>isBG}>6qUJ-?rA|fN1EG z2s-6rHgTYG!B!+H^bNKH{Nun+9kpPrFa`tUTc9^vtW9Ve+JwG%=Fk_C%NWT8=P`kE zFkOH@1AZJpUobvvGU6s=XCxca1Nc1)UoKo-Co#`&mkYcgLpKBh5V*8)G{ zfqY;yTQ0`8JOhw}iLe(5m;>Bl7y_9QR|OyrMtF(>4ik*u1jQk+gE5K~Fdim`8NdRJ z4|opp280>J`Q})>umyWTY{JG5=kow7fGukWqzAy9Kf)IBTZAdFE#l{BWPjimUI4^1 zu)TvV;K-OD;3vNB5l{F7&d?_oTg?#$Va%9pa2*w{LwE>?#BfBmMK(ucL^7lJf&3J+ z3&IZK4Dc1ig9twe0|--y&p}3rKmG;;kPGu~!MITz2RV)LU^hX|!}m29Bl1mX3t}}9 z#$yEhg>ZxUp(EleEdC(-BA&qP!*3sC;I|1rfWAW;LLNo-0N%#-nUm87&{Mm z8?ZqDd?Vk6{1D+9+o3O8unXD_ zAXqy`f}OCqijC3Al!{^>;!hm(jYB>M{s+E-Fo$es4*LyY{CwVq90P0xdJc-jFo1Fq zz%0*-2YHTv0OSX#A6w*4{QjfS`An3bKz~%^GaxH)A>_Z9PesGEAUpCCumOw{aE<%{ zi}B_V`v5y|4C0p%bI8|0m*!j~FY*<*7II`JY6H_X74sP^213pofP4w;1$e=71kA6A zupi@36q}%ZE%0ZgYa+xPBx9r%w2!WXelXo5zQ%lvAM1ca=_KF|gc$QzI)z*w{1D?0B48NoX^RIRhfg}jFMu6E+%bTB0O1tIhGHGr zCk*65`6n0hbDluopjULv9DI}xZ3IC4$MlDX+zXCj{ETn}ItE?|f*hO-aS>tP0djB1 zEwFqT%ZvGZ3%-Z^fdJS9dtf;+V9zz$%9#o}b_5>~j2--zFTXkDu&8}o$XgK~qI?F~ z66y&ozA=Hr5U)WEhT;Rt5&c2eSZ#%JXNad*%;oC>`4HMhc8C6nfCunBSI`mKkL3^u zCrA$PX&58e7>jvG9+cCg_`;8U$d(9~X#8*uQwu(!E$~CcV-SA;pCAYDFOLa&Fb6zh zoDS!h3vr2ZLew9U1N4RR3zW+vTcMZ%JnMk20~|YpFC#mn`~~v`AFO5p4uEq|{tLc^ zaz}qGZ-zJvc@N*$Aikg&fbwME34!v;ENzLU=`yNEPf(g0Y3@G1ip!4GT9t*WR%l_oiQ#) zIK=!CXU!i!5&1uA3-br$*N6|0&tUN#coOnqBr{+J>5c^X2FT?I{)=)_EJs3f2GAF( zmq2cmOZh{e4otL-ax#?f!1V#33j>H@a2(@bl#>D8A+CV$BN>stK=*Wkj<7la`b4@w zc_)(y7>61e=?d}$@DIoXiNIyh51n6gBVGYM!tyhS*a&q02G^vy$gI>IV8evD_p?+}-eKyKhgz&H3NU>}`} z^G3O!1H?D1UUVctY>38kJ;Wc#zGN!+9cq^jybg5$*aXIo<+dOP#5foeik(=zMKS}& zfIfl$Q5}c)9O)16k8ugeVT=cRfSsXcipKbuZx_g!0DrI_s|y5pz_+lxES>X^I}-MVnQx~Fp0Pr)oFlx@F!~r6oZg%>7Y{-w;TcQ zXif<7L#!5a1U(`?!(<1UVcrdk8`NkNBSA)}t5IA)u@vSz98eBRM>#g~A*@bBJOcST z;s8{qV6vm*Fn%~6)m)%+etw2*gXROVyu`{s5V#oSqo_R`ik(=l2mK-Jft|1#7sENG zBZ%d&58?pSY>=bD*inrK{tU6rnU8%c5p32`Tu2OwWS+=Vz4tHZE-4C1vpvLnPu z6jzz3MuU6{`5Mv@zb58$zi=)9<*eX0DCh9U@-E;#Ecb!=AMgb%r$aRY%C9gPKyP&D z1LnXmd@)Ig;}PE=Yy%e&usjInWdOfgFlMAT;5Oh^tnP6{{tozr7>_W6INBwI3;SUF z!0l*WLtqnctj@*e*obf*R*&<~LGi}TANqm#09=mv9>qDt5hyRmd>v$kdIH&qk9TOt z)EFIu_<)^*KKb)={G43Ci;!19Yz6-S`H=_*a4OVbD8_+2pcBZ0p@s%|Va%u|G{=Ju2`IMk<-=kppFdF^ z<6!59=}1t6BVF?6ECus}5D$RkkdNWPKM|f0C!=p*7UO;qxdF;i;2M5>hw*?+pjWI$ zLVhpMH^fD-7qkQ8!|aCU#DVj$SQ>!*66zADD=^F;Za{Gd=?-`tvm?sWk*$#bqqt=( z#2j!dHs6Kg>k^CQfIGc$+ zks|+(7WzGk^-y19aUSNcvAzH!z+q6|fc{X7lJPX4En*T>}0p^NfUJcEIqna4;BH2XS8`<0+<}a~%9`Jp>9t3^@T#waZ zsD9`BIJ6D2qp@LQCZgI7^o2f!JSmU?wMiv;^XpN8?J#VE&(NU_SCX|U_!IOEzJzRr z{0APd!NFgY8$-Oo@Q&s-k-kt}4p>Gp#@`|gvmcTj*$T;q-LnB(1R)=>#Us60K|KuP zMDq`Tdz6=>+yKKPx)-7Cjm=?V^*#@ab-+{c4 z2csN;Kd*!xNBkv-n-1m(Lug(KuxE?r(?ABqAlVWJhl=znaX|8JN7{25EOh`66t+x&RT zUr$?8TW8P;1;L7dL2Gdcy72fI|02IZ&tDMW!k^X${P)-Bp_LD4fyM9L=ohZu3JYLx z>F5z9wCLqGORfgp_76epxv_^#v<#KBkTq$7=OO5@_#HhxhM9Np;(FooELITQjW-Eo zdBeX!@bnpa@Quu-2f#AGKX3gN5j_BiA7tYn5g=TPH^}6|U4!jSgtaRC3x&%;{sQI} ztf%=KEWpyl5p)44Xh9Z#5f)KTu=x*GVZn(u3=Rt(mO=>oX=N7J6J7oNxePF`aL4!d zu;(;>Sl0D(i+qFrA&+YaUGHzdWJ^brIA}S%-cL)mz?bxd%csz%;E@NRPyG37w&3ZK z-xKI>yJidA<)2ddAGILsKeiw@JR}g7h-*f&0>F{sVPZ`-2L%MQ9fG#uJFEl`Vh4x8 zH?CiJ5DFB&=r1}%Wzjk8Ft#t((|{MwVPGMHV4_XJg+yum;+SSgFeDs*$~tf&2|~(` zoIxPx{*y=|j4nc-`3uAmffxT?+Q1Iy2C{+~M&BNtMrp&hM`+Q8a4FwETn;Wg z;iuK!KPMA|QV$`(gQA$|iE&L@WEfGC9>@;&@kGz?4SuK>78ROk>1q*4updtLVL@~d zW}Jct3Iz`JAAOpasQ2UZL;pG~h~jU-Ggg=YSTIQX+43kV5h z(So^}EH-dTWC%Q{1X(M3LeYdzPl$WqVOmJ$O;Dn5Vv4;nLBj~aOv5+`*2D(=iPS>dU zFi!(Ioe>uHC)OxhKUb-OXF^c+3YHcomxDk6uMZIfpuac<1S9zY1HXCD5W)(V2H}MR z#j72I$%4nbq6a-5_=i2w|7V1NpP%<3{=#~?+TWgu9&A40^J9OZ0Pw5l!hT~um_I;o zx#5@g@P+Tx-yiS%TiH*S2o=zv8b?540$0TA!aI8g|M_jw_w8*Be{s_aB>#QSG-1%V zkbHwtK@Z=a*Zun$l<)aMKGhOTlno-(Us_G*x_=>u{MEBOzj4U#R)Zk%H?`W24`+RQ zt;yfeWWOrqf5&8yg#NEE8BzF>fxn^2{)LM8$5+z)ZQnnoje~Ia13Tiizs+?Iww$&g zUJQJ1$w!Fa4*aO{H^kvZ=q3;-TbQHfcu=nmoN>s z=U=bjApN%Ba16uhLNFhCYa_g40_=xVF`}5m2fWaafb&MLZbawst^NnTt!9p}O~AyD z@2L5SPyVf7@Mlz^1U%fgCJegkDaacI7|{}X_aKM=Wd{5gM98n|mY>Y=Pv;>2<>@A2 zOFvFp^It#n)BN&pZj1kN&RjTMDxA}%v$z~qlx83;1YR$QO>6%d@W5+nX=?op2|>~` z2oj`UznzGvrKt@XM@s(-h5cVuMnB;K-tuRFUi1pnYV<%_SQv{Aw+jZ{aQM-Uzr8-> z+bcs{g}0*r;p;>ATKPk6DZ{-COvV57mNGxj{vW&sWRRtW74hKcAl&!oyov4G8%>2p zy6{z~LWuqGHmz2^4&OKgq5 z@5NR~4e}V_dMOA~C>nkD7qo#Cj_$nwf$k!_1^rLm1xv#IzqgwA8x{Y1oeti-#>_=er}&2oFe&y+9x4lk-P%=|A+1W z-6-(4>qJbj2NYm>4_jUE+ZkQ#K?2azzq8r^e#is;pv0h(UpQ+M$nZr+exC0MV)^)> zUmP*yFyMzO&>Ip3N!KqPi+~>;fOF6ZsKEK<9MRwM3S%`6k7Px+Bslwpd{6SOszVO>ia5RL&_D3%mMz5F; zfklD;(#_WYM}hP09`5e}M@X=Lap3%RevrZrW@-e&Phg&z^C6qPu2Wm<9~Yu z>VGG1;D!D#FhA*6xAcYQ3^u=T;qYIGH~#S|4D>TZ7y$Uc4+V@M_F~*+ax<1V?y>vjVZ-C;CPt{047#X0 z(emi%jF>MI3a|7f5}q|E*I#-U<5_KN*E}s_+4-eURNOKy$UN?uL?pW7eJfdKSq5UO ziZ-0ul%jM_HLC%)Lw&}0B=7Zo`o0mGPxbPjtReR6ZW1w$G(8)Pf z;nP#t_nY7LF-qO7o-f*>fBan2cv$^4o zcbGY9o6EL>smh$5uAO1&Z96X)F5aRT5vVSI{Ed@E`~7X+78#^s%Jf1%TEUVV2f5~F zXQjp5*umX${A2d#`mErRXZ@cqA34vuv4ixc%TiU@{#X%1MPvTP#zXI3v^ESxJf~-u zpZff1D5lI8YH@ytn6tkcc3tgF;1F-@w)?EMyYa)o4AXUz3QtT6kJsLKI}pq4jhf`3 znC5L%EoVDi&T2M;?P65CEoNn2ds%YR&5Tt2!q%FuC6ss*=D&q zYp=$3kFt$8dd6eqf_vxugW6jZ6?flw`X=5puv^+O`$bC$DLlyST&zx#R6}puuiH$>x{;kU*T#t>#e0<`?!RKUR_tzR zx#nAQ3}W3jGqcNIjXx)GNDLG{CQ(czA;vV;((}uS{I_r4UdYL%Ufsu4y|-_gzfwUkgU=MB-^5Z~6yyecCu4Rv0kzdt%>&KxHq*QZ+I_GHZ9wI0Y~#) z9pZ}ND|<)vE5`g6RXXEsH9xAv<<#U@ibtA0bKMix7UXcM(^xY&n$b{6F8%% z?m9K(v&z@vt~nje+tzTxjB|1?k#-)NJZk67PqH3e*LQ?YS$KcW;^1>G8eg6@)`&a# z`lY)ch;KOD_Au=Di1+m`jfcCxYuVH9TFo86tX(cIU#=OKeZlRGWd5|b z*#lOQx++y~o;n0}+oY5(Tdr5wx=L>#u=vp9wt^2a`S1N7JI_%wnLbX|J9$wQE_qKD zer~yTcC7LOo#MuT$+d|C4{P-JjIk6LgSdP7K*Nihch#?nzx-xpe}Odqeg9YUC&w*xA54DcIYxytK|*S515PGze|I=$ zNEH=s4jNX*8Y-v_nD*54?JKdI> zcGHzQ+^1d_k&E74+#YpoL)js%O<^i;Qg)8@e5`J}FMY~^7iyAvVN=P`2gbJfk2!lY zDtn65^DN6r6J=k#Z9mOyG_{O9PoGsMLw%V}cC^T$=XbXpwzwt#nc{bU%57^gR@ZI{ zFY?Ze$n*!#{C0$f`<{oPs7c5^;%`}h*iF<5}&o&Pam-yZWQ=5C1sm<83r?_+JEKL-#fJB?ZYnB@$%(gGd%{kOVRuIj0Jsw@dX?*wYwD~!c5RsyRht+&b56cld zZ%-`gkITv6S#9l5pv{~5d%2A_h-VeL1E^9YHGslRbR><{pvMsWcHTM8~1H@u1jcr zl}0YKzj@!;gywy#GW`BYcLGIRYwV|)d?Iyza7byzjf5_Z9WCxz$p#d;yGoO9hs~UQ zE5Kp+1(CS!^fAai;S8zMr72ZvUWuU z(Qbv(@v^$J%*@JhjcTg6hmAKClXh84MFm#kpz^|{a%elOu?`8bEdn_Vl!b_8@byyPOCm*+V=cSVA?R({8-X6QC z>6e-6H6x<-vQ@F&c;~{G`SZrMj3h3OD|oQU)>+RvYi%*!dF-*eCfZo#UB+T@sSCKP zCMz6XkyRcyuXgIngGWSW7?4*crFlq?Q_jngoD=y}i#+jpLotrC`2GU(@wS2Kb1$Yf zRyP+!B zIi>#0a~kKZq?}vxs*krCC+!Fi7f~0H#y2f38EkaX%}}cPmeu0&u515qp76Q6*8(z)GMk!NXl_O46RLU z3w}HTqkyqxV$%oS>U@YD(5b)TIsQd=OPWRh{JG9~O`%5VDGnXCI@*uz)Z5ZD<@x=o znyI?%`_-PFfp!`5oacI<*jqcD5*0@EE0u8z81mL4TSWXp*C)z3hYKEJ6cJq?^`tv^ zx#RPAv8>LuJJZ?TNR(?I{Tw_>%>~!1x&WZb} zd!J}#RXCm0%Z#9_wjQo;q16-~z=CXsShUsb`~A=69u8?*#PLp0)fl!WXaq?nvu0iz zb^3yqE%jO@Mg#XkHjnY1pled>Ga=SGJ`b9BQhJPxgM zQGT*#ct-rispi8c$Vwl6pm2ZU-A}__Z?b^{FUMG4T6MH6zJ}ac)cRz>`_gz1M~l}W3vzX%wG8ZA*aX0QPVPUc?-GN z*O7JAz8R&oL-#K*T0Hggg4644I_~P{AC8wBSlqU7MKu1zp`z%MvrlaIDi9e`Icupb zXQR}UEE|RUavXWns|QElA1581e{0o@cMm;eZN9wLl9@o!ox^mc4(!$0G}-1==881M zTl9_qruNe>t!q^1S(Y2TugodyB{UDE8x$!PzbomfaD?VvwDct6H``%7N%D9<)LMf^_tvWZx=60agM{=CM{6@^{R(?Uugh1A9kS3VVg zHbd0;#=_D7`*pPjZJiCfo;z&|J?j<~8a`}m$f4wPx6Rhu-!x|wd=hE%h&^&~Iz_x- zwS4rD_~cXem*%h?1KOI*g7rh4`>n?2K3eE`i&pP?XS2#t@v}?&^J&dV`*VvJ^V%Qo zo_M&Yf-QGeZK>?;rn3^(lRp&iW{UXMN}Fe7cI3+Dj@WA``SK=kSc{Sww9OJ~owX_<)hLA-V5~N+@lNWdGGA$~r zKc;>`-*WErwa-mmTJN4xy)x;hTZOI<@9lmEw^MU#1I9dyzEsznIn^jvzPK}d6Lm?` z!{N$fdbWoz%~m?bjo-dtqRx(E5G!$Mhx&L0>8D>fA0n%zob*n1y)vvKZ|mb5FRJHU z(S4(`!`D@k+PS2ALg)&e>os+UnjLqX%XDuBUbWwFnu#*s>Y>urXRp7x!gybMPVouX z^fpU5<@U0$BcBy#S2iBOhX*+tRp`2J9x+?vZk9<`P(#szV+^B{p}q!}#JAipSSA)5 zqTxO@zV}wb5z2+a9ll1Zo-4Z)Ro6|=k|_*ooH}*G+hg{x3W?G|??>e4SX~;p9vME? zdghkT6&pSq7vGXq9uv~*R{XNl=;Q=t>C)WZa~O?IaDGsIk-Mko{Y;C7`soAyEpXj9R1$(x-<$j46O`3E7HVc=8PxEx+1)p+ zc&~1JcRZ)9Ct5?w{TMJDl1$&^=+xF#Hi`v zvD!~k9&xP`3W_S`t*cJlmpxAP36nG1vX@;Q-y$lv?acc4SsF5*PCh?q*UW4k{aL#1 zO4`YXnw)99hNq9My}7Gm@6n-L=@M6?*^2kfm!2<5`#eQ=u7s!ESqSvQTdvH=itn3e zHKjPAUVQJWmWq^(3OajNAD@CjP|QKZbX^;+FS3ty$(Hll-Vqnq7_WGi{&>!Q2c?2U zpDuk-?+aIF)i1C-YxlOq|1s}L+~?XGgomxyUoP+@?N8ND-#?e0vR83Tb}E52`aMB4 z-+lQ@sgaz3ph}Jy_;lstIqc60+Z;JJqZ7OXK3%0Brp7e+WNp<|~6Q1WEET-(X~~z@~_&>EM&xx-Go@4ECKQtw?B&xM3ZThy|JCav=y`ij?<+drE z%~(2h503UBYM8jPO;*ckx72s*mbWDCtvc{N^67Jxov)@kl=>;9j6dnsXDx-78aw{+ zK;Wp1jC{+(&t0Z{?T^%q{kY^~?~NDX!S+Y96N)}s7VJu_?oOrEtT-Jy3Nv$Hv50o| ziT={3hkZKUe0h8>eDlhbE%PrLjN0JvQNC%g4XT2!4foJpF-wMx~y*uJiQccqg9JxaGaHm|ab@8QyMDuv>C{VljSr!qG8%y$W1p3O4r5w%`4vdY^1b zu1xl|p`P_0Pn~>~+BRMKq}~^cx`}*jS;a>#zfHBi+G=q;I=U=M;>se~=W`TJ2NtKF zI95WL-gDeN&>=IoW7qTX(+fvC=Qt^~_n$kP={{9PQjVMv$rXKC+Ldxz{>2k{*QYOb-yD}Qal5hMIB$8%`_r27 zCk_vr61=V^F+wfq&Su{J(hr}9cx891$uQ9rkCQ@(#Dhl_ou!%YJ4ThRtu-FoS}nHi z3a?~hc-5(bm>S8kr}{48C%wBDI&bUd4o>Ei3XrPqxlLx@__dBx%XThWpdq(q#r$N= zz1e2lTHQP@HpwL8;+3=?y}wpVysv)%e{$?XPM>Q1_6I%Bag&vCi?%eS6gr=jm-=+r zMvu4NMR|g<*g)w!d5njF$Xn$mI`ph!WF)_scV>)R*qPoHmgOq>veqk3^E5Kes&Lip zmg%$o>Ke|!=)NiGGMmyv`Wk>mHcKP)k!N|Vy4`f&TC!nHP;jm9H_RF8CVwO=pF zF~*0EN_i7z?A|Zx>0-bO%@J4K5Oz`CZdYw$hVtyWtDUCbr|d7yGmB~H^St0EQU7p{o@*m z23K8f&am|1kHt%(z(O=Bx#VSVusVM-UPR5S3a)wp8k{!1#4e6DbI*y~$(Mw^XYUuOP3+ymNfrvI?=YttC*yek%6fq& zy(BI>$I)t*QIC^_0xIko>y^D`Pv^69-+F(hR*j=o6}it83OL3Q21<5}45JHs7yW-o zJt1S#ZaA-9J$ky;+^SCn!J7xZcCAQUz)RlG{2KM8yUVnzx4L2U^5t*8(DwJ>xp;m&R(4_g<76q_i{uu>4Qhq8O$u&&`A7*> zfP80|lW42ee3z%6=6!E_3^dlQkA*_axkQyf#Ed78*ABXnw(`8-_8Iz60ngeoYr5xU zq0!3G2!;;o+gwz#i@0Ogpi{_+Viun%-7-&P@IxzPXeHHEv7bx)oA%IL!paEHCb>Y& zV!qdwk#Z#?W`k_qrTT9koo)Zzb-H-7#*%{$m{;F66uoywoV|y&HuOwp`2n#kca9Ww z>w+9w&AHe*N4i7-73}BN5gN?C4PGqsap{GTUJ^5{VCR|+yI`ALXlMJv%dhDYHP*1R zyMtI3nt^sc9TJz@Abr~g^gcO4+BsRi8#qXO<7g11JXg#DNBg))PBrsDZn^t(r)orn z`|LJf0$PdplDE0btYN)v9G;Zybuw-LT4gv`u>08Y(tY|*)V%s4?v9X4TkKb{>Ln<- zB;bM)V$N8AYj@-L_V)I}!T0CgJvIy0tRb|7M9y$`X^; z3qk6m9mgN;($wcPWT^5a>_w_?Nd2C@$=Ml786l`poP7T~(hqOwtLa>_7E3 zr9$%U!msadyoau(J4U+C(`j3F$S>^TCMDyhV}W-J>E|~TLgQ{O%WRs*doD|pm6JQ$ zW4T#fhF5bWv9G7|{u2GEjf)4qD86^8TBg@jy2M$3>I2O@xmvPr&$Q-zpp^yS@z+!u z_qh^Poy+pF)nkk4m2HyJuJ0o18u64@^`Ue=F_ok$|=L=#Q2?Ua#}w))IEOvt(!1 zTFnz{s=mCn3|&Ea1Q?WEAvS7xe=pO&xhy}VLBwHMlU(}ZtE=@-eC|9cp$n=otgbsA z_Pp-6Ov&QT%*;$GGq!B8*z3H1oJ~B5^TaGAIa$Ulj@OW@ zvysE$3`=LayXRG$zjKJm>V9xWY(d}qcF|S-=EAiAMw7Jz$ApMQReb$;ul-QNqMe@0 zdyVVZ54QTv?R$FNbb8?xxV73LvM8=BggQd*;oUHIHX@6nxP+b@-MPc9IOJ2}%4(ic z>?@wj-bm!@h8j{*&5pHj)xb~w@4z)-67#eD7y1uwB~(cyJ+)(Z@FnroT-*X!PaPF}tF|S8^w|?@d5m)vT zUs|6nmFJuMS#h~{|Cip3jEq_RuUd{DDj($@{WMc-gV)IRw$qDWx1IK^t~=RPrRFt6 zN<_=6S_I6HB_eiHVvK~JzM*S*ioW79NoU!&VtcH%E|}x3y}h|@#ehcH{AHhTnR7i` zOSKMr9u0K8-M?p|a?R0>ibwnO>r}TNZb+0jP0c+OZhT?hsL`K_sJSURbWf@Dz)Ic+ zzgzoUI|Iw59|TR=EKz5=ZXz*h=*UU4s?IN2e!al>?2|PYr?bCA1xSi0JiId1D_&}X z^73kNWoOp2o_)jh#e+p%X5eg3g9RRW6kBgAvdW()wRYIPsZ}p?^7A$Hy{>H0I_%%1 z8aiA`?(OnJ%!Y`Qa%1L;SxTu-TY7>k6JA^Ch1XvyA{W9oa}jZ%NsMU0g|I}%cv0g{ z4(Xg3Cnqu-7tixp_U0^!c{Oji&*}D}Tyc`$5a-av)noR}e7I$Ta?O$F6|KOZs@o49 zNl2hQ`cnPk?vcIOWAJw_-j?ju&^I_YLBUKeSX3l|vL-sJzxVmsS*w!`1Cko9Tq(3G zw74FyztSZ9n*gsdvSzt9MCfe|o%eF?VBe|Hpf5sdV+0(Q9t#hCC3FS94nT z>}tN6`Z$i*d4fYF5AFg?F&mzeC>1BVZ|J8{>5KJ;No`NmO=Va0CiQ=LXZ>cPx_HH? z<_9r#Mhc>Kny0^T443Xd@lv(txrj`K$C&x?Pm3#bUP|?xOO%q+{FGTE{kH4$fX5g` z(X}JhXZ$cYNHgu)>Rlu$++p)Fq?BXztx%PMWFkuJl-| ztf%61^=ai4_sIL)i zFKX!$+awphDz|nrrTR!*%=W9pmWhj>aZE6uiXUZmVMw>AjluZp#KZ~9HYQKQj|y2@ zEq(nM+f!z;XsW&;(9QO(rW>ZnC?tum9kOWhJZ{OF<8f;xg3>joSC4o!?}3R_a~)2m zY~lG2z7jK?A#A4ijq4mLuHbEWu5NVXnd>`ES+~n+kEl~RMNS1N7Ktf{=tyoK;v4BA zEiyW8nE22~>!+lvw2TQcP#7i3s5t^h-DR^w4tyDl7Y&(HT_dtKZgF;MI$oJQu{f^! z{PV3bRxS(1skJeApUyH zE9o`*acb(uKU>MW?a}MW>GG}188uJ(-<+Sm_Tp$MO|d7v-L<3dN0$?Z`0`xDMI^Ep zh$@+d1Uwiy)h6D2jNGiteZ!WKS7wTy+5BmQoZjXweVnidlUxYp?s7eG43SWY(j~{n zg^sUSoyHSWwsEdwDwyj?lH$}%hZ=f6GV0BKA-a&Tu)BJQn9K67ADl(yK8+5E6ZhIe zV5AItCB92^n8-AV#`IO=AQm?aF|(U@bEg{YDS`h&8*{n#;xN;Ru>*97g-hH zojt61>to&_v9!IAEl9-1eEAeADpEe>!D6{gGgoBti&dYb{(oIra@}#=&8o8?J}R zw^Xi?AI{9?XKF=b7P#zue!f;?qzAxu;I8O|TY=ot?l+s2)t6YuyD8f|dz$6(XyBR5 zmV|8$x55KwRVSv*JC%Plg||#YZOkr6bo*D;)~$HmxO67{NVuiSF~gDd0oPh$X~xmXS;lOkS=%1$g0B&7KS)GpN>|<52;NIudK(N*(}vzGCu3F^b3`0 z+`|=qBFe$}uid7KyNFQg zB`a&h2`yV1!~P(ay5MW zlIuIm8tgW_4j(~uh%M>x=%SfmZ}|eSvQPxAt%lF zliVEqh8LC6#XFTjAE{psGVgYu*shQyl_c;H@(juZ(P5?P!>V7cC>MYFMCR;fu7<=1 zigSkLj%N&eznaa8N#`<%is^3&SG&v&j5n3M%b^zSyRH8@)r-;F`+VETrt_LPP_T7C zliwPZp_r}FPp8HD-8=eP!f9BgsBOoAX1~ZImsE<(X6mKP@<=T;t#>_9Xkz~I#2p6X zn&sA8!*ceA9qnthKHLQ%p!i5YbBZ_2ifF29Jj_6?YYb{#mm==J>e}Qn{hOw>irraV!-x!sTz z+UwXI&a;DyhQs7Q^Ws%^$GOf66CIFq?UQtp$x*lPFu zf~AEkqhw|^WU9r#@G|yDl2UUB&%B7E9jx3Sxi>r^({jTUiB}`!c9iQ+XgAkCn=$dw zoFftT7MdM*$BVK0&c$YAr(4aLJ<7JpLVmaN$YxHXWamr&UAN=jzDLzp!mXxw`6+H@ zsWu0EF3LvUc}qMgD!F*ukeB^&1R^C-l=6w7H)+R;s@5YJ)VaMeG2UNKbv(D$@!WDx zM){SPjOfBMMyYphSX@8MvUfY=OVy8b5*?Kp`pnflDLL+h#`eo2Cx)1ZCZBEN25`+Ty$g7vhM)nr-){#3iTn)uOMPUgND4?FtH?Jh&5 z1Sxmn&7P{Qj-RgHsM)b9dSOcEr~+mbB_uZ!0|WE+%MVYuU)d3p``@-W@ozVX|WLj^L_CIxC}} z6c-oUb2w*8ujcBotz!G$%M9eCiTJId=FAJ<)BLEhu~9`yX;s9l+Xj$+;~#G>+AQ+{ zcjhxCd*0RxoKtnG*p8J_Cq>2U&b)27c!PN|e5IC{T;*X()aPk!7oYTg9TFKXb0$kx z`Bq@dtomj(>t{Xcqd#10J#S;0m?!PP_06V zDjSO9Oz!nn-LDm3KS#0WW0qtwchrQwnHQy&mfcRp%`m8b7n7$c?Ukx9!@wXQNQ7dB zOKTpz?5e~JS=rR-+b(w5PJ21)QjhaM$bg5uGA_pxa!tdblujkf&AXgEV>8R3E|`S( zP83-W(-0ZAN|%_5Y@A_~bgi{AW;X~+3U1L2z5n`Ho7ME#_c9N4;@(3sYIDTDsU+&n zBkqf9GkBfHpZj&T94AG*a&bH2FCX67;_9B8oo)8|-m&6_#RGYJoJwCFS+=bIjO6rd z{a-&zg&c&L3P|84QggM}*JrBDu3z!$!1+4qd}ZpEYaRPfTV*Yc$Qf4hVc&}Fu~VgR zGOs?xIgLt6)?Yp1IaA`MJWfn5>cI@{+7j2h8=Qt&7fze}QTBfGhYiEM6?Zv`S{j5- zsnCuv6sztU?mK);_ZN@WAq)kU#^|k`a@G^1jPq9Y`Qi5`dd(ir%5garbwEV&%VfFb zZB?->n1kTHx{de0Q8W_fIMeZRd*^O$G(?8LmAqQhIo@Lyv$ z#wcto9zJB7sNw%%>%GINe*gIKb2!GaIc7F>Y>ttUkxe-zWzTa^luhNnchLy;$q>Jin~2|YQa?po-1Eo&QBN7 zZqm|H+n%j605y2~&$o{4$qEt2LE1LedyKavQRq<~q-o)g7%tW@dI`*XGN8eHWf6Sg zr3iX-EOpMfHvLv#ftrgw6rb^p483~rp|`<1f?Rwh8R8stH2vtNHW{ph`&oX$`=Ep3 zz!zcVG>EUSjYVMgLT2UmPv^heQB`1tHUK2{^3|*4$UuO;y)}I!R6=Jchw}sIpy_j6 zc*DW`y|^QCL^$~WxH!nBGI|$`fov+D`r~I|JC}TO4z}tQIJk5)hj# zVVZ{BmqmCx30#H3Tkel@!Xdcn{Vo|N;e|aGjTbkGHo;|T(n^Va{U~f0wUJVS;w|ma z_abOQ1BCOLyh~aarIPmdj#M#WVVyP&5?C|z&c!%nchKNA06m2~wTLhRw2aD9DB|x) z;I_)?DZow}u#P|aavB!Xc~q{qq0x^O-aycVD_;t9sF;;ZN-Sl!d2JOcq6xl4R6)So zk_{*1s2ui~s$Zqm0IH53h=y5{<8kR04M*#&Xs(iRo4?bSZ2ta9s5xo8w>tdhT=L3f zLE{Hy;Thpy$3G*PMn;;Kh2A>6R-C(VoTTvcsC`pzTV+Q`u=%(Bcvwq~$dj@g@6nub za0uD&2Fp5cK|8-s=Y=#JzrMO6I%k%m_MeaUa{~ivmGK?RaK=M7H3cMDy&36%bgNKS*+K0 zITL~al*H%>weN63jTL!ZFP5U3rYOxw3Vm>4(>l~R>z!uZz9m$Y=?*>-IV=oCnjZGH zpbCWe9k@$r@NmSl34W(~p8K|+S~QtU3;T`(Oyj{&zwDkDg zK?8e%%RylmI;>~6{CkQstCWp*#b?+4953$5SNl7$PX$<*+!+v+4bh{iS|lY8NdiY% zGENkdW&%Sf>}{vIts)ZwKf3R!kG?Auk13*dxBOQKDmp3$T8eq^E}K;H^sTp{ur<$Z z?L}EpG>9r`9fOCm&Mz%L{8m2iVU1+HQt=Pu=R{TvT@5uY_b?E%kQAkWFprAwe^&iT zw;JNWA&bUKi*az0QIN$YO*C)_TT1NaJFX6SD0qwqfK9hysMw;BshZGK3)nVae1q_sm++G7=0`E`A+O#J^kdbNl7k0WVof{I%Hs+&1s+gf9DCzS$g*jMa-c{z$zUSw$#VLG}knFO$+u$QVMl`nb=z1#$}(3c03 z3~UsbDLoADsVid#M={d4qfz&>pPcx zK^!H|F38Ko_I(!4yM1FdK>9gS9E%AElKnxQPSkivoIXs#67!;4BKh}F*!^&%@-3l* z7TygDYdGTVcNUb%3q$6f-d@D(7&1->(?P%d_TcwHjr-xX4*fK98{#1K@3{V7nl%XW ztgHvA{yN!4if?gQCvKEzI!+XBE3dpw09##KDB&OgDKJA`!xP?**TIi-r+B72t--JM zly`x*{uIzF{**ZKxDYpmb=+C#ewdl51wnYh_o&d`jubD*td%G=9#k^vv(Zo@oyUBW zL`kTIMe(C4toE?d#+t#cMQ5ko&QE_zZV-A-&$r4!@vdW4FB7ZwDOmvuAaa}#dr|#l z=9^gOxrs|rm{KNt{sRUB&0-a`UR)L#TtZcu%@hC&Q6&gDRETNUi01aITOe3v-#@pt zyhdX=)_jWyJCgt}4E#C0nQs(^jC_2+P^_{ga{2(*Qmh(%h8^pZ`IbVvJacY0jNx9| z7t{NoERMGDi=8O{h6!(lBb^^ee^{4e&akR-zVui%&@;*~EQjjbh1;c?+>Lr0+z%7q zjiI6PrA+3QNO$C;(zpMc%;LNk!C7zvyyn`uYXq{n{=<9Tc#XNnf9UKG1A2q(C8^1tw1|Xj?J-m z37$=VZduoQ+G&oAiM{JN1}`-mj$Bv3e=4DEjcs~7hk3t6#ay9l>*GTvm}f68=@vK1 zXSXWpWT`#Zv$e-fbZ+UsKmk}z6O3NAe_++Cg~WKc*^P3UZm_&9U0FpJkdFFO&4V;dRS!@rPK%ji zHqAC>Wd$v4F99eKy&j}$tf-}!B=c1ivvm6Y!A)Y(un=pgHRSAqIDKOXROs>WCGYZS z%~QrEb&0B@26pN6r1b3VBkmUR_kb#`zV+!$UhsQvqp|kXxmq7~=ZrFh@MEsVMi_iS z>78xie)_jCI)v8mk55H%1#;US0W?gJQXBi;^>YuZREtrFFV%FYsVFrys>6FT%Sq}wU$kZ#Rg`|&AvDrfCmsSzoyQwN+bD?h(i2~)bQPCQ+~ z(>oPM?eEyU-!OJ2LB3EAkisI|eVsE<&yfclr@bgpE8(HFJu7DoPPAd=D zTOADFpE6+7I1qTM7JUEK=O>qbHtzFH1$|G|GM?_&IFejkSQyClYPx-SDsW?5FDEC* zv^gUDMvwdlCJC&zzpXwQ+V!Y6Mc`^<5WG}R+wJxM5w`mL(-QsJ3qz4@DmE`=fyjY6 zQ{4WeGVg`EiPtEn10E~f)&}f!!>GoXJQT?nzH4#@g~1(3K+p*cysk^1pPCk%OId?T zyJfs0@TWbA2(B|2eru*@XCJ)C_85gF%aXFm{wD!Y_7=E}bv!xT8!C6WNSv()3@JrE ziEjM-Ox;L5WH%q%8lTkUKKwSTjt?T|HLXUf5T^E8-aK;cpKXbG1M1Yf{p$Mz55;a2 zSynpk5@}@;l|JM}99|lt&l5R!ZV=Q%Kvb9+OK<-eAP)U%zvx=VV|EuGIebZE7S?T= z@M^Zx56n=NvKxseVkG_riK(?^+Sg3u$Rv zpW7w0*-XM1EPvgyYdwJPCVFMbvU!fo|A7P*S-pFuY^;xDlZb}F-}TZfd9+qGF~q}9 z!jTXJ-qay<<2*L!V1FiZ<>e!LmMvV8Uu!)5?#$oQM;YayIvsjx#4$(KvS3+etMd4~ zTlN9Z1E~_a^oys;0JP9PhJX+ISC8tARjAF%d+bmmxVX3+pWA^19t$G=g;oru2~Y>D zfbV++)zGkd|SZ(h3slKlBP5OvT%{s6@R%yoC=s{wkUE9Q6j(MIjvuUC}d2e$|E zqm&z2g5w!@#cjoLF(P?k*EO#tv7DP6Zi`|62Cr5*cTTS^VoP5M6pT|TN z5m+*=t-^Nh<7768wsf?$VU6dMxF&?)xAZjdI;eAE{UMh+f*{&V2~kQB=ye>`3meb+ zxSmHc#t)EJSpLSdLD>_itTw$ww*XtL@KiRU1;hGftRXHu79Q=VVrW zgofQHt7P|)HluK7M7*Upje{F2b$T_Q=#SX?rJ{bi+`O5DGekebD`aBg2xsqgGkN_o zgwSOuCVp@mDdRAOZzckm=qL`}=03X-e)N08(NTZB!^W%eTv==ElP6Em5*B4=Kb~); zfYuEnKW>n-WK?WOOrABYV9!T;Xoo_yaBv84!`$4Bi0%Sk}P zSBkhhSnW*-L5sVFgp6#;dR^8?V0E)~kO^j!vZ~V6l)V29EmIm>u`9@>(m2d>o>G{y4_1vSzIAjxg;A5nX2DK9d)*27Y0;YHE`YyoV8XHI|~ z$&safW^Prpk6criNQJxu0&W25<;j3e@k^bLQ?$ejFhs;J z&{`PFs#Rl%e{*8Z6k0{W~!{t`*ZqXr_cT#rPWD05zwKZF3tTq>) zKf6wltMu*s^Hpzg|5UO3zE>BHK4$!WJJ$i;o5|rtz*K# zAp)3roeTjo!MV@4BXUOW11n}>T(eVE=Y$f{4(3QyqIK2GKt@Y&$knXq4}qBQ#_VD;-UNtCb+ko7v4L|S@RkPFV6VmyDFU$nHLp+%a8*Jc8W4Bz?o z^-VHOK`cSuQ=1sCmd2A!=@AyYbl;@^kaG$yj}OpYJ5Prg9RIa6u~BiPfiMrqalicQ9xbiXacQoAqR~!h)y>8;(lbrqgTh-GP=}{%T8z6yrwc3zcWrFR zLu^|bqX^eQoL!D;l&TmP9tEWO(!+TNK)UpC; zvzPoHk#~x+96?Q9Sv3_P#ocRH`BT^0`tj&8P44YCXI1xQY>*%KOJwdvi%`2V?(Iz8 znXp-*V-asg&WyYTx%fHI8DZ_vsN5Ok;p*rjP_O$ZfE;&#v9Rky;nSl>SnbG24ODu0KWh3kZA9lC+3Vnkwg0v$4eSCisiH05c!@vheB2TZN8dnZo24B{tU*>HX%ozwux(mQ}b#Ond^ zfm4Tt=BvNOb}F1_Q^a3p8{cGc#9#Y5-B)aO2)J7;v9Hp#xC-ER!}3m?$P{`u2WZae zWpL+6nw7M;)98_lqw$xs1hEIOM@>3U4g!kTq00Uq);GQF-?mNF44Q>Jf))X0F=o z(GOUNSgDNZ5@)IR6+!j;_WOM)#GFP$-KjN@9|Wn5@__*V3gT9^iwpU@_GkTKff){r zZpg&8XH5cJLhs%G4fK?(gJI%aBmd0C3pAp-1{aU+oNI-Z|41BlfVS$k1xP9N-2y)t zEzzMVxIm(*;!x|_en#<01IvAZSklwzNI;10dO6`T32TZcM+pav>fLMw+SRHFT7~i7 zM`+72J@c!I=HGIA6<^^o-WOZ`3}naJ=>hAu#$ARDrMw zWYgxy|Di%}GGE}>`olG%)pmt-L6|l_t;zEJFH8ro*N|=~WUY_ATYcju%af0p_Nr2f$cR6wo` ztX$L}Orn`mWs)qxAB1Y$=#^5i#0t|VnQY4CxYZn49$a^!*nN^+;KejunG{$zLUp&c z%}Q{MKyX_T_}6=b>;{MxRO9+%8&HP{$@v$@+jxQdx0LgqnXVX2w8q{U`dIEs4Cn=V;Ywp;-z#V{NUm8b|HbM1 zyeKn?9#p|w0S7M;(%iNzNoK`22QZwTwS+D+Irrpn6CZ*Uqc5l+H9|~-(2={ zHVA-NZm5<82mNpMUzh)H$N3jumH_Z`>8Jp5x6eQ3Y0RLc{qp&>4`qZ;W*~~eBIA_@ z8m1#U=XY+7|J!Zeh3583p#Qy85^=pgdfyz#;)kvv6T8;6ULFqo9=-yWWfViYTUmX4 z1Snc)rENgbL#n5v*bPASygTNSM=E{9K##fEwN(4ryBFRV=V=T9MPkEFk3_0H`~5KU z-{l~p@YLN*v$?T`(06%9MT#=?^d?I=cez0*mL3i;h>)xKZZ*tTALbK~_FysXpm`q;anLIcJ#ite24?d|`kLfr-Q>UEOX8n7D$01ZJ1 z(1qRw3@;w27Bzt`wjlV|4|s(p7a(DT2_)}>E0D4eJ=dcpmF~Bv8vJ*`3k`zO;u1(K z3mJGXBp#8W)et4jBn2+W7C}>h14(SJE-u<9z=mW|THAzQS_D;q`CH6lu3P?IJkWff zc+^h)XsQMp*w_oCQ6`m!gQJyBZ$M!=^dZn|&N^r=j-v?Z!i5-5)q&hSGZ2tH)j`{i zP%STC(<26GgkSi;M?7EJ>b4eFP>=!vSYw)TCUDJ%O z1qsOkV-{A(`_lo<5m>?4nI^c|Cr+e03<26LSn}6+&DdkmtP)0oUHTVNNENr*ENtBb0T-xfV!>HfECGgb+7;uUHJ*k zgS-9e@wZ3qC}v93AdsEjnW!oCcKe4Sfk{1ELSKOdr9o;#z=k-k5Ev^OP=ck5Zr@4@ zdS#j?CC~!w-vNxqvCVEcHa14|hz?2td0c!_7=>wnN)mf-ESp!!w~Oq57y`hFMr3Hj zd}IJy7%}JuS^w>q#_9v(gii%7IRuzY3=cL_bJpqlbrzDtLdctiof2k9e7gmB%r8K_ zFDfBn)TRf4fgm;{>Gy$8BUz0ED*-pnm;_qVy4tYKdbW=R(pF?rxD1RLh+yWe6)C;K zT;YM!JwU3GHX;5MuvB8362O^y@b~-tm8m;IPo{N=6i6mia3^oz01!Q#)#pI|QStjh zQZ?uj&YIs8pg*IKsPXsM@9<~jUy0xNj8F>*jLB*7SYk2B(f|`6NJZmmSAG!o zW2hKk)lP>PE$EeW0L-I(m9ddDo9WN~&1Uj4$vn1uQYsfY5uxfKu~>~jAGnDeXM!TD zd^CkdLZ-TDndD;uB_TX28$d1d7wa@LW?7=*$l$%onV6SbkG${h&$TARalZssnbWF? zoluZ^w#G^&&{_28|NA<5p);fer>UX!^MfnP2__3ugP%`&QOv>LQ*E5fK&td(_4bQu z04kGcAh@uOsN63Unl+;Nt=bcs=HIq4L{ri`t`;BLd;I(X&gv6f@se_vH2au7X4k zOI+uh+Yus}rUv$lfIjAxr`+~mmw^NXnAYNhv;uR8k!_`a-5B77@Xv2uWK5aL^3#h3 zA3ZmG?_YLUz-vF%@}8apaIxoXki?g->z~Du+?!Z2{>kwFPP|#?xnfe@fI@R}3%ci^ zULlqmfX^yv>kL~>Lekd#2X5R_q#v%`j)yy4@R5#B^5!Q8PneFe{i|&Pq8txoULAMm zZNcAZPy%y6BUSxr+;gC1mL20lrmVEpgTD(0nPF@wqwfi=9uy?Y%8;*-wP2%2YTc+f z^V)r^YLnL2&#!&P$;s)2!|^K*SX(48`s?^up>;rF4r<(3|JRs~mD`;;QsMPYA=l_8 z1r15n9)9BU#pCbk$!(IM0u2Dve6TZJBYB~Y=GSMM^MJ1A#bnY<#F5lLQxkA=EmmD4 zOxF9o@%i2XiZKp0QO4IN5x>V=NJ^J&m8%C+^?o7%OYMDlr0*!Gj}cN=e|rDHgGNRE z*JnY6KDib+emd}h59RhE@At+833U}7I%jKanJom;GcDa$p66R`eKI_@^2_b}rxSgC z_4R}C8V@5NdD;a+mmA=CxeX4GT>?2HlO#*z@peKf_Bq&QND$SHN78mQ3R}P!X8H=v z1=RvFB?W~`IqE-%rE0<`L6I=r5}SpsNq!QE9X#R-HX1*L&WoT#A z^diK~Plu%sh~h5wTm@kf>LqDa6csgYsemwcylTn z9u@(R_b+A^cIPx~047b(B^Ud9bIJ&OXdxJ56L9wxus;b~8bjy}501gm-~GLA6|$IR zq5CXd<1q2`Xy$LjS~@595ZwG;dDCGs;DWSBj<9c!Qc~zhhd2;?7%lt-r_C~WJ60x& zdwt-P*PT#9j8nSNBO4=54wGtt*UMNT{P1qUCjf320Z@Avph+LzKLN5gQAYob!NP<( zFW_r(?FardG{6Pu&wEWjA(_)irZw6<5d<|_(xxV*tm$x-cC}cEuli&gT$$+eYe0u_ z@NjSK4RDO@?)1vgN^ssCS`!JDznw!msbjz4^isZ(KyK%pi;)^YM`OJ!q7c6EexwMP z+qC^2rba|_Gvuo(&C|S_6|G zRGlOWBAK`FMFD^z0%+Vhbh7u;N&gYMq1VRzQ2ja$Br+cGOa|U7`4K=)_FtBh$@oBE z7O)m}j$d?|a^zjUfR#jhUiuspUen?E6*=hHZLh{{2O+@uKI8gRwqq^A`9Y40OoFl+Ap>F-IAAB7)mDr{Rka_ER-*X_7@!Gt^#kj2=`up0;%UQ0ogeaj7C_=^F zepaVbhyQ`vq_|gE5I?@EEUVcA*oz#+JnC?t=%yNJR+!bUv*F1sTGXL*XIZiOgJ}aT^zKxfU^h6BVo@ysO^P5B3!bcQWM1 zJ;b3d5{{7Lx4m0Li<_&N@I*M#nA5m#FjCvUyOqlMft->qADAB>0LvN@LjL_1i2IQZ z5r<~@yFah2^PBLFZswRjZFL^liwY0tQZ|)IxDW5_~_#^)f9h(!2bE|7xNoQ{9>aff8ukRTj& z8)XZTvLY_D-{#_+62|{}u(=$7JuKBVXdA<__!G^tT<}|ElE@Y?el&rQ=~ZWK6cWjy zHoE;f;Cw^3#|siU&-qDFGKjJpdeOXQjlwCno*+)rIQVkb!6GBJgS!;$;7+YrKi6f{v2O;=4h#H;L$YdV;xg}RLo^NdFTT5N!)0%uyF zO))1P-BEEFqAgp-y#EWF2ef$2X2q#@_Mk3deKJ&3vv33ynmG>R zpqW3r0DG=sz%ctq)Yo8*^SYDNEU)s6{q3VZH>AM#d5 zy1>HbRSD#)Ily4rqj^kG*3NywQ6Dp(Q11}hkLjrIIsX;~UCW#ff*g0BYJP;x+sy7n z?cD+fTgQ+2c|eKKwn9jST0)q*FP*J~XTUeq;ZZUP(s3Wh|KO!%qaD~9O zYEqT1tenvrQsWy1bKb?+g$w46$_|_fuT8A(Y`b^#T*JaX_+_FCp$AHkpVr+DNSeKh zK1_U%%Z1(ym1fM6p^5x^QtRy%WQ|14VSHPhky8@hjhHGb>e~nfZ8gX$?r`n`j$yjv z+*uwKUjWCHG1)Ta*MCn_1s*3B48>!?S7gwqKt~;zI}g$~kIY)s17>{2rU+n|LcT*c z?w>#hh~n(aNdFdPt;tj+e8$Py_nEQ?$c5V`(Zw|V-j*srl_E8NE@O+ULspU%$GLm2 z`Z@IZ+>f1MWApul|B`8Ji%rw2&P3vYIa*PFb4`koQhA!V1=-Es&8C~fCR2&;@ zdSaOI+t!)^m++o2!SJ-gc{X*_fw(}9f!tm8h=mmk<6w)I0%5aM0?|qiB|EqP%TI!c z=<9PrE$=N~=9j9L$hyjy^N(WdKQiODUJEzvp9gsiG*K5N8SNW$#oIssA`op`Yx=42 zLq#}s>ZnQ(5YslT{5({X^G6El-C^UFZ=Y7z+ySIY8WY^VF)5}(>|Ak(NN=HE-Fsh6 z8)3ZA!p+Mk+C}?79Np*(XVs+ey0DXK_}E_S2j_YsO(!{}hQ-HvF01G935M)kM0*^-<(WT1{r08Q_mx(R^^%4gR5muBj8?x^(LZe5&{G-G$p?CuB`t}v>%+}=Y zj;`=M44K!y^@aF72-JDYEyQL#4kDzcTk#R@5%sgyrT-bxg=#!IN?*X?W9A>wdea1# z^RndvG>T`EjOFJ)eqnhFMuHaPG%r~rXYDh4(;*c4*+m7B81dM19u;}Ua zhb7BEr0AsX9{0Orv#*|LN2-`swc_El^J=E?I2k-htFuPV=~O5ZYLhi3VB45fxq0?o zjJ7p@MKDcvbZ`t@my_G-IpqN%y(;gTlQ_DqF%*uh+a&=$Z6dK+OhN4ri{25l-km>gb7_%uelZ18uj zF1?7LqjBiMSITa|QNs_5?$}ew68r$*DfV0e546k-y%SfjFlb4u@`%(x$MR*#`9(Q& zEHippic5R<{3M-aR#a>dMTviWJLEwlSOM95*xS4;^Wxr`EEqCR<~Cc$g$?KbqXlpc zw34sVr8oMm7RaVM_N@1H30G6cW%>Z^UuF&eVBI(6(!X7CS-pNE22PERR;dMOK>L+p~@=xnt=)_NX zW*TfM-o4~)ZTUGJ%j{UGL}u5dr-xg4wq&pE$l~~E?=wG&1uN40{cwrz#weKn=}?Xj zOpaqbik_`2Pvl=SqL@_Pcjd)n4>zt^9mC-ZqnGK}ZA8 z!(4c*;~I9|F4~gMas;Cu!%c5v>lDel9*3JkR_Y)Fs@i%4-38*<=ti%eQN2dg#?{`i zT%P@+lfoqFsyt|tyJdcF%G$@cL|2{8!dPZUi@ zcBVZZf9fR5<=9MDe{@dq!cI*;9Zt8L*Y$yY!a1OC}_bf~C{m8N_r$vay)wZfZ?sT^-)G!fjq1j|iCaS-+3z(Lu_Mh#7KI z%#PYx`c2H1>on=m!k(CT%`p6pAJ|LhIV< zrJfn2lZ!%`A-cOz2D;X|HqYlFC$yr2X^*J*!v>}lR(@z=)D-FD<*v#~@1{w2UM+DY z7hrcOpDkfq|3*_cP@Fe-W!&^x79i2gW~wvaT-MVRUi5oLH2Yr=pl78RR5ctC=iPs$ zr1rn4Khx<)j$#@St`+Ej=LLOln>fq&Wry>&`_t4D3ygiXh>MURCu3O7Sv02B&>hQ2 zD`l=X7r`mtC1IiQ=fZtkO4;x9N&~_JJ{mETR2{$CuA-emP0FU~;mBds7=8;g-`V!P zly?CAQ`CRiZRrIW!uREwNdX)MWDEe8l8ceBw*%e5|Ed#;KM&n)g;+?z-L0a(M~FKAP%=9-@umJWwi+MDGJ{q=+%he6-0EU^5QI6UM z#D!>&#Mn1rBBOi$A4XuJ&4d+~6qKpusJOXDM%KMvI0_E)wk5c(Zr zC4M0WkA!FM0)}Be!qE2#1ijb6Pv|oIm$Q$$pHIkSSW=4;%ike~uV3x(0}a3vlB8^E z5)TpqSe{&qf+1QB=#0pwyS9F();0rQ*oX9KN;KiEAYLJLF@5ylmfe#z(vOU*M?wDf8!lMqTA!qFk zp-*s+ASqOVY-&?YA?c_@dHkF9oyKEZqU!pkt>s zGCYyu<9?GXiU3R7=+L;J{E^9%&XRXFTT?)0{tjkJ>#xuz)Etf;!lyLy zs;3>xK@b@<9-u1$OXKMtj||XdfEj^Y`?>6!65i%2zz*A|ki@z{hHT0s^exT+?W@Z3 zdUtceJu*OUp#Uag9+cb0EuVcbC&;iyHhqYhHz3N+P?zcU!4zwVqj;Ev>wKLTVcll!x^D+fS>?S{rp}@cmJKui^C>EQZYAhFsuDTWpzz9u zd`u9H>tZSBn&Pp~Ujs6xntqr7lR0(UhyXT@e0r4m#l2X~VN#c%Cw{T{R{e#YbL?d3 zE+8~aTBdE=7O40-!My+MdpkE;;FMA%_6*l+&YvLI$ax^m5M(uU8+3%I)M!exRr)e> z86_Kb;Qe~Ct*h(P@uJC_l-epCX8F1DE63^*vu|+!NV5nu_BsXiEZvzb|05RUHNL){ zpyF0vEr;%j%mqs}$jrydYH_^qKq}vrO^-Y-;f(>+%jyUA6{nYlQP^sB%d2oDCiC7P zzDmC3!S79nBd*~o3px&=AYf)C(yu{+oNm%M##hY4FTIQamKuCRcPzy=70|8a$f;~Jf4zx(v z;jnw{>&g$lWrif3z{e24)HB=r`g#=V0SMSh{Syj{7WwR7K*GJxi<1(&=DHe15;SdT z0D$YSNq(~E*dr1Vzt6XD$dOFCrLzm3gmi=E8~d_=G~QJ2+LK`=kvMaZKJ697rrVri z1kW_os$|JRHGrH!mIy>AaoRD4%~U^eR?6yq*-6Afb(YN@6!W3IDG=OH+k8>IQtYp+ zp+>Cy{L0^wmDRcP^k%OvE1jC>^ZVeDmv<=qD|_C30e%S#tQBf*FUqPM0tNc{Ek5rATD18q#| zW2G;=;$&yU@_vC<`vL0?@kvBpL|EVvscqV!#=niu()(Fr4^xH+bx3y9#*Ia>ps0uG z!ihe|(pMX9sIYdAsB=hJ>jG<<@Z?%Nw`YEt-3Q9>Z(0H#@{cVRsT_CtqJB8xsZC`+ z^LzZ`FgneiWdVXPHVM?8$-;NvztN7P(Q1LD4NBd3MF*NG?!> zAO(QJxl!vmwH(J`k6L5zSu8r%eCSnn18@XpJ-+psfGp5m4!!;lThhTV0}CTSk=xb` zA7AhnPyK(1mC571B{#Y%Eu$?2qRgOHBNSKfYOkcB+#C>obZ^aF#)pbws0tNJhtC^l(|Pg1c=$- zugB*?KAZ(`jy*Ss3LwPkU7igY1!@OC#iWp?@d&MJ(+8X0lpUN2nDE%l1oGl@hoEnn z4k%Ykm7jV83Fc6~7P-&5Po742EL;A6q;6_APcTWvdyhkgU0@67h-rGY{_HH?{-3Jb zCrs+<)0pA;yvWl6MUU}ofOl@+t^wW4rSm>Yn~d?i(BI{$wlY!q`EV!fjJ zV0o~R)HiN*?41Z`K9p`Bvhs4^FU#G{q8;));D@HEh9KDADhV}U(E6|zg(t|dO8?Vp>V7%=t86Zp$`s$m&+UK_ejvhj z_SkmJ@xgX1{|rDBivl*r$vBT&+5oa$WLxj+*$dmeSCxUu>sLSma zMA|!xFy#x}+WC%Z(Zioc{LOcPT%lS5z2#Y9ol$aD!f|h%#75C_V{5U-?YRWWL zvR*6j7O`VQ67x3Q7EZP%4U<>?|4kgL)@?6zKOkAbL7SHIPzmvh`(%W{7ncxrz`{$# zr6xATV~y98>;QsvnR%%U;EIo%fNgWa;qcA37q{CGrY|qEoly&_IB*B6k4zDKiyq*1 z=*%f^kiY|BE@a;%^T<$IN;DAzkG(he7B4IjL2EJ~6_M-e-~6W(M=uBDUD^eiPa`ni ze#=%!R7?M1^Y72(w#kmcM=zssq5{dBnkZVw&vQbKpH-H4G@1S4-O@N^3lmkAaLLId z)Z+R0YfM@&R`N^H)HC~Tr+$;~)}PVKzf+ytSW@YadM7_M?6r5b@rIq~K?<1RyjdOS|MVmf19}xnOOpu=G=+z{jaR?1^KPNoeC#>ZUtTdnuSKRr^v`LYd<_%c ze}`@uTLELs)CApYIaiPlG>}xZHB$i-D#AY(%W%|8Ucg+!7|83xmH|EeTth9(F|A0I z_uXT8xcJeBY)g7a!Ni#^_aFlSUJ$hbk<-4tQX2pS4{XUdxh;ffcN3y3N#?h+i1&E- zV#fQMH{Dyt%2+$RkIf`9S!_7ny#GT zYOK}W4@5r91c}j{zj_<}M0wDoP{Dl^wY2zTneeIew*hCpBu%&F@-Gx${tlW|+b-`> zjOM7mSjtarm0##_b?llHLr@aBEd||%HPO4$0WOPh>(Et!+>k{K+q7W0U!t05hjk<_ z2q%=*F83)kpUv!-H-R(lQ53^^hPj2457}~xrb7~Uy1=EP5ggA;(j9No+IY41%#WVRmj-TvD_Qj;z8NfZ$IdjaI6j=E zA~+t7oFCQ``1dxOj}upy|Mxa;?_FP@vGID@HNRwiaZPx~ndmhXCQA^Pu$2{UKYA=s zyA!Rc?JPvi`e4LhRKGjD(vWYw<&&s;BZbKADfK^A{6dJVFZVl`$+)N!(XJ{B+5+x{ z3!RpRPJfuGZM?FG)BT4`IYke%71EfPat6+b;=tH~yJYO>B3G@+g%F4F)KPLwTQ>6g z&ywUcja#II5Zk7{qz@2}=(@G=3vDWN>-qKMFUs9ogr5zpIfkFDseO@H?sFGAgt~%} zrR5cS=3*bi7IUZ-x^=0?&YoygOT2`oK9GhTml%u|spjFbBo3KNbBMXP|D0d|I`;KK2!C;2wa|D{bflHy7w8 zwFK}=;E$-g^7O*+TumE`(I_~ETs!$f-N%$80Gaq3bi5eNw(+!b;kWU4pT!$E=@T$y zVvwb7o}m<9S}SmqzE~zRsUpLyIZ0pBndqK+{#%7`y4n}@Au*r7p!=WA&*?@F;QdI8 z#YSwmb8G-gw-)U1-|E>TfwN=(RwHQrX&(Lwth#6+1Zk;zeAuW702JRCHi7rYR_IQ* z$Ohgitr9pWCKy!Lud!#lJoV51gqLkCEo>ME4OZO&kNtiz+or6rEnlG0n zRIay7tOC2&lfimmnF5Tj{X5Xs@Eghy(ADpNqGlA3gkC|^->h7CRTm%LSO-Tvz|^Nl zKdbNCy5LEj3YESAI>trHy1D`!>HTRJPlIw*{9gI5j}Bf{+tytJd(qKfK&kws0~?pe z(!wYJ-#6a8epUtCZGB}QcP@W#`MGzJ_xHFHxR0DbLk4d?w#321wep{3ool`XDAni{ zr77ool2wTxUfI-o>L>pNwCqS({f{$&8z=iSUQG7^JzxLF7W72%lm^lwyj0`vm(9PR zF+iwEvATO9D{Bts1SD5(^EgO#%oRgUsbR{x#TaZFz=QqH7cn6nUj{4M2@ zHsqGHT$HTWP*G+hP##{*Ron8aUt|aVnWwzryMt+vt#`*ey{g}%e7Kao^MRkb`xQE8 zK1D_U2gq$JK--t$U{$~k&++}v3)@MOM}i(2wK@zcZH>_Ho1T+(m6h$jYo)d0z!fsr z)dH+LKT_CraS@867;=LM&V+5*-Q#c|;Q72IkBrYeosRq)9=g>+ZGXP{l>D?6Wdwlw8!t~v$Rf&5t- z#tBghJtQ#y&F?<;%7q%YgYmlqVycF8kY2SQwXh}8eGKKGf9_CXg4;|}qx|}DK8M<+ zNm{WM9jM?(z~p;$pL=w7!VHkEuQvdcaMjrTHBZoNl*(Rz<1+Y1dOj@NH{yXu^*!?@ z!sC^k-w&Hh6y-KZgKe=Xws_zZ>6j4Yxf9Z^{Aou6pHY4H znhnq>Oif5T6F(%JgwpYPwS!e_SL(q)4eOxz^YJ7ig~6*i$C+#lrCfbQajcDtM~#Ww zqdN0L8OYA{X9YdMT56tZ`tyU^#S+-xyl7=UR>-HUpZuyxeI_W<=U>^*E>Snos(4u| z1HP+`?)z$d(F61_HvX)l^?YI-O3kr#HSu3VfQa1gK}NC$-Ic3A2xnOxG2)m zQc~{!y7|cN2c5>z^l@H7C@4Ih(DL-hjgb2L?gt*)-+A+LGEY`?Jy(ZG0I}PeTVZeu z7zXb+b)>w}L!yq=BqRSUa@YV%+Z4N@v|J|FXzkals`XMdDJ(X8V;gCzo8I6$4L4wvg)}VNVmt z=kCwYL4oAO$v{)SyA9}4rvj+#h0-&g1pmlU=?3FcSRS^NEs6%QK(M+=i2``3iL zJ?1}m$J-&-R?mm?g%dv?grC}IuVKkI^0GbLJwK$nHT+r2HNJB?b(iSM9kK6Ek>DEH z;t}v;ymz#cO6Gtd7W7WqkeBSzIRvNsbf2s!Cu0rO$J($IY9Ffg%wIo28TUa7Zacvg z2ZV8|g)NFuuN}Xo&;DMgA5$G4CeU;La1?jtq&ZXpJU4I7mZQQ>COjG?$Q?whCag25 zBQ59i<&sGK0mEYhq<74_)TrZU*ceT)Z+Fzs$#=tSMB3;z<+gB4Yb{Gb7BufGQt$IS zWs0ft!PU??&d~5EDwtCoo4L0$i#p;vfmXs^q>GXd*%iN3*<*kdzy<{gr0m|CKA2wE zK`(r{7}8gJDu8&Rw&N&41In)@>{{u-fmZmYOStYc^ubi_X9_vz=Sw617j17IPG$GL z53}vqrffq9vA3BrZF8Y*9+N4QA)%5X5<0d1Cd!~p-_erO1;

C0Ypr{&bzj$co!5D_G$!HblVbwo6w4dF_kA`u;Aiz0 zI{3Sx=v}VNkYuKebp(G&#{HR)R1IPO4}!Ur{KQqN?)%Suc^A+&EFHw(XzF`ws5vp- zV4sjsS?bTg%=Z1c&Tcs2l%$$-Io!cO?IBYgJLhc&9Y?Xz*AA6Otdyyb^JTVbwthx2 zaJ1d6f3vGN#n{~pM!W6r1Kr22XA5v-I(YInM305?G;efs_gZW4VCLQkvff;Kc2_`^ zocJXE)sA}xo9n+Oj=5j-WZ+8q62l354l=4*#GiTnHxYMF(VuyO!royA42Tm+7HR}_ z2Z=_UC3O(~VrP3O--aai^qfSEldw0u9+$21lPNJDkM{TNMXO}4bIYHPHd^1E+gg+$ z3O{=4K7Y0Ax+z(<|3`c7aLas<;-!63(wLPM`_jO%?+*P-J=!>0XZ{m?otLsb78$6S zg1AJay&`KLoSH`;VDhh%b>R_O@JL$=m8ctS-)Fp`C~`u&z5LuWZ_4!VZSt#aH2T3Nz=Ia}ENElWiy;FFp7&u5Egs;LX@bd)0Tp=R&HqN#HwV@Mo2!R^?UIxyHYu|?D|3P-f;Dm z!Gh{ZKkdGA0&}118v~YhgM06}HZg^?c1uhRSl(0UPgHgZY3J!OXUi{Svr9!2Oye8< zow`h%zAImA>v(M96t@6LnSQ!VvXx6(=!rvUwZLlDuJ2uzQP1Vu)Kn&pa^m*pX=+T} zJJQ;+Wv>Pqb1t}bMkZt8Mp513b&($Fzd7ShMTIFcWLgu` zq<&XjwWdeE7*HfNE;IG%%9Q8)urg{H8;K}??(HnT#0z%5d@gO0k&%5}c}M-(d0~0y z3tv*-jGm|e+obfOkI)lQd{6c7uDnQQ{nP&LI+uq+t3S`mdi36t@-TRlF)49MII*VV z)vfwg)lZ`5ehSi%o+igExI7`q(oTr(%CKjs<8mLuvkda*E(b>&#qSRPPNii#MX}iV zZo6js^1G7$6IumZgQnZ7yCPPu5-vG(_?hRI;>lW(Y=>$uv?~~d#B0CJds0#lTiPsb zqgIXo!R7YTZ_yJ_w`*%TO~LPlvHah@??zQ!vjNBI^FDdbyM6ARK+R?iYXY%}>5t;b z-}x2^_i1_S?f`xqOSu}K!zq#94rS#ZhyC1|(!*qq{*WkHR|**xDu^|*u)ZL3Sn?mA zr4!x$IQnqil(@VY_+_Y5gpFIYZeee2W|#6O_vTI@V}EkCY)G^Jzy#OR!#QPt+#U%-dH}D>CysTjC<<*(yRR4)(&(Rahu9fR`6VOpI0nbSFN3r5P8L~*3Oif*UqO~ zqxA5I#nIbFGiUMojJh;p>_P>!J zV(|gx--VIS+OyD=W7#LRrgPYat5SRq|F!H&4DnvfijBx=SqxG+JFB;>k#+u0Z|0sh zhuxrP`P*#DC=%Ume*vZE?*i7LBE&?1@J{=6CR%44ZIGD$X2&)~&tG3lreKdh0yl#^9|4ylJu3@Pf=&;Lth@zpMP`NftY2N z$`9!YWFag5TNE}DH@`MqHeDg+#uPYg1~ab+SSfo-u?o}1R9tQfbI{>QDHnZvr!wLf z@E_+{Ze2Sv?VFmtp~BDo$jX(+Zk9xRRcIsfMwIpmxheo;5u3gdo~hM$AC)jpQCJte z!qumhdpgK-qhLLVp$6U!;__B%rO^^?N+*UC8h`8sykNa8pL@wo&clJ@uWgHQeC-S7 zsji#*b`1|71RXAsAhz><40KL#8?DNJ!+Cq+ft2J~jM$c80ggBCclN+LPM`P_k)F~| zlW8{>GzCkd^rks45q_#`kVjvIzmn~CuDS`FWZU6}pr0w%4l08*$5PEqif{5@V|wnQ zy87JVmv5&O4mhb>3LEDvV^B;XQ(8D(PT#$P2pM@!m){TAUKW}yPOF&cU0zM0#nAu2 z;%|buOc?z3h$}g9Of{+WZ_>BM5{qO{fk8lP3j3WFQwdvE<_W`3@k6{_$IN`nbM^h z&19|{^ldfIoM0-Q|J8lt5R ze!l!;ski!3%U}1jY$?y$l|wk@vE1mtA9P+szEj9no%(ZQ%JjmQV$12m)rQYKjuwi+N;@#$S8%3NhtugvE4%ZlkN8M@YEU)|Cwa}>KHo-tMz znKHzFANQa2FYBsZJhbBIp>DBZUlp>Uq2Ao^fZwjURHq0&%k6@!7CUdRa{7D^Q^dox z#;f+x8}xs`-E3ZWPNgMNXeA#%qN3?fYTcqtdHZpZEGc2U&++x4h3bA<0;>j#51e=E zZ@6`Cfp5s%S%_x~=dfuTf0pB^SLgB~eW=o^Z~x{)j+S!y2o9Q*!>lP1nrUZmovX~? zc*IAY>=&w& zb5Y5W8Eo$P)YP{njOFaHK_}dn_hd@uvrSIsyF^~qvR`9;`H#!p!7YXFD+v<%SUS|k zF5X8wj-+6iE4|w?T6fnRNf#}?CV{480V|JS7`OGL-RV=&!eXv%+jz)`=4jy7(eu?m zf4!Ey&|SCJ?D$ID-_APj*-YyJ$#qPZy4$nIzMtOf-x@p2w{Od8bk<7IU*O!nQm5p& z`=?k|+J(KZP2Y0L4r;#nqvrX&`4j8yP`9M#Zr`GXrl^eds>t|5Q@r@|{&TF-JI%Yb zN7Ub;xvHs_lRVhS=!LwCZ|+p+F?ogr>< zYg-x5Dko)0d72&_ErHozl+N%a<%)n+s>IIA(`MsqGkF3KaIQ#)m|GQ(p5yTp?-R;B;;#=(YD=^7bjK0x(ZHjY2U|t}|ZkL%~wHH|6N=e<=T35CQ{2kY$I}EjfX1k4R%De&5+aap6b`&ZJ~fI zek`4Ln41U6yxWzE1eBOtBH>Rn%-AzO?^EL%+ zI9151-{_pfMycoc{AUEHm}u(^ymU2u0+NYYn7TK*!pV&7;WX$ZngjX%+^C~K92N75 zVL7S(5mR5q6j!Y4%-`tDe_q)$o+>KB*A!H#ehwQxVRB<-Ixt21aRo~q&-tnKT9Ubb zx;5SQsEk9iy5_lmrVva}JLKiq_W1|B3b@i^)qdpLfxynV)*C6`)TZAV5!!CtbGV*M zxqO(=HcZ<`Uv~u$RdN@^C3?qkQQdJJ7|jtd{unZ^|M>hr4k>5h+{;(FPsNT_UXGJc zoj!8DXf-^ZnRA&Te+LQ##O=W-Q;6nAI4#AN#-bm{7& zI62jpnofn#&)NGM_v`vyxt4oNWyF7Ywt?XxeAcC%dxwev_5d7S(eB8!$1Cw`dc+C+8Zi&mRaWdu49;3c+I5L_^;cB)$j6Ne$Y4< z8T!q)Vqn4FX(Phho3YY`Q^Ng4B)*oc6;m=7xt>iTM5`IF+1wT3p-iE@MYm1XN;h`g z$?pH%1h)H-Fl&Rfln$#BVm3DG!AI?6{6Z>MiZ%?oJ zvSXa;_6^#i-`H5(o~UJJqvR2YIqZCp-O%tKTelT1{nt&b^6Gxm!;ZEYN4v8Kbm^Dj zxSYcz&eer?Y#LaEp{YsQGQm4%`Cguj%;;~OQM$q^{K@;uL)yyavy*?@ z=(l?G9zCZdq?|Hpv{!n-<1a{B+07nl`PndXtB5_kyOaUdRq-ZhfGlfzShdR|9Z)B- zmeNsvcqszDRWEeZ?)z0UdBoqS@Se`KB0bC-dvFs?fiktSML!7WY>RtAemNx|kUOGr zjlE5Bu;hp3Dg>dTvla^^PLJy7JM;7GgZblCc(87SoEGEw^O4Nd*aSQA(_vq-jvwxk zuJF#yiaynn>p$L2t4QgnNk#q)s1i5#G$j(9`RUH-ec8QJ$m)fde_&k8KP>@cVEu&d;HUz zoD6K;RC~(tQF~TRVHK)g1@tvKxiBGFCqW#=uG-IL&ddil7dp3$7QXZe9R6+J(s53{ z`cS-3pH@|zyuphYBQ1UIo)jVXhk^v=-gC4zp0j#K+$&@7tgV~xem-2yP7zp}_;#yG z-qNlI|7;|2Vgz?*e>bb_iKAOZqj!1VZr|4aw9n6*53ci#1pK@iym_ml=vwfib>EL$ z4~PGTpUDm^Q$X>v4h<6tuK<9LF^Z_Y3dp9C8O?-S0a>f+eTgDInUAtX-pMCRv+O7b z>OYfX_n*nJ6a5k(9Cp7VkQLRBOdwZabQasc_sH=g;|^@I5B`)X{g6D@-0D2!$jD&e zo9=+t5@vZ~%V$nMA^LfDRa4pbBiDcK{ZA~FEt*RgW5Gh15@S$sW&BF;m&9y3j;%F&>4p#RUpOOffqJm9oOdKR2 zv5W#WMe}gs`=7_t)y)U4+=CxS#0axypiJj6Bm;iz8G@-kgWMe~76qm2I5JIgV_d|{ z2MVcR@IK3DYuQlOG+M24V~A-+I}9_?tjnoqFgxlHKx7JRw8CIWCHOp_anldc3;GA( zMV6Y>3X7r5!eK&OEy`nbXrUF+E1ESJO8g85oNg_cFClmnYPj=a=p&nmP;B%u_;<>g z1hgRm{-Hlyu_%llxD7ww^QkzjO94N}h^q6$&#|0voupox7(v{CLyavdqk;{kf!o6z zD^y|aw@MW>;)NS}K9pV(M9(FLR;sXGVt08=d-Cwpxe82=6gn1WzQDTB=ROQHL!&im zKQYK7(STHPErKWn!NwB7aV{`@NRF_3M5%@B1kn5u(2Iz4yeah33yEaoF{FDC@MV^b z)VIYaqhJx4{smIwJ~(U|piGK}Wg;k9qHk zg-*L-cKBIe%QDOR8sy5syH25&a7)y{*<*I#k1naS5Bl>q!t4g0Tr^{rok>T zH2D|exWiCN(!wdPhn^Cj%m7_W&41Ng>%jB`vN*mLLxn`z2j!1M(El_s1iPq!tHjF7 zsF}qcs#w?`&>c3>AY$MRB(wjsZ?5zmojqkb6@xx%W6p|#OBT#9MDZ}b?f04Xv;iXR zIyU?8ARKxiu2n;QDN|1>y+13U&Prn{#^tl_05pU{7exL0f_xswl+30X1gsm#xedRC zL~S?C+Xu@dNsc0B4T5r4;LZr*eaw#XuC7lmS*x9$b9J*}DYq6Fzx|MoXkl(?DRP44 zk;^Gvq<>f(Oh08`ZRa(bA`Ritj*+2!Hu?8rtV8y*_#Ly7qwvy=Mwxf@!or@dYh9JC=!@MhTd`l)H_5~_b%<-~~gX>pe5Jn73L zqdi|++4*KbMzWp4&29G+-`{I8qTZL73M^?`!vm4Xymtg-JshmczS`Kse%6`!7f^=Q zo_;W{1??0iPZ}-CK3yOsx#2VPg&Tc7J2tJ>#0BqtF5U*z{amH}i;vH~`vQyN#OV$w z5ekIHt2!5I{~NIvp|asZN`oLgreP3zvAGa=$Q>XudVgU^%XhkaKBQq1xCwRN2Mi|= zlnaWN`0|p>mUe-7W&;u_Yf!kvr-pw5p%ZxJ&z{MPU4al74>f2AR!{itLgmHPbaUfs zXlG0T(lSqg7_bMh3j81%()bIZh=_2HZg^Hv7(Q-?T=aI(qgOcrvL%!Lz6pHQLSomD zg@8l7+%zgd2cYG-s`S)TVgR0d)vK~)blC+;;CSc?k4Ck6)kN?$uo%tv#DJ zq8LFdNnz5*{O7LnQjQZOELVYQ;P~Msc+@*%rCIcR9OdV>nXeE5r%$HU>xU4T2Z#-n zG%=li3mLOqzW79YxxnXQ{nONAs&}} z?fIl%SxV*_*7;&w{$POPU;h@wwh%PTRivlaI z4qWo#kKx4&1#yx!=BHJM0%V#!5_wev>jS0`I&2`B%K8J2BTnjNeUsYs&N#1r+g)38 zHWVe!qFRY`FqwYn{igm}Nr#M$3L%%PW zK?bQj%#LFvNBwjF;zZ@pDdjCKo%mfK(p&~L%Ev2AA0Yt$WdWGMTIDi#tQ~28!8uV5 zUjPMAWVQ*}{p1#$dsuchg|?1Z&+Aw>XlB^3vK#O55I9fAzU}x&4gPbeM`A^XEWseq zkh}2UZX*%m&r|g>?>lAROMmpI9JLHJ%JCV{mlq)dupM$zGPsU8`T^(3kY?GOWLg!v zPQ~{)diMtl{H)(kGf-CCs?%GS#>$d@hFYT-WD{=2q&OH-g%MQ_Ff!3nD^19%$F~8| z(dPP*2bJ}6u~%0f4h94|hk2xCSe`3o&!o5uZ?3VM3dK%-(aq(iG=@LtPDMJ9d#F2# z;^Ix=^&Fx#q|9Ghc~YCmO;u1c8^wLxlls9YuJg|P7tSLev4{v*qCl+^)Yf`7Va9=R z4kK#yQMENoTOgr`6Ia@7yg;fo$0|%B0yq|`QkktYm38B;k}hcy^kFP`d{?)TLm}hcPxsD?`w3oCX?#I9Mh=5XDGva!ZGud@F>?zBXBgw zyZF|JlW0g18D>joIWmct97UHoHYpnLE*aL4-G7fpJsXHmPw3w|8-}kP43VYjlU0A) zX@7I=6N)g_PRUevF}erGuIbGGc$(NFgq|b)I}+YNMx)orNao;hE0ss8%YH*q;AxOJ zz_0iZG+2f?%FO`bbw{Gw`f)El&jHS15FMMcJbN+YoYP>&hcL#cz(7bA)&SyDaLwVG{fL``hgeZD~StI!<;ks%9?o2dSp%_Od`MV>! zE^jaeKEyFvc@{Q!LJIj}@-4Fvc0IpJp6rE$Ob3HRzkwTejCa*^(iL65LOr4a*#dv= zFtQ$P1CclJmSn?1s(b-9TL#KIKQEf|2wzCsf+_Znh*l0L9}HkWXV#fP#7OUT20PD1 zJ%Lci7Rui^lh6f4%x6Lf+^>k4+?2)kt6KRRKN_GH8hKb$hs{ieW0X z3Njw`{ByHoMCibkS9)hZ#h&vZm{xpjJpHdlGDiZ7LFM~`p+O4m`Ss72kak0{qM?W# zaVHx7lm0~PALHq9H2mpMTjk(;;CpJEoBzjf8i+ye`wrX~l#p92Vz`jR!RRv8UOy`T zl&n>hJgoH3AAR`qR9MB{H~p{ng7{~S_I3W1QYo=uRXc?JF#(a&3VhE$c4>(k%)o($ zWNqQcl^rHRqUoE*2it}M+_5Gg(_Uf_Mn^IMLk@Gm(pMZk5FYyAwqBsgfKeo|hIxI9 zC;2k}n44xTD@q$bvxGMrq-fNGZjl9MmXyJo5BOm~#ABg-UjA9bVrIlt7j}(SCNS4R-hX%jZHeBm8%C@>VM4w(GRJ&iUH{H2z8Oi> zK#bC8etAZ`i4gqpF}gYG5Xw{oj6#F)8#-J0SGc(DLHSzz;j@3n%+1i#}8&_R2lRMzbLUOUa6kV1{Ms4&$fM5`GF3Eccv#DVF^VieRI0%(_Q>(|;`Y3($V(=V6J5v4>@pmA=yr6);pu zhmuZu>il53Q@Gs~CNLj;R%B*<-njsLoUNy1^Mf)oor)xBvq_=D6?Zgk|Jf^3 z%>i3;1k;{Od-Qjrapl)}wd>`R^=>Ac9O1A(IKopaN{^!gaaMK*Sn`a}PyL`m=<_Z* z#HTAg`l9^{DaU^DRq*1urfVa43sThZcSn-}`7^piriI64L=@!14PVV{{aj~_IF3hs zi>$0wlj&DNAPu7XG!!DIJOAXUcof{zl{ZX0gZKimGj7Xs%zEuqyNF>JXrPmzo0NI(#w4&;{n}|o2KOdz{n_H1hdL7zBg*aY33t2A;~kAyZbEtm zhO*Gsc!o|S5f3Yg4qwPHm=|JmEy7glUz&l6|9F#h-$??NZZx^my6p`+j^%PIC+LAGfdc)P=*WnhjgxV*<9_QB*dQa14Ba{g_1DAHB8QqeRHX8L7>XnQd=CdeC6 zKSJz>Um)ejqdex-!MH}E;q}@=>YM-9%5KovKH@jpcBwDdRUJA)gN9J_{V+ydr~Ib?39*KKFekG z|BRj|a5;Y)JOCvDuw#9mKYQPD>sH5lMRhmVFsLZ$O7h-ZmAMHHkq1y%|1o0M=&Rb4 zG>HgnH^+Gnj_NwZkPZ+I`Msgr1n$I<0EdqVX#%m#!hMdxhouhG`6!{i-uB6j`(b!K_3*Td0G!4U4GZ?IYmU z)_DK})e=G=HjXhut50fs33{g?6UJ#j0~SrfGn8OZKR^h9j9DS4HYc8WCU*?@w?Zw6fKbG#TyH0Udn`PU~V=A zvg^CR`_g|OzG%V;8?&ABbL>5V4gqt^>wx!_86l9WZNS#~1zKA@@0uM6KR3QZdDR zqHd_)V&-K@B6OZIlM@R02~fy2Gj?9CI?&NPiaLwfvz9YJ(qFA}u9={u;c+Iz7s74MgxGnGS9!DU=PRoXR0=s%>rz#QRj3-(@S_m_k?lU4EF9LlZGd-+dN5 z6xS4575d$UP$S471-Iv%bJ(tiF_M`#;SkPf8&4B~mU<&z_GB)aC6|dy#5TG=?;f`^ z%CiUbgAV_^c$AQWVSIF%g`f3vzpv}%EH*;_GdC+>CjWH(%-r$1u6w{xYOU@VxG!HE znMRL;{kcXHFK-`*_$f+X^lcB=;5t>Yx24^NkF9_iil7S0J?N=0#D_|F!;YSy^_Ri% zxhFk&I$+=jZ0^Q2ra4*Tllwj#G_xMpiQbz%>eCHmwBSXc&3oC%#Q$8a?c=KE?li#? zUt7{#Z#Az59h=6!MCC=!l$gPgH}V^<0$_u9FuMGH&#Xt6Nj{@-UxEfD5OXC|?>%`X zG#}X;9w3HwqZseP8DQ)=li^?!KJ8BXKKi-~a6u4F*Js~Tj`pDo=exN*eMF8ak!C$I z9CkK%5F(+1&$7vh`FL4dX;B?Dh&CLn>FI>xE%$lcZ@9PYZ(p?s_G1l#N^DQwIR6W_ z$o=L{m-m?M2(}IB4%rQGhU;5D2tI`~Rd8ATx+wBlyAbcA{gzp;gDL><1qCo8ZT!YA zx;cudJga7GtwT@XZC^1_#-ZiCnPh0~n>Cdmch{0`hk8)+ih^KZ8Fy0o?YDPMCpLC# z>FhxQ*t3-hx8M6f481{mgiR={lY25*_(jrVYg1b|{W8DD8=>5-Au8qEz_dZS)NiyMIYvTX^8fzJH&c8*KBm?_CJk+cjHQ_BcM2mwd)DG)jt_vV0N&sbmoQ zMCY|ZZAl=m`0THZ+@|5H%|Apw$7#Y>O)1CcOa^mFH&rHuy0Q6sT6%YmML~1T_>=DO z@>K@;EUYwZ=6mtz1E?W?TpE6}FwOX}{%#H}n=&i;1u+rBqL#uGHh)K5KJjv@U)Fru z_r4bWKMT+0uMBh^Vi4HKICl|S0xi9LD;JC1L15OB|Ugi^F$L@k&(bF>1*`lsRE;G9{Dt;^FZ;h|-n%kxOObFgC z=u|u_k&+Mq$l8cdMV5j`*49hnxnw>mOz9J`zo$|qV!Z+q=pLri{7f}Ay%k<~rm#gv zp;_dkVT-@(J=Vig`mWm3@bw6^4-d4z-f+!D!@EK(nuZFx408Gpa9o8QQG&_pEG)!ly+DV5clbLj>5p|&fo^x# zQjC(56QTBO5A&ffu2sGo)CTd2nG(DEaGnmaErc;Fh%deK-FL;kL5+8)na z8iG2D*dj%Le%>&Yt6DS2F;~c88@B+}yk^Odrv*&ebpil0xqe7h?5=UOj0yo83)w#t zXBdk1#GF!+V^_2NE%fdNLv3Iri6n*N!CPO3 zq9Bmu7{r`$R=eKuqPY|`X9q;i=!w-w=6BIkGOx#8Hw{kb;wE?IlxEMKq5OgFe2Ajl zs5^g8tFD!y|Xg*BQp3e17sKBR*e$65q`QfWYOC=krq|i#pA7t&ZM(aP&mggmNibPXU zXGf(JiL&#?Hj>d=luQQ_X7p89{1p%^{rMRUv1_i5CYN$3-n@;f?s$K*s}-2ry3yJ| zJM+zung(NU4d%fYP3mT+v|&u9twdc}9K}*%D7QN4!;1N9)enk}e)_Q#+&ztFAkWEH zJ)ENHr0S!eP>>^QsRa?tZ)oZO;5qMTM&Q0xY;pYhLv?l>c2J>b@UGQj&|GIsqTFl> zF>00HEn{cO!fDOY(Vj~n0K6B$)5Te)?8vcknZ|+LP#BRs%u)=Q%84mzZ3;DwjKIEX z9j1_>U0pLDeeK3J%+DS^oqVv4sbn?g73@AwGUghp;q_fj>*T`$IOa|!>E3Ua(fS%=Cq^j zX4vSj3wWh-(k-sT$7~dgV;A6_^8!faKLoQ;4;PLq*LF;Xjp=q06k`Fa=`(H#7 zwiZ5x)Ft8U{DoRmTp#nZ%KS|={QVgDuKFI*3$0>?(cs=EUdffnf)SbbmOr9D4RDWJ5_iY-fD`hZW8_uBT; zn+=wakm4Xok>358&iKO2mnDe@inpALX1hWi)^Cu}6EavTPi_*cfIm0kP3Cnb5e*#C z|J=Qkoe`~Ob)q|x_T{|HP?1`}W1_10Kcyg%E$4+wa<*Qu$VgIk$k?YmbmbLeoUiu9reA?@EHo zt<=agV5HK{?op>;o71Ws}^bb~VE;AKW0kS+Cq_s+ui~DoOZdfIDxG}%Nhaq$>$ztpHjuwR$9nf!*$;7aPzvp*%=f5RD%7b5Rt6 z_x27r8MxDPsqb@;n6Pv4sN^4GJ0w(^id~=2x7Bmz*EdwZ&WtL; z&jbiHH!a*3WjtvoflfsNyQxo08!#j~{^YXyyQy=QCX`$T7%dFEa*UW}0ftnMOJ2)J zzQHe zeGH`b^j>Y=&{-KQO$%URzP~C;t7CTz%@PR@E*LEB!I0LO{UOT66SID9TeGu|xq7{5 z1Ova0Acaa1@>N(blxPLhk^B+7ils8s;2eN(oA5tE8i%EyRUi=6*dmq)SIbUVuisdS|G2w28=&i5GF z3_<(E2cPKEks6WkG#%hN{{vFt@W>IXm68>OEn$HXo6Rs&|FC7=*XOEw@{Po1@x7Rx6kYveXcS;^ z-1;-^C7LiHjtEO&)k2sQNnD3+qLWGk*-%#yEA-U(ux%uf7hc*^Ihd?S3TOw5ElRz^p?S!T*VZbuuAS^w|hT(~69S$IMq0ZHBzH$AjfPbfk~)9G8m#bk4wB z(w!XI!edwp^iFAFED*ATIA!I9NMbrnaCEAIU>r7@4<5PkAybeYMS{bUS*h@g z38oMMkSt1V(hi|wAp;(v1o%TUVXrJy=}4n^T;G4jRfsf`eq1mfTXGPZ8EUh8NJlC} zpgguyuDlwpH1J2Y)(_>d67G>B*h&BYpozEyXGefqEGz9*C1_#dQ3%n70z0I=iM4i4 z(RK$gt0(*3C5!M@5dVGxN05dVEKCYyjZtX%rl_F5j8uAZa=&O0KK=YHX5aY!@iWf+ znO}yUJVwrwL&TBfeqCa>@ZRxFf&A|)G^&$F(d+<)X_RST7Km_R?2X3Wsk#vPrq{^V zCg82SBLg<2+IsR8E9K#A z|1ag%rA5<@DFeR9QKBUr+o5k&!~j>EkJ|o!v`*P&L_ z6*sMGyBXa9u!#O>anYT>IF)!HHH0eC{5~xuctHFm7*ujhOIEq_`9Fk?gd4ww-!x9| z(=hmSIHf}d4RZHW@@cNU z9<{ibY2j}2`MINsTV6V{oLRK3Ze|rF$GBG>=nPxzI%~lnZQlK8{HXB9jiPCmCxJlACm44`8IS%1$?b}BdOpy)qHOLD@4uf zzC12NCRW@(NBG&0PSs=+8qM@s>uB$*BV;XRBxBDk<_;IfP zZ0zp4ZKF)D<6OWq=#zvcgB2BFsjH0@jWWXNVJCP^g;_aly-2cekP zM&K6!;%-2vc`j+!SpNWG_DYpO2pY;w6ZUl6wM_CZP8%9 zNFY&m?M#D--MdqJNE!+qK~;*Z2~?}V`#KMs=cn3-TOp$VHJ;$iZ`DlVcqoH_*}<** z0D?aEc=2PnS62GsYc>Ql&Pm(|Szk;tjWRLfQ*K?#fN z^HA1CWN8PeKWB_T4qH1V)C^aWwa!+37KQ^SU*5zHZGdMDq1Be}=|o6MNKy$REH+%y z-y(yXxY|^{5q|Ab+$=!b-gFmi!q#p>C71HH|-K{Uazs+#BuGysKz)#GMwa zI2*tMg))x*ROrpVsYW2-MHWNL!=XmO7=X*}2*ecE;II;2jgX*p*#JlS0+itU50li! zyN&|Uq0{IiK()`vo%$m8)2-AneMnpeDvRVaT?Rq)?qQm#6u^#=$`>gjbH`lV=do68 zciv@K*?)FasCgw)C%|zvhh41r-;_;eut3gHozcnTA(&nzA|sHV4?KS{ml)Ce*_?y>># zwEHxa8X64Lo_n0<8I=FwAyu4234D&rBg1^zi0m#nsbCn73%^fG-7L-xd_1v;GY(Tm z0Au+H9{0*nO7%>CLriC9c$gJR1#;e-4>x$-gj|Ey=f?hLPb_43U42|s(K40Mq42GDD?TBBexAl zn#k$8Pj||KpR%9E*c|j%4yXHd&LLu%sc;b_`Xo{=o_8gEMfPR8WPl*P4#EpA8ktq_hl^ZYy`2(zXbee;C?#L$aNyohZ)JCoID zue#|4uwUcgyJVVFrgC8De3e7B?Dn`9dTocOamfw^9E<&NZtx7xF;{ridWq4OsEOWM zX@#Z>_i*$V<;Rhv(`pFUFU=S)U7@GJ2fxUN8Ww?QB)nf8xyIi0xwS71q2EFX#~ z$FXARD|)Ub(_(m(kA%5g!?V84P)-v{`@x~bnu$Sa|G=aFfbpVHmT!y(S)wIxL5JV; z&y4tIQ>ZbhzuRQ07TFk`#lP^MZR;X`i2@B_y+y6w17wb(&eBAE;T|X!qAV3Ll6&h< zz*03%{fsi?olwwSFii^ocxZ4k&so{#=pZdgj%cEH<9_BySW6l=szy`CIQ3M(r}wqr zvSlwD_j)fnHwaP==idB!1z{8loGRQAzAyyL!R2U{%%ga9!BT!3ks*`AaXJM@2@xz- z;fhhN51%|=-AGAk#LOIs=lX@F+W=z7O@E zP)2t7mldns9>K99DnMJ&IFaDwqO>r(%%7%roq&M*_G~W?3VkJCjb?t1#6K@IqA6O^ zq^^psxvJr{qgYy9+MymQnW>7$SujSX^FWEJ3eIzNA8q^YFcjWy6bQ*CN1R9dw*15F zjj+_ZkHK2^OdR_)G&aVyl@eF_;nCP&>4(+u7+H_?2iI-?{yL`6Qa@CZGCk61;$O{{ zOEL9V^>oxy8<|~t+AsQs#=_h3C7Qt8(H}lHXFtDsGvI~ord}PbSbu!otQPe!0OH_F z8Kpb~buQP~h}qpdQ^B}VI>HqK6PbR2ze!zKy4POJD|#LQFu&Yzo}!_IlrSDCRAFR2 zwX-Kk@W(~w(}_*dsn8XEf0QL2*;^7yH#UmI7{7R##op9@0Rdh$E2oI#M;aC`9c$*x z6npi?tRi#e!D=>^R~~%L-{A7i@^x7JAhrrc(T3|?5Ubqq%i7)A+M)GZVyg8RULD(B zSm^#*^Ng#vZkzE>-NQTgZ^h27-?h2YG4R$ePRRC{a4B-h?8ykq zQN}rT44i3?``v#>z27{0Dnv-syhP&}_<_7_s>j`D%mqkeg}^mwY7-LK_wlZ9uC#Z|KR=LvzJ;(Hf=?C%<1@AJ zop@i7b)~kQV<5Wy#PO_vbAo9qRUAoiA8)?-$)FHC|8g#6s7)*IVMl zZl-!hNw#d7yqNX*9id{H-*vcnMrC14z1B9=vQ$b@+g)J2{8V=G7urNa)Fl_+n|=%2 z0LAsUz`7mpGz$8&yYyAzb@F_M%T#lN>}z+$s-on{Q9n|r$y|0+n}`}XMC|k*?^fn0 z{|uNA$DY_znu`{&{YVx3L20rdG;p#C&C^*f{|a}u2U)Gktq6Z_^DjXli*Vi{NV?F| znCxujAfY>=zWhArnr{+TPaIidL++i68GUY~dkrXM)zehe;vZI^e)keyZ!?+E%A`9= z2{YCCq8{Cyspy#t&TB7@JEs7{&&>KtleXTu2|ZvM+a{!g(ii~#@$qk4=Dk56?cV*F zUFDenxn^p1f9^}~vOYEI?JfwDSzrA8GITUK<4Af46|>HL3<-^R`V>Ol9&~V|k_B3A zJT7Ic1k}8dAY$>|ikLo4{M#^s!u+cwMPnOj5tPu7fi4BV#=21M!Mdn07xmAIn6rbo z|C?mv_;!gHp+=jiCveJZ5{y+_P2N#Wra4iEQtW{%?ihzo8wzH#qen=fV0iqrF>vq= zTa^2bsDDwE&If8|v<%FJjHNw`_ENO7kP1^|Ra&)Jgh}(syCL)CC-wPMVaXO0v+A=^ zqMhCO^CGa?$sw9(E4yMOCCuWnU7BP>X#6RB#N2+vcW%#p{k>%b6IH#hSAfCcoLDJ^ zB1FT!bMx1KIi3N?OrhCYmJc3UqQkR&>nob}UpYOXi$IObpS4)DDFB4Rh~l-F3#YMd zXGTf^`r^-U9Vw*8Wm3hCHf8q{YYL_M*_jBaVLjk%LJT0{)_q5yZxU^?HAAh z Date: Fri, 5 Apr 2024 19:04:12 +0300 Subject: [PATCH 30/74] fix(install.sh): remove extracted files after installation (#12879) --- install.sh | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/install.sh b/install.sh index cdb0bed420..50c3c85a8f 100755 --- a/install.sh +++ b/install.sh @@ -639,19 +639,21 @@ install_standalone() { # fails we can ignore the error as the -w check will then swap us to sudo. sh_c mkdir -p "$STANDALONE_INSTALL_PREFIX" 2>/dev/null || true + sh_c mkdir -p "$CACHE_DIR/tmp" + if [ "$STANDALONE_ARCHIVE_FORMAT" = tar.gz ]; then + sh_c tar -C "$CACHE_DIR/tmp" -xzf "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.tar.gz" + else + sh_c unzip -d "$CACHE_DIR/tmp" -o "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.zip" + fi + + STANDALONE_BINARY_LOCATION="$STANDALONE_INSTALL_PREFIX/bin/$STANDALONE_BINARY_NAME" + sh_c="sh_c" if [ ! -w "$STANDALONE_INSTALL_PREFIX" ]; then sh_c="sudo_sh_c" fi "$sh_c" mkdir -p "$STANDALONE_INSTALL_PREFIX/bin" - if [ "$STANDALONE_ARCHIVE_FORMAT" = tar.gz ]; then - "$sh_c" tar -C "$CACHE_DIR" -xzf "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.tar.gz" - else - "$sh_c" unzip -d "$CACHE_DIR" -o "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.zip" - fi - - STANDALONE_BINARY_LOCATION="$STANDALONE_INSTALL_PREFIX/bin/$STANDALONE_BINARY_NAME" # Remove the file if it already exists to # avoid https://github.com/coder/coder/issues/2086 @@ -660,7 +662,10 @@ install_standalone() { fi # Copy the binary to the correct location. - "$sh_c" cp "$CACHE_DIR/coder" "$STANDALONE_BINARY_LOCATION" + "$sh_c" cp "$CACHE_DIR/tmp/coder" "$STANDALONE_BINARY_LOCATION" + + # Clean up the extracted files (note, not using sudo: $sh_c -> sh_c). + sh_c rm -rv "$CACHE_DIR/tmp" echo_standalone_postinstall } From a2b28f80d7a14e1a42af2f6527aedfce757727b1 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 5 Apr 2024 12:29:08 -0500 Subject: [PATCH 31/74] fix(coderd): prevent agent reverse proxy from using `HTTP[S]_PROXY` envs (#12875) Updates https://github.com/coder/coder/issues/12790 --- coderd/tailnet.go | 7 +++++-- coderd/tailnet_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index f684b05cd2..0bcf21bb9d 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -32,11 +32,14 @@ import ( var tailnetTransport *http.Transport func init() { - var valid bool - tailnetTransport, valid = http.DefaultTransport.(*http.Transport) + tp, valid := http.DefaultTransport.(*http.Transport) if !valid { panic("dev error: default transport is the wrong type") } + tailnetTransport = tp.Clone() + // We do not want to respect the proxy settings from the environment, since + // all network traffic happens over wireguard. + tailnetTransport.Proxy = nil } var _ workspaceapps.AgentProvider = (*ServerTailnet)(nil) diff --git a/coderd/tailnet_test.go b/coderd/tailnet_test.go index b7b7ad1df9..0a78a8349c 100644 --- a/coderd/tailnet_test.go +++ b/coderd/tailnet_test.go @@ -68,6 +68,35 @@ func TestServerTailnet_AgentConn_NoSTUN(t *testing.T) { assert.True(t, conn.AwaitReachable(ctx)) } +//nolint:paralleltest // t.Setenv +func TestServerTailnet_ReverseProxy_ProxyEnv(t *testing.T) { + t.Setenv("HTTP_PROXY", "http://169.254.169.254:12345") + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + agents, serverTailnet := setupServerTailnetAgent(t, 1) + a := agents[0] + + u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) + require.NoError(t, err) + + rp := serverTailnet.ReverseProxy(u, u, a.id) + + rw := httptest.NewRecorder() + req := httptest.NewRequest( + http.MethodGet, + u.String(), + nil, + ).WithContext(ctx) + + rp.ServeHTTP(rw, req) + res := rw.Result() + defer res.Body.Close() + + assert.Equal(t, http.StatusOK, res.StatusCode) +} + func TestServerTailnet_ReverseProxy(t *testing.T) { t.Parallel() From c4b26f335a344584da38f4bac28592d247baf95e Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Fri, 5 Apr 2024 11:45:32 -0600 Subject: [PATCH 32/74] test: verify that enterprise tests are being run (#12871) --- Makefile | 6 +-- site/e2e/constants.ts | 3 ++ site/e2e/global.setup.ts | 9 +++- site/e2e/helpers.ts | 5 +++ site/e2e/reporter.ts | 71 +++++++++++++++++++------------ site/e2e/tests/enterprise.spec.ts | 10 +++++ 6 files changed, 72 insertions(+), 32 deletions(-) create mode 100644 site/e2e/tests/enterprise.spec.ts diff --git a/Makefile b/Makefile index 84a97323cd..e588279384 100644 --- a/Makefile +++ b/Makefile @@ -382,9 +382,9 @@ install: build/coder_$(VERSION)_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT) cp "$<" "$$output_file" .PHONY: install -BOLD := $(shell tput bold) -GREEN := $(shell tput setaf 2) -RESET := $(shell tput sgr0) +BOLD := $(shell tput bold 2>/dev/null) +GREEN := $(shell tput setaf 2 2>/dev/null) +RESET := $(shell tput sgr0 2>/dev/null) fmt: fmt/eslint fmt/prettier fmt/terraform fmt/shfmt fmt/go .PHONY: fmt diff --git a/site/e2e/constants.ts b/site/e2e/constants.ts index 8b4fbe50d5..351af63be2 100644 --- a/site/e2e/constants.ts +++ b/site/e2e/constants.ts @@ -33,4 +33,7 @@ export const gitAuth = { installationsPath: "/installations", }; +export const requireEnterpriseTests = Boolean( + process.env.CODER_E2E_REQUIRE_ENTERPRISE_TESTS, +); export const enterpriseLicense = process.env.CODER_E2E_ENTERPRISE_LICENSE ?? ""; diff --git a/site/e2e/global.setup.ts b/site/e2e/global.setup.ts index 9a9d4d026f..b4f92c423b 100644 --- a/site/e2e/global.setup.ts +++ b/site/e2e/global.setup.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { expect, test } from "@playwright/test"; import { Language } from "pages/CreateUserPage/CreateUserForm"; import * as constants from "./constants"; import { storageState } from "./playwright.config"; @@ -18,7 +18,12 @@ test("setup deployment", async ({ page }) => { await page.getByTestId("button-select-template").isVisible(); // Setup license - if (constants.enterpriseLicense) { + if (constants.requireEnterpriseTests || constants.enterpriseLicense) { + // Make sure that we have something that looks like a real license + expect(constants.enterpriseLicense).toBeTruthy(); + expect(constants.enterpriseLicense.length).toBeGreaterThan(92); // the signature alone should be this long + expect(constants.enterpriseLicense.split(".").length).toBe(3); // otherwise it's invalid + await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" }); await page.getByText("Add a license").click(); diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index a1fb47816f..79e5a8ac5f 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -18,6 +18,7 @@ import { coderPort, enterpriseLicense, prometheusPort, + requireEnterpriseTests, } from "./constants"; import { Agent, @@ -33,6 +34,10 @@ import { // requiresEnterpriseLicense will skip the test if we're not running with an enterprise license export function requiresEnterpriseLicense() { + if (requireEnterpriseTests) { + return; + } + test.skip(!enterpriseLicense); } diff --git a/site/e2e/reporter.ts b/site/e2e/reporter.ts index 9b4eaabd5b..fe214d8aed 100644 --- a/site/e2e/reporter.ts +++ b/site/e2e/reporter.ts @@ -11,12 +11,13 @@ import type { import axios from "axios"; import * as fs from "fs/promises"; import type { Writable } from "stream"; -import { coderdPProfPort } from "./constants"; +import { coderdPProfPort, enterpriseLicense } from "./constants"; class CoderReporter implements Reporter { config: FullConfig | null = null; testOutput = new Map>(); passedCount = 0; + skippedCount = 0; failedTests: TestCase[] = []; timedOutTests: TestCase[] = []; @@ -31,45 +32,56 @@ class CoderReporter implements Reporter { } onStdOut(chunk: string, test?: TestCase, _?: TestResult): void { - for (const line of filteredServerLogLines(chunk)) { - console.log(`[stdout] ${line}`); - } + // If there's no associated test, just print it now if (!test) { + for (const line of logLines(chunk)) { + console.log(`[stdout] ${line}`); + } return; } + // Will be printed if the test fails this.testOutput.get(test.id)!.push([process.stdout, chunk]); } onStdErr(chunk: string, test?: TestCase, _?: TestResult): void { - for (const line of filteredServerLogLines(chunk)) { - console.error(`[stderr] ${line}`); - } + // If there's no associated test, just print it now if (!test) { + for (const line of logLines(chunk)) { + console.error(`[stderr] ${line}`); + } return; } + // Will be printed if the test fails this.testOutput.get(test.id)!.push([process.stderr, chunk]); } async onTestEnd(test: TestCase, result: TestResult) { - console.log(`==> Finished test ${test.title}: ${result.status}`); + try { + if (test.expectedStatus === "skipped") { + console.log(`==> Skipping test ${test.title}`); + this.skippedCount++; + return; + } - if (result.status === "passed") { - this.passedCount++; - } + console.log(`==> Finished test ${test.title}: ${result.status}`); - if (result.status === "failed") { - this.failedTests.push(test); - } + if (result.status === "passed") { + this.passedCount++; + return; + } - if (result.status === "timedOut") { - this.timedOutTests.push(test); - } + if (result.status === "failed") { + this.failedTests.push(test); + } - const fsTestTitle = test.title.replaceAll(" ", "-"); - const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`; - await exportDebugPprof(outputFile); + if (result.status === "timedOut") { + this.timedOutTests.push(test); + } + + const fsTestTitle = test.title.replaceAll(" ", "-"); + const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`; + await exportDebugPprof(outputFile); - if (result.status !== "passed") { console.log(`Data from pprof has been saved to ${outputFile}`); console.log("==> Output"); const output = this.testOutput.get(test.id)!; @@ -90,13 +102,22 @@ class CoderReporter implements Reporter { console.log(attachment); } } + } finally { + this.testOutput.delete(test.id); } - this.testOutput.delete(test.id); } onEnd(result: FullResult) { console.log(`==> Tests ${result.status}`); + if (!enterpriseLicense) { + console.log( + "==> Enterprise tests were skipped, because no license was provided", + ); + } console.log(`${this.passedCount} passed`); + if (this.skippedCount > 0) { + console.log(`${this.skippedCount} skipped`); + } if (this.failedTests.length > 0) { console.log(`${this.failedTests.length} failed`); for (const test of this.failedTests) { @@ -112,11 +133,7 @@ class CoderReporter implements Reporter { } } -const shouldPrintLine = (line: string) => - [" error=EOF", "coderd: audit_log"].every((noise) => !line.includes(noise)); - -const filteredServerLogLines = (chunk: string): string[] => - chunk.trimEnd().split("\n").filter(shouldPrintLine); +const logLines = (chunk: string): string[] => chunk.trimEnd().split("\n"); const exportDebugPprof = async (outputFile: string) => { const response = await axios.get( diff --git a/site/e2e/tests/enterprise.spec.ts b/site/e2e/tests/enterprise.spec.ts new file mode 100644 index 0000000000..4758d43ae1 --- /dev/null +++ b/site/e2e/tests/enterprise.spec.ts @@ -0,0 +1,10 @@ +import { expect, test } from "@playwright/test"; +import { requiresEnterpriseLicense } from "../helpers"; + +test("license was added successfully", async ({ page }) => { + requiresEnterpriseLicense(); + + await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" }); + const license = page.locator(".MuiPaper-root", { hasText: "#1" }); + await expect(license).toBeVisible(); +}); From f96ce80ab9b1de2ba04b553924956dbdc1e06c49 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Fri, 5 Apr 2024 15:06:17 -0400 Subject: [PATCH 33/74] feat: add owner groups to workspace data (#12841) --- coderd/database/dbauthz/dbauthz.go | 5 + coderd/database/dbauthz/dbauthz_test.go | 8 + coderd/database/dbmem/dbmem.go | 24 ++ coderd/database/dbmetrics/dbmetrics.go | 7 + coderd/database/dbmock/dbmock.go | 15 + coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 61 ++++ coderd/database/queries/groups.sql | 25 ++ .../provisionerdserver/provisionerdserver.go | 12 + .../provisionerdserver_test.go | 10 + go.mod | 2 +- go.sum | 4 +- provisioner/terraform/provision.go | 8 + provisionersdk/proto/provisioner.pb.go | 266 +++++++++--------- provisionersdk/proto/provisioner.proto | 1 + site/e2e/provisionerGenerated.ts | 4 + 16 files changed, 323 insertions(+), 130 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 97a695cb37..b0cf2f8c35 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -174,6 +174,7 @@ var ( // When org scoped provisioner credentials are implemented, // this can be reduced to read a specific org. rbac.ResourceOrganization.Type: {rbac.ActionRead}, + rbac.ResourceGroup.Type: {rbac.ActionRead}, }), Org: map[string][]rbac.Permission{}, User: []rbac.Permission{}, @@ -1141,6 +1142,10 @@ func (q *querier) GetGroupMembers(ctx context.Context, id uuid.UUID) ([]database return q.db.GetGroupMembers(ctx, id) } +func (q *querier) GetGroupsByOrganizationAndUserID(ctx context.Context, arg database.GetGroupsByOrganizationAndUserIDParams) ([]database.Group, error) { + return fetchWithPostFilter(q.auth, q.db.GetGroupsByOrganizationAndUserID)(ctx, arg) +} + func (q *querier) GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]database.Group, error) { return fetchWithPostFilter(q.auth, q.db.GetGroupsByOrganizationID)(ctx, organizationID) } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 71345ccf09..3619837732 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -314,6 +314,14 @@ func (s *MethodTestSuite) TestGroup() { _ = dbgen.GroupMember(s.T(), db, database.GroupMember{}) check.Args(g.ID).Asserts(g, rbac.ActionRead) })) + s.Run("GetGroupsByOrganizationAndUserID", s.Subtest(func(db database.Store, check *expects) { + g := dbgen.Group(s.T(), db, database.Group{}) + gm := dbgen.GroupMember(s.T(), db, database.GroupMember{GroupID: g.ID}) + check.Args(database.GetGroupsByOrganizationAndUserIDParams{ + OrganizationID: g.OrganizationID, + UserID: gm.UserID, + }).Asserts(g, rbac.ActionRead) + })) s.Run("InsertAllUsersGroup", s.Subtest(func(db database.Store, check *expects) { o := dbgen.Organization(s.T(), db, database.Organization{}) check.Args(o.ID).Asserts(rbac.ResourceGroup.InOrg(o.ID), rbac.ActionCreate) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 8bb8559be7..b30184773b 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -2250,6 +2250,30 @@ func (q *FakeQuerier) GetGroupMembers(_ context.Context, id uuid.UUID) ([]databa return users, nil } +func (q *FakeQuerier) GetGroupsByOrganizationAndUserID(_ context.Context, arg database.GetGroupsByOrganizationAndUserIDParams) ([]database.Group, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + q.mutex.RLock() + defer q.mutex.RUnlock() + var groupIds []uuid.UUID + for _, member := range q.groupMembers { + if member.UserID == arg.UserID { + groupIds = append(groupIds, member.GroupID) + } + } + groups := []database.Group{} + for _, group := range q.groups { + if slices.Contains(groupIds, group.ID) && group.OrganizationID == arg.OrganizationID { + groups = append(groups, group) + } + } + + return groups, nil +} + func (q *FakeQuerier) GetGroupsByOrganizationID(_ context.Context, id uuid.UUID) ([]database.Group, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 5cd452d328..08ef5d2991 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -559,6 +559,13 @@ func (m metricsStore) GetGroupMembers(ctx context.Context, groupID uuid.UUID) ([ return users, err } +func (m metricsStore) GetGroupsByOrganizationAndUserID(ctx context.Context, arg database.GetGroupsByOrganizationAndUserIDParams) ([]database.Group, error) { + start := time.Now() + r0, r1 := m.s.GetGroupsByOrganizationAndUserID(ctx, arg) + m.queryLatencies.WithLabelValues("GetGroupsByOrganizationAndUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]database.Group, error) { start := time.Now() groups, err := m.s.GetGroupsByOrganizationID(ctx, organizationID) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 32049ba072..f6cb941fb1 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1095,6 +1095,21 @@ func (mr *MockStoreMockRecorder) GetGroupMembers(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupMembers", reflect.TypeOf((*MockStore)(nil).GetGroupMembers), arg0, arg1) } +// GetGroupsByOrganizationAndUserID mocks base method. +func (m *MockStore) GetGroupsByOrganizationAndUserID(arg0 context.Context, arg1 database.GetGroupsByOrganizationAndUserIDParams) ([]database.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGroupsByOrganizationAndUserID", arg0, arg1) + ret0, _ := ret[0].([]database.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetGroupsByOrganizationAndUserID indicates an expected call of GetGroupsByOrganizationAndUserID. +func (mr *MockStoreMockRecorder) GetGroupsByOrganizationAndUserID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupsByOrganizationAndUserID", reflect.TypeOf((*MockStore)(nil).GetGroupsByOrganizationAndUserID), arg0, arg1) +} + // GetGroupsByOrganizationID mocks base method. func (m *MockStore) GetGroupsByOrganizationID(arg0 context.Context, arg1 uuid.UUID) ([]database.Group, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index bf1a1909fe..532f393ac2 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -123,6 +123,7 @@ type sqlcQuerier interface { // If the group is a user made group, then we need to check the group_members table. // If it is the "Everyone" group, then we need to check the organization_members table. GetGroupMembers(ctx context.Context, groupID uuid.UUID) ([]User, error) + GetGroupsByOrganizationAndUserID(ctx context.Context, arg GetGroupsByOrganizationAndUserIDParams) ([]Group, error) GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error) GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b3216fc2d8..35c55dd6fe 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1484,6 +1484,67 @@ func (q *sqlQuerier) GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrg return i, err } +const getGroupsByOrganizationAndUserID = `-- name: GetGroupsByOrganizationAndUserID :many +SELECT + groups.id, groups.name, groups.organization_id, groups.avatar_url, groups.quota_allowance, groups.display_name, groups.source +FROM + groups + -- If the group is a user made group, then we need to check the group_members table. +LEFT JOIN + group_members +ON + group_members.group_id = groups.id AND + group_members.user_id = $1 + -- If it is the "Everyone" group, then we need to check the organization_members table. +LEFT JOIN + organization_members +ON + organization_members.organization_id = groups.id AND + organization_members.user_id = $1 +WHERE + -- In either case, the group_id will only match an org or a group. + (group_members.user_id = $1 OR organization_members.user_id = $1) +AND + -- Ensure the group or organization is the specified organization. + groups.organization_id = $2 +` + +type GetGroupsByOrganizationAndUserIDParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` +} + +func (q *sqlQuerier) GetGroupsByOrganizationAndUserID(ctx context.Context, arg GetGroupsByOrganizationAndUserIDParams) ([]Group, error) { + rows, err := q.db.QueryContext(ctx, getGroupsByOrganizationAndUserID, arg.UserID, arg.OrganizationID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Group + for rows.Next() { + var i Group + if err := rows.Scan( + &i.ID, + &i.Name, + &i.OrganizationID, + &i.AvatarURL, + &i.QuotaAllowance, + &i.DisplayName, + &i.Source, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getGroupsByOrganizationID = `-- name: GetGroupsByOrganizationID :many SELECT id, name, organization_id, avatar_url, quota_allowance, display_name, source diff --git a/coderd/database/queries/groups.sql b/coderd/database/queries/groups.sql index e772d21a58..53d0b25874 100644 --- a/coderd/database/queries/groups.sql +++ b/coderd/database/queries/groups.sql @@ -28,6 +28,31 @@ FROM WHERE organization_id = $1; +-- name: GetGroupsByOrganizationAndUserID :many +SELECT + groups.* +FROM + groups + -- If the group is a user made group, then we need to check the group_members table. +LEFT JOIN + group_members +ON + group_members.group_id = groups.id AND + group_members.user_id = @user_id + -- If it is the "Everyone" group, then we need to check the organization_members table. +LEFT JOIN + organization_members +ON + organization_members.organization_id = groups.id AND + organization_members.user_id = @user_id +WHERE + -- In either case, the group_id will only match an org or a group. + (group_members.user_id = @user_id OR organization_members.user_id = @user_id) +AND + -- Ensure the group or organization is the specified organization. + groups.organization_id = @organization_id; + + -- name: InsertGroup :one INSERT INTO groups ( id, diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 6183ffc028..9665b43f31 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -467,6 +467,17 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo if err != nil { return nil, failJob(fmt.Sprintf("get owner: %s", err)) } + ownerGroups, err := s.Database.GetGroupsByOrganizationAndUserID(ctx, database.GetGroupsByOrganizationAndUserIDParams{ + UserID: owner.ID, + OrganizationID: s.OrganizationID, + }) + if err != nil { + return nil, failJob(fmt.Sprintf("get owner group names: %s", err)) + } + ownerGroupNames := []string{} + for _, group := range ownerGroups { + ownerGroupNames = append(ownerGroupNames, group.Name) + } err = s.Pubsub.Publish(codersdk.WorkspaceNotifyChannel(workspace.ID), []byte{}) if err != nil { return nil, failJob(fmt.Sprintf("publish workspace update: %s", err)) @@ -567,6 +578,7 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo WorkspaceOwner: owner.Username, WorkspaceOwnerEmail: owner.Email, WorkspaceOwnerName: owner.Name, + WorkspaceOwnerGroups: ownerGroupNames, WorkspaceOwnerOidcAccessToken: workspaceOwnerOIDCAccessToken, WorkspaceId: workspace.ID.String(), WorkspaceOwnerId: owner.ID.String(), diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 05572d381e..7e24372e66 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -182,6 +182,15 @@ func TestAcquireJob(t *testing.T) { defer cancel() user := dbgen.User(t, db, database.User{}) + group1 := dbgen.Group(t, db, database.Group{ + Name: "group1", + OrganizationID: pd.OrganizationID, + }) + err := db.InsertGroupMember(ctx, database.InsertGroupMemberParams{ + UserID: user.ID, + GroupID: group1.ID, + }) + require.NoError(t, err) link := dbgen.UserLink(t, db, database.UserLink{ LoginType: database.LoginTypeOIDC, UserID: user.ID, @@ -340,6 +349,7 @@ func TestAcquireJob(t *testing.T) { WorkspaceOwnerEmail: user.Email, WorkspaceOwnerName: user.Name, WorkspaceOwnerOidcAccessToken: link.OAuthAccessToken, + WorkspaceOwnerGroups: []string{group1.Name}, WorkspaceId: workspace.ID.String(), WorkspaceOwnerId: user.ID.String(), TemplateId: template.ID.String(), diff --git a/go.mod b/go.mod index cd3c83bba8..bfbf5050cc 100644 --- a/go.mod +++ b/go.mod @@ -104,7 +104,7 @@ require ( github.com/coder/flog v1.1.0 github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 github.com/coder/retry v1.5.1 - github.com/coder/terraform-provider-coder v0.19.0 + github.com/coder/terraform-provider-coder v0.20.1 github.com/coder/wgtunnel v0.1.13-0.20231127054351-578bfff9b92a github.com/coreos/go-oidc/v3 v3.10.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf diff --git a/go.sum b/go.sum index 3f620bd14a..72d6471d75 100644 --- a/go.sum +++ b/go.sum @@ -217,8 +217,8 @@ github.com/coder/ssh v0.0.0-20231128192721-70855dedb788 h1:YoUSJ19E8AtuUFVYBpXuO github.com/coder/ssh v0.0.0-20231128192721-70855dedb788/go.mod h1:aGQbuCLyhRLMzZF067xc84Lh7JDs1FKwCmF1Crl9dxQ= github.com/coder/tailscale v1.1.1-0.20240401202854-d329bbdb530d h1:IMvBC1GrCIiZFxpOYRQacZtdjnmsdWNAMilPz+kvdG4= github.com/coder/tailscale v1.1.1-0.20240401202854-d329bbdb530d/go.mod h1:L8tPrwSi31RAMEMV8rjb0vYTGs7rXt8rAHbqY/p41j4= -github.com/coder/terraform-provider-coder v0.19.0 h1:mmUXSXcar1h2wgwoHIUwdEKy9Kw0GW7fLO4Vzzf+4R4= -github.com/coder/terraform-provider-coder v0.19.0/go.mod h1:pACHRoXSHBGyY696mLeQ1hR/Ag1G2wFk5bw0mT5Zp2g= +github.com/coder/terraform-provider-coder v0.20.1 h1:hz0yvDl8rDJyDgUlFH8QrGUxFKrwmyAQpOhaoTMEmtY= +github.com/coder/terraform-provider-coder v0.20.1/go.mod h1:pACHRoXSHBGyY696mLeQ1hR/Ag1G2wFk5bw0mT5Zp2g= github.com/coder/wgtunnel v0.1.13-0.20231127054351-578bfff9b92a h1:KhR9LUVllMZ+e9lhubZ1HNrtJDgH5YLoTvpKwmrGag4= github.com/coder/wgtunnel v0.1.13-0.20231127054351-578bfff9b92a/go.mod h1:QzfptVUdEO+XbkzMKx1kw13i9wwpJlfI1RrZ6SNZ0hA= github.com/coder/wireguard-go v0.0.0-20230807234434-d825b45ccbf5 h1:eDk/42Kj4xN4yfE504LsvcFEo3dWUiCOaBiWJ2uIH2A= diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 40f24ecfb8..542006f27e 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -2,12 +2,14 @@ package terraform import ( "context" + "encoding/json" "fmt" "os" "strings" "time" "github.com/spf13/afero" + "golang.org/x/xerrors" "cdr.dev/slog" "github.com/coder/terraform-provider-coder/provider" @@ -186,6 +188,11 @@ func provisionEnv( richParams []*proto.RichParameterValue, externalAuth []*proto.ExternalAuthProvider, ) ([]string, error) { env := safeEnviron() + ownerGroups, err := json.Marshal(metadata.GetWorkspaceOwnerGroups()) + if err != nil { + return nil, xerrors.Errorf("marshal owner groups: %w", err) + } + env = append(env, "CODER_AGENT_URL="+metadata.GetCoderUrl(), "CODER_WORKSPACE_TRANSITION="+strings.ToLower(metadata.GetWorkspaceTransition().String()), @@ -194,6 +201,7 @@ func provisionEnv( "CODER_WORKSPACE_OWNER_EMAIL="+metadata.GetWorkspaceOwnerEmail(), "CODER_WORKSPACE_OWNER_NAME="+metadata.GetWorkspaceOwnerName(), "CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN="+metadata.GetWorkspaceOwnerOidcAccessToken(), + "CODER_WORKSPACE_OWNER_GROUPS="+string(ownerGroups), "CODER_WORKSPACE_ID="+metadata.GetWorkspaceId(), "CODER_WORKSPACE_OWNER_ID="+metadata.GetWorkspaceOwnerId(), "CODER_WORKSPACE_OWNER_SESSION_TOKEN="+metadata.GetWorkspaceOwnerSessionToken(), diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index f91be315a5..99d7b2e26a 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1637,6 +1637,7 @@ type Metadata struct { WorkspaceOwnerSessionToken string `protobuf:"bytes,11,opt,name=workspace_owner_session_token,json=workspaceOwnerSessionToken,proto3" json:"workspace_owner_session_token,omitempty"` TemplateId string `protobuf:"bytes,12,opt,name=template_id,json=templateId,proto3" json:"template_id,omitempty"` WorkspaceOwnerName string `protobuf:"bytes,13,opt,name=workspace_owner_name,json=workspaceOwnerName,proto3" json:"workspace_owner_name,omitempty"` + WorkspaceOwnerGroups []string `protobuf:"bytes,14,rep,name=workspace_owner_groups,json=workspaceOwnerGroups,proto3" json:"workspace_owner_groups,omitempty"` } func (x *Metadata) Reset() { @@ -1762,6 +1763,13 @@ func (x *Metadata) GetWorkspaceOwnerName() string { return "" } +func (x *Metadata) GetWorkspaceOwnerGroups() []string { + if x != nil { + return x.WorkspaceOwnerGroups + } + return nil +} + // Config represents execution configuration shared by all subsequent requests in the Session type Config struct { state protoimpl.MessageState @@ -2868,7 +2876,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, - 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0x81, 0x05, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xb7, 0x05, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, @@ -2908,133 +2916,137 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, - 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, - 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x64, 0x6d, 0x65, 0x22, 0xb5, 0x02, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, - 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, - 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, - 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xf8, 0x01, 0x0a, - 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, + 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, + 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, + 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, + 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8b, 0x01, 0x0a, + 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, + 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x22, 0xb5, 0x02, 0x0a, 0x0b, 0x50, + 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, + 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x8f, 0x02, 0x0a, 0x0d, 0x41, - 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, - 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, - 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x0f, 0x0a, 0x0d, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, - 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, - 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, - 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, - 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, - 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, - 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, - 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, - 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, - 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, - 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, - 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, - 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, - 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, - 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, - 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, - 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, - 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, - 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, + 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, + 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x41, 0x0a, + 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x8f, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, + 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, + 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, + 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, + 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, + 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, + 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, + 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, + 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, + 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, + 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, + 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, + 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, + 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, + 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, + 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, + 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0x49, 0x0a, + 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index fb683293a4..1ee779aa76 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -225,6 +225,7 @@ message Metadata { string workspace_owner_session_token = 11; string template_id = 12; string workspace_owner_name = 13; + repeated string workspace_owner_groups = 14; } // Config represents execution configuration shared by all subsequent requests in the Session diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index 962544ba2c..1ba6f6f64a 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -230,6 +230,7 @@ export interface Metadata { workspaceOwnerSessionToken: string; templateId: string; workspaceOwnerName: string; + workspaceOwnerGroups: string[]; } /** Config represents execution configuration shared by all subsequent requests in the Session */ @@ -832,6 +833,9 @@ export const Metadata = { if (message.workspaceOwnerName !== "") { writer.uint32(106).string(message.workspaceOwnerName); } + for (const v of message.workspaceOwnerGroups) { + writer.uint32(114).string(v!); + } return writer; }, }; From 3b7380fa006510c1d8931711a8b30662d99754d7 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 8 Apr 2024 16:22:33 +0400 Subject: [PATCH 34/74] fix: fix race in assertWorkspaceLastUsedAtUpdated (#12899) fixes #12789 Stats are collected asynchronously with respect to sessions ending. Flush repeatedly so that we pick up the collection if we missed it. --- coderd/workspaceapps/apptest/apptest.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go index dea232c867..2e91953d67 100644 --- a/coderd/workspaceapps/apptest/apptest.go +++ b/coderd/workspaceapps/apptest/apptest.go @@ -1765,9 +1765,11 @@ func assertWorkspaceLastUsedAtUpdated(t testing.TB, details *Details) { require.NotNil(t, details.Workspace, "can't assert LastUsedAt on a nil workspace!") before, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID) require.NoError(t, err) - // Wait for stats to fully flush. - details.FlushStats() require.Eventually(t, func() bool { + // We may need to flush multiple times, since the stats from the app we are testing might be + // collected asynchronously from when we see the connection close, and thus, could race + // against being flushed. + details.FlushStats() after, err := details.SDKClient.Workspace(context.Background(), details.Workspace.ID) return assert.NoError(t, err) && after.LastUsedAt.After(before.LastUsedAt) }, testutil.WaitShort, testutil.IntervalMedium) From 24135a2d0f70d603985e1f3f83b0633f91ded0a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:27:46 +0300 Subject: [PATCH 35/74] chore: bump golang.org/x/term from 0.18.0 to 0.19.0 (#12886) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.18.0 to 0.19.0. - [Commits](https://github.com/golang/term/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index bfbf5050cc..59295587f5 100644 --- a/go.mod +++ b/go.mod @@ -194,8 +194,8 @@ require ( golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.18.0 golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 - golang.org/x/term v0.18.0 + golang.org/x/sys v0.19.0 + golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 golang.org/x/tools v0.19.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 diff --git a/go.sum b/go.sum index 72d6471d75..aab24ea053 100644 --- a/go.sum +++ b/go.sum @@ -1104,8 +1104,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1115,8 +1115,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 8ba8ec2f19ae7626479c815b4e368c65214433f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:30:32 -0500 Subject: [PATCH 36/74] chore: bump github.com/elastic/go-sysinfo from 1.13.1 to 1.14.0 (#12894) Bumps [github.com/elastic/go-sysinfo](https://github.com/elastic/go-sysinfo) from 1.13.1 to 1.14.0. - [Release notes](https://github.com/elastic/go-sysinfo/releases) - [Commits](https://github.com/elastic/go-sysinfo/compare/v1.13.1...v1.14.0) --- updated-dependencies: - dependency-name: github.com/elastic/go-sysinfo dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 59295587f5..fec8384070 100644 --- a/go.mod +++ b/go.mod @@ -111,7 +111,7 @@ require ( github.com/creack/pty v1.1.21 github.com/dave/dst v0.27.2 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/elastic/go-sysinfo v1.13.1 + github.com/elastic/go-sysinfo v1.14.0 github.com/fatih/color v1.16.0 github.com/fatih/structs v1.1.0 github.com/fatih/structtag v1.2.0 @@ -343,7 +343,6 @@ require ( github.com/imdario/mergo v0.3.15 // indirect github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect github.com/jsimonetti/rtnetlink v1.3.5 // indirect diff --git a/go.sum b/go.sum index aab24ea053..e091b4de4c 100644 --- a/go.sum +++ b/go.sum @@ -275,8 +275,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= -github.com/elastic/go-sysinfo v1.13.1 h1:U5Jlx6c/rLkR72O8wXXXo1abnGlWGJU/wbzNJ2AfQa4= -github.com/elastic/go-sysinfo v1.13.1/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ= +github.com/elastic/go-sysinfo v1.14.0 h1:dQRtiqLycoOOla7IflZg3aN213vqJmP0lpVpKQ9lUEY= +github.com/elastic/go-sysinfo v1.14.0/go.mod h1:FKUXnZWhnYI0ueO7jhsGV3uQJ5hiz8OqM5b3oGyaRr8= github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -580,8 +580,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= From f99fd807b1fd5d96a34c3c9f691c6a0a88b39a83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:30:47 -0500 Subject: [PATCH 37/74] chore: bump golang.org/x/sync from 0.6.0 to 0.7.0 (#12895) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.6.0 to 0.7.0. - [Commits](https://github.com/golang/sync/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fec8384070..31d9efd753 100644 --- a/go.mod +++ b/go.mod @@ -193,7 +193,7 @@ require ( golang.org/x/mod v0.16.0 golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.18.0 - golang.org/x/sync v0.6.0 + golang.org/x/sync v0.7.0 golang.org/x/sys v0.19.0 golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 diff --git a/go.sum b/go.sum index e091b4de4c..93ba0cd7a3 100644 --- a/go.sum +++ b/go.sum @@ -1058,8 +1058,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 9a7d8034cbdb2264845598739ccfc24e1077d2ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:31:09 -0500 Subject: [PATCH 38/74] chore: bump golang.org/x/net from 0.22.0 to 0.24.0 (#12888) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.24.0. - [Commits](https://github.com/golang/net/compare/v0.22.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 31d9efd753..540f7c1d55 100644 --- a/go.mod +++ b/go.mod @@ -188,10 +188,10 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/goleak v1.2.1 go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/mod v0.16.0 - golang.org/x/net v0.22.0 + golang.org/x/net v0.24.0 golang.org/x/oauth2 v0.18.0 golang.org/x/sync v0.7.0 golang.org/x/sys v0.19.0 diff --git a/go.sum b/go.sum index 93ba0cd7a3..913f07e72b 100644 --- a/go.sum +++ b/go.sum @@ -1005,8 +1005,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= @@ -1045,8 +1045,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= From 589434e8d8e518b64dec40151ce045039a58caa0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:46:16 -0500 Subject: [PATCH 39/74] chore: bump golang.org/x/tools from 0.19.0 to 0.20.0 (#12890) Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.19.0 to 0.20.0. - [Release notes](https://github.com/golang/tools/releases) - [Commits](https://github.com/golang/tools/compare/v0.19.0...v0.20.0) --- updated-dependencies: - dependency-name: golang.org/x/tools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 540f7c1d55..a93bf50776 100644 --- a/go.mod +++ b/go.mod @@ -190,14 +190,14 @@ require ( go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/mod v0.16.0 + golang.org/x/mod v0.17.0 golang.org/x/net v0.24.0 golang.org/x/oauth2 v0.18.0 golang.org/x/sync v0.7.0 golang.org/x/sys v0.19.0 golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 - golang.org/x/tools v0.19.0 + golang.org/x/tools v0.20.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 google.golang.org/api v0.172.0 diff --git a/go.sum b/go.sum index 913f07e72b..f94ba3cc44 100644 --- a/go.sum +++ b/go.sum @@ -1021,8 +1021,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1142,8 +1142,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 11123018a282cceb20c38f5524f15e2b48c7ddb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:57:52 +0000 Subject: [PATCH 40/74] chore: bump google.golang.org/grpc from 1.62.1 to 1.63.0 (#12892) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.1 to 1.63.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.62.1...v1.63.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index a93bf50776..b25571930a 100644 --- a/go.mod +++ b/go.mod @@ -201,7 +201,7 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 google.golang.org/api v0.172.0 - google.golang.org/grpc v1.62.1 + google.golang.org/grpc v1.63.0 google.golang.org/protobuf v1.33.0 gopkg.in/DataDog/dd-trace-go.v1 v1.61.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 @@ -232,7 +232,7 @@ require ( ) require ( - cloud.google.com/go/compute v1.23.4 // indirect + cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/logging v1.9.0 // indirect cloud.google.com/go/longrunning v0.5.5 // indirect filippo.io/edwards25519 v1.0.0 // indirect @@ -315,7 +315,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v23.1.21+incompatible // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -427,8 +427,8 @@ require ( golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v1.0.0 // indirect diff --git a/go.sum b/go.sum index f94ba3cc44..cf2e40707a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 h1:KHblWIE/KHOwQ6lEbMZt6YpcGve2FEZ1sDtrW1Am5UI= cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw= -cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= @@ -442,8 +442,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k= @@ -1166,10 +1166,10 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1177,8 +1177,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= +google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 7179c86df326150ff431d44044f0e023b5c1453f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:52:35 -0500 Subject: [PATCH 41/74] chore: bump golang.org/x/oauth2 from 0.18.0 to 0.19.0 (#12893) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.18.0 to 0.19.0. - [Commits](https://github.com/golang/oauth2/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b25571930a..9346fc7427 100644 --- a/go.mod +++ b/go.mod @@ -192,7 +192,7 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/mod v0.17.0 golang.org/x/net v0.24.0 - golang.org/x/oauth2 v0.18.0 + golang.org/x/oauth2 v0.19.0 golang.org/x/sync v0.7.0 golang.org/x/sys v0.19.0 golang.org/x/term v0.19.0 diff --git a/go.sum b/go.sum index cf2e40707a..10e0890965 100644 --- a/go.sum +++ b/go.sum @@ -1048,8 +1048,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From d82f2fd4166119ad7ce51cc343bf82ca4968936b Mon Sep 17 00:00:00 2001 From: coryb Date: Mon, 8 Apr 2024 11:57:38 -0700 Subject: [PATCH 42/74] fix: update typo in audit log field (#12907) --- coderd/audit/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/audit/audit.go b/coderd/audit/audit.go index bdd32abfae..097b0c6f49 100644 --- a/coderd/audit/audit.go +++ b/coderd/audit/audit.go @@ -21,7 +21,7 @@ type AdditionalFields struct { BuildNumber string `json:"build_number"` BuildReason database.BuildReason `json:"build_reason"` WorkspaceOwner string `json:"workspace_owner"` - WorkspaceID uuid.UUID `json:"workpace_id"` + WorkspaceID uuid.UUID `json:"workspace_id"` } func NewNop() Auditor { From 28754a79e5836381d11e915b49394b00e00d9512 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 9 Apr 2024 12:33:06 +0200 Subject: [PATCH 43/74] docs: describe air-gapped architecture (#12897) --- docs/about/architecture.md | 82 +++++++++++++++++- docs/images/architecture-air-gapped.png | Bin 0 -> 94057 bytes docs/manifest.json | 6 +- .../{devcontainers.md => dev-containers.md} | 0 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 docs/images/architecture-air-gapped.png rename docs/templates/{devcontainers.md => dev-containers.md} (100%) diff --git a/docs/about/architecture.md b/docs/about/architecture.md index 15af701c9a..61b06d68d4 100644 --- a/docs/about/architecture.md +++ b/docs/about/architecture.md @@ -269,7 +269,85 @@ Coder on Kubernetes. - For GCP: [Google Cloud Identity Platform](https://cloud.google.com/architecture/identity/single-sign-on) -### Dev Container +### Air-gapped architecture + +The air-gapped deployment model refers to the setup of Coder's development +environment within a restricted network environment that lacks internet +connectivity. This deployment model is often required for organizations with +strict security policies or those operating in isolated environments, such as +government agencies or certain enterprise setups. + +The key features of the air-gapped architecture include: + +- _Offline installation_: Deploy workspaces without relying on an external + internet connection. +- _Isolated package/plugin repositories_: Depend on local repositories for + software installation, updates, and security patches. +- _Secure data transfer_: Enable encrypted communication channels and robust + access controls to safeguard sensitive information. + +Learn more about [offline deployments](../install/offline.md) of Coder. + +![Architecture Diagram](../images/architecture-air-gapped.png) + +#### Components + +The deployment model includes: + +- _Workspace provisioners_ with direct access to self-hosted package and plugin + repositories and restricted internet access. +- _Mirror of Terraform Registry_ with multiple versions of Terraform plugins. +- _Certificate Authority_ with all TLS certificates to build secure + communication channels. + +The model is compatible with various infrastructure models, enabling deployment +across multiple regions and diverse cloud platforms. + +##### Workload resources + +**Workspace provisioner** + +- Includes Terraform binary in the container or system image. +- Checks out Terraform plugins from self-hosted _Registry_ mirror. +- Deploys workspace images stored in the self-hosted _Container Registry_. + +**Coder server** + +- Update checks are disabled (`CODER_UPDATE_CHECK=false`). +- Telemetry data is not collected (`CODER_TELEMETRY_ENABLE=false`). +- Direct connections are not possible, workspace traffic is relayed through + control plane's DERP proxy. + +##### Workload supporting resources + +**Self-hosted Database** + +- In the air-gapped deployment model, _Coderd_ instance is unable to download + Postgres binaries from the internet, so external database must be provided. + +**Container Registry** + +- Since the _Registry_ is isolated from the internet, platform engineers are + responsible for maintaining Workspace container images and conducting periodic + updates of base Docker images. +- It is recommended to keep [Dev Containers](../templates/devcontainers.md) up + to date with the latest released + [Envbuilder](https://github.com/coder/envbuilder) runtime. + +**Mirror of Terraform Registry** + +- Stores all necessary Terraform plugin dependencies, ensuring successful + workspace provisioning and maintenance without internet access. +- Platform engineers are responsible for periodically updating the mirrored + Terraform plugins, including + [terraform-provider-coder](https://github.com/coder/terraform-provider-coder). + +**Certificate Authority** + +- Manages and issues TLS certificates to facilitate secure communication + channels within the infrastructure. + +### Dev Containers Note: _Dev containers_ are at early stage and considered experimental at the moment. @@ -302,7 +380,7 @@ models, in multiple regions, or across various cloud platforms. ##### Workload resources -**Workspace** +**Coder workspace** - Docker and Kubernetes based templates are supported. - The `docker_container` resource uses `ghcr.io/coder/envbuilder` as the base diff --git a/docs/images/architecture-air-gapped.png b/docs/images/architecture-air-gapped.png new file mode 100644 index 0000000000000000000000000000000000000000..b907eae15044d306db6dd26e7cb343ce2a34bfa5 GIT binary patch literal 94057 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 literal 0 HcmV?d00001 diff --git a/docs/manifest.json b/docs/manifest.json index 6620160b0f..a7896946fe 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -230,9 +230,9 @@ "icon_path": "./images/icons/docker.svg" }, { - "title": "Devcontainers", - "description": "Use devcontainers in workspaces", - "path": "./templates/devcontainers.md", + "title": "Dev Containers", + "description": "Use Dev Containers in workspaces", + "path": "./templates/dev-containers.md", "state": "alpha" }, { diff --git a/docs/templates/devcontainers.md b/docs/templates/dev-containers.md similarity index 100% rename from docs/templates/devcontainers.md rename to docs/templates/dev-containers.md From 0178bfe134f4faab043ca69fba1ee50014ef68c2 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 9 Apr 2024 14:54:17 +0300 Subject: [PATCH 44/74] fix(examples): copy /etc/skel on init in docker template (#12913) Fixes #10209 --- examples/templates/docker/main.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/templates/docker/main.tf b/examples/templates/docker/main.tf index baa0bbab66..3d8bef5c59 100644 --- a/examples/templates/docker/main.tf +++ b/examples/templates/docker/main.tf @@ -28,6 +28,12 @@ resource "coder_agent" "main" { startup_script = <<-EOT set -e + # Prepare user home with default files on first start. + if [ ! -f ~/.init_done ]; then + cp -rT /etc/skel ~ + touch ~/.init_done + fi + # install and start code-server curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.19.1 /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 & From 08451ce80c165b2fab8090a49a0a48f22cd6a248 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 9 Apr 2024 14:47:47 +0200 Subject: [PATCH 45/74] feat: remove health link from deployment sidebar (#12914) --- site/src/pages/DeploySettingsPage/Sidebar.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/Sidebar.tsx b/site/src/pages/DeploySettingsPage/Sidebar.tsx index 15cf879e87..e473ab94ca 100644 --- a/site/src/pages/DeploySettingsPage/Sidebar.tsx +++ b/site/src/pages/DeploySettingsPage/Sidebar.tsx @@ -3,7 +3,6 @@ import HubOutlinedIcon from "@mui/icons-material/HubOutlined"; import InsertChartIcon from "@mui/icons-material/InsertChart"; import LaunchOutlined from "@mui/icons-material/LaunchOutlined"; import LockRounded from "@mui/icons-material/LockOutlined"; -import MonitorHeartOutlined from "@mui/icons-material/MonitorHeartOutlined"; import Globe from "@mui/icons-material/PublicOutlined"; import ApprovalIcon from "@mui/icons-material/VerifiedUserOutlined"; import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined"; @@ -48,9 +47,6 @@ export const Sidebar: FC = () => { Observability - - Health - ); }; From 189b8626d0cb9bd32feaae32d3bb7ab0432e9a66 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 9 Apr 2024 09:38:26 -0500 Subject: [PATCH 46/74] chore: deprecate agent report-stats endpoint (#12880) * chore: deprecate agent report-stats endpoint Agent API is now used instead. * Update coderd/workspaceagents.go Co-authored-by: Spike Curtis --------- Co-authored-by: Spike Curtis --- coderd/apidoc/docs.go | 1 + coderd/apidoc/swagger.json | 1 + coderd/workspaceagents.go | 1 + 3 files changed, 3 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 7bfd521b09..750cc20998 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5904,6 +5904,7 @@ const docTemplate = `{ ], "summary": "Submit workspace agent stats", "operationId": "submit-workspace-agent-stats", + "deprecated": true, "parameters": [ { "description": "Stats request", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index c4dabcacaf..4643dc6fca 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5201,6 +5201,7 @@ "tags": ["Agents"], "summary": "Submit workspace agent stats", "operationId": "submit-workspace-agent-stats", + "deprecated": true, "parameters": [ { "description": "Stats request", diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 34170b3bf7..4848fef38c 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1132,6 +1132,7 @@ func convertScripts(dbScripts []database.WorkspaceAgentScript) []codersdk.Worksp // @Param request body agentsdk.Stats true "Stats request" // @Success 200 {object} agentsdk.StatsResponse // @Router /workspaceagents/me/report-stats [post] +// @Deprecated Uses agent API v2 endpoint instead. func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() From 1d4bf30c0d2de68560331e4c99e6538dde746b08 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 9 Apr 2024 12:06:22 -0400 Subject: [PATCH 47/74] feat: add s suffix to use HTTPS for ports (#12862) --- coderd/workspaceapps/db_test.go | 68 ++++++++++++++++++++++++++++ coderd/workspaceapps/request.go | 20 +++++--- coderd/workspaceapps/request_test.go | 20 ++++++++ coderd/workspaceapps/token_test.go | 48 ++++++++++++++++++++ 4 files changed, 150 insertions(+), 6 deletions(-) diff --git a/coderd/workspaceapps/db_test.go b/coderd/workspaceapps/db_test.go index eccc96d008..e8c7464f88 100644 --- a/coderd/workspaceapps/db_test.go +++ b/coderd/workspaceapps/db_test.go @@ -40,6 +40,7 @@ func Test_ResolveRequest(t *testing.T) { // Users can access unhealthy and initializing apps (as of 2024-02). appNameUnhealthy = "app-unhealthy" appNameInitializing = "app-initializing" + appNameEndsInS = "app-ends-in-s" // This agent will never connect, so it will never become "connected". // Users cannot access unhealthy agents. @@ -166,6 +167,12 @@ func Test_ResolveRequest(t *testing.T) { Threshold: 1000, }, }, + { + Slug: appNameEndsInS, + DisplayName: appNameEndsInS, + SharingLevel: proto.AppSharingLevel_OWNER, + Url: appURL, + }, }, }, { @@ -644,6 +651,67 @@ func Test_ResolveRequest(t *testing.T) { require.Equal(t, "http://127.0.0.1:9090", token.AppURL) }) + t.Run("PortSubdomainHTTPSS", func(t *testing.T) { + t.Parallel() + + req := (workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + BasePath: "/", + UsernameOrID: me.Username, + WorkspaceNameOrID: workspace.Name, + AgentNameOrID: agentName, + AppSlugOrPort: "9090ss", + }).Normalize() + + rw := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken()) + + _, ok := workspaceapps.ResolveRequest(rw, r, workspaceapps.ResolveRequestOptions{ + Logger: api.Logger, + SignedTokenProvider: api.WorkspaceAppsProvider, + DashboardURL: api.AccessURL, + PathAppBaseURL: api.AccessURL, + AppHostname: api.AppHostname, + AppRequest: req, + }) + // should parse as app and fail to find app "9090ss" + require.False(t, ok) + w := rw.Result() + _ = w.Body.Close() + b, err := io.ReadAll(w.Body) + require.NoError(t, err) + require.Contains(t, string(b), "404 - Application Not Found") + }) + + t.Run("SubdomainEndsInS", func(t *testing.T) { + t.Parallel() + + req := (workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + BasePath: "/", + UsernameOrID: me.Username, + WorkspaceNameOrID: workspace.Name, + AgentNameOrID: agentName, + AppSlugOrPort: appNameEndsInS, + }).Normalize() + + rw := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken()) + + token, ok := workspaceapps.ResolveRequest(rw, r, workspaceapps.ResolveRequestOptions{ + Logger: api.Logger, + SignedTokenProvider: api.WorkspaceAppsProvider, + DashboardURL: api.AccessURL, + PathAppBaseURL: api.AccessURL, + AppHostname: api.AppHostname, + AppRequest: req, + }) + require.True(t, ok) + require.Equal(t, req.AppSlugOrPort, token.AppSlugOrPort) + }) + t.Run("Terminal", func(t *testing.T) { t.Parallel() diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go index 0f3eddf6cb..d0fba4256c 100644 --- a/coderd/workspaceapps/request.go +++ b/coderd/workspaceapps/request.go @@ -287,12 +287,20 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR // whether the app is a slug or a port and whether there are multiple agents // in the workspace or not. var ( - agentNameOrID = r.AgentNameOrID - appURL string - appSharingLevel database.AppSharingLevel - portUint, portUintErr = strconv.ParseUint(r.AppSlugOrPort, 10, 16) + agentNameOrID = r.AgentNameOrID + appURL string + appSharingLevel database.AppSharingLevel + // First check if it's a port-based URL with an optional "s" suffix for HTTPS. + potentialPortStr = strings.TrimSuffix(r.AppSlugOrPort, "s") + portUint, portUintErr = strconv.ParseUint(potentialPortStr, 10, 16) ) + //nolint:nestif if portUintErr == nil { + protocol := "http" + if strings.HasSuffix(r.AppSlugOrPort, "s") { + protocol = "https" + } + if r.AccessMethod != AccessMethodSubdomain { // TODO(@deansheather): this should return a 400 instead of a 500. return nil, xerrors.New("port-based URLs are only supported for subdomain-based applications") @@ -309,10 +317,10 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR } // If the app slug is a port number, then route to the port as an - // "anonymous app". We only support HTTP for port-based URLs. + // "anonymous app". // // This is only supported for subdomain-based applications. - appURL = fmt.Sprintf("http://127.0.0.1:%d", portUint) + appURL = fmt.Sprintf("%s://127.0.0.1:%d", protocol, portUint) appSharingLevel = database.AppSharingLevelOwner // Port sharing authorization diff --git a/coderd/workspaceapps/request_test.go b/coderd/workspaceapps/request_test.go index 7240937a06..b6e4bb7a2e 100644 --- a/coderd/workspaceapps/request_test.go +++ b/coderd/workspaceapps/request_test.go @@ -57,6 +57,26 @@ func Test_RequestValidate(t *testing.T) { AppSlugOrPort: "baz", }, }, + { + name: "OK5", + req: workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + BasePath: "/", + UsernameOrID: "foo", + WorkspaceNameOrID: "bar", + AppSlugOrPort: "8080", + }, + }, + { + name: "OK6", + req: workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + BasePath: "/", + UsernameOrID: "foo", + WorkspaceNameOrID: "bar", + AppSlugOrPort: "8080s", + }, + }, { name: "NoAccessMethod", req: workspaceapps.Request{ diff --git a/coderd/workspaceapps/token_test.go b/coderd/workspaceapps/token_test.go index 06ab8a2acd..c656ae2ab7 100644 --- a/coderd/workspaceapps/token_test.go +++ b/coderd/workspaceapps/token_test.go @@ -222,6 +222,54 @@ func Test_TokenMatchesRequest(t *testing.T) { }, want: false, }, + { + name: "PortPortocolHTTP", + req: workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + Prefix: "yolo--", + BasePath: "/", + UsernameOrID: "foo", + WorkspaceNameOrID: "bar", + AgentNameOrID: "baz", + AppSlugOrPort: "8080", + }, + token: workspaceapps.SignedToken{ + Request: workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + Prefix: "yolo--", + BasePath: "/", + UsernameOrID: "foo", + WorkspaceNameOrID: "bar", + AgentNameOrID: "baz", + AppSlugOrPort: "8080", + }, + }, + want: true, + }, + { + name: "PortPortocolHTTPS", + req: workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + Prefix: "yolo--", + BasePath: "/", + UsernameOrID: "foo", + WorkspaceNameOrID: "bar", + AgentNameOrID: "baz", + AppSlugOrPort: "8080s", + }, + token: workspaceapps.SignedToken{ + Request: workspaceapps.Request{ + AccessMethod: workspaceapps.AccessMethodSubdomain, + Prefix: "yolo--", + BasePath: "/", + UsernameOrID: "foo", + WorkspaceNameOrID: "bar", + AgentNameOrID: "baz", + AppSlugOrPort: "8080s", + }, + }, + want: true, + }, } for _, c := range cases { From 0a8c8ce5cc40df6cfb86f7e3175e99ccf5f20508 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 9 Apr 2024 12:35:27 -0500 Subject: [PATCH 48/74] chore: remove InsertWorkspaceAgentStat query (#12869) * chore: remove InsertWorkspaceAgentStat query InsertWorkspaceAgentStats (batch) exists. We only used the singular in a single unit test place. Removing the single for the batch, reducing the interface size. --- coderd/database/dbauthz/dbauthz.go | 14 --- coderd/database/dbauthz/dbauthz_test.go | 6 -- coderd/database/dbgen/dbgen.go | 62 ++++++++----- coderd/database/dbmem/dbmem.go | 31 ------- coderd/database/dbmetrics/dbmetrics.go | 7 -- coderd/database/dbmock/dbmock.go | 15 ---- coderd/database/dbrollup/dbrollup_test.go | 10 ++- coderd/database/querier.go | 1 - coderd/database/queries.sql.go | 88 ------------------- .../database/queries/workspaceagentstats.sql | 24 ----- coderd/metricscache/metricscache_test.go | 28 ++++-- .../prometheusmetrics_test.go | 2 +- 12 files changed, 71 insertions(+), 217 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index b0cf2f8c35..a638b705a5 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2532,20 +2532,6 @@ func (q *querier) InsertWorkspaceAgentScripts(ctx context.Context, arg database. return q.db.InsertWorkspaceAgentScripts(ctx, arg) } -func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { - // TODO: This is a workspace agent operation. Should users be able to query this? - // Not really sure what this is for. - workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) - if err != nil { - return database.WorkspaceAgentStat{}, err - } - err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) - if err != nil { - return database.WorkspaceAgentStat{}, err - } - return q.db.InsertWorkspaceAgentStat(ctx, arg) -} - func (q *querier) InsertWorkspaceAgentStats(ctx context.Context, arg database.InsertWorkspaceAgentStatsParams) error { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil { return err diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 3619837732..7be33d58c8 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1520,12 +1520,6 @@ func (s *MethodTestSuite) TestWorkspace() { AutomaticUpdates: database.AutomaticUpdatesAlways, }).Asserts(w, rbac.ActionUpdate) })) - s.Run("InsertWorkspaceAgentStat", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - check.Args(database.InsertWorkspaceAgentStatParams{ - WorkspaceID: ws.ID, - }).Asserts(ws, rbac.ActionUpdate) - })) s.Run("UpdateWorkspaceAppHealthByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 707e977178..596885c9d2 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -707,27 +707,49 @@ func WorkspaceAgentStat(t testing.TB, db database.Store, orig database.Workspace if orig.ConnectionsByProto == nil { orig.ConnectionsByProto = json.RawMessage([]byte("{}")) } - scheme, err := db.InsertWorkspaceAgentStat(genCtx, database.InsertWorkspaceAgentStatParams{ - ID: takeFirst(orig.ID, uuid.New()), - CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()), - UserID: takeFirst(orig.UserID, uuid.New()), - TemplateID: takeFirst(orig.TemplateID, uuid.New()), - WorkspaceID: takeFirst(orig.WorkspaceID, uuid.New()), - AgentID: takeFirst(orig.AgentID, uuid.New()), - ConnectionsByProto: orig.ConnectionsByProto, - ConnectionCount: takeFirst(orig.ConnectionCount, 0), - RxPackets: takeFirst(orig.RxPackets, 0), - RxBytes: takeFirst(orig.RxBytes, 0), - TxPackets: takeFirst(orig.TxPackets, 0), - TxBytes: takeFirst(orig.TxBytes, 0), - SessionCountVSCode: takeFirst(orig.SessionCountVSCode, 0), - SessionCountJetBrains: takeFirst(orig.SessionCountJetBrains, 0), - SessionCountReconnectingPTY: takeFirst(orig.SessionCountReconnectingPTY, 0), - SessionCountSSH: takeFirst(orig.SessionCountSSH, 0), - ConnectionMedianLatencyMS: takeFirst(orig.ConnectionMedianLatencyMS, 0), - }) + jsonProto := []byte(fmt.Sprintf("[%s]", orig.ConnectionsByProto)) + + params := database.InsertWorkspaceAgentStatsParams{ + ID: []uuid.UUID{takeFirst(orig.ID, uuid.New())}, + CreatedAt: []time.Time{takeFirst(orig.CreatedAt, dbtime.Now())}, + UserID: []uuid.UUID{takeFirst(orig.UserID, uuid.New())}, + TemplateID: []uuid.UUID{takeFirst(orig.TemplateID, uuid.New())}, + WorkspaceID: []uuid.UUID{takeFirst(orig.WorkspaceID, uuid.New())}, + AgentID: []uuid.UUID{takeFirst(orig.AgentID, uuid.New())}, + ConnectionsByProto: jsonProto, + ConnectionCount: []int64{takeFirst(orig.ConnectionCount, 0)}, + RxPackets: []int64{takeFirst(orig.RxPackets, 0)}, + RxBytes: []int64{takeFirst(orig.RxBytes, 0)}, + TxPackets: []int64{takeFirst(orig.TxPackets, 0)}, + TxBytes: []int64{takeFirst(orig.TxBytes, 0)}, + SessionCountVSCode: []int64{takeFirst(orig.SessionCountVSCode, 0)}, + SessionCountJetBrains: []int64{takeFirst(orig.SessionCountJetBrains, 0)}, + SessionCountReconnectingPTY: []int64{takeFirst(orig.SessionCountReconnectingPTY, 0)}, + SessionCountSSH: []int64{takeFirst(orig.SessionCountSSH, 0)}, + ConnectionMedianLatencyMS: []float64{takeFirst(orig.ConnectionMedianLatencyMS, 0)}, + } + err := db.InsertWorkspaceAgentStats(genCtx, params) require.NoError(t, err, "insert workspace agent stat") - return scheme + + return database.WorkspaceAgentStat{ + ID: params.ID[0], + CreatedAt: params.CreatedAt[0], + UserID: params.UserID[0], + AgentID: params.AgentID[0], + WorkspaceID: params.WorkspaceID[0], + TemplateID: params.TemplateID[0], + ConnectionsByProto: orig.ConnectionsByProto, + ConnectionCount: params.ConnectionCount[0], + RxPackets: params.RxPackets[0], + RxBytes: params.RxBytes[0], + TxPackets: params.TxPackets[0], + TxBytes: params.TxBytes[0], + ConnectionMedianLatencyMS: params.ConnectionMedianLatencyMS[0], + SessionCountVSCode: params.SessionCountVSCode[0], + SessionCountJetBrains: params.SessionCountJetBrains[0], + SessionCountReconnectingPTY: params.SessionCountReconnectingPTY[0], + SessionCountSSH: params.SessionCountSSH[0], + } } func OAuth2ProviderApp(t testing.TB, db database.Store, seed database.OAuth2ProviderApp) database.OAuth2ProviderApp { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index b30184773b..2b9db8b1f2 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6481,37 +6481,6 @@ func (q *FakeQuerier) InsertWorkspaceAgentScripts(_ context.Context, arg databas return scripts, nil } -func (q *FakeQuerier) InsertWorkspaceAgentStat(_ context.Context, p database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { - if err := validateDatabaseType(p); err != nil { - return database.WorkspaceAgentStat{}, err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - stat := database.WorkspaceAgentStat{ - ID: p.ID, - CreatedAt: p.CreatedAt, - WorkspaceID: p.WorkspaceID, - AgentID: p.AgentID, - UserID: p.UserID, - ConnectionsByProto: p.ConnectionsByProto, - ConnectionCount: p.ConnectionCount, - RxPackets: p.RxPackets, - RxBytes: p.RxBytes, - TxPackets: p.TxPackets, - TxBytes: p.TxBytes, - TemplateID: p.TemplateID, - SessionCountVSCode: p.SessionCountVSCode, - SessionCountJetBrains: p.SessionCountJetBrains, - SessionCountReconnectingPTY: p.SessionCountReconnectingPTY, - SessionCountSSH: p.SessionCountSSH, - ConnectionMedianLatencyMS: p.ConnectionMedianLatencyMS, - } - q.workspaceAgentStats = append(q.workspaceAgentStats, stat) - return stat, nil -} - func (q *FakeQuerier) InsertWorkspaceAgentStats(_ context.Context, arg database.InsertWorkspaceAgentStatsParams) error { err := validateDatabaseType(arg) if err != nil { diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 08ef5d2991..53dc3f2feb 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -1649,13 +1649,6 @@ func (m metricsStore) InsertWorkspaceAgentScripts(ctx context.Context, arg datab return r0, r1 } -func (m metricsStore) InsertWorkspaceAgentStat(ctx context.Context, arg database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { - start := time.Now() - stat, err := m.s.InsertWorkspaceAgentStat(ctx, arg) - m.queryLatencies.WithLabelValues("InsertWorkspaceAgentStat").Observe(time.Since(start).Seconds()) - return stat, err -} - func (m metricsStore) InsertWorkspaceAgentStats(ctx context.Context, arg database.InsertWorkspaceAgentStatsParams) error { start := time.Now() r0 := m.s.InsertWorkspaceAgentStats(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index f6cb941fb1..2bb62e8c92 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3471,21 +3471,6 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceAgentScripts(arg0, arg1 any) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentScripts", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentScripts), arg0, arg1) } -// InsertWorkspaceAgentStat mocks base method. -func (m *MockStore) InsertWorkspaceAgentStat(arg0 context.Context, arg1 database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InsertWorkspaceAgentStat", arg0, arg1) - ret0, _ := ret[0].(database.WorkspaceAgentStat) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// InsertWorkspaceAgentStat indicates an expected call of InsertWorkspaceAgentStat. -func (mr *MockStoreMockRecorder) InsertWorkspaceAgentStat(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentStat", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentStat), arg0, arg1) -} - // InsertWorkspaceAgentStats mocks base method. func (m *MockStore) InsertWorkspaceAgentStats(arg0 context.Context, arg1 database.InsertWorkspaceAgentStatsParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/dbrollup/dbrollup_test.go b/coderd/database/dbrollup/dbrollup_test.go index f2455e8d7a..6c8e96b847 100644 --- a/coderd/database/dbrollup/dbrollup_test.go +++ b/coderd/database/dbrollup/dbrollup_test.go @@ -143,8 +143,8 @@ func TestRollupTemplateUsageStats(t *testing.T) { db, ps := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure()) logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) - anHourAgo := dbtime.Now().Add(-time.Hour).Truncate(time.Hour) - anHourAndSixMonthsAgo := anHourAgo.AddDate(0, -6, 0) + anHourAgo := dbtime.Now().Add(-time.Hour).Truncate(time.Hour).UTC() + anHourAndSixMonthsAgo := anHourAgo.AddDate(0, -6, 0).UTC() var ( org = dbgen.Organization(t, db, database.Organization{}) @@ -242,6 +242,12 @@ func TestRollupTemplateUsageStats(t *testing.T) { require.NoError(t, err) require.Len(t, stats, 1) + // I do not know a better way to do this. Our database runs in a *random* + // timezone. So the returned time is in a random timezone and fails on the + // equal even though they are the same time if converted back to the same timezone. + stats[0].EndTime = stats[0].EndTime.UTC() + stats[0].StartTime = stats[0].StartTime.UTC() + require.Equal(t, database.TemplateUsageStat{ TemplateID: tpl.ID, UserID: user.ID, diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 532f393ac2..7d8f504cb5 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -336,7 +336,6 @@ type sqlcQuerier interface { InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) - InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error) InsertWorkspaceAgentStats(ctx context.Context, arg InsertWorkspaceAgentStatsParams) error InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) InsertWorkspaceAppStats(ctx context.Context, arg InsertWorkspaceAppStatsParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 35c55dd6fe..5b2b54929d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -10447,94 +10447,6 @@ func (q *sqlQuerier) GetWorkspaceAgentStatsAndLabels(ctx context.Context, create return items, nil } -const insertWorkspaceAgentStat = `-- name: InsertWorkspaceAgentStat :one -INSERT INTO - workspace_agent_stats ( - id, - created_at, - user_id, - workspace_id, - template_id, - agent_id, - connections_by_proto, - connection_count, - rx_packets, - rx_bytes, - tx_packets, - tx_bytes, - session_count_vscode, - session_count_jetbrains, - session_count_reconnecting_pty, - session_count_ssh, - connection_median_latency_ms - ) -VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING id, created_at, user_id, agent_id, workspace_id, template_id, connections_by_proto, connection_count, rx_packets, rx_bytes, tx_packets, tx_bytes, connection_median_latency_ms, session_count_vscode, session_count_jetbrains, session_count_reconnecting_pty, session_count_ssh -` - -type InsertWorkspaceAgentStatParams struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UserID uuid.UUID `db:"user_id" json:"user_id"` - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - ConnectionsByProto json.RawMessage `db:"connections_by_proto" json:"connections_by_proto"` - ConnectionCount int64 `db:"connection_count" json:"connection_count"` - RxPackets int64 `db:"rx_packets" json:"rx_packets"` - RxBytes int64 `db:"rx_bytes" json:"rx_bytes"` - TxPackets int64 `db:"tx_packets" json:"tx_packets"` - TxBytes int64 `db:"tx_bytes" json:"tx_bytes"` - SessionCountVSCode int64 `db:"session_count_vscode" json:"session_count_vscode"` - SessionCountJetBrains int64 `db:"session_count_jetbrains" json:"session_count_jetbrains"` - SessionCountReconnectingPTY int64 `db:"session_count_reconnecting_pty" json:"session_count_reconnecting_pty"` - SessionCountSSH int64 `db:"session_count_ssh" json:"session_count_ssh"` - ConnectionMedianLatencyMS float64 `db:"connection_median_latency_ms" json:"connection_median_latency_ms"` -} - -func (q *sqlQuerier) InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error) { - row := q.db.QueryRowContext(ctx, insertWorkspaceAgentStat, - arg.ID, - arg.CreatedAt, - arg.UserID, - arg.WorkspaceID, - arg.TemplateID, - arg.AgentID, - arg.ConnectionsByProto, - arg.ConnectionCount, - arg.RxPackets, - arg.RxBytes, - arg.TxPackets, - arg.TxBytes, - arg.SessionCountVSCode, - arg.SessionCountJetBrains, - arg.SessionCountReconnectingPTY, - arg.SessionCountSSH, - arg.ConnectionMedianLatencyMS, - ) - var i WorkspaceAgentStat - err := row.Scan( - &i.ID, - &i.CreatedAt, - &i.UserID, - &i.AgentID, - &i.WorkspaceID, - &i.TemplateID, - &i.ConnectionsByProto, - &i.ConnectionCount, - &i.RxPackets, - &i.RxBytes, - &i.TxPackets, - &i.TxBytes, - &i.ConnectionMedianLatencyMS, - &i.SessionCountVSCode, - &i.SessionCountJetBrains, - &i.SessionCountReconnectingPTY, - &i.SessionCountSSH, - ) - return i, err -} - const insertWorkspaceAgentStats = `-- name: InsertWorkspaceAgentStats :exec INSERT INTO workspace_agent_stats ( diff --git a/coderd/database/queries/workspaceagentstats.sql b/coderd/database/queries/workspaceagentstats.sql index cf059121de..4b7f86fba4 100644 --- a/coderd/database/queries/workspaceagentstats.sql +++ b/coderd/database/queries/workspaceagentstats.sql @@ -1,27 +1,3 @@ --- name: InsertWorkspaceAgentStat :one -INSERT INTO - workspace_agent_stats ( - id, - created_at, - user_id, - workspace_id, - template_id, - agent_id, - connections_by_proto, - connection_count, - rx_packets, - rx_bytes, - tx_packets, - tx_bytes, - session_count_vscode, - session_count_jetbrains, - session_count_reconnecting_pty, - session_count_ssh, - connection_median_latency_ms - ) -VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING *; - -- name: InsertWorkspaceAgentStats :exec INSERT INTO workspace_agent_stats ( diff --git a/coderd/metricscache/metricscache_test.go b/coderd/metricscache/metricscache_test.go index 391017aaba..bcc9396d3c 100644 --- a/coderd/metricscache/metricscache_test.go +++ b/coderd/metricscache/metricscache_test.go @@ -3,6 +3,7 @@ package metricscache_test import ( "context" "database/sql" + "encoding/json" "testing" "time" @@ -280,14 +281,25 @@ func TestCache_DeploymentStats(t *testing.T) { }) defer cache.Close() - _, err := db.InsertWorkspaceAgentStat(context.Background(), database.InsertWorkspaceAgentStatParams{ - ID: uuid.New(), - AgentID: uuid.New(), - CreatedAt: dbtime.Now(), - ConnectionCount: 1, - RxBytes: 1, - TxBytes: 1, - SessionCountVSCode: 1, + err := db.InsertWorkspaceAgentStats(context.Background(), database.InsertWorkspaceAgentStatsParams{ + ID: []uuid.UUID{uuid.New()}, + CreatedAt: []time.Time{dbtime.Now()}, + WorkspaceID: []uuid.UUID{uuid.New()}, + UserID: []uuid.UUID{uuid.New()}, + TemplateID: []uuid.UUID{uuid.New()}, + AgentID: []uuid.UUID{uuid.New()}, + ConnectionsByProto: json.RawMessage(`[{}]`), + + RxPackets: []int64{0}, + RxBytes: []int64{1}, + TxPackets: []int64{0}, + TxBytes: []int64{1}, + ConnectionCount: []int64{1}, + SessionCountVSCode: []int64{1}, + SessionCountJetBrains: []int64{0}, + SessionCountReconnectingPTY: []int64{0}, + SessionCountSSH: []int64{0}, + ConnectionMedianLatencyMS: []float64{10}, }) require.NoError(t, err) diff --git a/coderd/prometheusmetrics/prometheusmetrics_test.go b/coderd/prometheusmetrics/prometheusmetrics_test.go index 0ca7884cfb..2322982a65 100644 --- a/coderd/prometheusmetrics/prometheusmetrics_test.go +++ b/coderd/prometheusmetrics/prometheusmetrics_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - "github.com/coder/coder/v2/cryptorand" "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" @@ -32,6 +31,7 @@ import ( "github.com/coder/coder/v2/coderd/prometheusmetrics" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/tailnet" From 54690110188f600c8b43030423d3f7ac68b3f0f2 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 10 Apr 2024 11:50:46 +0400 Subject: [PATCH 49/74] fix: stop logging session shutdown as warning (#12922) A customer hit like 200k of ErrSessionShutdown, which just dupes any errors we would have generated when shutting down the session for e.g. Ping failures. --- coderd/workspaceagentsrpc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/workspaceagentsrpc.go b/coderd/workspaceagentsrpc.go index 7130d0b88e..a0cd4c1032 100644 --- a/coderd/workspaceagentsrpc.go +++ b/coderd/workspaceagentsrpc.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "io" "net/http" "runtime/pprof" "sync" @@ -156,7 +157,7 @@ func (api *API) workspaceAgentRPC(rw http.ResponseWriter, r *http.Request) { ctx = tailnet.WithStreamID(ctx, streamID) ctx = agentapi.WithAPIVersion(ctx, version) err = agentAPI.Serve(ctx, mux) - if err != nil { + if err != nil && !xerrors.Is(err, yamux.ErrSessionShutdown) && !xerrors.Is(err, io.EOF) { logger.Warn(ctx, "workspace agent RPC listen error", slog.Error(err)) _ = conn.Close(websocket.StatusInternalError, err.Error()) return From b6359b0a897f615794f42796ee63dbe1d11bfff1 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 10 Apr 2024 10:48:56 +0200 Subject: [PATCH 50/74] fix: ignore gomock temporary files (#12924) --- tailnet/tailnettest/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 tailnet/tailnettest/.gitignore diff --git a/tailnet/tailnettest/.gitignore b/tailnet/tailnettest/.gitignore new file mode 100644 index 0000000000..d3b709ea9c --- /dev/null +++ b/tailnet/tailnettest/.gitignore @@ -0,0 +1 @@ +gomock_*/ From 2f2a395ba946f689d7f9ea05c803de66b04b5ecd Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 10 Apr 2024 15:00:39 +0200 Subject: [PATCH 51/74] e2e tests for deployment/licenses (#12926) --- site/e2e/tests/deployment/licenses.spec.ts | 30 +++++++++++++++++++ site/e2e/tests/enterprise.spec.ts | 10 ------- .../LicensesSettingsPage/LicenseCard.tsx | 14 ++++++--- .../LicensesSettingsPageView.tsx | 2 +- 4 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 site/e2e/tests/deployment/licenses.spec.ts delete mode 100644 site/e2e/tests/enterprise.spec.ts diff --git a/site/e2e/tests/deployment/licenses.spec.ts b/site/e2e/tests/deployment/licenses.spec.ts new file mode 100644 index 0000000000..89546bbec8 --- /dev/null +++ b/site/e2e/tests/deployment/licenses.spec.ts @@ -0,0 +1,30 @@ +import { expect, test } from "@playwright/test"; +import { requiresEnterpriseLicense } from "../../helpers"; + +test("license was added successfully", async ({ page }) => { + requiresEnterpriseLicense(); + + await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" }); + const firstLicense = page.locator(".licenses > .license-card", { + hasText: "#1", + }); + await expect(firstLicense).toBeVisible(); + + // Trial vs. Enterprise? + const accountType = firstLicense.locator(".account-type"); + await expect(accountType).toHaveText("Enterprise"); + + // User limit 1/1 + const userLimit = firstLicense.locator(".user-limit"); + await expect(userLimit).toHaveText("1 / 1"); + + // License should not be expired yet + const licenseExpires = firstLicense.locator(".license-expires"); + const licenseExpiresDate = new Date(await licenseExpires.innerText()); + const now = new Date(); + expect(licenseExpiresDate.getTime()).toBeGreaterThan(now.getTime()); + + // "Remove" button should be visible + const removeButton = firstLicense.locator(".remove-button"); + await expect(removeButton).toBeVisible(); +}); diff --git a/site/e2e/tests/enterprise.spec.ts b/site/e2e/tests/enterprise.spec.ts deleted file mode 100644 index 4758d43ae1..0000000000 --- a/site/e2e/tests/enterprise.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { requiresEnterpriseLicense } from "../helpers"; - -test("license was added successfully", async ({ page }) => { - requiresEnterpriseLicense(); - - await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" }); - const license = page.locator(".MuiPaper-root", { hasText: "#1" }); - await expect(license).toBeVisible(); -}); diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicenseCard.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicenseCard.tsx index e22330e663..f3c9707c19 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicenseCard.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicenseCard.tsx @@ -32,7 +32,12 @@ export const LicenseCard: FC = ({ license.claims.features["user_limit"] || userLimitLimit; return ( - + = ({ alignItems="center" > #{license.id} - + {license.claims.trial ? "Trial" : "Enterprise"} = ({ > Users - + {userLimitActual} {` / ${currentUserLimit || "Unlimited"}`} @@ -92,7 +97,7 @@ export const LicenseCard: FC = ({ ) : ( Valid Until )} - + {dayjs .unix(license.claims.license_expires) .format("MMMM D, YYYY")} @@ -104,6 +109,7 @@ export const LicenseCard: FC = ({ variant="contained" size="small" onClick={() => setLicenseIDMarkedForRemoval(license.id)} + className="remove-button" > Remove… diff --git a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 08c2db5862..9d023c1749 100644 --- a/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -84,7 +84,7 @@ const LicensesSettingsPageView: FC = ({ {isLoading && } {!isLoading && licenses && licenses?.length > 0 && ( - + {licenses ?.sort( (a, b) => From acaa25409958a8af9763d5554bed4db886150b92 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 10 Apr 2024 09:29:24 -0400 Subject: [PATCH 52/74] feat: link with protocol on shared ports (#12908) --- coderd/workspaceapps/apptest/apptest.go | 3 ++- coderd/workspaceapps/apptest/setup.go | 7 +++++++ coderd/workspaceapps/appurl/appurl.go | 3 ++- coderd/workspaceapps/request.go | 4 ---- site/src/modules/resources/PortForwardButton.tsx | 1 + site/src/utils/portForward.ts | 6 +++--- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go index 2e91953d67..5ba60fbb58 100644 --- a/coderd/workspaceapps/apptest/apptest.go +++ b/coderd/workspaceapps/apptest/apptest.go @@ -1165,6 +1165,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) { appDetails := setupProxyTest(t, &DeploymentOptions{ ServeHTTPS: true, }) + // using the fact that Apps.Port and Apps.PortHTTPS are the same port here port, err := strconv.ParseInt(appDetails.Apps.Port.AppSlugOrPort, 10, 32) require.NoError(t, err) _, err = appDetails.SDKClient.UpsertWorkspaceAgentPortShare(ctx, appDetails.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{ @@ -1178,7 +1179,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) { publicAppClient := appDetails.AppClient(t) publicAppClient.SetSessionToken("") - resp, err := requestWithRetries(ctx, t, publicAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil) + resp, err := requestWithRetries(ctx, t, publicAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.PortHTTPS).String(), nil) require.NoError(t, err) defer resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode) diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index 702789e4cf..c27032c192 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -116,6 +116,7 @@ type Details struct { Authenticated App Public App Port App + PortHTTPS App } } @@ -247,6 +248,12 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De AgentName: agnt.Name, AppSlugOrPort: strconv.Itoa(int(opts.port)), } + details.Apps.PortHTTPS = App{ + Username: me.Username, + WorkspaceName: workspace.Name, + AgentName: agnt.Name, + AppSlugOrPort: strconv.Itoa(int(opts.port)) + "s", + } return details } diff --git a/coderd/workspaceapps/appurl/appurl.go b/coderd/workspaceapps/appurl/appurl.go index 4daa05a7e3..8b8cfd74d3 100644 --- a/coderd/workspaceapps/appurl/appurl.go +++ b/coderd/workspaceapps/appurl/appurl.go @@ -90,9 +90,10 @@ func (a ApplicationURL) Path() string { // // Subdomains should be in the form: // -// ({PREFIX}---)?{PORT/APP_SLUG}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME} +// ({PREFIX}---)?{PORT{s?}/APP_SLUG}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME} // e.g. // https://8080--main--dev--dean.hi.c8s.io +// https://8080s--main--dev--dean.hi.c8s.io // https://app--main--dev--dean.hi.c8s.io // https://prefix---8080--main--dev--dean.hi.c8s.io // https://prefix---app--main--dev--dean.hi.c8s.io diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go index d0fba4256c..4f6a6f3a64 100644 --- a/coderd/workspaceapps/request.go +++ b/coderd/workspaceapps/request.go @@ -350,10 +350,6 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR } // No port share found, so we keep default to owner. } else { - if ps.Protocol == database.PortShareProtocolHttps { - // Apply HTTPS protocol if specified. - appURL = fmt.Sprintf("https://127.0.0.1:%d", portUint) - } appSharingLevel = ps.ShareLevel } } else { diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index f9e1ccfff1..e445f99ea1 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -393,6 +393,7 @@ export const PortForwardPopoverView: FC = ({ agent.name, workspaceName, username, + share.protocol === "https", ); const label = share.port; return ( diff --git a/site/src/utils/portForward.ts b/site/src/utils/portForward.ts index 6d2dc4cbef..bd666823b2 100644 --- a/site/src/utils/portForward.ts +++ b/site/src/utils/portForward.ts @@ -4,12 +4,12 @@ export const portForwardURL = ( agentName: string, workspaceName: string, username: string, + https = false, ): string => { const { location } = window; + const suffix = https ? "s" : ""; - const subdomain = `${ - isNaN(port) ? 3000 : port - }--${agentName}--${workspaceName}--${username}`; + const subdomain = `${port}${suffix}--${agentName}--${workspaceName}--${username}`; return `${location.protocol}//${host}`.replace("*", subdomain); }; From e266ecf91b0673385084940dfc97a5b396fa46cd Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 10 Apr 2024 16:09:44 +0200 Subject: [PATCH 53/74] test(site): fix flaky outdated agent test (#12927) --- site/e2e/helpers.ts | 4 +++- site/e2e/tests/outdatedAgent.spec.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 79e5a8ac5f..040bcb6d55 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -60,7 +60,9 @@ export const createWorkspace = async ( await fillParameters(page, richParameters, buildParameters); await page.getByTestId("form-submit").click(); - await expect(page).toHaveURL("/@admin/" + name); + // Workaround: OutdatedAgent lands at "http://localhost:3111/@admin/8d6225b7?resources=echo_dev" + // and this is also a correct location. + await page.waitForURL(new RegExp("/@admin/" + name)); await page.waitForSelector("*[data-testid='build-status'] >> text=Running", { state: "visible", diff --git a/site/e2e/tests/outdatedAgent.spec.ts b/site/e2e/tests/outdatedAgent.spec.ts index 24f1442f7d..56207e9dbc 100644 --- a/site/e2e/tests/outdatedAgent.spec.ts +++ b/site/e2e/tests/outdatedAgent.spec.ts @@ -17,6 +17,8 @@ const agentVersion = "v0.27.0"; test.beforeEach(async ({ page }) => await beforeCoderTest(page)); test("ssh with agent " + agentVersion, async ({ page }) => { + test.setTimeout(40_000); // This is a slow test, 20s may not be enough on Mac. + const token = randomUUID(); const template = await createTemplate(page, { apply: [ From 4dc293d930fedcca862a7a36ba775987f1db5a44 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Apr 2024 09:41:05 -0500 Subject: [PATCH 54/74] chore: add date information to windows startup logs (#12905) --- provisionersdk/scripts/bootstrap_windows.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provisionersdk/scripts/bootstrap_windows.ps1 b/provisionersdk/scripts/bootstrap_windows.ps1 index aa3089f90d..e51dd9415a 100644 --- a/provisionersdk/scripts/bootstrap_windows.ps1 +++ b/provisionersdk/scripts/bootstrap_windows.ps1 @@ -14,20 +14,20 @@ while ($true) { # executing shell to be named "sshd", otherwise it fails. See: # https://github.com/microsoft/vscode-remote-release/issues/5699 $BINARY_URL="${ACCESS_URL}/bin/coder-windows-${ARCH}.exe" - Write-Output "Fetching coder agent from ${BINARY_URL}" + Write-Output "$(Get-Date) Fetching coder agent from ${BINARY_URL}" Invoke-WebRequest -Uri "${BINARY_URL}" -OutFile $env:TEMP\sshd.exe break } catch { - Write-Output "error: unhandled exception fetching coder agent:" + Write-Output "$(Get-Date) error: unhandled exception fetching coder agent:" Write-Output $_ - Write-Output "trying again in 30 seconds..." + Write-Output "$(Get-Date) trying again in 30 seconds..." Start-Sleep -Seconds 30 } } # Check if running in a Windows container if (-not (Get-Command 'Set-MpPreference' -ErrorAction SilentlyContinue)) { - Write-Output "Set-MpPreference not available, skipping..." + Write-Output "$(Get-Date) Set-MpPreference not available, skipping..." } else { Set-MpPreference -DisableRealtimeMonitoring $true -ExclusionPath $env:TEMP\sshd.exe } From 838e8df5be4ff3b536861f5d03a20d484d1bad17 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Apr 2024 10:34:49 -0500 Subject: [PATCH 55/74] chore: merge apikey/token session config values (#12817) * chore: merge apikey/token session config values There is a confusing difference between an apikey and a token. This difference leaks into our configs. This change does not resolve the difference. It only groups the config values to try and manage any bloat that occurs from adding more similar config values --- coderd/apidoc/docs.go | 28 +++++++++----- coderd/apidoc/swagger.json | 28 +++++++++----- coderd/apikey.go | 10 ++--- coderd/apikey_test.go | 8 ++-- coderd/coderd.go | 6 +-- coderd/identityprovider/tokens.go | 31 ++++++++------- coderd/oauth2.go | 2 +- .../provisionerdserver/provisionerdserver.go | 4 +- .../provisionerdserver_test.go | 10 +++-- coderd/userauth.go | 4 +- coderd/workspaceapps.go | 8 ++-- coderd/workspaceapps/db.go | 2 +- codersdk/deployment.go | 37 +++++++++++++++--- docs/api/general.md | 8 ++-- docs/api/schemas.md | 38 ++++++++++++++----- enterprise/coderd/coderd.go | 4 +- site/src/api/typesGenerated.ts | 11 ++++-- 17 files changed, 157 insertions(+), 82 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 750cc20998..bcd2b3b15c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9296,9 +9296,6 @@ const docTemplate = `{ "disable_path_apps": { "type": "boolean" }, - "disable_session_expiry_refresh": { - "type": "boolean" - }, "docs_url": { "$ref": "#/definitions/serpent.URL" }, @@ -9336,12 +9333,6 @@ const docTemplate = `{ "logging": { "$ref": "#/definitions/codersdk.LoggingConfig" }, - "max_session_expiry": { - "type": "integer" - }, - "max_token_lifetime": { - "type": "integer" - }, "metrics_cache_refresh_interval": { "type": "integer" }, @@ -9393,6 +9384,9 @@ const docTemplate = `{ "secure_auth_cookie": { "type": "boolean" }, + "session_lifetime": { + "$ref": "#/definitions/codersdk.SessionLifetime" + }, "ssh_keygen_algorithm": { "type": "string" }, @@ -11085,6 +11079,22 @@ const docTemplate = `{ } } }, + "codersdk.SessionLifetime": { + "type": "object", + "properties": { + "default_duration": { + "description": "DefaultDuration is for api keys, not tokens.", + "type": "integer" + }, + "disable_expiry_refresh": { + "description": "DisableExpiryRefresh will disable automatically refreshing api\nkeys when they are used from the api. This means the api key lifetime at\ncreation is the lifetime of the api key.", + "type": "boolean" + }, + "max_token_lifetime": { + "type": "integer" + } + } + }, "codersdk.SupportConfig": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 4643dc6fca..47bac4fc4e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8301,9 +8301,6 @@ "disable_path_apps": { "type": "boolean" }, - "disable_session_expiry_refresh": { - "type": "boolean" - }, "docs_url": { "$ref": "#/definitions/serpent.URL" }, @@ -8341,12 +8338,6 @@ "logging": { "$ref": "#/definitions/codersdk.LoggingConfig" }, - "max_session_expiry": { - "type": "integer" - }, - "max_token_lifetime": { - "type": "integer" - }, "metrics_cache_refresh_interval": { "type": "integer" }, @@ -8398,6 +8389,9 @@ "secure_auth_cookie": { "type": "boolean" }, + "session_lifetime": { + "$ref": "#/definitions/codersdk.SessionLifetime" + }, "ssh_keygen_algorithm": { "type": "string" }, @@ -9987,6 +9981,22 @@ } } }, + "codersdk.SessionLifetime": { + "type": "object", + "properties": { + "default_duration": { + "description": "DefaultDuration is for api keys, not tokens.", + "type": "integer" + }, + "disable_expiry_refresh": { + "description": "DisableExpiryRefresh will disable automatically refreshing api\nkeys when they are used from the api. This means the api key lifetime at\ncreation is the lifetime of the api key.", + "type": "boolean" + }, + "max_token_lifetime": { + "type": "integer" + } + } + }, "codersdk.SupportConfig": { "type": "object", "properties": { diff --git a/coderd/apikey.go b/coderd/apikey.go index b1d31ff613..10a83a05f4 100644 --- a/coderd/apikey.go +++ b/coderd/apikey.go @@ -84,7 +84,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) { cookie, key, err := api.createAPIKey(ctx, apikey.CreateParams{ UserID: user.ID, LoginType: database.LoginTypeToken, - DefaultLifetime: api.DeploymentValues.SessionDuration.Value(), + DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(), ExpiresAt: dbtime.Now().Add(lifeTime), Scope: scope, LifetimeSeconds: int64(lifeTime.Seconds()), @@ -128,7 +128,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { lifeTime := time.Hour * 24 * 7 cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{ UserID: user.ID, - DefaultLifetime: api.DeploymentValues.SessionDuration.Value(), + DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(), LoginType: database.LoginTypePassword, RemoteAddr: r.RemoteAddr, // All api generated keys will last 1 week. Browser login tokens have @@ -354,7 +354,7 @@ func (api *API) tokenConfig(rw http.ResponseWriter, r *http.Request) { httpapi.Write( r.Context(), rw, http.StatusOK, codersdk.TokenConfig{ - MaxTokenLifetime: values.MaxTokenLifetime.Value(), + MaxTokenLifetime: values.Sessions.MaximumTokenDuration.Value(), }, ) } @@ -364,10 +364,10 @@ func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error { return xerrors.New("lifetime must be positive number greater than 0") } - if lifetime > api.DeploymentValues.MaxTokenLifetime.Value() { + if lifetime > api.DeploymentValues.Sessions.MaximumTokenDuration.Value() { return xerrors.Errorf( "lifetime must be less than %v", - api.DeploymentValues.MaxTokenLifetime, + api.DeploymentValues.Sessions.MaximumTokenDuration, ) } diff --git a/coderd/apikey_test.go b/coderd/apikey_test.go index a20acf5ff3..29d0f01126 100644 --- a/coderd/apikey_test.go +++ b/coderd/apikey_test.go @@ -125,7 +125,7 @@ func TestTokenUserSetMaxLifetime(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() dc := coderdtest.DeploymentValues(t) - dc.MaxTokenLifetime = serpent.Duration(time.Hour * 24 * 7) + dc.Sessions.MaximumTokenDuration = serpent.Duration(time.Hour * 24 * 7) client := coderdtest.New(t, &coderdtest.Options{ DeploymentValues: dc, }) @@ -165,7 +165,7 @@ func TestSessionExpiry(t *testing.T) { // // We don't support updating the deployment config after startup, but for // this test it works because we don't copy the value (and we use pointers). - dc.SessionDuration = serpent.Duration(time.Second) + dc.Sessions.DefaultDuration = serpent.Duration(time.Second) userClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID) @@ -174,8 +174,8 @@ func TestSessionExpiry(t *testing.T) { apiKey, err := db.GetAPIKeyByID(ctx, strings.Split(token, "-")[0]) require.NoError(t, err) - require.EqualValues(t, dc.SessionDuration.Value().Seconds(), apiKey.LifetimeSeconds) - require.WithinDuration(t, apiKey.CreatedAt.Add(dc.SessionDuration.Value()), apiKey.ExpiresAt, 2*time.Second) + require.EqualValues(t, dc.Sessions.DefaultDuration.Value().Seconds(), apiKey.LifetimeSeconds) + require.WithinDuration(t, apiKey.CreatedAt.Add(dc.Sessions.DefaultDuration.Value()), apiKey.ExpiresAt, 2*time.Second) // Update the session token to be expired so we can test that it is // rejected for extra points. diff --git a/coderd/coderd.go b/coderd/coderd.go index 0cc0962316..67b16e9032 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -566,7 +566,7 @@ func New(options *Options) *API { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: false, - DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), + DisableSessionExpiryRefresh: options.DeploymentValues.Sessions.DisableExpiryRefresh.Value(), Optional: false, SessionTokenFunc: nil, // Default behavior PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc, @@ -576,7 +576,7 @@ func New(options *Options) *API { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: true, - DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), + DisableSessionExpiryRefresh: options.DeploymentValues.Sessions.DisableExpiryRefresh.Value(), Optional: false, SessionTokenFunc: nil, // Default behavior PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc, @@ -586,7 +586,7 @@ func New(options *Options) *API { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: false, - DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), + DisableSessionExpiryRefresh: options.DeploymentValues.Sessions.DisableExpiryRefresh.Value(), Optional: true, SessionTokenFunc: nil, // Default behavior PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc, diff --git a/coderd/identityprovider/tokens.go b/coderd/identityprovider/tokens.go index 0673eb7d1a..e9c9e743e7 100644 --- a/coderd/identityprovider/tokens.go +++ b/coderd/identityprovider/tokens.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" "net/url" - "time" "github.com/google/uuid" "golang.org/x/oauth2" @@ -75,7 +74,11 @@ func extractTokenParams(r *http.Request, callbackURL *url.URL) (tokenParams, []c return params, nil, nil } -func Tokens(db database.Store, defaultLifetime time.Duration) http.HandlerFunc { +// Tokens +// TODO: the sessions lifetime config passed is for coder api tokens. +// Should there be a separate config for oauth2 tokens? They are related, +// but they are not the same. +func Tokens(db database.Store, lifetimes codersdk.SessionLifetime) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() app := httpmw.OAuth2ProviderApp(r) @@ -104,9 +107,9 @@ func Tokens(db database.Store, defaultLifetime time.Duration) http.HandlerFunc { switch params.grantType { // TODO: Client creds, device code. case codersdk.OAuth2ProviderGrantTypeRefreshToken: - token, err = refreshTokenGrant(ctx, db, app, defaultLifetime, params) + token, err = refreshTokenGrant(ctx, db, app, lifetimes, params) case codersdk.OAuth2ProviderGrantTypeAuthorizationCode: - token, err = authorizationCodeGrant(ctx, db, app, defaultLifetime, params) + token, err = authorizationCodeGrant(ctx, db, app, lifetimes, params) default: // Grant types are validated by the parser, so getting through here means // the developer added a type but forgot to add a case here. @@ -137,7 +140,7 @@ func Tokens(db database.Store, defaultLifetime time.Duration) http.HandlerFunc { } } -func authorizationCodeGrant(ctx context.Context, db database.Store, app database.OAuth2ProviderApp, defaultLifetime time.Duration, params tokenParams) (oauth2.Token, error) { +func authorizationCodeGrant(ctx context.Context, db database.Store, app database.OAuth2ProviderApp, lifetimes codersdk.SessionLifetime, params tokenParams) (oauth2.Token, error) { // Validate the client secret. secret, err := parseSecret(params.clientSecret) if err != nil { @@ -195,11 +198,9 @@ func authorizationCodeGrant(ctx context.Context, db database.Store, app database // TODO: We are ignoring scopes for now. tokenName := fmt.Sprintf("%s_%s_oauth_session_token", dbCode.UserID, app.ID) key, sessionToken, err := apikey.Generate(apikey.CreateParams{ - UserID: dbCode.UserID, - LoginType: database.LoginTypeOAuth2ProviderApp, - // TODO: This is just the lifetime for api keys, maybe have its own config - // settings. #11693 - DefaultLifetime: defaultLifetime, + UserID: dbCode.UserID, + LoginType: database.LoginTypeOAuth2ProviderApp, + DefaultLifetime: lifetimes.DefaultDuration.Value(), // For now, we allow only one token per app and user at a time. TokenName: tokenName, }) @@ -271,7 +272,7 @@ func authorizationCodeGrant(ctx context.Context, db database.Store, app database }, nil } -func refreshTokenGrant(ctx context.Context, db database.Store, app database.OAuth2ProviderApp, defaultLifetime time.Duration, params tokenParams) (oauth2.Token, error) { +func refreshTokenGrant(ctx context.Context, db database.Store, app database.OAuth2ProviderApp, lifetimes codersdk.SessionLifetime, params tokenParams) (oauth2.Token, error) { // Validate the token. token, err := parseSecret(params.refreshToken) if err != nil { @@ -326,11 +327,9 @@ func refreshTokenGrant(ctx context.Context, db database.Store, app database.OAut // TODO: We are ignoring scopes for now. tokenName := fmt.Sprintf("%s_%s_oauth_session_token", prevKey.UserID, app.ID) key, sessionToken, err := apikey.Generate(apikey.CreateParams{ - UserID: prevKey.UserID, - LoginType: database.LoginTypeOAuth2ProviderApp, - // TODO: This is just the lifetime for api keys, maybe have its own config - // settings. #11693 - DefaultLifetime: defaultLifetime, + UserID: prevKey.UserID, + LoginType: database.LoginTypeOAuth2ProviderApp, + DefaultLifetime: lifetimes.DefaultDuration.Value(), // For now, we allow only one token per app and user at a time. TokenName: tokenName, }) diff --git a/coderd/oauth2.go b/coderd/oauth2.go index 9e2df641bf..ef68e93a1f 100644 --- a/coderd/oauth2.go +++ b/coderd/oauth2.go @@ -354,7 +354,7 @@ func (api *API) getOAuth2ProviderAppAuthorize() http.HandlerFunc { // @Success 200 {object} oauth2.Token // @Router /oauth2/tokens [post] func (api *API) postOAuth2ProviderAppToken() http.HandlerFunc { - return identityprovider.Tokens(api.Database, api.DeploymentValues.SessionDuration.Value()) + return identityprovider.Tokens(api.Database, api.DeploymentValues.Sessions) } // @Summary Delete OAuth2 application tokens. diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 9665b43f31..ee1d455265 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1737,9 +1737,9 @@ func (s *server) regenerateSessionToken(ctx context.Context, user database.User, newkey, sessionToken, err := apikey.Generate(apikey.CreateParams{ UserID: user.ID, LoginType: user.LoginType, - DefaultLifetime: s.DeploymentValues.SessionDuration.Value(), TokenName: workspaceSessionTokenName(workspace), - LifetimeSeconds: int64(s.DeploymentValues.MaxTokenLifetime.Value().Seconds()), + DefaultLifetime: s.DeploymentValues.Sessions.DefaultDuration.Value(), + LifetimeSeconds: int64(s.DeploymentValues.Sessions.MaximumTokenDuration.Value().Seconds()), }) if err != nil { return "", xerrors.Errorf("generate API key: %w", err) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 7e24372e66..6757bd2c63 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -166,7 +166,11 @@ func TestAcquireJob(t *testing.T) { // Set the max session token lifetime so we can assert we // create an API key with an expiration within the bounds of the // deployment config. - dv := &codersdk.DeploymentValues{MaxTokenLifetime: serpent.Duration(time.Hour)} + dv := &codersdk.DeploymentValues{ + Sessions: codersdk.SessionLifetime{ + MaximumTokenDuration: serpent.Duration(time.Hour), + }, + } gitAuthProvider := &sdkproto.ExternalAuthProviderResource{ Id: "github", } @@ -319,8 +323,8 @@ func TestAcquireJob(t *testing.T) { require.Len(t, toks, 2, "invalid api key") key, err := db.GetAPIKeyByID(ctx, toks[0]) require.NoError(t, err) - require.Equal(t, int64(dv.MaxTokenLifetime.Value().Seconds()), key.LifetimeSeconds) - require.WithinDuration(t, time.Now().Add(dv.MaxTokenLifetime.Value()), key.ExpiresAt, time.Minute) + require.Equal(t, int64(dv.Sessions.MaximumTokenDuration.Value().Seconds()), key.LifetimeSeconds) + require.WithinDuration(t, time.Now().Add(dv.Sessions.MaximumTokenDuration.Value()), key.ExpiresAt, time.Minute) want, err := json.Marshal(&proto.AcquiredJob_WorkspaceBuild_{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ diff --git a/coderd/userauth.go b/coderd/userauth.go index 366f566c59..eda4dd60ab 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -252,7 +252,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { UserID: user.ID, LoginType: database.LoginTypePassword, RemoteAddr: r.RemoteAddr, - DefaultLifetime: api.DeploymentValues.SessionDuration.Value(), + DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(), }) if err != nil { logger.Error(ctx, "unable to create API key", slog.Error(err)) @@ -1612,7 +1612,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C cookie, newKey, err := api.createAPIKey(dbauthz.AsSystemRestricted(ctx), apikey.CreateParams{ UserID: user.ID, LoginType: params.LoginType, - DefaultLifetime: api.DeploymentValues.SessionDuration.Value(), + DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(), RemoteAddr: r.RemoteAddr, }) if err != nil { diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index d4a31e1822..8c6ffdb62e 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -102,14 +102,14 @@ func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request // the current session. exp := apiKey.ExpiresAt lifetimeSeconds := apiKey.LifetimeSeconds - if exp.IsZero() || time.Until(exp) > api.DeploymentValues.SessionDuration.Value() { - exp = dbtime.Now().Add(api.DeploymentValues.SessionDuration.Value()) - lifetimeSeconds = int64(api.DeploymentValues.SessionDuration.Value().Seconds()) + if exp.IsZero() || time.Until(exp) > api.DeploymentValues.Sessions.DefaultDuration.Value() { + exp = dbtime.Now().Add(api.DeploymentValues.Sessions.DefaultDuration.Value()) + lifetimeSeconds = int64(api.DeploymentValues.Sessions.DefaultDuration.Value().Seconds()) } cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{ UserID: apiKey.UserID, LoginType: database.LoginTypePassword, - DefaultLifetime: api.DeploymentValues.SessionDuration.Value(), + DefaultLifetime: api.DeploymentValues.Sessions.DefaultDuration.Value(), ExpiresAt: exp, LifetimeSeconds: lifetimeSeconds, Scope: database.APIKeyScopeApplicationConnect, diff --git a/coderd/workspaceapps/db.go b/coderd/workspaceapps/db.go index 32eaec1cf0..619bdd95ba 100644 --- a/coderd/workspaceapps/db.go +++ b/coderd/workspaceapps/db.go @@ -85,7 +85,7 @@ func (p *DBTokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r * DB: p.Database, OAuth2Configs: p.OAuth2Configs, RedirectToLogin: false, - DisableSessionExpiryRefresh: p.DeploymentValues.DisableSessionExpiryRefresh.Value(), + DisableSessionExpiryRefresh: p.DeploymentValues.Sessions.DisableExpiryRefresh.Value(), // Optional is true to allow for public apps. If the authorization check // (later on) fails and the user is not authenticated, they will be // redirected to the login page or app auth endpoint using code below. diff --git a/codersdk/deployment.go b/codersdk/deployment.go index ee174075a7..34eaa4edd4 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -182,13 +182,11 @@ type DeploymentValues struct { RateLimit RateLimitConfig `json:"rate_limit,omitempty" typescript:",notnull"` Experiments serpent.StringArray `json:"experiments,omitempty" typescript:",notnull"` UpdateCheck serpent.Bool `json:"update_check,omitempty" typescript:",notnull"` - MaxTokenLifetime serpent.Duration `json:"max_token_lifetime,omitempty" typescript:",notnull"` Swagger SwaggerConfig `json:"swagger,omitempty" typescript:",notnull"` Logging LoggingConfig `json:"logging,omitempty" typescript:",notnull"` Dangerous DangerousConfig `json:"dangerous,omitempty" typescript:",notnull"` DisablePathApps serpent.Bool `json:"disable_path_apps,omitempty" typescript:",notnull"` - SessionDuration serpent.Duration `json:"max_session_expiry,omitempty" typescript:",notnull"` - DisableSessionExpiryRefresh serpent.Bool `json:"disable_session_expiry_refresh,omitempty" typescript:",notnull"` + Sessions SessionLifetime `json:"session_lifetime,omitempty" typescript:",notnull"` DisablePasswordAuth serpent.Bool `json:"disable_password_auth,omitempty" typescript:",notnull"` Support SupportConfig `json:"support,omitempty" typescript:",notnull"` ExternalAuthConfigs serpent.Struct[[]ExternalAuthConfig] `json:"external_auth,omitempty" typescript:",notnull"` @@ -244,6 +242,33 @@ func ParseSSHConfigOption(opt string) (key string, value string, err error) { return opt[:idx], opt[idx+1:], nil } +// SessionLifetime refers to "sessions" authenticating into Coderd. Coder has +// multiple different session types: api keys, tokens, workspace app tokens, +// agent tokens, etc. This configuration struct should be used to group all +// settings referring to any of these session lifetime controls. +// TODO: These config options were created back when coder only had api keys. +// Today, the config is ambigously used for all of them. For example: +// - cli based api keys ignore all settings +// - login uses the default lifetime, not the MaximumTokenDuration +// - Tokens use the Default & MaximumTokenDuration +// - ... etc ... +// The rational behind each decision is undocumented. The naming behind these +// config options is also confusing without any clear documentation. +// 'CreateAPIKey' is used to make all sessions, and it's parameters are just +// 'LifetimeSeconds' and 'DefaultLifetime'. Which does not directly correlate to +// the config options here. +type SessionLifetime struct { + // DisableExpiryRefresh will disable automatically refreshing api + // keys when they are used from the api. This means the api key lifetime at + // creation is the lifetime of the api key. + DisableExpiryRefresh serpent.Bool `json:"disable_expiry_refresh,omitempty" typescript:",notnull"` + + // DefaultDuration is for api keys, not tokens. + DefaultDuration serpent.Duration `json:"default_duration" typescript:",notnull"` + + MaximumTokenDuration serpent.Duration `json:"max_token_lifetime,omitempty" typescript:",notnull"` +} + type DERP struct { Server DERPServerConfig `json:"server" typescript:",notnull"` Config DERPConfig `json:"config" typescript:",notnull"` @@ -1579,7 +1604,7 @@ when required by your organization's security policy.`, // We have to add in the 25 leap days for the frontend to show the // "100 years" correctly. Default: ((100 * 365 * time.Hour * 24) + (25 * time.Hour * 24)).String(), - Value: &c.MaxTokenLifetime, + Value: &c.Sessions.MaximumTokenDuration, Group: &deploymentGroupNetworkingHTTP, YAML: "maxTokenLifetime", Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), @@ -1773,7 +1798,7 @@ when required by your organization's security policy.`, Flag: "session-duration", Env: "CODER_SESSION_DURATION", Default: (24 * time.Hour).String(), - Value: &c.SessionDuration, + Value: &c.Sessions.DefaultDuration, Group: &deploymentGroupNetworkingHTTP, YAML: "sessionDuration", Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), @@ -1784,7 +1809,7 @@ when required by your organization's security policy.`, Flag: "disable-session-expiry-refresh", Env: "CODER_DISABLE_SESSION_EXPIRY_REFRESH", - Value: &c.DisableSessionExpiryRefresh, + Value: &c.Sessions.DisableExpiryRefresh, Group: &deploymentGroupNetworkingHTTP, YAML: "disableSessionExpiryRefresh", }, diff --git a/docs/api/general.md b/docs/api/general.md index 69f57b9a99..330c41a335 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -200,7 +200,6 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "disable_owner_workspace_exec": true, "disable_password_auth": true, "disable_path_apps": true, - "disable_session_expiry_refresh": true, "docs_url": { "forceQuery": true, "fragment": "string", @@ -252,8 +251,6 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "log_filter": ["string"], "stackdriver": "string" }, - "max_session_expiry": 0, - "max_token_lifetime": 0, "metrics_cache_refresh_interval": 0, "oauth2": { "github": { @@ -341,6 +338,11 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "redirect_to_access_url": true, "scim_api_key": "string", "secure_auth_cookie": true, + "session_lifetime": { + "default_duration": 0, + "disable_expiry_refresh": true, + "max_token_lifetime": 0 + }, "ssh_keygen_algorithm": "string", "strict_transport_security": 0, "strict_transport_security_options": ["string"], diff --git a/docs/api/schemas.md b/docs/api/schemas.md index f0b5646fea..efc3a38f01 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1925,7 +1925,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "disable_owner_workspace_exec": true, "disable_password_auth": true, "disable_path_apps": true, - "disable_session_expiry_refresh": true, "docs_url": { "forceQuery": true, "fragment": "string", @@ -1977,8 +1976,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "log_filter": ["string"], "stackdriver": "string" }, - "max_session_expiry": 0, - "max_token_lifetime": 0, "metrics_cache_refresh_interval": 0, "oauth2": { "github": { @@ -2066,6 +2063,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "redirect_to_access_url": true, "scim_api_key": "string", "secure_auth_cookie": true, + "session_lifetime": { + "default_duration": 0, + "disable_expiry_refresh": true, + "max_token_lifetime": 0 + }, "ssh_keygen_algorithm": "string", "strict_transport_security": 0, "strict_transport_security_options": ["string"], @@ -2295,7 +2297,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "disable_owner_workspace_exec": true, "disable_password_auth": true, "disable_path_apps": true, - "disable_session_expiry_refresh": true, "docs_url": { "forceQuery": true, "fragment": "string", @@ -2347,8 +2348,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "log_filter": ["string"], "stackdriver": "string" }, - "max_session_expiry": 0, - "max_token_lifetime": 0, "metrics_cache_refresh_interval": 0, "oauth2": { "github": { @@ -2436,6 +2435,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "redirect_to_access_url": true, "scim_api_key": "string", "secure_auth_cookie": true, + "session_lifetime": { + "default_duration": 0, + "disable_expiry_refresh": true, + "max_token_lifetime": 0 + }, "ssh_keygen_algorithm": "string", "strict_transport_security": 0, "strict_transport_security_options": ["string"], @@ -2526,7 +2530,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `disable_owner_workspace_exec` | boolean | false | | | | `disable_password_auth` | boolean | false | | | | `disable_path_apps` | boolean | false | | | -| `disable_session_expiry_refresh` | boolean | false | | | | `docs_url` | [serpent.URL](#serpenturl) | false | | | | `enable_terraform_debug_mode` | boolean | false | | | | `experiments` | array of string | false | | | @@ -2537,8 +2540,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `in_memory_database` | boolean | false | | | | `job_hang_detector_interval` | integer | false | | | | `logging` | [codersdk.LoggingConfig](#codersdkloggingconfig) | false | | | -| `max_session_expiry` | integer | false | | | -| `max_token_lifetime` | integer | false | | | | `metrics_cache_refresh_interval` | integer | false | | | | `oauth2` | [codersdk.OAuth2Config](#codersdkoauth2config) | false | | | | `oidc` | [codersdk.OIDCConfig](#codersdkoidcconfig) | false | | | @@ -2554,6 +2555,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `redirect_to_access_url` | boolean | false | | | | `scim_api_key` | string | false | | | | `secure_auth_cookie` | boolean | false | | | +| `session_lifetime` | [codersdk.SessionLifetime](#codersdksessionlifetime) | false | | | | `ssh_keygen_algorithm` | string | false | | | | `strict_transport_security` | integer | false | | | | `strict_transport_security_options` | array of string | false | | | @@ -4294,6 +4296,24 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `ssh` | integer | false | | | | `vscode` | integer | false | | | +## codersdk.SessionLifetime + +```json +{ + "default_duration": 0, + "disable_expiry_refresh": true, + "max_token_lifetime": 0 +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `default_duration` | integer | false | | Default duration is for api keys, not tokens. | +| `disable_expiry_refresh` | boolean | false | | Disable expiry refresh will disable automatically refreshing api keys when they are used from the api. This means the api key lifetime at creation is the lifetime of the api key. | +| `max_token_lifetime` | integer | false | | | + ## codersdk.SupportConfig ```json diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index c3b8cc0199..0ac2086f86 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -148,7 +148,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: false, - DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), + DisableSessionExpiryRefresh: options.DeploymentValues.Sessions.DisableExpiryRefresh.Value(), Optional: false, SessionTokenFunc: nil, // Default behavior PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc, @@ -157,7 +157,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: false, - DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), + DisableSessionExpiryRefresh: options.DeploymentValues.Sessions.DisableExpiryRefresh.Value(), Optional: true, SessionTokenFunc: nil, // Default behavior PostAuthAdditionalHeadersFunc: options.PostAuthAdditionalHeadersFunc, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index bdf744e104..be751559f2 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -427,13 +427,11 @@ export interface DeploymentValues { readonly rate_limit?: RateLimitConfig; readonly experiments?: string[]; readonly update_check?: boolean; - readonly max_token_lifetime?: number; readonly swagger?: SwaggerConfig; readonly logging?: LoggingConfig; readonly dangerous?: DangerousConfig; readonly disable_path_apps?: boolean; - readonly max_session_expiry?: number; - readonly disable_session_expiry_refresh?: boolean; + readonly session_lifetime?: SessionLifetime; readonly disable_password_auth?: boolean; readonly support?: SupportConfig; readonly external_auth?: ExternalAuthConfig[]; @@ -998,6 +996,13 @@ export interface SessionCountDeploymentStats { readonly reconnecting_pty: number; } +// From codersdk/deployment.go +export interface SessionLifetime { + readonly disable_expiry_refresh?: boolean; + readonly default_duration: number; + readonly max_token_lifetime?: number; +} + // From codersdk/deployment.go export interface SupportConfig { readonly links: LinkConfig[]; From a607d5610e3be26833fb0f0f7b00544110e5f17b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Apr 2024 11:05:55 -0500 Subject: [PATCH 56/74] chore: disable pgcoord (HA) when --in-memory (#12919) * chore: disable pgcoord (HA) when --in-memory HA does not make any sense while using in-memory database --- enterprise/coderd/coderd.go | 8 +++++++- site/e2e/playwright.config.ts | 11 ++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 0ac2086f86..2bb9eb3bc0 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -630,7 +630,13 @@ func (api *API) updateEntitlements(ctx context.Context) error { if initial, changed, enabled := featureChanged(codersdk.FeatureHighAvailability); shouldUpdate(initial, changed, enabled) { var coordinator agpltailnet.Coordinator - if enabled { + // If HA is enabled, but the database is in-memory, we can't actually + // run HA and the PG coordinator. So throw a log line, and continue to use + // the in memory AGPL coordinator. + if enabled && api.DeploymentValues.InMemoryDatabase.Value() { + api.Logger.Warn(ctx, "high availability is enabled, but cannot be configured due to the database being set to in-memory") + } + if enabled && !api.DeploymentValues.InMemoryDatabase.Value() { haCoordinator, err := tailnet.NewPGCoord(api.ctx, api.Logger, api.Pubsub, api.Database) if err != nil { api.Logger.Error(ctx, "unable to set up high availability coordinator", slog.Error(err)) diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index d345de379d..5aa8c2186e 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -1,12 +1,6 @@ import { defineConfig } from "@playwright/test"; import * as path from "path"; -import { - coderMain, - coderPort, - coderdPProfPort, - enterpriseLicense, - gitAuth, -} from "./constants"; +import { coderMain, coderPort, coderdPProfPort, gitAuth } from "./constants"; export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT; @@ -54,8 +48,7 @@ export default defineConfig({ "--global-config $(mktemp -d -t e2e-XXXXXXXXXX)", `--access-url=http://localhost:${coderPort}`, `--http-address=localhost:${coderPort}`, - // Adding an enterprise license causes issues with pgcoord when running with `--in-memory`. - !enterpriseLicense && "--in-memory", + "--in-memory", "--telemetry=false", "--dangerous-disable-rate-limits", "--provisioner-daemons 10", From 06eae954c978db7767d984348f3d471f64858b19 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 10 Apr 2024 22:49:13 +0400 Subject: [PATCH 57/74] fix: stop sending DeleteTailnetPeer when coordinator is unhealthy (#12925) fixes #12923 Prevents Coordinate peer connections from generating spurious database queries like DeleteTailnetPeer when the coordinator is unhealthy. It does this by checking the health of the querier before accepting a connection, rather than unconditionally accepting it only for it to get swatted down later. --- enterprise/tailnet/pgcoord.go | 24 +++++++++- enterprise/tailnet/pgcoord_internal_test.go | 51 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/enterprise/tailnet/pgcoord.go b/enterprise/tailnet/pgcoord.go index aabb21eef6..aecdcde828 100644 --- a/enterprise/tailnet/pgcoord.go +++ b/enterprise/tailnet/pgcoord.go @@ -231,6 +231,17 @@ func (c *pgCoord) Coordinate( logger := c.logger.With(slog.F("peer_id", id)) reqs := make(chan *proto.CoordinateRequest, agpl.RequestBufferSize) resps := make(chan *proto.CoordinateResponse, agpl.ResponseBufferSize) + if !c.querier.isHealthy() { + // If the coordinator is unhealthy, we don't want to hook this Coordinate call up to the + // binder, as that can cause an unnecessary call to DeleteTailnetPeer when the connIO is + // closed. Instead, we just close the response channel and bail out. + // c.f. https://github.com/coder/coder/issues/12923 + c.logger.Info(ctx, "closed incoming coordinate call while unhealthy", + slog.F("peer_id", id), + ) + close(resps) + return reqs, resps + } cIO := newConnIO(c.ctx, ctx, logger, c.bindings, c.tunnelerCh, reqs, resps, id, name, a) err := agpl.SendCtx(c.ctx, c.newConnections, cIO) if err != nil { @@ -842,7 +853,12 @@ func (q *querier) newConn(c *connIO) { defer q.mu.Unlock() if !q.healthy { err := c.Close() - q.logger.Info(q.ctx, "closed incoming connection while unhealthy", + // This can only happen during a narrow window where we were healthy + // when pgCoord checked before accepting the connection, but now are + // unhealthy now that we get around to processing it. Seeing a small + // number of these logs is not worrying, but a large number probably + // indicates something is amiss. + q.logger.Warn(q.ctx, "closed incoming connection while unhealthy", slog.Error(err), slog.F("peer_id", c.UniqueID()), ) @@ -865,6 +881,12 @@ func (q *querier) newConn(c *connIO) { }) } +func (q *querier) isHealthy() bool { + q.mu.Lock() + defer q.mu.Unlock() + return q.healthy +} + func (q *querier) cleanupConn(c *connIO) { logger := q.logger.With(slog.F("peer_id", c.UniqueID())) q.mu.Lock() diff --git a/enterprise/tailnet/pgcoord_internal_test.go b/enterprise/tailnet/pgcoord_internal_test.go index d5b79d6225..b1bfb371f0 100644 --- a/enterprise/tailnet/pgcoord_internal_test.go +++ b/enterprise/tailnet/pgcoord_internal_test.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "golang.org/x/xerrors" gProto "google.golang.org/protobuf/proto" "cdr.dev/slog" @@ -21,6 +22,8 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbmock" "github.com/coder/coder/v2/coderd/database/dbtestutil" + "github.com/coder/coder/v2/coderd/database/pubsub" + agpl "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet/proto" "github.com/coder/coder/v2/testutil" ) @@ -291,3 +294,51 @@ func TestGetDebug(t *testing.T) { require.Equal(t, peerID, debug.Tunnels[0].SrcID) require.Equal(t, dstID, debug.Tunnels[0].DstID) } + +// TestPGCoordinatorUnhealthy tests that when the coordinator fails to send heartbeats and is +// unhealthy it disconnects any peers and does not send any extraneous database queries. +func TestPGCoordinatorUnhealthy(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) + + ctrl := gomock.NewController(t) + mStore := dbmock.NewMockStore(ctrl) + ps := pubsub.NewInMemory() + + // after 3 failed heartbeats, the coordinator is unhealthy + mStore.EXPECT(). + UpsertTailnetCoordinator(gomock.Any(), gomock.Any()). + MinTimes(3). + Return(database.TailnetCoordinator{}, xerrors.New("badness")) + mStore.EXPECT(). + DeleteCoordinator(gomock.Any(), gomock.Any()). + Times(1). + Return(nil) + // But, in particular we DO NOT want the coordinator to call DeleteTailnetPeer, as this is + // unnecessary and can spam the database. c.f. https://github.com/coder/coder/issues/12923 + + // these cleanup queries run, but we don't care for this test + mStore.EXPECT().CleanTailnetCoordinators(gomock.Any()).AnyTimes().Return(nil) + mStore.EXPECT().CleanTailnetLostPeers(gomock.Any()).AnyTimes().Return(nil) + mStore.EXPECT().CleanTailnetTunnels(gomock.Any()).AnyTimes().Return(nil) + + coordinator, err := newPGCoordInternal(ctx, logger, ps, mStore) + require.NoError(t, err) + + require.Eventually(t, func() bool { + return !coordinator.querier.isHealthy() + }, testutil.WaitShort, testutil.IntervalFast) + + pID := uuid.UUID{5} + _, resps := coordinator.Coordinate(ctx, pID, "test", agpl.AgentCoordinateeAuth{ID: pID}) + resp := testutil.RequireRecvCtx(ctx, t, resps) + require.Nil(t, resp, "channel should be closed") + + // give the coordinator some time to process any pending work. We are + // testing here that a database call is absent, so we don't want to race to + // shut down the test. + time.Sleep(testutil.IntervalMedium) + _ = coordinator.Close() + require.Eventually(t, ctrl.Satisfied, testutil.WaitShort, testutil.IntervalFast) +} From 566f8f231db75d8a558d2526c7fcb20e4cc80107 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Apr 2024 13:58:29 -0500 Subject: [PATCH 58/74] chore: add unit test for pass through external auth query params (#12928) * chore: verify pass through external auth query params Unit test added to verify behavior of query params set in the auth url for external apps. This behavior is intended to specifically support Auth0 audience query param. --- coderd/coderdtest/oidctest/idp.go | 4 +- coderd/coderdtest/oidctest/idp_test.go | 6 +- coderd/externalauth/externalauth_test.go | 79 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/coderd/coderdtest/oidctest/idp.go b/coderd/coderdtest/oidctest/idp.go index ff42e31997..c0b95619d4 100644 --- a/coderd/coderdtest/oidctest/idp.go +++ b/coderd/coderdtest/oidctest/idp.go @@ -604,7 +604,7 @@ func (f *FakeIDP) CreateAuthCode(t testing.TB, state string) string { // something. // Essentially this is used to fake the Coderd side of the exchange. // The flow starts at the user hitting the OIDC login page. -func (f *FakeIDP) OIDCCallback(t testing.TB, state string, idTokenClaims jwt.MapClaims) (*http.Response, error) { +func (f *FakeIDP) OIDCCallback(t testing.TB, state string, idTokenClaims jwt.MapClaims) *http.Response { t.Helper() if f.serve { panic("cannot use OIDCCallback with WithServing. This is only for the in memory usage") @@ -625,7 +625,7 @@ func (f *FakeIDP) OIDCCallback(t testing.TB, state string, idTokenClaims jwt.Map _ = resp.Body.Close() } }) - return resp, nil + return resp } // ProviderJSON is the .well-known/configuration JSON diff --git a/coderd/coderdtest/oidctest/idp_test.go b/coderd/coderdtest/oidctest/idp_test.go index 519635b067..7706834785 100644 --- a/coderd/coderdtest/oidctest/idp_test.go +++ b/coderd/coderdtest/oidctest/idp_test.go @@ -54,12 +54,12 @@ func TestFakeIDPBasicFlow(t *testing.T) { token = oauthToken }) - resp, err := fake.OIDCCallback(t, expectedState, jwt.MapClaims{}) - require.NoError(t, err) + //nolint:bodyclose + resp := fake.OIDCCallback(t, expectedState, jwt.MapClaims{}) require.Equal(t, http.StatusOK, resp.StatusCode) // Test the user info - _, err = cfg.Provider.UserInfo(ctx, oauth2.StaticTokenSource(token)) + _, err := cfg.Provider.UserInfo(ctx, oauth2.StaticTokenSource(token)) require.NoError(t, err) // Now test it can refresh diff --git a/coderd/externalauth/externalauth_test.go b/coderd/externalauth/externalauth_test.go index 84fbe4ff5d..88f3b7a3b5 100644 --- a/coderd/externalauth/externalauth_test.go +++ b/coderd/externalauth/externalauth_test.go @@ -3,9 +3,11 @@ package externalauth_test import ( "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "net/url" + "strings" "testing" "time" @@ -13,6 +15,7 @@ import ( "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/oauth2" "golang.org/x/xerrors" @@ -417,6 +420,78 @@ func TestConvertYAML(t *testing.T) { }) } +// TestConstantQueryParams verifies a constant query parameter can be set in the +// "authenticate" url for external auth applications, and it will be carried forward +// to actual auth requests. +// This unit test was specifically created for Auth0 which can set an +// audience query parameter in it's /authorize endpoint. +func TestConstantQueryParams(t *testing.T) { + t.Parallel() + const constantQueryParamKey = "audience" + const constantQueryParamValue = "foobar" + constantQueryParam := fmt.Sprintf("%s=%s", constantQueryParamKey, constantQueryParamValue) + fake, config, _ := setupOauth2Test(t, testConfig{ + FakeIDPOpts: []oidctest.FakeIDPOpt{ + oidctest.WithMiddlewares(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if strings.Contains(request.URL.Path, "authorize") { + // Assert has the audience query param + assert.Equal(t, request.URL.Query().Get(constantQueryParamKey), constantQueryParamValue) + } + next.ServeHTTP(writer, request) + }) + }), + }, + CoderOIDCConfigOpts: []func(cfg *coderd.OIDCConfig){ + func(cfg *coderd.OIDCConfig) { + // Include a constant query parameter. + authURL, err := url.Parse(cfg.OAuth2Config.(*oauth2.Config).Endpoint.AuthURL) + require.NoError(t, err) + + authURL.RawQuery = url.Values{constantQueryParamKey: []string{constantQueryParamValue}}.Encode() + cfg.OAuth2Config.(*oauth2.Config).Endpoint.AuthURL = authURL.String() + require.Contains(t, cfg.OAuth2Config.(*oauth2.Config).Endpoint.AuthURL, constantQueryParam) + }, + }, + }) + + callbackCalled := false + fake.SetCoderdCallbackHandler(func(writer http.ResponseWriter, request *http.Request) { + // Just record the callback was hit, and the auth succeeded. + callbackCalled = true + }) + + // Verify the AuthURL endpoint contains the constant query parameter and is a valid URL. + // It should look something like: + // http://127.0.0.1:>/oauth2/authorize? + // audience=foobar& + // client_id=d& + // redirect_uri=& + // response_type=code& + // scope=openid+email+profile& + // state=state + const state = "state" + rawAuthURL := config.AuthCodeURL(state) + // Parsing the url is not perfect. It allows imperfections like the query + // params having 2 question marks '?a=foo?b=bar'. + // So use it to validate, then verify the raw url is as expected. + authURL, err := url.Parse(rawAuthURL) + require.NoError(t, err) + require.Equal(t, authURL.Query().Get(constantQueryParamKey), constantQueryParamValue) + // We are not using a real server, so it fakes https://coder.com + require.Equal(t, authURL.Scheme, "https") + // Validate the raw URL. + // Double check only 1 '?' exists. Url parsing allows multiple '?' in the query string. + require.Equal(t, strings.Count(rawAuthURL, "?"), 1) + + // Actually run an auth request. Although it says OIDC, the flow is the same + // for oauth2. + //nolint:bodyclose + resp := fake.OIDCCallback(t, state, jwt.MapClaims{}) + require.True(t, callbackCalled) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + type testConfig struct { FakeIDPOpts []oidctest.FakeIDPOpt CoderOIDCConfigOpts []func(cfg *coderd.OIDCConfig) @@ -433,6 +508,10 @@ type testConfig struct { func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *externalauth.Config, database.ExternalAuthLink) { t.Helper() + if settings.ExternalAuthOpt == nil { + settings.ExternalAuthOpt = func(_ *externalauth.Config) {} + } + const providerID = "test-idp" fake := oidctest.NewFakeIDP(t, append([]oidctest.FakeIDPOpt{}, settings.FakeIDPOpts...)..., From 7fd9a75ad9c7cba3533c6eb7010684013faacded Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Apr 2024 14:08:25 -0500 Subject: [PATCH 59/74] chore: nix shell to support playwright e2e tests (#12917) * chore: nix shell to support playwright e2e tests nix is running an older version of chromium, so had to reduce the playwright version. * Add to e2e readme * add enterprise test comment * add note about install to readme * make fmt * remove shellhook message Co-authored-by: Kayla Washburn-Love * add link to nixos playwright package to get version * formatting --------- Co-authored-by: Kayla Washburn-Love --- flake.lock | 12 ++++++------ flake.nix | 9 ++++++++- site/e2e/README.md | 26 ++++++++++++++++++++++++++ site/package.json | 2 +- site/pnpm-lock.yaml | 20 ++++++++++---------- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/flake.lock b/flake.lock index fe4bb7c34f..2bbf425275 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -73,11 +73,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1706550542, - "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=", + "lastModified": 1712439257, + "narHash": "sha256-aSpiNepFOMk9932HOax0XwNxbA38GOUVOiXfUVPOrck=", "owner": "nixos", "repo": "nixpkgs", - "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652", + "rev": "ff0dbd94265ac470dda06a657d5fe49de93b4599", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f906f3c3d5..c0407c9e18 100644 --- a/flake.nix +++ b/flake.nix @@ -58,6 +58,7 @@ pango pixman pkg-config + playwright-driver.browsers postgresql_13 protobuf protoc-gen-go @@ -86,7 +87,13 @@ in { defaultPackage = formatter; # or replace it with your desired default package. - devShell = pkgs.mkShell { buildInputs = devShellPackages; }; + devShell = pkgs.mkShell { + buildInputs = devShellPackages; + shellHook = '' + export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} + export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true + ''; + }; packages.all = allPackages; } ); diff --git a/site/e2e/README.md b/site/e2e/README.md index 698d470e34..291344a67c 100644 --- a/site/e2e/README.md +++ b/site/e2e/README.md @@ -20,3 +20,29 @@ pnpm playwright:test # Run a specific test (`-g` stands for grep. It accepts regex). pnpm playwright:test -g '' ``` + +# Using nix + +If this breaks, it is likely because the flake chromium version and playwright +are no longer compatible. To fix this, update the flake to get the latest +chromium version, and adjust the playwright version in the package.json. + +You can see the playwright version here: +https://search.nixos.org/packages?channel=unstable&show=playwright-driver&from=0&size=50&sort=relevance&type=packages&query=playwright-driver + +```shell +# Optionally add '--command zsh' to choose your shell. +nix develop +cd site +pnpm install +pnpm build +pnpm playwright:test +``` + +# Enterprise tests + +Enterprise tests require a license key to run. + +```shell +export CODER_E2E_ENTERPRISE_LICENSE= +``` diff --git a/site/package.json b/site/package.json index 4d2ffd9fd6..016f0bdafa 100644 --- a/site/package.json +++ b/site/package.json @@ -95,7 +95,7 @@ }, "devDependencies": { "@octokit/types": "12.3.0", - "@playwright/test": "1.42.1", + "@playwright/test": "1.40.1", "@storybook/addon-actions": "8.0.5", "@storybook/addon-essentials": "8.0.5", "@storybook/addon-interactions": "8.0.5", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 0f135505b2..b723b4cf25 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -204,8 +204,8 @@ devDependencies: specifier: 12.3.0 version: 12.3.0 '@playwright/test': - specifier: 1.42.1 - version: 1.42.1 + specifier: 1.40.1 + version: 1.40.1 '@storybook/addon-actions': specifier: 8.0.5 version: 8.0.5 @@ -3295,12 +3295,12 @@ packages: dev: true optional: true - /@playwright/test@1.42.1: - resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==} + /@playwright/test@1.40.1: + resolution: {integrity: sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==} engines: {node: '>=16'} hasBin: true dependencies: - playwright: 1.42.1 + playwright: 1.40.1 dev: true /@popperjs/core@2.11.8: @@ -10706,18 +10706,18 @@ packages: find-up: 5.0.0 dev: true - /playwright-core@1.42.1: - resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==} + /playwright-core@1.40.1: + resolution: {integrity: sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==} engines: {node: '>=16'} hasBin: true dev: true - /playwright@1.42.1: - resolution: {integrity: sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==} + /playwright@1.40.1: + resolution: {integrity: sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==} engines: {node: '>=16'} hasBin: true dependencies: - playwright-core: 1.42.1 + playwright-core: 1.40.1 optionalDependencies: fsevents: 2.3.2 dev: true From 9cf2358114a3c35386ea2f80cd57930a99e6c7f9 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Wed, 10 Apr 2024 15:42:53 -0600 Subject: [PATCH 60/74] ci: execute enterprise and non-enterprise e2e tests concurrently (#12872) --- .github/workflows/ci.yaml | 50 +++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b255a0e442..912d68dd58 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -432,6 +432,15 @@ jobs: needs: changes if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main' timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + variant: + - enterprise: false + name: test-e2e + - enterprise: true + name: test-e2e-enterprise + name: ${{ matrix.variant.name }} steps: - name: Checkout uses: actions/checkout@v4 @@ -444,40 +453,35 @@ jobs: - name: Setup Go uses: ./.github/actions/setup-go - - name: Setup Terraform - uses: ./.github/actions/setup-tf + # Assume that the checked-in versions are up-to-date + - run: make gen/mark-fresh + name: make gen - - name: go install tools - run: | - go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30 - go install storj.io/drpc/cmd/protoc-gen-go-drpc@v0.0.33 - go install golang.org/x/tools/cmd/goimports@latest - go install github.com/mikefarah/yq/v4@v4.30.6 - go install go.uber.org/mock/mockgen@v0.4.0 - - - name: Install Protoc - run: | - mkdir -p /tmp/proto - pushd /tmp/proto - curl -L -o protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v23.3/protoc-23.3-linux-x86_64.zip - unzip protoc.zip - cp -r ./bin/* /usr/local/bin - cp -r ./include /usr/local/bin/include - popd - - - name: Build - run: | - make -B site/out/index.html + - run: pnpm build + working-directory: site - run: pnpm playwright:install working-directory: site # Run tests that don't require an enterprise license without an enterprise license - run: pnpm playwright:test --forbid-only --workers 1 + if: ${{ !matrix.variant.enterprise }} env: DEBUG: pw:api working-directory: site + # Run all of the tests with an enterprise license + - run: pnpm playwright:test --forbid-only --workers 1 + if: ${{ matrix.variant.enterprise }} + env: + DEBUG: pw:api + CODER_E2E_ENTERPRISE_LICENSE: ${{ secrets.CODER_E2E_ENTERPRISE_LICENSE }} + CODER_E2E_REQUIRE_ENTERPRISE_TESTS: "1" + working-directory: site + # Temporarily allow these to fail so that I can gather data about which + # tests are failing. + continue-on-error: true + - name: Upload Playwright Failed Tests if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 From e801e878ba8d27c90da0a17635928a4537db07f8 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 10 Apr 2024 17:15:33 -0500 Subject: [PATCH 61/74] feat: add agent acks to in-memory coordinator (#12786) When an agent receives a node, it responds with an ACK which is relayed to the client. After the client receives the ACK, it's allowed to begin pinging. --- codersdk/workspacesdk/connector.go | 4 +- ...nal_test.go => connector_internal_test.go} | 2 + enterprise/coderd/workspaceproxy.go | 2 +- tailnet/configmaps.go | 202 +++++++++++--- tailnet/configmaps_internal_test.go | 255 +++++++++++++++++- tailnet/conn.go | 5 +- tailnet/coordinator.go | 88 ++++++ tailnet/coordinator_test.go | 143 ++++++++++ tailnet/proto/tailnet.pb.go | 252 +++++++++++------ tailnet/proto/tailnet.proto | 11 + tailnet/tailnettest/coordinateemock.go | 13 + tailnet/tunnel.go | 10 + tailnet/tunnel_internal_test.go | 15 ++ 13 files changed, 879 insertions(+), 123 deletions(-) rename codersdk/workspacesdk/{workspacesdk_internal_test.go => connector_internal_test.go} (98%) diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go index 5c1d9e600a..7955e8fb33 100644 --- a/codersdk/workspacesdk/connector.go +++ b/codersdk/workspacesdk/connector.go @@ -86,9 +86,11 @@ func runTailnetAPIConnector( func (tac *tailnetAPIConnector) manageGracefulTimeout() { defer tac.cancelGracefulCtx() <-tac.ctx.Done() + timer := time.NewTimer(time.Second) + defer timer.Stop() select { case <-tac.closed: - case <-time.After(time.Second): + case <-timer.C: } } diff --git a/codersdk/workspacesdk/workspacesdk_internal_test.go b/codersdk/workspacesdk/connector_internal_test.go similarity index 98% rename from codersdk/workspacesdk/workspacesdk_internal_test.go rename to codersdk/workspacesdk/connector_internal_test.go index 57e6f751ff..9f70891fda 100644 --- a/codersdk/workspacesdk/workspacesdk_internal_test.go +++ b/codersdk/workspacesdk/connector_internal_test.go @@ -102,6 +102,8 @@ func (*fakeTailnetConn) SetNodeCallback(func(*tailnet.Node)) {} func (*fakeTailnetConn) SetDERPMap(*tailcfg.DERPMap) {} +func (*fakeTailnetConn) SetTunnelDestination(uuid.UUID) {} + func newFakeTailnetConn() *fakeTailnetConn { return &fakeTailnetConn{} } diff --git a/enterprise/coderd/workspaceproxy.go b/enterprise/coderd/workspaceproxy.go index 379d01ad43..234212f479 100644 --- a/enterprise/coderd/workspaceproxy.go +++ b/enterprise/coderd/workspaceproxy.go @@ -658,7 +658,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request) if err != nil { return xerrors.Errorf("insert replica: %w", err) } - } else if err != nil { + } else { return xerrors.Errorf("get replica: %w", err) } diff --git a/tailnet/configmaps.go b/tailnet/configmaps.go index 57a2d9f2d1..8b3aee1585 100644 --- a/tailnet/configmaps.go +++ b/tailnet/configmaps.go @@ -186,7 +186,7 @@ func (c *configMaps) close() { c.L.Lock() defer c.L.Unlock() for _, lc := range c.peers { - lc.resetTimer() + lc.resetLostTimer() } c.closing = true c.Broadcast() @@ -216,6 +216,12 @@ func (c *configMaps) netMapLocked() *netmap.NetworkMap { func (c *configMaps) peerConfigLocked() []*tailcfg.Node { out := make([]*tailcfg.Node, 0, len(c.peers)) for _, p := range c.peers { + // Don't add nodes that we havent received a READY_FOR_HANDSHAKE for + // yet, if they're a destination. If we received a READY_FOR_HANDSHAKE + // for a peer before we receive their node, the node will be nil. + if (!p.readyForHandshake && p.isDestination) || p.node == nil { + continue + } n := p.node.Clone() if c.blockEndpoints { n.Endpoints = nil @@ -225,6 +231,19 @@ func (c *configMaps) peerConfigLocked() []*tailcfg.Node { return out } +func (c *configMaps) setTunnelDestination(id uuid.UUID) { + c.L.Lock() + defer c.L.Unlock() + lc, ok := c.peers[id] + if !ok { + lc = &peerLifecycle{ + peerID: id, + } + c.peers[id] = lc + } + lc.isDestination = true +} + // setAddresses sets the addresses belonging to this node to the given slice. It // triggers configuration of the engine if the addresses have changed. // c.L MUST NOT be held. @@ -331,8 +350,10 @@ func (c *configMaps) updatePeers(updates []*proto.CoordinateResponse_PeerUpdate) // worry about them being up-to-date when handling updates below, and it covers // all peers, not just the ones we got updates about. for _, lc := range c.peers { - if peerStatus, ok := status.Peer[lc.node.Key]; ok { - lc.lastHandshake = peerStatus.LastHandshake + if lc.node != nil { + if peerStatus, ok := status.Peer[lc.node.Key]; ok { + lc.lastHandshake = peerStatus.LastHandshake + } } } @@ -363,7 +384,7 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger := c.logger.With(slog.F("peer_id", id)) - lc, ok := c.peers[id] + lc, peerOk := c.peers[id] var node *tailcfg.Node if update.Kind == proto.CoordinateResponse_PeerUpdate_NODE { // If no preferred DERP is provided, we can't reach the node. @@ -377,48 +398,76 @@ func (c *configMaps) updatePeerLocked(update *proto.CoordinateResponse_PeerUpdat return false } logger = logger.With(slog.F("key_id", node.Key.ShortString()), slog.F("node", node)) - peerStatus, ok := status.Peer[node.Key] - // Starting KeepAlive messages at the initialization of a connection - // causes a race condition. If we send the handshake before the peer has - // our node, we'll have to wait for 5 seconds before trying again. - // Ideally, the first handshake starts when the user first initiates a - // connection to the peer. After a successful connection we enable - // keep alives to persist the connection and keep it from becoming idle. - // SSH connections don't send packets while idle, so we use keep alives - // to avoid random hangs while we set up the connection again after - // inactivity. - node.KeepAlive = ok && peerStatus.Active + node.KeepAlive = c.nodeKeepalive(lc, status, node) } switch { - case !ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: + case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // new! var lastHandshake time.Time if ps, ok := status.Peer[node.Key]; ok { lastHandshake = ps.LastHandshake } - c.peers[id] = &peerLifecycle{ + lc = &peerLifecycle{ peerID: id, node: node, lastHandshake: lastHandshake, lost: false, } + c.peers[id] = lc logger.Debug(context.Background(), "adding new peer") - return true - case ok && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: + return lc.validForWireguard() + case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_NODE: // update - node.Created = lc.node.Created + if lc.node != nil { + node.Created = lc.node.Created + } dirty = !lc.node.Equal(node) lc.node = node + // validForWireguard checks that the node is non-nil, so should be + // called after we update the node. + dirty = dirty && lc.validForWireguard() lc.lost = false - lc.resetTimer() + lc.resetLostTimer() + if lc.isDestination && !lc.readyForHandshake { + // We received the node of a destination peer before we've received + // their READY_FOR_HANDSHAKE. Set a timer + lc.setReadyForHandshakeTimer(c) + logger.Debug(context.Background(), "setting ready for handshake timeout") + } logger.Debug(context.Background(), "node update to existing peer", slog.F("dirty", dirty)) return dirty - case !ok: + case peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: + dirty := !lc.readyForHandshake + lc.readyForHandshake = true + if lc.readyForHandshakeTimer != nil { + lc.readyForHandshakeTimer.Stop() + } + if lc.node != nil { + old := lc.node.KeepAlive + lc.node.KeepAlive = c.nodeKeepalive(lc, status, lc.node) + dirty = dirty || (old != lc.node.KeepAlive) + } + logger.Debug(context.Background(), "peer ready for handshake") + // only force a reconfig if the node populated + return dirty && lc.node != nil + case !peerOk && update.Kind == proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE: + // When we receive a READY_FOR_HANDSHAKE for a peer we don't know about, + // we create a peerLifecycle with the peerID and set readyForHandshake + // to true. Eventually we should receive a NODE update for this peer, + // and it'll be programmed into wireguard. + logger.Debug(context.Background(), "got peer ready for handshake for unknown peer") + lc = &peerLifecycle{ + peerID: id, + readyForHandshake: true, + } + c.peers[id] = lc + return false + case !peerOk: // disconnected or lost, but we don't have the node. No op logger.Debug(context.Background(), "skipping update for peer we don't recognize") return false case update.Kind == proto.CoordinateResponse_PeerUpdate_DISCONNECTED: - lc.resetTimer() + lc.resetLostTimer() delete(c.peers, id) logger.Debug(context.Background(), "disconnected peer") return true @@ -476,10 +525,12 @@ func (c *configMaps) peerLostTimeout(id uuid.UUID) { "timeout triggered for peer that is removed from the map") return } - if peerStatus, ok := status.Peer[lc.node.Key]; ok { - lc.lastHandshake = peerStatus.LastHandshake + if lc.node != nil { + if peerStatus, ok := status.Peer[lc.node.Key]; ok { + lc.lastHandshake = peerStatus.LastHandshake + } + logger = logger.With(slog.F("key_id", lc.node.Key.ShortString())) } - logger = logger.With(slog.F("key_id", lc.node.Key.ShortString())) if !lc.lost { logger.Debug(context.Background(), "timeout triggered for peer that is no longer lost") @@ -522,7 +573,7 @@ func (c *configMaps) nodeAddresses(publicKey key.NodePublic) ([]netip.Prefix, bo c.L.Lock() defer c.L.Unlock() for _, lc := range c.peers { - if lc.node.Key == publicKey { + if lc.node != nil && lc.node.Key == publicKey { return lc.node.Addresses, true } } @@ -539,9 +590,10 @@ func (c *configMaps) fillPeerDiagnostics(d *PeerDiagnostics, peerID uuid.UUID) { } } lc, ok := c.peers[peerID] - if !ok { + if !ok || lc.node == nil { return } + d.ReceivedNode = lc.node ps, ok := status.Peer[lc.node.Key] if !ok { @@ -550,34 +602,102 @@ func (c *configMaps) fillPeerDiagnostics(d *PeerDiagnostics, peerID uuid.UUID) { d.LastWireguardHandshake = ps.LastHandshake } -type peerLifecycle struct { - peerID uuid.UUID - node *tailcfg.Node - lost bool - lastHandshake time.Time - timer *clock.Timer +func (c *configMaps) peerReadyForHandshakeTimeout(peerID uuid.UUID) { + logger := c.logger.With(slog.F("peer_id", peerID)) + logger.Debug(context.Background(), "peer ready for handshake timeout") + c.L.Lock() + defer c.L.Unlock() + lc, ok := c.peers[peerID] + if !ok { + logger.Debug(context.Background(), + "ready for handshake timeout triggered for peer that is removed from the map") + return + } + + wasReady := lc.readyForHandshake + lc.readyForHandshake = true + if !wasReady { + logger.Info(context.Background(), "setting peer ready for handshake after timeout") + c.netmapDirty = true + c.Broadcast() + } } -func (l *peerLifecycle) resetTimer() { - if l.timer != nil { - l.timer.Stop() - l.timer = nil +func (*configMaps) nodeKeepalive(lc *peerLifecycle, status *ipnstate.Status, node *tailcfg.Node) bool { + // If the peer is already active, keepalives should be enabled. + if peerStatus, statusOk := status.Peer[node.Key]; statusOk && peerStatus.Active { + return true + } + // If the peer is a destination, we should only enable keepalives if we've + // received the READY_FOR_HANDSHAKE. + if lc != nil && lc.isDestination && lc.readyForHandshake { + return true + } + + // If none of the above are true, keepalives should not be enabled. + return false +} + +type peerLifecycle struct { + peerID uuid.UUID + // isDestination specifies if the peer is a destination, meaning we + // initiated a tunnel to the peer. When the peer is a destination, we do not + // respond to node updates with `READY_FOR_HANDSHAKE`s, and we wait to + // program the peer into wireguard until we receive a READY_FOR_HANDSHAKE + // from the peer or the timeout is reached. + isDestination bool + // node is the tailcfg.Node for the peer. It may be nil until we receive a + // NODE update for it. + node *tailcfg.Node + lost bool + lastHandshake time.Time + lostTimer *clock.Timer + readyForHandshake bool + readyForHandshakeTimer *clock.Timer +} + +func (l *peerLifecycle) resetLostTimer() { + if l.lostTimer != nil { + l.lostTimer.Stop() + l.lostTimer = nil } } func (l *peerLifecycle) setLostTimer(c *configMaps) { - if l.timer != nil { - l.timer.Stop() + if l.lostTimer != nil { + l.lostTimer.Stop() } ttl := lostTimeout - c.clock.Since(l.lastHandshake) if ttl <= 0 { ttl = time.Nanosecond } - l.timer = c.clock.AfterFunc(ttl, func() { + l.lostTimer = c.clock.AfterFunc(ttl, func() { c.peerLostTimeout(l.peerID) }) } +const readyForHandshakeTimeout = 5 * time.Second + +func (l *peerLifecycle) setReadyForHandshakeTimer(c *configMaps) { + if l.readyForHandshakeTimer != nil { + l.readyForHandshakeTimer.Stop() + } + l.readyForHandshakeTimer = c.clock.AfterFunc(readyForHandshakeTimeout, func() { + c.logger.Debug(context.Background(), "ready for handshake timeout", slog.F("peer_id", l.peerID)) + c.peerReadyForHandshakeTimeout(l.peerID) + }) +} + +// validForWireguard returns true if the peer is ready to be programmed into +// wireguard. +func (l *peerLifecycle) validForWireguard() bool { + valid := l.node != nil + if l.isDestination { + return valid && l.readyForHandshake + } + return valid +} + // prefixesDifferent returns true if the two slices contain different prefixes // where order doesn't matter. func prefixesDifferent(a, b []netip.Prefix) bool { diff --git a/tailnet/configmaps_internal_test.go b/tailnet/configmaps_internal_test.go index 1008562904..49171ecf03 100644 --- a/tailnet/configmaps_internal_test.go +++ b/tailnet/configmaps_internal_test.go @@ -185,6 +185,258 @@ func TestConfigMaps_updatePeers_new(t *testing.T) { _ = testutil.RequireRecvCtx(ctx, t, done) } +func TestConfigMaps_updatePeers_new_waitForHandshake_neverConfigures(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + uut.setTunnelDestination(p1ID) + + // it should not send the peer to the netmap + requireNeverConfigures(ctx, t, &uut.phased) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + +func TestConfigMaps_updatePeers_new_waitForHandshake_outOfOrder(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + uut.setTunnelDestination(p1ID) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u2 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, + }, + } + uut.updatePeers(u2) + + // it should not send the peer to the netmap yet + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + // it should now send the peer to the netmap + + nm := testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap) + r := testutil.RequireRecvCtx(ctx, t, fEng.reconfig) + + require.Len(t, nm.Peers, 1) + n1 := getNodeWithID(t, nm.Peers, 1) + require.Equal(t, "127.3.3.40:1", n1.DERP) + require.Equal(t, p1Node.Endpoints, n1.Endpoints) + require.True(t, n1.KeepAlive) + + // we rely on nmcfg.WGCfg() to convert the netmap to wireguard config, so just + // require the right number of peers. + require.Len(t, r.wg.Peers, 1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + +func TestConfigMaps_updatePeers_new_waitForHandshake(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + uut.setTunnelDestination(p1ID) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + // it should not send the peer to the netmap yet + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u2 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, + }, + } + uut.updatePeers(u2) + + // it should now send the peer to the netmap + + nm := testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap) + r := testutil.RequireRecvCtx(ctx, t, fEng.reconfig) + + require.Len(t, nm.Peers, 1) + n1 := getNodeWithID(t, nm.Peers, 1) + require.Equal(t, "127.3.3.40:1", n1.DERP) + require.Equal(t, p1Node.Endpoints, n1.Endpoints) + require.True(t, n1.KeepAlive) + + // we rely on nmcfg.WGCfg() to convert the netmap to wireguard config, so just + // require the right number of peers. + require.Len(t, r.wg.Peers, 1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + +func TestConfigMaps_updatePeers_new_waitForHandshake_timeout(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + fEng := newFakeEngineConfigurable() + nodePrivateKey := key.NewNode() + nodeID := tailcfg.NodeID(5) + discoKey := key.NewDisco() + uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public()) + defer uut.close() + start := time.Date(2024, time.March, 29, 8, 0, 0, 0, time.UTC) + mClock := clock.NewMock() + mClock.Set(start) + uut.clock = mClock + + p1ID := uuid.UUID{1} + p1Node := newTestNode(1) + p1n, err := NodeToProto(p1Node) + require.NoError(t, err) + uut.setTunnelDestination(p1ID) + + go func() { + <-fEng.status + fEng.statusDone <- struct{}{} + }() + + u1 := []*proto.CoordinateResponse_PeerUpdate{ + { + Id: p1ID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: p1n, + }, + } + uut.updatePeers(u1) + + mClock.Add(5 * time.Second) + + // it should now send the peer to the netmap + + nm := testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap) + r := testutil.RequireRecvCtx(ctx, t, fEng.reconfig) + + require.Len(t, nm.Peers, 1) + n1 := getNodeWithID(t, nm.Peers, 1) + require.Equal(t, "127.3.3.40:1", n1.DERP) + require.Equal(t, p1Node.Endpoints, n1.Endpoints) + require.False(t, n1.KeepAlive) + + // we rely on nmcfg.WGCfg() to convert the netmap to wireguard config, so just + // require the right number of peers. + require.Len(t, r.wg.Peers, 1) + + done := make(chan struct{}) + go func() { + defer close(done) + uut.close() + }() + _ = testutil.RequireRecvCtx(ctx, t, done) +} + func TestConfigMaps_updatePeers_same(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitShort) @@ -274,7 +526,7 @@ func TestConfigMaps_updatePeers_disconnect(t *testing.T) { peerID: p1ID, node: p1tcn, lastHandshake: time.Date(2024, 1, 7, 12, 0, 10, 0, time.UTC), - timer: timer, + lostTimer: timer, } uut.L.Unlock() @@ -947,6 +1199,7 @@ func requireNeverConfigures(ctx context.Context, t *testing.T, uut *phased) { t.Helper() waiting := make(chan struct{}) go func() { + t.Helper() // ensure that we never configure, and go straight to closed uut.L.Lock() defer uut.L.Unlock() diff --git a/tailnet/conn.go b/tailnet/conn.go index e6dbdfdc38..d4d58c7cc9 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -88,7 +88,6 @@ type Options struct { // falling back. This is useful for misbehaving proxies that prevent // fallback due to odd behavior, like Azure App Proxy. DERPForceWebSockets bool - // BlockEndpoints specifies whether P2P endpoints are blocked. // If so, only DERPs can establish connections. BlockEndpoints bool @@ -311,6 +310,10 @@ type Conn struct { trafficStats *connstats.Statistics } +func (c *Conn) SetTunnelDestination(id uuid.UUID) { + c.configMaps.setTunnelDestination(id) +} + func (c *Conn) GetBlockEndpoints() bool { return c.configMaps.getBlockEndpoints() && c.nodeUpdater.getBlockEndpoints() } diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index ce9c8e99b2..95f61637f7 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -99,6 +99,9 @@ type Coordinatee interface { UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error SetAllPeersLost() SetNodeCallback(func(*Node)) + // SetTunnelDestination indicates to tailnet that the peer id is a + // destination. + SetTunnelDestination(id uuid.UUID) } type Coordination interface { @@ -111,6 +114,7 @@ type remoteCoordination struct { closed bool errChan chan error coordinatee Coordinatee + tgt uuid.UUID logger slog.Logger protocol proto.DRPCTailnet_CoordinateClient respLoopDone chan struct{} @@ -161,11 +165,37 @@ func (c *remoteCoordination) respLoop() { c.sendErr(xerrors.Errorf("read: %w", err)) return } + err = c.coordinatee.UpdatePeers(resp.GetPeerUpdates()) if err != nil { c.sendErr(xerrors.Errorf("update peers: %w", err)) return } + + // Only send acks from peers without a target. + if c.tgt == uuid.Nil { + // Send an ack back for all received peers. This could + // potentially be smarter to only send an ACK once per client, + // but there's nothing currently stopping clients from reusing + // IDs. + rfh := []*proto.CoordinateRequest_ReadyForHandshake{} + for _, peer := range resp.GetPeerUpdates() { + if peer.Kind != proto.CoordinateResponse_PeerUpdate_NODE { + continue + } + + rfh = append(rfh, &proto.CoordinateRequest_ReadyForHandshake{Id: peer.Id}) + } + if len(rfh) > 0 { + err := c.protocol.Send(&proto.CoordinateRequest{ + ReadyForHandshake: rfh, + }) + if err != nil { + c.sendErr(xerrors.Errorf("send: %w", err)) + return + } + } + } } } @@ -179,11 +209,14 @@ func NewRemoteCoordination(logger slog.Logger, c := &remoteCoordination{ errChan: make(chan error, 1), coordinatee: coordinatee, + tgt: tunnelTarget, logger: logger, protocol: protocol, respLoopDone: make(chan struct{}), } if tunnelTarget != uuid.Nil { + // TODO: reenable in upstack PR + // c.coordinatee.SetTunnelDestination(tunnelTarget) c.Lock() err := c.protocol.Send(&proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{Id: tunnelTarget[:]}}) c.Unlock() @@ -327,6 +360,13 @@ func (c *inMemoryCoordination) respLoop() { } } +func (*inMemoryCoordination) AwaitAck() <-chan struct{} { + // This is only used for tests, so just return a closed channel. + ch := make(chan struct{}) + close(ch) + return ch +} + func (c *inMemoryCoordination) Close() error { c.Lock() defer c.Unlock() @@ -658,6 +698,54 @@ func (c *core) handleRequest(p *peer, req *proto.CoordinateRequest) error { if req.Disconnect != nil { c.removePeerLocked(p.id, proto.CoordinateResponse_PeerUpdate_DISCONNECTED, "graceful disconnect") } + if rfhs := req.ReadyForHandshake; rfhs != nil { + err := c.handleReadyForHandshakeLocked(pr, rfhs) + if err != nil { + return xerrors.Errorf("handle ack: %w", err) + } + } + return nil +} + +func (c *core) handleReadyForHandshakeLocked(src *peer, rfhs []*proto.CoordinateRequest_ReadyForHandshake) error { + for _, rfh := range rfhs { + dstID, err := uuid.FromBytes(rfh.Id) + if err != nil { + // this shouldn't happen unless there is a client error. Close the connection so the client + // doesn't just happily continue thinking everything is fine. + return xerrors.Errorf("unable to convert bytes to UUID: %w", err) + } + + if !c.tunnels.tunnelExists(src.id, dstID) { + // We intentionally do not return an error here, since it's + // inherently racy. It's possible for a source to connect, then + // subsequently disconnect before the agent has sent back the RFH. + // Since this could potentially happen to a non-malicious agent, we + // don't want to kill its connection. + select { + case src.resps <- &proto.CoordinateResponse{ + Error: fmt.Sprintf("you do not share a tunnel with %q", dstID.String()), + }: + default: + return ErrWouldBlock + } + continue + } + + dst, ok := c.peers[dstID] + if ok { + select { + case dst.resps <- &proto.CoordinateResponse{ + PeerUpdates: []*proto.CoordinateResponse_PeerUpdate{{ + Id: src.id[:], + Kind: proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, + }}, + }: + default: + return ErrWouldBlock + } + } + } return nil } diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index d8a6f297b5..c4e269c53c 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -412,6 +412,68 @@ func TestCoordinator(t *testing.T) { _ = testutil.RequireRecvCtx(ctx, t, clientErrChan) _ = testutil.RequireRecvCtx(ctx, t, closeClientChan) }) + + t.Run("AgentAck", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + coordinator := tailnet.NewCoordinator(logger) + ctx := testutil.Context(t, testutil.WaitShort) + + clientID := uuid.New() + agentID := uuid.New() + + aReq, aRes := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) + cReq, cRes := coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) + + { + nk, err := key.NewNode().Public().MarshalBinary() + require.NoError(t, err) + dk, err := key.NewDisco().Public().MarshalText() + require.NoError(t, err) + cReq <- &proto.CoordinateRequest{UpdateSelf: &proto.CoordinateRequest_UpdateSelf{ + Node: &proto.Node{ + Id: 3, + Key: nk, + Disco: string(dk), + }, + }} + } + + cReq <- &proto.CoordinateRequest{AddTunnel: &proto.CoordinateRequest_Tunnel{ + Id: agentID[:], + }} + + testutil.RequireRecvCtx(ctx, t, aRes) + + aReq <- &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{{ + Id: clientID[:], + }}} + ack := testutil.RequireRecvCtx(ctx, t, cRes) + require.NotNil(t, ack.PeerUpdates) + require.Len(t, ack.PeerUpdates, 1) + require.Equal(t, proto.CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE, ack.PeerUpdates[0].Kind) + require.Equal(t, agentID[:], ack.PeerUpdates[0].Id) + }) + + t.Run("AgentAck_NoPermission", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug) + coordinator := tailnet.NewCoordinator(logger) + ctx := testutil.Context(t, testutil.WaitShort) + + clientID := uuid.New() + agentID := uuid.New() + + aReq, aRes := coordinator.Coordinate(ctx, agentID, agentID.String(), tailnet.AgentCoordinateeAuth{ID: agentID}) + _, _ = coordinator.Coordinate(ctx, clientID, clientID.String(), tailnet.ClientCoordinateeAuth{AgentID: agentID}) + + aReq <- &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{{ + Id: clientID[:], + }}} + + rfhError := testutil.RequireRecvCtx(ctx, t, aRes) + require.NotEmpty(t, rfhError.Error) + }) } // TestCoordinator_AgentUpdateWhileClientConnects tests for regression on @@ -638,6 +700,76 @@ func TestRemoteCoordination(t *testing.T) { } } +func TestRemoteCoordination_SendsReadyForHandshake(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitShort) + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + clientID := uuid.UUID{1} + agentID := uuid.UUID{2} + mCoord := tailnettest.NewMockCoordinator(gomock.NewController(t)) + fConn := &fakeCoordinatee{} + + reqs := make(chan *proto.CoordinateRequest, 100) + resps := make(chan *proto.CoordinateResponse, 100) + mCoord.EXPECT().Coordinate(gomock.Any(), clientID, gomock.Any(), tailnet.ClientCoordinateeAuth{agentID}). + Times(1).Return(reqs, resps) + + var coord tailnet.Coordinator = mCoord + coordPtr := atomic.Pointer[tailnet.Coordinator]{} + coordPtr.Store(&coord) + svc, err := tailnet.NewClientService( + logger.Named("svc"), &coordPtr, + time.Hour, + func() *tailcfg.DERPMap { panic("not implemented") }, + ) + require.NoError(t, err) + sC, cC := net.Pipe() + + serveErr := make(chan error, 1) + go func() { + err := svc.ServeClient(ctx, proto.CurrentVersion.String(), sC, clientID, agentID) + serveErr <- err + }() + + client, err := tailnet.NewDRPCClient(cC, logger) + require.NoError(t, err) + protocol, err := client.Coordinate(ctx) + require.NoError(t, err) + + uut := tailnet.NewRemoteCoordination(logger.Named("coordination"), protocol, fConn, uuid.UUID{}) + defer uut.Close() + + nk, err := key.NewNode().Public().MarshalBinary() + require.NoError(t, err) + dk, err := key.NewDisco().Public().MarshalText() + require.NoError(t, err) + testutil.RequireSendCtx(ctx, t, resps, &proto.CoordinateResponse{ + PeerUpdates: []*proto.CoordinateResponse_PeerUpdate{{ + Id: clientID[:], + Kind: proto.CoordinateResponse_PeerUpdate_NODE, + Node: &proto.Node{ + Id: 3, + Key: nk, + Disco: string(dk), + }, + }}, + }) + + rfh := testutil.RequireRecvCtx(ctx, t, reqs) + require.NotNil(t, rfh.ReadyForHandshake) + require.Len(t, rfh.ReadyForHandshake, 1) + require.Equal(t, clientID[:], rfh.ReadyForHandshake[0].Id) + + require.NoError(t, uut.Close()) + + select { + case err := <-uut.Error(): + require.ErrorContains(t, err, "stream terminated by sending close") + default: + // OK! + } +} + // coordinationTest tests that a coordination behaves correctly func coordinationTest( ctx context.Context, t *testing.T, @@ -698,6 +830,7 @@ type fakeCoordinatee struct { callback func(*tailnet.Node) updates [][]*proto.CoordinateResponse_PeerUpdate setAllPeersLostCalls int + tunnelDestinations map[uuid.UUID]struct{} } func (f *fakeCoordinatee) UpdatePeers(updates []*proto.CoordinateResponse_PeerUpdate) error { @@ -713,6 +846,16 @@ func (f *fakeCoordinatee) SetAllPeersLost() { f.setAllPeersLostCalls++ } +func (f *fakeCoordinatee) SetTunnelDestination(id uuid.UUID) { + f.Lock() + defer f.Unlock() + + if f.tunnelDestinations == nil { + f.tunnelDestinations = map[uuid.UUID]struct{}{} + } + f.tunnelDestinations[id] = struct{}{} +} + func (f *fakeCoordinatee) SetNodeCallback(callback func(*tailnet.Node)) { f.Lock() defer f.Unlock() diff --git a/tailnet/proto/tailnet.pb.go b/tailnet/proto/tailnet.pb.go index 63444f2173..5f623cf2b8 100644 --- a/tailnet/proto/tailnet.pb.go +++ b/tailnet/proto/tailnet.pb.go @@ -24,10 +24,11 @@ const ( type CoordinateResponse_PeerUpdate_Kind int32 const ( - CoordinateResponse_PeerUpdate_KIND_UNSPECIFIED CoordinateResponse_PeerUpdate_Kind = 0 - CoordinateResponse_PeerUpdate_NODE CoordinateResponse_PeerUpdate_Kind = 1 - CoordinateResponse_PeerUpdate_DISCONNECTED CoordinateResponse_PeerUpdate_Kind = 2 - CoordinateResponse_PeerUpdate_LOST CoordinateResponse_PeerUpdate_Kind = 3 + CoordinateResponse_PeerUpdate_KIND_UNSPECIFIED CoordinateResponse_PeerUpdate_Kind = 0 + CoordinateResponse_PeerUpdate_NODE CoordinateResponse_PeerUpdate_Kind = 1 + CoordinateResponse_PeerUpdate_DISCONNECTED CoordinateResponse_PeerUpdate_Kind = 2 + CoordinateResponse_PeerUpdate_LOST CoordinateResponse_PeerUpdate_Kind = 3 + CoordinateResponse_PeerUpdate_READY_FOR_HANDSHAKE CoordinateResponse_PeerUpdate_Kind = 4 ) // Enum value maps for CoordinateResponse_PeerUpdate_Kind. @@ -37,12 +38,14 @@ var ( 1: "NODE", 2: "DISCONNECTED", 3: "LOST", + 4: "READY_FOR_HANDSHAKE", } CoordinateResponse_PeerUpdate_Kind_value = map[string]int32{ - "KIND_UNSPECIFIED": 0, - "NODE": 1, - "DISCONNECTED": 2, - "LOST": 3, + "KIND_UNSPECIFIED": 0, + "NODE": 1, + "DISCONNECTED": 2, + "LOST": 3, + "READY_FOR_HANDSHAKE": 4, } ) @@ -291,10 +294,11 @@ type CoordinateRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UpdateSelf *CoordinateRequest_UpdateSelf `protobuf:"bytes,1,opt,name=update_self,json=updateSelf,proto3" json:"update_self,omitempty"` - Disconnect *CoordinateRequest_Disconnect `protobuf:"bytes,2,opt,name=disconnect,proto3" json:"disconnect,omitempty"` - AddTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,3,opt,name=add_tunnel,json=addTunnel,proto3" json:"add_tunnel,omitempty"` - RemoveTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,4,opt,name=remove_tunnel,json=removeTunnel,proto3" json:"remove_tunnel,omitempty"` + UpdateSelf *CoordinateRequest_UpdateSelf `protobuf:"bytes,1,opt,name=update_self,json=updateSelf,proto3" json:"update_self,omitempty"` + Disconnect *CoordinateRequest_Disconnect `protobuf:"bytes,2,opt,name=disconnect,proto3" json:"disconnect,omitempty"` + AddTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,3,opt,name=add_tunnel,json=addTunnel,proto3" json:"add_tunnel,omitempty"` + RemoveTunnel *CoordinateRequest_Tunnel `protobuf:"bytes,4,opt,name=remove_tunnel,json=removeTunnel,proto3" json:"remove_tunnel,omitempty"` + ReadyForHandshake []*CoordinateRequest_ReadyForHandshake `protobuf:"bytes,5,rep,name=ready_for_handshake,json=readyForHandshake,proto3" json:"ready_for_handshake,omitempty"` } func (x *CoordinateRequest) Reset() { @@ -357,12 +361,20 @@ func (x *CoordinateRequest) GetRemoveTunnel() *CoordinateRequest_Tunnel { return nil } +func (x *CoordinateRequest) GetReadyForHandshake() []*CoordinateRequest_ReadyForHandshake { + if x != nil { + return x.ReadyForHandshake + } + return nil +} + type CoordinateResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields PeerUpdates []*CoordinateResponse_PeerUpdate `protobuf:"bytes,1,rep,name=peer_updates,json=peerUpdates,proto3" json:"peer_updates,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } func (x *CoordinateResponse) Reset() { @@ -404,6 +416,13 @@ func (x *CoordinateResponse) GetPeerUpdates() []*CoordinateResponse_PeerUpdate { return nil } +func (x *CoordinateResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + type DERPMap_HomeParams struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -813,6 +832,57 @@ func (x *CoordinateRequest_Tunnel) GetId() []byte { return nil } +// ReadyForHandskales are sent from destinations back to the source, +// acknowledging receipt of the source's node. If the source starts pinging +// before a ReadyForHandshake, the Wireguard handshake will likely be +// dropped. +type CoordinateRequest_ReadyForHandshake struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *CoordinateRequest_ReadyForHandshake) Reset() { + *x = CoordinateRequest_ReadyForHandshake{} + if protoimpl.UnsafeEnabled { + mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CoordinateRequest_ReadyForHandshake) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CoordinateRequest_ReadyForHandshake) ProtoMessage() {} + +func (x *CoordinateRequest_ReadyForHandshake) ProtoReflect() protoreflect.Message { + mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CoordinateRequest_ReadyForHandshake.ProtoReflect.Descriptor instead. +func (*CoordinateRequest_ReadyForHandshake) Descriptor() ([]byte, []int) { + return file_tailnet_proto_tailnet_proto_rawDescGZIP(), []int{3, 3} +} + +func (x *CoordinateRequest_ReadyForHandshake) GetId() []byte { + if x != nil { + return x.Id + } + return nil +} + type CoordinateResponse_PeerUpdate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -827,7 +897,7 @@ type CoordinateResponse_PeerUpdate struct { func (x *CoordinateResponse_PeerUpdate) Reset() { *x = CoordinateResponse_PeerUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -840,7 +910,7 @@ func (x *CoordinateResponse_PeerUpdate) String() string { func (*CoordinateResponse_PeerUpdate) ProtoMessage() {} func (x *CoordinateResponse_PeerUpdate) ProtoReflect() protoreflect.Message { - mi := &file_tailnet_proto_tailnet_proto_msgTypes[15] + mi := &file_tailnet_proto_tailnet_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -992,7 +1062,7 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb2, 0x03, 0x0a, 0x11, 0x43, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x04, 0x0a, 0x11, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, @@ -1013,50 +1083,62 @@ var file_tailnet_proto_tailnet_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x1a, 0x38, 0x0a, - 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x2a, 0x0a, 0x04, 0x6e, - 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x0c, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, - 0xd9, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, + 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x65, 0x0a, + 0x13, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, + 0x68, 0x61, 0x6b, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6f, 0x64, + 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, + 0x65, 0x52, 0x11, 0x72, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, + 0x68, 0x61, 0x6b, 0x65, 0x1a, 0x38, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, + 0x6c, 0x66, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x0c, + 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x1a, 0x18, 0x0a, 0x06, + 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x1a, 0x23, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, + 0x6f, 0x72, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x88, 0x03, 0x0a, 0x12, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, - 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0xee, 0x01, 0x0a, 0x0a, 0x50, - 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, 0x6f, 0x64, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, - 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, - 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, - 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, - 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, - 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x03, 0x32, 0xbe, 0x01, 0x0a, 0x07, - 0x54, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, - 0x5b, 0x0a, 0x0a, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, + 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x87, 0x02, 0x0a, + 0x0a, 0x50, 0x65, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x6e, + 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, + 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, + 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, + 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x44, 0x45, 0x10, + 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, + 0x44, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x03, 0x12, 0x17, 0x0a, + 0x13, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x53, + 0x48, 0x41, 0x4b, 0x45, 0x10, 0x04, 0x32, 0xbe, 0x01, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6c, 0x6e, + 0x65, 0x74, 0x12, 0x56, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, 0x52, 0x50, + 0x4d, 0x61, 0x70, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, + 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x45, + 0x52, 0x50, 0x4d, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, - 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x44, 0x45, 0x52, 0x50, 0x4d, 0x61, 0x70, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x61, 0x69, 0x6c, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1072,7 +1154,7 @@ func file_tailnet_proto_tailnet_proto_rawDescGZIP() []byte { } var file_tailnet_proto_tailnet_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_tailnet_proto_tailnet_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (CoordinateResponse_PeerUpdate_Kind)(0), // 0: coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind (*DERPMap)(nil), // 1: coder.tailnet.v2.DERPMap @@ -1090,35 +1172,37 @@ var file_tailnet_proto_tailnet_proto_goTypes = []interface{}{ (*CoordinateRequest_UpdateSelf)(nil), // 13: coder.tailnet.v2.CoordinateRequest.UpdateSelf (*CoordinateRequest_Disconnect)(nil), // 14: coder.tailnet.v2.CoordinateRequest.Disconnect (*CoordinateRequest_Tunnel)(nil), // 15: coder.tailnet.v2.CoordinateRequest.Tunnel - (*CoordinateResponse_PeerUpdate)(nil), // 16: coder.tailnet.v2.CoordinateResponse.PeerUpdate - (*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp + (*CoordinateRequest_ReadyForHandshake)(nil), // 16: coder.tailnet.v2.CoordinateRequest.ReadyForHandshake + (*CoordinateResponse_PeerUpdate)(nil), // 17: coder.tailnet.v2.CoordinateResponse.PeerUpdate + (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp } var file_tailnet_proto_tailnet_proto_depIdxs = []int32{ 6, // 0: coder.tailnet.v2.DERPMap.home_params:type_name -> coder.tailnet.v2.DERPMap.HomeParams 8, // 1: coder.tailnet.v2.DERPMap.regions:type_name -> coder.tailnet.v2.DERPMap.RegionsEntry - 17, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp + 18, // 2: coder.tailnet.v2.Node.as_of:type_name -> google.protobuf.Timestamp 11, // 3: coder.tailnet.v2.Node.derp_latency:type_name -> coder.tailnet.v2.Node.DerpLatencyEntry 12, // 4: coder.tailnet.v2.Node.derp_forced_websocket:type_name -> coder.tailnet.v2.Node.DerpForcedWebsocketEntry 13, // 5: coder.tailnet.v2.CoordinateRequest.update_self:type_name -> coder.tailnet.v2.CoordinateRequest.UpdateSelf 14, // 6: coder.tailnet.v2.CoordinateRequest.disconnect:type_name -> coder.tailnet.v2.CoordinateRequest.Disconnect 15, // 7: coder.tailnet.v2.CoordinateRequest.add_tunnel:type_name -> coder.tailnet.v2.CoordinateRequest.Tunnel 15, // 8: coder.tailnet.v2.CoordinateRequest.remove_tunnel:type_name -> coder.tailnet.v2.CoordinateRequest.Tunnel - 16, // 9: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate - 9, // 10: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry - 10, // 11: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node - 7, // 12: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region - 3, // 13: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node - 3, // 14: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node - 0, // 15: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind - 2, // 16: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest - 4, // 17: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest - 1, // 18: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap - 5, // 19: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse - 18, // [18:20] is the sub-list for method output_type - 16, // [16:18] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 16, // 9: coder.tailnet.v2.CoordinateRequest.ready_for_handshake:type_name -> coder.tailnet.v2.CoordinateRequest.ReadyForHandshake + 17, // 10: coder.tailnet.v2.CoordinateResponse.peer_updates:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate + 9, // 11: coder.tailnet.v2.DERPMap.HomeParams.region_score:type_name -> coder.tailnet.v2.DERPMap.HomeParams.RegionScoreEntry + 10, // 12: coder.tailnet.v2.DERPMap.Region.nodes:type_name -> coder.tailnet.v2.DERPMap.Region.Node + 7, // 13: coder.tailnet.v2.DERPMap.RegionsEntry.value:type_name -> coder.tailnet.v2.DERPMap.Region + 3, // 14: coder.tailnet.v2.CoordinateRequest.UpdateSelf.node:type_name -> coder.tailnet.v2.Node + 3, // 15: coder.tailnet.v2.CoordinateResponse.PeerUpdate.node:type_name -> coder.tailnet.v2.Node + 0, // 16: coder.tailnet.v2.CoordinateResponse.PeerUpdate.kind:type_name -> coder.tailnet.v2.CoordinateResponse.PeerUpdate.Kind + 2, // 17: coder.tailnet.v2.Tailnet.StreamDERPMaps:input_type -> coder.tailnet.v2.StreamDERPMapsRequest + 4, // 18: coder.tailnet.v2.Tailnet.Coordinate:input_type -> coder.tailnet.v2.CoordinateRequest + 1, // 19: coder.tailnet.v2.Tailnet.StreamDERPMaps:output_type -> coder.tailnet.v2.DERPMap + 5, // 20: coder.tailnet.v2.Tailnet.Coordinate:output_type -> coder.tailnet.v2.CoordinateResponse + 19, // [19:21] is the sub-list for method output_type + 17, // [17:19] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_tailnet_proto_tailnet_proto_init() } @@ -1260,6 +1344,18 @@ func file_tailnet_proto_tailnet_proto_init() { } } file_tailnet_proto_tailnet_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CoordinateRequest_ReadyForHandshake); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tailnet_proto_tailnet_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CoordinateResponse_PeerUpdate); i { case 0: return &v.state @@ -1278,7 +1374,7 @@ func file_tailnet_proto_tailnet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tailnet_proto_tailnet_proto_rawDesc, NumEnums: 1, - NumMessages: 16, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/tailnet/proto/tailnet.proto b/tailnet/proto/tailnet.proto index 83445e7579..1e948ebac6 100644 --- a/tailnet/proto/tailnet.proto +++ b/tailnet/proto/tailnet.proto @@ -68,6 +68,15 @@ message CoordinateRequest { } Tunnel add_tunnel = 3; Tunnel remove_tunnel = 4; + + // ReadyForHandskales are sent from destinations back to the source, + // acknowledging receipt of the source's node. If the source starts pinging + // before a ReadyForHandshake, the Wireguard handshake will likely be + // dropped. + message ReadyForHandshake { + bytes id = 1; + } + repeated ReadyForHandshake ready_for_handshake = 5; } message CoordinateResponse { @@ -80,12 +89,14 @@ message CoordinateResponse { NODE = 1; DISCONNECTED = 2; LOST = 3; + READY_FOR_HANDSHAKE = 4; } Kind kind = 3; string reason = 4; } repeated PeerUpdate peer_updates = 1; + string error = 2; } service Tailnet { diff --git a/tailnet/tailnettest/coordinateemock.go b/tailnet/tailnettest/coordinateemock.go index 51f2dd2bce..c06243685a 100644 --- a/tailnet/tailnettest/coordinateemock.go +++ b/tailnet/tailnettest/coordinateemock.go @@ -14,6 +14,7 @@ import ( tailnet "github.com/coder/coder/v2/tailnet" proto "github.com/coder/coder/v2/tailnet/proto" + uuid "github.com/google/uuid" gomock "go.uber.org/mock/gomock" ) @@ -64,6 +65,18 @@ func (mr *MockCoordinateeMockRecorder) SetNodeCallback(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNodeCallback", reflect.TypeOf((*MockCoordinatee)(nil).SetNodeCallback), arg0) } +// SetTunnelDestination mocks base method. +func (m *MockCoordinatee) SetTunnelDestination(arg0 uuid.UUID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetTunnelDestination", arg0) +} + +// SetTunnelDestination indicates an expected call of SetTunnelDestination. +func (mr *MockCoordinateeMockRecorder) SetTunnelDestination(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTunnelDestination", reflect.TypeOf((*MockCoordinatee)(nil).SetTunnelDestination), arg0) +} + // UpdatePeers mocks base method. func (m *MockCoordinatee) UpdatePeers(arg0 []*proto.CoordinateResponse_PeerUpdate) error { m.ctrl.T.Helper() diff --git a/tailnet/tunnel.go b/tailnet/tunnel.go index bc5becbc94..68b78d4f92 100644 --- a/tailnet/tunnel.go +++ b/tailnet/tunnel.go @@ -52,6 +52,10 @@ func (c ClientCoordinateeAuth) Authorize(req *proto.CoordinateRequest) error { } } + if rfh := req.GetReadyForHandshake(); rfh != nil { + return xerrors.Errorf("clients may not send ready_for_handshake") + } + return nil } @@ -147,6 +151,12 @@ func (s *tunnelStore) findTunnelPeers(id uuid.UUID) []uuid.UUID { return out } +func (s *tunnelStore) tunnelExists(src, dst uuid.UUID) bool { + _, srcOK := s.bySrc[src][dst] + _, dstOK := s.byDst[src][dst] + return srcOK || dstOK +} + func (s *tunnelStore) htmlDebug() []HTMLTunnel { out := make([]HTMLTunnel, 0) for src, dsts := range s.bySrc { diff --git a/tailnet/tunnel_internal_test.go b/tailnet/tunnel_internal_test.go index 3ba7cc4165..b05871f086 100644 --- a/tailnet/tunnel_internal_test.go +++ b/tailnet/tunnel_internal_test.go @@ -43,3 +43,18 @@ func TestTunnelStore_RemoveAll(t *testing.T) { require.Len(t, uut.findTunnelPeers(p2), 0) require.Len(t, uut.findTunnelPeers(p3), 0) } + +func TestTunnelStore_TunnelExists(t *testing.T) { + t.Parallel() + p1 := uuid.UUID{1} + p2 := uuid.UUID{2} + uut := newTunnelStore() + require.False(t, uut.tunnelExists(p1, p2)) + require.False(t, uut.tunnelExists(p2, p1)) + uut.add(p1, p2) + require.True(t, uut.tunnelExists(p1, p2)) + require.True(t, uut.tunnelExists(p2, p1)) + uut.remove(p1, p2) + require.False(t, uut.tunnelExists(p1, p2)) + require.False(t, uut.tunnelExists(p2, p1)) +} From 8da8b89af7aa79fea8672ed655141dbcbcfe1349 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 10 Apr 2024 18:02:08 -0500 Subject: [PATCH 62/74] test: verify actually uploaded license with assert (#12934) Prior page.GetByText did not assert it existed --- site/e2e/global.setup.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/e2e/global.setup.ts b/site/e2e/global.setup.ts index b4f92c423b..fd37b29ea5 100644 --- a/site/e2e/global.setup.ts +++ b/site/e2e/global.setup.ts @@ -30,6 +30,8 @@ test("setup deployment", async ({ page }) => { await page.getByRole("textbox").fill(constants.enterpriseLicense); await page.getByText("Upload License").click(); - await page.getByText("You have successfully added a license").isVisible(); + await expect( + page.getByText("You have successfully added a license"), + ).toBeVisible(); } }); From ab116af5436feba876283a3c63a1872d1dcd7f65 Mon Sep 17 00:00:00 2001 From: Stephen Kirby <58410745+stirby@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:31:21 -0500 Subject: [PATCH 63/74] added releases.md to manifest (#12936) --- docs/manifest.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/manifest.json b/docs/manifest.json index a7896946fe..0c93da879b 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -55,6 +55,11 @@ "title": "1-click install", "description": "Install Coder on a cloud provider with a single click", "path": "./install/1-click.md" + }, + { + "title": "Releases", + "description": "Coder Release Channels and Cadence", + "path": "./install/releases.md" } ] }, From a231b5aef503ada90e37bd755a9574b858d8d60e Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 11 Apr 2024 10:05:53 +0400 Subject: [PATCH 64/74] feat: add src_id and dst_id indexes to tailnet_tunnels (#12911) Fixes #12780 Adds indexes to the `tailnet_tunnels` table to speed up `GetTailnetTunnelPeerIDs` and `GetTailnetTunnelPeerBindings` queries, which match on `src_id` and `dst_id`. --- coderd/database/dump.sql | 4 ++++ .../migrations/000206_add_tailnet_tunnels_indexes.down.sql | 2 ++ .../migrations/000206_add_tailnet_tunnels_indexes.up.sql | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 coderd/database/migrations/000206_add_tailnet_tunnels_indexes.down.sql create mode 100644 coderd/database/migrations/000206_add_tailnet_tunnels_indexes.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 830f8a1825..03d3640f8d 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1624,6 +1624,10 @@ CREATE INDEX idx_tailnet_clients_coordinator ON tailnet_clients USING btree (coo CREATE INDEX idx_tailnet_peers_coordinator ON tailnet_peers USING btree (coordinator_id); +CREATE INDEX idx_tailnet_tunnels_dst_id ON tailnet_tunnels USING hash (dst_id); + +CREATE INDEX idx_tailnet_tunnels_src_id ON tailnet_tunnels USING hash (src_id); + CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false); CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false); diff --git a/coderd/database/migrations/000206_add_tailnet_tunnels_indexes.down.sql b/coderd/database/migrations/000206_add_tailnet_tunnels_indexes.down.sql new file mode 100644 index 0000000000..475e509ac6 --- /dev/null +++ b/coderd/database/migrations/000206_add_tailnet_tunnels_indexes.down.sql @@ -0,0 +1,2 @@ +DROP INDEX idx_tailnet_tunnels_src_id; +DROP INDEX idx_tailnet_tunnels_dst_id; diff --git a/coderd/database/migrations/000206_add_tailnet_tunnels_indexes.up.sql b/coderd/database/migrations/000206_add_tailnet_tunnels_indexes.up.sql new file mode 100644 index 0000000000..42f5729e14 --- /dev/null +++ b/coderd/database/migrations/000206_add_tailnet_tunnels_indexes.up.sql @@ -0,0 +1,3 @@ +-- Since src_id and dst_id are UUIDs, we only ever compare them with equality, so hash is better +CREATE INDEX idx_tailnet_tunnels_src_id ON tailnet_tunnels USING hash (src_id); +CREATE INDEX idx_tailnet_tunnels_dst_id ON tailnet_tunnels USING hash (dst_id); From fad97a14f96668b5dd01a32141f51aacd057c5e9 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 11 Apr 2024 10:09:10 +0100 Subject: [PATCH 65/74] fix(cli): allow generating partial support bundles with no workspace or agent (#12933) * fix(cli): allow generating partial support bundles with no workspace or agent * nolint control flag --- cli/support.go | 58 ++++++++------- cli/support_test.go | 172 +++++++++++++++++++++++++++++++++----------- 2 files changed, 163 insertions(+), 67 deletions(-) diff --git a/cli/support.go b/cli/support.go index 2e87b01479..88372278c1 100644 --- a/cli/support.go +++ b/cli/support.go @@ -13,6 +13,7 @@ import ( "text/tabwriter" "time" + "github.com/google/uuid" "golang.org/x/xerrors" "cdr.dev/slog" @@ -114,32 +115,41 @@ func (r *RootCmd) supportBundle() *serpent.Command { client.URL = u } + var ( + wsID uuid.UUID + agtID uuid.UUID + ) + if len(inv.Args) == 0 { - return xerrors.Errorf("must specify workspace name") - } - ws, err := namedWorkspace(inv.Context(), client, inv.Args[0]) - if err != nil { - return xerrors.Errorf("invalid workspace: %w", err) - } - cliLog.Debug(inv.Context(), "found workspace", - slog.F("workspace_name", ws.Name), - slog.F("workspace_id", ws.ID), - ) + cliLog.Warn(inv.Context(), "no workspace specified") + _, _ = fmt.Fprintln(inv.Stderr, "Warning: no workspace specified. This will result in incomplete information.") + } else { + ws, err := namedWorkspace(inv.Context(), client, inv.Args[0]) + if err != nil { + return xerrors.Errorf("invalid workspace: %w", err) + } + cliLog.Debug(inv.Context(), "found workspace", + slog.F("workspace_name", ws.Name), + slog.F("workspace_id", ws.ID), + ) + wsID = ws.ID + agentName := "" + if len(inv.Args) > 1 { + agentName = inv.Args[1] + } - agentName := "" - if len(inv.Args) > 1 { - agentName = inv.Args[1] + agt, found := findAgent(agentName, ws.LatestBuild.Resources) + if !found { + cliLog.Warn(inv.Context(), "could not find agent in workspace", slog.F("agent_name", agentName)) + } else { + cliLog.Debug(inv.Context(), "found workspace agent", + slog.F("agent_name", agt.Name), + slog.F("agent_id", agt.ID), + ) + agtID = agt.ID + } } - agt, found := findAgent(agentName, ws.LatestBuild.Resources) - if !found { - return xerrors.Errorf("could not find agent named %q for workspace", agentName) - } - cliLog.Debug(inv.Context(), "found workspace agent", - slog.F("agent_name", agt.Name), - slog.F("agent_id", agt.ID), - ) - if outputPath == "" { cwd, err := filepath.Abs(".") if err != nil { @@ -165,8 +175,8 @@ func (r *RootCmd) supportBundle() *serpent.Command { Client: client, // Support adds a sink so we don't need to supply one ourselves. Log: clientLog, - WorkspaceID: ws.ID, - AgentID: agt.ID, + WorkspaceID: wsID, + AgentID: agtID, } bun, err := support.Run(inv.Context(), &deps) diff --git a/cli/support_test.go b/cli/support_test.go index 7f2fce53e4..c40119c474 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -95,33 +95,50 @@ func TestSupportBundle(t *testing.T) { clitest.SetupConfig(t, client, root) err = inv.Run() require.NoError(t, err) - assertBundleContents(t, path, secretValue) + assertBundleContents(t, path, true, true, []string{secretValue}) }) t.Run("NoWorkspace", func(t *testing.T) { t.Parallel() - client := coderdtest.New(t, nil) + var dc codersdk.DeploymentConfig + secretValue := uuid.NewString() + seedSecretDeploymentOptions(t, &dc, secretValue) + client := coderdtest.New(t, &coderdtest.Options{ + DeploymentValues: dc.Values, + }) _ = coderdtest.CreateFirstUser(t, client) - inv, root := clitest.New(t, "support", "bundle", "--yes") + + d := t.TempDir() + path := filepath.Join(d, "bundle.zip") + inv, root := clitest.New(t, "support", "bundle", "--output-file", path, "--yes") //nolint: gocritic // requires owner privilege clitest.SetupConfig(t, client, root) err := inv.Run() - require.ErrorContains(t, err, "must specify workspace name") + require.NoError(t, err) + assertBundleContents(t, path, false, false, []string{secretValue}) }) t.Run("NoAgent", func(t *testing.T) { t.Parallel() - client, db := coderdtest.NewWithDatabase(t, nil) + var dc codersdk.DeploymentConfig + secretValue := uuid.NewString() + seedSecretDeploymentOptions(t, &dc, secretValue) + client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{ + DeploymentValues: dc.Values, + }) admin := coderdtest.CreateFirstUser(t, client) r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: admin.OrganizationID, OwnerID: admin.UserID, }).Do() // without agent! - inv, root := clitest.New(t, "support", "bundle", r.Workspace.Name, "--yes") + d := t.TempDir() + path := filepath.Join(d, "bundle.zip") + inv, root := clitest.New(t, "support", "bundle", r.Workspace.Name, "--output-file", path, "--yes") //nolint: gocritic // requires owner privilege clitest.SetupConfig(t, client, root) err := inv.Run() - require.ErrorContains(t, err, "could not find agent") + require.NoError(t, err) + assertBundleContents(t, path, true, false, []string{secretValue}) }) t.Run("NoPrivilege", func(t *testing.T) { @@ -140,7 +157,8 @@ func TestSupportBundle(t *testing.T) { }) } -func assertBundleContents(t *testing.T, path string, badValues ...string) { +// nolint:revive // It's a control flag, but this is just a test. +func assertBundleContents(t *testing.T, path string, wantWorkspace bool, wantAgent bool, badValues []string) { t.Helper() r, err := zip.OpenReader(path) require.NoError(t, err, "open zip file") @@ -173,64 +191,132 @@ func assertBundleContents(t *testing.T, path string, badValues ...string) { case "network/netcheck.json": var v workspacesdk.AgentConnectionInfo decodeJSONFromZip(t, f, &v) + if !wantAgent || !wantWorkspace { + require.Empty(t, v, "expected connection info to be empty") + continue + } require.NotEmpty(t, v, "connection info should not be empty") case "workspace/workspace.json": var v codersdk.Workspace decodeJSONFromZip(t, f, &v) + if !wantWorkspace { + require.Empty(t, v, "expected workspace to be empty") + continue + } require.NotEmpty(t, v, "workspace should not be empty") case "workspace/build_logs.txt": bs := readBytesFromZip(t, f) + if !wantWorkspace || !wantAgent { + require.Empty(t, bs, "expected workspace build logs to be empty") + continue + } require.Contains(t, string(bs), "provision done") - case "agent/agent.json": - var v codersdk.WorkspaceAgent - decodeJSONFromZip(t, f, &v) - require.NotEmpty(t, v, "agent should not be empty") - case "agent/listening_ports.json": - var v codersdk.WorkspaceAgentListeningPortsResponse - decodeJSONFromZip(t, f, &v) - require.NotEmpty(t, v, "agent listening ports should not be empty") - case "agent/logs.txt": - bs := readBytesFromZip(t, f) - require.NotEmpty(t, bs, "logs should not be empty") - case "agent/agent_magicsock.html": - bs := readBytesFromZip(t, f) - require.NotEmpty(t, bs, "agent magicsock should not be empty") - case "agent/client_magicsock.html": - bs := readBytesFromZip(t, f) - require.NotEmpty(t, bs, "client magicsock should not be empty") - case "agent/manifest.json": - var v agentsdk.Manifest - decodeJSONFromZip(t, f, &v) - require.NotEmpty(t, v, "agent manifest should not be empty") - case "agent/peer_diagnostics.json": - var v *tailnet.PeerDiagnostics - decodeJSONFromZip(t, f, &v) - require.NotEmpty(t, v, "peer diagnostics should not be empty") - case "agent/ping_result.json": - var v *ipnstate.PingResult - decodeJSONFromZip(t, f, &v) - require.NotEmpty(t, v, "ping result should not be empty") - case "agent/prometheus.txt": - bs := readBytesFromZip(t, f) - require.NotEmpty(t, bs, "agent prometheus metrics should not be empty") - case "agent/startup_logs.txt": - bs := readBytesFromZip(t, f) - require.Contains(t, string(bs), "started up") case "workspace/template.json": var v codersdk.Template decodeJSONFromZip(t, f, &v) + if !wantWorkspace { + require.Empty(t, v, "expected workspace template to be empty") + continue + } require.NotEmpty(t, v, "workspace template should not be empty") case "workspace/template_version.json": var v codersdk.TemplateVersion decodeJSONFromZip(t, f, &v) + if !wantWorkspace { + require.Empty(t, v, "expected workspace template version to be empty") + continue + } require.NotEmpty(t, v, "workspace template version should not be empty") case "workspace/parameters.json": var v []codersdk.WorkspaceBuildParameter decodeJSONFromZip(t, f, &v) + if !wantWorkspace { + require.Empty(t, v, "expected workspace parameters to be empty") + continue + } require.NotNil(t, v, "workspace parameters should not be nil") case "workspace/template_file.zip": bs := readBytesFromZip(t, f) + if !wantWorkspace { + require.Empty(t, bs, "expected template file to be empty") + continue + } require.NotNil(t, bs, "template file should not be nil") + case "agent/agent.json": + var v codersdk.WorkspaceAgent + decodeJSONFromZip(t, f, &v) + if !wantAgent { + require.Empty(t, v, "expected agent to be empty") + continue + } + require.NotEmpty(t, v, "agent should not be empty") + case "agent/listening_ports.json": + var v codersdk.WorkspaceAgentListeningPortsResponse + decodeJSONFromZip(t, f, &v) + if !wantAgent { + require.Empty(t, v, "expected agent listening ports to be empty") + continue + } + require.NotEmpty(t, v, "agent listening ports should not be empty") + case "agent/logs.txt": + bs := readBytesFromZip(t, f) + if !wantAgent { + require.Empty(t, bs, "expected agent logs to be empty") + continue + } + require.NotEmpty(t, bs, "logs should not be empty") + case "agent/agent_magicsock.html": + bs := readBytesFromZip(t, f) + if !wantAgent { + require.Empty(t, bs, "expected agent magicsock to be empty") + continue + } + require.NotEmpty(t, bs, "agent magicsock should not be empty") + case "agent/client_magicsock.html": + bs := readBytesFromZip(t, f) + if !wantAgent { + require.Empty(t, bs, "expected client magicsock to be empty") + continue + } + require.NotEmpty(t, bs, "client magicsock should not be empty") + case "agent/manifest.json": + var v agentsdk.Manifest + decodeJSONFromZip(t, f, &v) + if !wantAgent { + require.Empty(t, v, "expected agent manifest to be empty") + continue + } + require.NotEmpty(t, v, "agent manifest should not be empty") + case "agent/peer_diagnostics.json": + var v *tailnet.PeerDiagnostics + decodeJSONFromZip(t, f, &v) + if !wantAgent { + require.Empty(t, v, "expected peer diagnostics to be empty") + continue + } + require.NotEmpty(t, v, "peer diagnostics should not be empty") + case "agent/ping_result.json": + var v *ipnstate.PingResult + decodeJSONFromZip(t, f, &v) + if !wantAgent { + require.Empty(t, v, "expected ping result to be empty") + continue + } + require.NotEmpty(t, v, "ping result should not be empty") + case "agent/prometheus.txt": + bs := readBytesFromZip(t, f) + if !wantAgent { + require.Empty(t, bs, "expected agent prometheus metrics to be empty") + continue + } + require.NotEmpty(t, bs, "agent prometheus metrics should not be empty") + case "agent/startup_logs.txt": + bs := readBytesFromZip(t, f) + if !wantAgent { + require.Empty(t, bs, "expected agent startup logs to be empty") + continue + } + require.Contains(t, string(bs), "started up") case "logs.txt": bs := readBytesFromZip(t, f) require.NotEmpty(t, bs, "logs should not be empty") From b9936a4671887a41f03d851b308cb5bbf43868ec Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 11 Apr 2024 09:42:21 -0500 Subject: [PATCH 66/74] chore: deconflict e2e enterprise and AGPL artifacts in ci (#12941) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 912d68dd58..1fa2936b3b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -486,7 +486,7 @@ jobs: if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 with: - name: failed-test-videos + name: failed-test-videos${{ matrix.variant.enterprise && '-enterprise' }} path: ./site/test-results/**/*.webm retention-days: 7 @@ -494,7 +494,7 @@ jobs: if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 with: - name: debug-pprof-dumps + name: debug-pprof-dumps${{ matrix.variant.enterprise && '-enterprise' }} path: ./site/test-results/**/debug-pprof-*.txt retention-days: 7 From 22785a307c3330e37e5fc9057cda549e050f7d0b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 11 Apr 2024 11:57:40 -0500 Subject: [PATCH 67/74] chore: add -agpl to agpl e2e artifacts (#12943) * chore: -agpl added to agpl e2e artifacts Before was doing 'false' at the end of artifacts --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1fa2936b3b..8aaaa74398 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -486,7 +486,7 @@ jobs: if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 with: - name: failed-test-videos${{ matrix.variant.enterprise && '-enterprise' }} + name: failed-test-videos${{ matrix.variant.enterprise && '-enterprise' || '-agpl' }} path: ./site/test-results/**/*.webm retention-days: 7 @@ -494,7 +494,7 @@ jobs: if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 with: - name: debug-pprof-dumps${{ matrix.variant.enterprise && '-enterprise' }} + name: debug-pprof-dumps${{ matrix.variant.enterprise && '-enterprise' || '-agpl' }} path: ./site/test-results/**/debug-pprof-*.txt retention-days: 7 From 2ad7fcc0b7a8223a2c0d2a5c622f7ae62dec25cf Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 11 Apr 2024 13:08:51 -0600 Subject: [PATCH 68/74] fix: show template autostop setting when it overrides the workspace setting (#12910) --- coderd/workspaces.go | 5 +++ coderd/workspaces_test.go | 6 +-- enterprise/coderd/workspaces_test.go | 38 +++++++++++++++++++ .../WorkspaceSettingsPage.tsx | 3 +- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index d3456fab00..87aea6919a 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1649,6 +1649,11 @@ func convertWorkspace( } ttlMillis := convertWorkspaceTTLMillis(workspace.Ttl) + // If the template doesn't allow a workspace-configured value, then report the + // template value instead. + if !template.AllowUserAutostop { + ttlMillis = convertWorkspaceTTLMillis(sql.NullInt64{Valid: true, Int64: template.DefaultTTL}) + } // Only show favorite status if you own the workspace. requesterFavorite := workspace.OwnerID == requesterID && workspace.Favorite diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index f16d40f072..c01f9689d6 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -761,8 +761,8 @@ func TestPostWorkspacesByOrganization(t *testing.T) { coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) // TTL should be set by the template - require.Equal(t, template.DefaultTTLMillis, templateTTL) - require.Equal(t, template.DefaultTTLMillis, *workspace.TTLMillis) + require.Equal(t, templateTTL, template.DefaultTTLMillis) + require.Equal(t, templateTTL, *workspace.TTLMillis) }) t.Run("InvalidTTL", func(t *testing.T) { @@ -789,7 +789,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) { require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) require.Len(t, apiErr.Validations, 1) - require.Equal(t, apiErr.Validations[0].Field, "ttl_ms") + require.Equal(t, "ttl_ms", apiErr.Validations[0].Field) require.Equal(t, "time until shutdown must be at least one minute", apiErr.Validations[0].Detail) }) }) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 6d40d77bc2..b44357c5b5 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -913,6 +913,44 @@ func TestWorkspaceAutobuild(t *testing.T) { ws = coderdtest.MustWorkspace(t, client, ws.ID) require.Equal(t, version2.ID, ws.LatestBuild.TemplateVersionID) }) + + t.Run("TemplateDoesNotAllowUserAutostop", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), + }) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + templateTTL := 24 * time.Hour.Milliseconds() + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.DefaultTTLMillis = ptr.Ref(templateTTL) + ctr.AllowUserAutostop = ptr.Ref(false) + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.TTLMillis = nil // ensure that no default TTL is set + }) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + + // TTL should be set by the template + require.Equal(t, false, template.AllowUserAutostop) + require.Equal(t, templateTTL, template.DefaultTTLMillis) + require.Equal(t, templateTTL, *workspace.TTLMillis) + + // Change the template's default TTL and refetch the workspace + templateTTL = 72 * time.Hour.Milliseconds() + ctx := testutil.Context(t, testutil.WaitShort) + template = coderdtest.UpdateTemplateMeta(t, client, template.ID, codersdk.UpdateTemplateMeta{ + DefaultTTLMillis: templateTTL, + }) + workspace, err := client.Workspace(ctx, workspace.ID) + require.NoError(t, err) + + // Ensure that the new value is reflected in the template and workspace + require.Equal(t, templateTTL, template.DefaultTTLMillis) + require.Equal(t, templateTTL, *workspace.TTLMillis) + }) } // Blocked by autostart requirements diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx index af05809a9a..e289a58c5c 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx @@ -1,3 +1,4 @@ +import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation } from "react-query"; import { useNavigate, useParams } from "react-router-dom"; @@ -8,7 +9,7 @@ import type { WorkspaceSettingsFormValues } from "./WorkspaceSettingsForm"; import { useWorkspaceSettings } from "./WorkspaceSettingsLayout"; import { WorkspaceSettingsPageView } from "./WorkspaceSettingsPageView"; -const WorkspaceSettingsPage = () => { +const WorkspaceSettingsPage: FC = () => { const params = useParams() as { workspace: string; username: string; From 93b46fe1f681e0e6471a54a02ec79894900f6c0e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 11 Apr 2024 16:10:40 -0500 Subject: [PATCH 69/74] chore: skip global.setup if first user already exists (#12930) * chore: skip global.setup if first user already exists treat test as a setup, rather than a test Co-authored-by: Kayla Washburn-Love --------- Co-authored-by: Kayla Washburn-Love --- site/e2e/README.md | 8 ++++++++ site/e2e/api.ts | 8 ++++++-- site/e2e/global.setup.ts | 9 +++++++++ site/package.json | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/site/e2e/README.md b/site/e2e/README.md index 291344a67c..315de9dd47 100644 --- a/site/e2e/README.md +++ b/site/e2e/README.md @@ -46,3 +46,11 @@ Enterprise tests require a license key to run. ```shell export CODER_E2E_ENTERPRISE_LICENSE= ``` + +# Debugging tests + +To debug a test, it is more helpful to run it in `ui` mode. + +``` +pnpm playwright:test-ui +``` diff --git a/site/e2e/api.ts b/site/e2e/api.ts index 88f8666475..9bb8d62719 100644 --- a/site/e2e/api.ts +++ b/site/e2e/api.ts @@ -6,8 +6,12 @@ import { findSessionToken, randomName } from "./helpers"; let currentOrgId: string; export const setupApiCalls = async (page: Page) => { - const token = await findSessionToken(page); - API.setSessionToken(token); + try { + const token = await findSessionToken(page); + API.setSessionToken(token); + } catch { + // If this fails, we have an unauthenticated client. + } API.setHost(`http://127.0.0.1:${coderPort}`); }; diff --git a/site/e2e/global.setup.ts b/site/e2e/global.setup.ts index fd37b29ea5..b99c461308 100644 --- a/site/e2e/global.setup.ts +++ b/site/e2e/global.setup.ts @@ -1,10 +1,19 @@ import { expect, test } from "@playwright/test"; +import { hasFirstUser } from "api/api"; import { Language } from "pages/CreateUserPage/CreateUserForm"; +import { setupApiCalls } from "./api"; import * as constants from "./constants"; import { storageState } from "./playwright.config"; test("setup deployment", async ({ page }) => { await page.goto("/", { waitUntil: "domcontentloaded" }); + await setupApiCalls(page); + const exists = await hasFirstUser(); + // First user already exists, abort early. All tests execute this as a dependency, + // if you run multiple tests in the UI, this will fail unless we check this. + if (exists) { + return; + } // Setup first user await page.getByLabel(Language.usernameLabel).fill(constants.username); diff --git a/site/package.json b/site/package.json index 016f0bdafa..24ba4d5262 100644 --- a/site/package.json +++ b/site/package.json @@ -16,6 +16,7 @@ "lint:types": "tsc -p .", "playwright:install": "playwright install --with-deps chromium", "playwright:test": "playwright test --config=e2e/playwright.config.ts", + "playwright:test-ui": "playwright test --config=e2e/playwright.config.ts --ui $([[ \"$CODER\" == \"true\" ]] && echo --ui-port=7500 --ui-host=0.0.0.0)", "gen:provisioner": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./e2e/ --ts_proto_opt=outputJsonMethods=false,outputEncodeMethods=encode-no-creation,outputClientImpl=false,nestJs=false,outputPartialMethods=false,fileSuffix=Generated,suffix=hey -I ../provisionersdk/proto ../provisionersdk/proto/provisioner.proto && pnpm exec prettier --ignore-path '/dev/null' --cache --write './e2e/provisionerGenerated.ts'", "storybook": "STORYBOOK=true storybook dev -p 6006", "storybook:build": "storybook build", From c5367c201b287918db7a90f827e22405bb5fefa6 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 11 Apr 2024 15:48:53 -0600 Subject: [PATCH 70/74] test: fix url checks in e2e tests (#12881) --- site/e2e/expectUrl.ts | 29 +++++++++++++++ site/e2e/global.setup.ts | 3 +- site/e2e/helpers.ts | 37 ++++++++++--------- site/e2e/tests/updateTemplate.spec.ts | 5 ++- .../pages/CreateTokenPage/CreateTokenForm.tsx | 1 + 5 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 site/e2e/expectUrl.ts diff --git a/site/e2e/expectUrl.ts b/site/e2e/expectUrl.ts new file mode 100644 index 0000000000..eb3777f577 --- /dev/null +++ b/site/e2e/expectUrl.ts @@ -0,0 +1,29 @@ +import { expect, type Page } from "@playwright/test"; + +type PollingOptions = { timeout?: number; intervals?: number[] }; + +export const expectUrl = expect.extend({ + /** + * toHavePathName is an alternative to `toHaveURL` that won't fail if the URL contains query parameters. + */ + async toHavePathName(page: Page, expected: string, options?: PollingOptions) { + let actual: string = new URL(page.url()).pathname; + let pass: boolean; + try { + await expect + .poll(() => (actual = new URL(page.url()).pathname), options) + .toBe(expected); + pass = true; + } catch { + pass = false; + } + + return { + name: "toHavePathName", + pass, + actual, + expected, + message: () => "The page does not have the expected URL pathname.", + }; + }, +}); diff --git a/site/e2e/global.setup.ts b/site/e2e/global.setup.ts index b99c461308..8c8526af9a 100644 --- a/site/e2e/global.setup.ts +++ b/site/e2e/global.setup.ts @@ -3,6 +3,7 @@ import { hasFirstUser } from "api/api"; import { Language } from "pages/CreateUserPage/CreateUserForm"; import { setupApiCalls } from "./api"; import * as constants from "./constants"; +import { expectUrl } from "./expectUrl"; import { storageState } from "./playwright.config"; test("setup deployment", async ({ page }) => { @@ -21,7 +22,7 @@ test("setup deployment", async ({ page }) => { await page.getByLabel(Language.passwordLabel).fill(constants.password); await page.getByTestId("create").click(); - await expect(page).toHaveURL(/\/workspaces.*/); + await expectUrl(page).toHavePathName("/workspaces"); await page.context().storageState({ path: storageState }); await page.getByTestId("button-select-template").isVisible(); diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 040bcb6d55..05ce694a97 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -20,6 +20,7 @@ import { prometheusPort, requireEnterpriseTests, } from "./constants"; +import { expectUrl } from "./expectUrl"; import { Agent, type App, @@ -49,10 +50,10 @@ export const createWorkspace = async ( richParameters: RichParameter[] = [], buildParameters: WorkspaceBuildParameter[] = [], ): Promise => { - await page.goto("/templates/" + templateName + "/workspace", { + await page.goto(`/templates/${templateName}/workspace`, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL("/templates/" + templateName + "/workspace"); + await expectUrl(page).toHavePathName(`/templates/${templateName}/workspace`); const name = randomName(); await page.getByLabel("name").fill(name); @@ -60,9 +61,7 @@ export const createWorkspace = async ( await fillParameters(page, richParameters, buildParameters); await page.getByTestId("form-submit").click(); - // Workaround: OutdatedAgent lands at "http://localhost:3111/@admin/8d6225b7?resources=echo_dev" - // and this is also a correct location. - await page.waitForURL(new RegExp("/@admin/" + name)); + await expectUrl(page).toHavePathName("/@admin/" + name); await page.waitForSelector("*[data-testid='build-status'] >> text=Running", { state: "visible", @@ -79,8 +78,8 @@ export const verifyParameters = async ( await page.goto("/@admin/" + workspaceName + "/settings/parameters", { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL( - "/@admin/" + workspaceName + "/settings/parameters", + await expectUrl(page).toHavePathName( + `/@admin/${workspaceName}/settings/parameters`, ); for (const buildParameter of expectedBuildParameters) { @@ -141,7 +140,7 @@ export const createTemplate = async ( }); await page.goto("/templates/new", { waitUntil: "domcontentloaded" }); - await expect(page).toHaveURL("/templates/new"); + await expectUrl(page).toHavePathName("/templates/new"); await page.getByTestId("file-upload").setInputFiles({ buffer: await createTemplateVersionTar(responses), @@ -151,7 +150,7 @@ export const createTemplate = async ( const name = randomName(); await page.getByLabel("Name *").fill(name); await page.getByTestId("form-submit").click(); - await expect(page).toHaveURL(`/templates/${name}/files`, { + await expectUrl(page).toHavePathName(`/templates/${name}/files`, { timeout: 30000, }); return name; @@ -161,7 +160,7 @@ export const createTemplate = async ( // random name. export const createGroup = async (page: Page): Promise => { await page.goto("/groups/create", { waitUntil: "domcontentloaded" }); - await expect(page).toHaveURL("/groups/create"); + await expectUrl(page).toHavePathName("/groups/create"); const name = randomName(); await page.getByLabel("Name", { exact: true }).fill(name); @@ -222,7 +221,7 @@ export const stopWorkspace = async (page: Page, workspaceName: string) => { await page.goto("/@admin/" + workspaceName, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL("/@admin/" + workspaceName); + await expectUrl(page).toHavePathName(`/@admin/${workspaceName}`); await page.getByTestId("workspace-stop-button").click(); @@ -241,7 +240,7 @@ export const buildWorkspaceWithParameters = async ( await page.goto("/@admin/" + workspaceName, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL("/@admin/" + workspaceName); + await expectUrl(page).toHavePathName(`/@admin/${workspaceName}`); await page.getByTestId("build-parameters-button").click(); @@ -753,7 +752,7 @@ export const updateTemplateSettings = async ( await page.goto(`/templates/${templateName}/settings`, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL(`/templates/${templateName}/settings`); + await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`); for (const [key, value] of Object.entries(templateSettingValues)) { // Skip max_port_share_level for now since the frontend is not yet able to handle it @@ -767,7 +766,7 @@ export const updateTemplateSettings = async ( await page.getByTestId("form-submit").click(); const name = templateSettingValues.name ?? templateName; - await expect(page).toHaveURL(`/templates/${name}`); + await expectUrl(page).toHavePathName(`/templates/${name}`); }; export const updateWorkspace = async ( @@ -779,7 +778,7 @@ export const updateWorkspace = async ( await page.goto("/@admin/" + workspaceName, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL("/@admin/" + workspaceName); + await expectUrl(page).toHavePathName(`/@admin/${workspaceName}`); await page.getByTestId("workspace-update-button").click(); await page.getByTestId("confirm-button").click(); @@ -801,8 +800,8 @@ export const updateWorkspaceParameters = async ( await page.goto("/@admin/" + workspaceName + "/settings/parameters", { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL( - "/@admin/" + workspaceName + "/settings/parameters", + await expectUrl(page).toHavePathName( + `/@admin/${workspaceName}/settings/parameters`, ); await fillParameters(page, richParameters, buildParameters); @@ -827,7 +826,9 @@ export async function openTerminalWindow( // Specify that the shell should be `bash`, to prevent inheriting a shell that // isn't POSIX compatible, such as Fish. const commandQuery = `?command=${encodeURIComponent("/usr/bin/env bash")}`; - await expect(terminal).toHaveURL(`/@admin/${workspaceName}.dev/terminal`); + await expectUrl(terminal).toHavePathName( + `/@admin/${workspaceName}.dev/terminal`, + ); await terminal.goto(`/@admin/${workspaceName}.dev/terminal${commandQuery}`); return terminal; diff --git a/site/e2e/tests/updateTemplate.spec.ts b/site/e2e/tests/updateTemplate.spec.ts index 95182ca19e..4e967b2947 100644 --- a/site/e2e/tests/updateTemplate.spec.ts +++ b/site/e2e/tests/updateTemplate.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from "@playwright/test"; +import { expectUrl } from "../expectUrl"; import { createGroup, createTemplate, @@ -25,7 +26,7 @@ test("add and remove a group", async ({ page }) => { await page.goto(`/templates/${templateName}/settings/permissions`, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL( + await expectUrl(page).toHavePathName( `/templates/${templateName}/settings/permissions`, ); @@ -55,7 +56,7 @@ test("require latest version", async ({ page }) => { await page.goto(`/templates/${templateName}/settings`, { waitUntil: "domcontentloaded", }); - await expect(page).toHaveURL(`/templates/${templateName}/settings`); + await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`); let checkbox = await page.waitForSelector("#require_active_version"); await checkbox.click(); await page.getByTestId("form-submit").click(); diff --git a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx index d679e8f812..15af6174cb 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx @@ -116,6 +116,7 @@ export const CreateTokenForm: FC = ({ {lifetimeDays === "custom" && ( Date: Fri, 12 Apr 2024 09:40:04 +0100 Subject: [PATCH 71/74] fix(support): correctly rename existing agent connection info, add real netcheck (#12946) --- cli/support.go | 23 ++++++++++++----------- cli/support_test.go | 13 +++++++------ support/support.go | 38 ++++++++++++++++++++++++++------------ support/support_test.go | 6 ++++-- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/cli/support.go b/cli/support.go index 88372278c1..f2f962a358 100644 --- a/cli/support.go +++ b/cli/support.go @@ -232,20 +232,21 @@ func findAgent(agentName string, haystack []codersdk.WorkspaceResource) (*coders func writeBundle(src *support.Bundle, dest *zip.Writer) error { // We JSON-encode the following: for k, v := range map[string]any{ - "deployment/buildinfo.json": src.Deployment.BuildInfo, - "deployment/config.json": src.Deployment.Config, - "deployment/experiments.json": src.Deployment.Experiments, - "deployment/health.json": src.Deployment.HealthReport, - "network/netcheck.json": src.Network.Netcheck, - "workspace/workspace.json": src.Workspace.Workspace, "agent/agent.json": src.Agent.Agent, "agent/listening_ports.json": src.Agent.ListeningPorts, "agent/manifest.json": src.Agent.Manifest, "agent/peer_diagnostics.json": src.Agent.PeerDiagnostics, "agent/ping_result.json": src.Agent.PingResult, + "deployment/buildinfo.json": src.Deployment.BuildInfo, + "deployment/config.json": src.Deployment.Config, + "deployment/experiments.json": src.Deployment.Experiments, + "deployment/health.json": src.Deployment.HealthReport, + "network/connection_info.json": src.Network.ConnectionInfo, + "network/netcheck.json": src.Network.Netcheck, "workspace/template.json": src.Workspace.Template, "workspace/template_version.json": src.Workspace.TemplateVersion, "workspace/parameters.json": src.Workspace.Parameters, + "workspace/workspace.json": src.Workspace.Workspace, } { f, err := dest.Create(k) if err != nil { @@ -265,17 +266,17 @@ func writeBundle(src *support.Bundle, dest *zip.Writer) error { // The below we just write as we have them: for k, v := range map[string]string{ - "network/coordinator_debug.html": src.Network.CoordinatorDebug, - "network/tailnet_debug.html": src.Network.TailnetDebug, - "workspace/build_logs.txt": humanizeBuildLogs(src.Workspace.BuildLogs), "agent/logs.txt": string(src.Agent.Logs), "agent/agent_magicsock.html": string(src.Agent.AgentMagicsockHTML), "agent/client_magicsock.html": string(src.Agent.ClientMagicsockHTML), "agent/startup_logs.txt": humanizeAgentLogs(src.Agent.StartupLogs), "agent/prometheus.txt": string(src.Agent.Prometheus), - "workspace/template_file.zip": string(templateVersionBytes), - "logs.txt": strings.Join(src.Logs, "\n"), "cli_logs.txt": string(src.CLILogs), + "logs.txt": strings.Join(src.Logs, "\n"), + "network/coordinator_debug.html": src.Network.CoordinatorDebug, + "network/tailnet_debug.html": src.Network.TailnetDebug, + "workspace/build_logs.txt": humanizeBuildLogs(src.Workspace.BuildLogs), + "workspace/template_file.zip": string(templateVersionBytes), } { f, err := dest.Create(k) if err != nil { diff --git a/cli/support_test.go b/cli/support_test.go index c40119c474..d9bee0fb2f 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -23,6 +23,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/healthsdk" @@ -182,6 +183,10 @@ func assertBundleContents(t *testing.T, path string, wantWorkspace bool, wantAge var v healthsdk.HealthcheckReport decodeJSONFromZip(t, f, &v) require.NotEmpty(t, v, "health report should not be empty") + case "network/connection_info.json": + var v workspacesdk.AgentConnectionInfo + decodeJSONFromZip(t, f, &v) + require.NotEmpty(t, v, "agent connection info should not be empty") case "network/coordinator_debug.html": bs := readBytesFromZip(t, f) require.NotEmpty(t, bs, "coordinator debug should not be empty") @@ -189,13 +194,9 @@ func assertBundleContents(t *testing.T, path string, wantWorkspace bool, wantAge bs := readBytesFromZip(t, f) require.NotEmpty(t, bs, "tailnet debug should not be empty") case "network/netcheck.json": - var v workspacesdk.AgentConnectionInfo + var v derphealth.Report decodeJSONFromZip(t, f, &v) - if !wantAgent || !wantWorkspace { - require.Empty(t, v, "expected connection info to be empty") - continue - } - require.NotEmpty(t, v, "connection info should not be empty") + require.NotEmpty(t, v, "netcheck should not be empty") case "workspace/workspace.json": var v codersdk.Workspace decodeJSONFromZip(t, f, &v) diff --git a/support/support.go b/support/support.go index 47cad76a7d..341e01e186 100644 --- a/support/support.go +++ b/support/support.go @@ -13,6 +13,9 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/netcheck" + + "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/google/uuid" @@ -46,9 +49,16 @@ type Deployment struct { } type Network struct { - CoordinatorDebug string `json:"coordinator_debug"` - TailnetDebug string `json:"tailnet_debug"` - Netcheck *workspacesdk.AgentConnectionInfo `json:"netcheck"` + ConnectionInfo workspacesdk.AgentConnectionInfo + CoordinatorDebug string `json:"coordinator_debug"` + Netcheck *derphealth.Report `json:"netcheck"` + TailnetDebug string `json:"tailnet_debug"` +} + +type Netcheck struct { + Report *netcheck.Report `json:"report"` + Error string `json:"error"` + Logs []string `json:"logs"` } type Workspace struct { @@ -62,6 +72,7 @@ type Workspace struct { type Agent struct { Agent *codersdk.WorkspaceAgent `json:"agent"` + ConnectionInfo *workspacesdk.AgentConnectionInfo `json:"connection_info"` ListeningPorts *codersdk.WorkspaceAgentListeningPortsResponse `json:"listening_ports"` Logs []byte `json:"logs"` ClientMagicsockHTML []byte `json:"client_magicsock_html"` @@ -136,7 +147,7 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge return d } -func NetworkInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, agentID uuid.UUID) Network { +func NetworkInfo(ctx context.Context, client *codersdk.Client, log slog.Logger) Network { var ( n Network eg errgroup.Group @@ -171,15 +182,18 @@ func NetworkInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, }) eg.Go(func() error { - if agentID == uuid.Nil { - log.Warn(ctx, "agent id required for agent connection info") + // Need connection info to get DERP map for netcheck + connInfo, err := workspacesdk.New(client).AgentConnectionInfoGeneric(ctx) + if err != nil { + log.Warn(ctx, "unable to fetch generic agent connection info") return nil } - connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID) - if err != nil { - return xerrors.Errorf("fetch agent conn info: %w", err) - } - n.Netcheck = &connInfo + n.ConnectionInfo = connInfo + var rpt derphealth.Report + rpt.Run(ctx, &derphealth.ReportOptions{ + DERPMap: connInfo.DERPMap, + }) + n.Netcheck = &rpt return nil }) @@ -482,7 +496,7 @@ func Run(ctx context.Context, d *Deps) (*Bundle, error) { return nil }) eg.Go(func() error { - ni := NetworkInfo(ctx, d.Client, d.Log, d.AgentID) + ni := NetworkInfo(ctx, d.Client, d.Log) b.Network = ni return nil }) diff --git a/support/support_test.go b/support/support_test.go index 58d5c9731a..55eb6a1f23 100644 --- a/support/support_test.go +++ b/support/support_test.go @@ -62,9 +62,10 @@ func TestRun(t *testing.T) { assertSanitizedDeploymentConfig(t, bun.Deployment.Config) assertNotNilNotEmpty(t, bun.Deployment.HealthReport, "deployment health report should be present") assertNotNilNotEmpty(t, bun.Deployment.Experiments, "deployment experiments should be present") + assertNotNilNotEmpty(t, bun.Network.ConnectionInfo, "agent connection info should be present") assertNotNilNotEmpty(t, bun.Network.CoordinatorDebug, "network coordinator debug should be present") - assertNotNilNotEmpty(t, bun.Network.TailnetDebug, "network tailnet debug should be present") assertNotNilNotEmpty(t, bun.Network.Netcheck, "network netcheck should be present") + assertNotNilNotEmpty(t, bun.Network.TailnetDebug, "network tailnet debug should be present") assertNotNilNotEmpty(t, bun.Workspace.Workspace, "workspace should be present") assertSanitizedWorkspace(t, bun.Workspace.Workspace) assertNotNilNotEmpty(t, bun.Workspace.BuildLogs, "workspace build logs should be present") @@ -109,9 +110,10 @@ func TestRun(t *testing.T) { assertSanitizedDeploymentConfig(t, bun.Deployment.Config) assertNotNilNotEmpty(t, bun.Deployment.HealthReport, "deployment health report should be present") assertNotNilNotEmpty(t, bun.Deployment.Experiments, "deployment experiments should be present") + assertNotNilNotEmpty(t, bun.Network.ConnectionInfo, "agent connection info should be present") assertNotNilNotEmpty(t, bun.Network.CoordinatorDebug, "network coordinator debug should be present") + assertNotNilNotEmpty(t, bun.Network.Netcheck, "network netcheck should be present") assertNotNilNotEmpty(t, bun.Network.TailnetDebug, "network tailnet debug should be present") - assert.Empty(t, bun.Network.Netcheck, "did not expect netcheck to be present") assert.Empty(t, bun.Workspace.Workspace, "did not expect workspace to be present") assert.Empty(t, bun.Agent, "did not expect agent to be present") assertNotNilNotEmpty(t, bun.Logs, "bundle logs should be present") From dcf1d3a9ae740e1502a6926a08e1f09125514502 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 12 Apr 2024 10:42:27 +0200 Subject: [PATCH 72/74] test(site): add e2e tests for experiments (#12940) --- site/e2e/constants.ts | 4 ++ site/e2e/playwright.config.ts | 14 ++++++- site/e2e/tests/deployment/general.spec.ts | 39 +++++++++++++++++++ site/src/pages/DeploySettingsPage/Option.tsx | 5 ++- .../pages/DeploySettingsPage/OptionsTable.tsx | 4 +- 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 site/e2e/tests/deployment/general.spec.ts diff --git a/site/e2e/constants.ts b/site/e2e/constants.ts index 351af63be2..6998968977 100644 --- a/site/e2e/constants.ts +++ b/site/e2e/constants.ts @@ -37,3 +37,7 @@ export const requireEnterpriseTests = Boolean( process.env.CODER_E2E_REQUIRE_ENTERPRISE_TESTS, ); export const enterpriseLicense = process.env.CODER_E2E_ENTERPRISE_LICENSE ?? ""; + +// Fake experiments to verify that site presents them as enabled. +export const e2eFakeExperiment1 = "e2e-fake-experiment-1"; +export const e2eFakeExperiment2 = "e2e-fake-experiment-2"; diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index 5aa8c2186e..2fe84b17a2 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -1,6 +1,13 @@ import { defineConfig } from "@playwright/test"; import * as path from "path"; -import { coderMain, coderPort, coderdPProfPort, gitAuth } from "./constants"; +import { + coderMain, + coderPort, + coderdPProfPort, + e2eFakeExperiment1, + e2eFakeExperiment2, + gitAuth, +} from "./constants"; export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT; @@ -22,7 +29,7 @@ export default defineConfig({ testMatch: /.*\.spec\.ts/, dependencies: ["testsSetup"], use: { storageState }, - timeout: 20_000, + timeout: 50_000, }, ], reporter: [["./reporter.ts"]], @@ -60,6 +67,8 @@ export default defineConfig({ .join(" "), env: { ...process.env, + // Otherwise, the runner fails on Mac with: could not determine kind of name for C.uuid_string_t + CGO_ENABLED: "0", // This is the test provider for git auth with devices! CODER_GITAUTH_0_ID: gitAuth.deviceProvider, @@ -101,6 +110,7 @@ export default defineConfig({ gitAuth.validatePath, ), CODER_PPROF_ADDRESS: "127.0.0.1:" + coderdPProfPort, + CODER_EXPERIMENTS: e2eFakeExperiment1 + "," + e2eFakeExperiment2, }, reuseExistingServer: false, }, diff --git a/site/e2e/tests/deployment/general.spec.ts b/site/e2e/tests/deployment/general.spec.ts new file mode 100644 index 0000000000..de334a95b0 --- /dev/null +++ b/site/e2e/tests/deployment/general.spec.ts @@ -0,0 +1,39 @@ +import { expect, test } from "@playwright/test"; +import * as API from "api/api"; +import { setupApiCalls } from "../../api"; +import { e2eFakeExperiment1, e2eFakeExperiment2 } from "../../constants"; + +test("experiments", async ({ page }) => { + await setupApiCalls(page); + + // Load experiments from backend API + const availableExperiments = await API.getAvailableExperiments(); + + // Verify if the site lists the same experiments + await page.goto("/deployment/general", { waitUntil: "networkidle" }); + + const experimentsLocator = page.locator( + "div.options-table tr.option-experiments ul.option-array", + ); + await expect(experimentsLocator).toBeVisible(); + + // Firstly, check if all enabled experiments are listed + expect( + experimentsLocator.locator( + `li.option-array-item-${e2eFakeExperiment1}.option-enabled`, + ), + ).toBeVisible; + expect( + experimentsLocator.locator( + `li.option-array-item-${e2eFakeExperiment2}.option-enabled`, + ), + ).toBeVisible; + + // Secondly, check if available experiments are listed + for (const experiment of availableExperiments.safe) { + const experimentLocator = experimentsLocator.locator( + `li.option-array-item-${experiment}`, + ); + await expect(experimentLocator).toBeVisible(); + } +}); diff --git a/site/src/pages/DeploySettingsPage/Option.tsx b/site/src/pages/DeploySettingsPage/Option.tsx index de9ead5cb9..9e5e9f7abd 100644 --- a/site/src/pages/DeploySettingsPage/Option.tsx +++ b/site/src/pages/DeploySettingsPage/Option.tsx @@ -51,7 +51,7 @@ export const OptionValue: FC = (props) => { if (typeof value === "object" && !Array.isArray(value)) { return ( -

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 literal 0 HcmV?d00001 diff --git a/docs/changelogs/v2.10.0.md b/docs/changelogs/v2.10.0.md new file mode 100644 index 0000000000..9d7b76a88f --- /dev/null +++ b/docs/changelogs/v2.10.0.md @@ -0,0 +1,130 @@ +## Changelog + +> [!NOTE] +> This is a mainline Coder release. We advise enterprise customers without a staging environment to install our [latest stable release](https://github.com/coder/coder/releases/latest) while we refine this version. Learn more about our [Release Schedule](../install/releases.md). + +### BREAKING CHANGES + +- Removed `max_ttl` from templates (#12644) (@Emyrk) + > Maximum Workspace Lifetime, or `MAX_TTL`, has been removed from the product in favor of Autostop Requirement. Max Lifetime was designed to automate workspace shutdowns to enable security policy enforcement, enforce routine updates, and reduce idle resource costs. + > + > If you use Maximum Lifetime in your templates, workspaces will no longer stop at the end of this timer. Instead, we advise migrating to Autostop Requirement. + > + > Autostop Requirement shares the benefits of `MAX_TTL`, but also respects user-configured quiet hours to avoid forcing shutdowns while developers are connected. + > + > We only completely deprecate features after a 2-month heads up in the UI. + +### Features + +- Make agent stats' cardinality configurable (#12535) (@dannykopping) +- Upgrade tailscale fork to set TCP options for performance (#12574) (@spikecurtis) +- Add AWS IAM RDS Database auth driver (#12566) (@f0ssel) +- Support Windows containers in bootstrap script (#12662) (@kylecarbs) +- Add `workspace_id` to `workspace_build` audit logs (#12718) (@sreya) +- Make OAuth2 provider not enterprise-only (#12732) (@code-asher) +- Allow number options with monotonic validation (#12726) (@dannykopping) +- Expose workspace statuses (with details) as a prometheus metric (#12762) (@dannykopping) +- Agent: Support adjusting child process OOM scores (#12655) (@sreya) + > This opt-in configuration protects the Agent process from crashing via OOM. To prevent the agent from being killed in most scenarios, set `CODER_PROC_PRIO_MGMT=1` on your container. +- Expose HTTP debug server over tailnet API (#12582) (@johnstcn) +- Show queue position during workspace builds (#12606) (@dannykopping) +- Unhide support bundle command (#12745) (@johnstcn) + > The Coder support bundle grabs a variety of deployment health information to improve and expedite the debugging experience. + > ![Coder Support Bundle](https://raw.githubusercontent.com/coder/coder/main/docs/changelogs/images/support-bundle.png) +- Add golden tests for errors (#11588) (#12698) (@elasticspoon) +- Enforce confirmation before creating bundle (#12684) (@johnstcn) +- Add enabled experiments to telemetry (#12656) (@dannykopping) +- Export metric indicating each experiment's status (#12657) (@dannykopping) +- Add sftp to insights apps (#12675) (@mafredri) +- Add `template_usage_stats` table and rollup query (#12664) (@mafredri) +- Add `dbrollup` service to rollup insights (#12665) (@mafredri) +- Use `template_usage_stats` in `GetTemplateInsights` query (#12666) (@mafredri) +- Use `template_usage_stats` in `GetTemplateInsightsByInterval` query (#12667) (@mafredri) +- Use `template_usage_stats` in `GetTemplateAppInsights` query (#12669) (@mafredri) +- Use `template_usage_stats` in `GetUserLatencyInsights` query (#12671) (@mafredri) +- Use `template_usage_stats` in `GetUserActivityInsights` query (#12672) (@mafredri) +- Use `template_usage_stats` in `*ByTemplate` insights queries (#12668) (@mafredri) +- Add debug handlers for logs, manifest, and token to agent (#12593) (@johnstcn) +- Add linting to all examples (#12595) (@mafredri) +- Add C++ icon (#12572) (@michaelbrewer) +- Add support for `--mainline` (default) and `--stable` (#12858) (@mafredri) +- Make listening ports scrollable (#12660) (@BrunoQuaresma) +- Fetch agent network info over tailnet (#12577) (@johnstcn) +- Add client magicsock and agent prometheus metrics to support bundle (#12604) (@johnstcn) + +### Bug fixes + +- Server: Fix data race in TestLabelsAggregation tests (#12578) (@dannykopping) +- Dashboard: Hide actions and notifications from deleted workspaces (#12563) (@aslilac) +- VSCode: Importing api into vscode-coder (#12570) (@code-asher) +- CLI: Clean template destination path for `pull` (#12559) (@dannykopping) +- Agent: Ensure agent token is from latest build in middleware (#12443) (@f0ssel) +- CLI: Handle CLI default organization when none exists in Coder supports two release channels: mainline for the true latest version of +> Coder, and stable for large enterprise deployments. Before installing your +> control plane via Helm, please read the [Releases](./releases.md) document to +> identify the best-suited release for your team, then specify the version using +> Helm's `--version` flag. + +> The version flags for both stable and mainline are automatically filled in +> this page. + ## Install Coder with Helm 1. Create a namespace for Coder, such as `coder`: @@ -112,10 +121,22 @@ locally in order to log in and manage templates. 1. Run the following command to install the chart in your cluster. + For the **mainline** Coder release: + ```shell helm install coder coder-v2/coder \ --namespace coder \ - --values values.yaml + --values values.yaml \ + --version 2.10.0 + ``` + + For the **stable** Coder release: + + ```shell + helm install coder coder-v2/coder \ + --namespace coder \ + --values values.yaml \ + --version 2.9.1 ``` You can watch Coder start up by running `kubectl get pods -n coder`. Once diff --git a/docs/install/releases.md b/docs/install/releases.md new file mode 100644 index 0000000000..701c9793e4 --- /dev/null +++ b/docs/install/releases.md @@ -0,0 +1,56 @@ +# Releases + +Coder releases are cut directly from main in our +[Github](https://github.com/coder/coder) on the first Tuesday of each month. + +We recommend enterprise customers test the compatibility of new releases with +their infrastructure on a staging environment before upgrading a production +deployment. + +We support two release channels: +[mainline](https://github.com/coder/coder/2.10.0) for the edge version of Coder +and [stable](https://github.com/coder/coder/releases/latest) for those with +lower tolerance for fault. We field our mainline releases publicly for two weeks +before promoting them to stable. + +### Mainline releases + +- Intended for customers with a staging environment +- Gives earliest access to new features +- May include minor bugs +- All bugfixes and security patches are supported + +### Stable releases + +- Safest upgrade/installation path +- May not include the latest features +- Security vulnerabilities and major bugfixes are supported + +> Note: We support major security vulnerabilities (CVEs) for the past three +> versions of Coder. + +## Installing stable + +When installing Coder, we generally advise specifying the desired version from +our Github [releases page](https://github.com/coder/coder/releases). + +You can also use our `install.sh` script with the `stable` flag to install the +latest stable release: + +```shell +curl -fsSL https://coder.com/install.sh | sh -s -- --stable +``` + +Best practices for installing Coder can be found on our [install](./index.md) +pages. + +## Release schedule + +| Release name | Date | Status | +| ------------ | ------------------ | ---------------- | +| 2.7.0 | January 01, 2024 | Not Supported | +| 2.8.0 | Februrary 06, 2024 | Security Support | +| 2.9.0 | March 07, 2024 | Stable | +| 2.10.0 | April 03, 2024 | Mainline | +| 2.11.0 | May 07, 2024 | Not Released | +| 2.12.0 | June 04, 2024 | Not Released | From 41b8ff3e810e10433699bdbd6bddb51d88f33f9d Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Thu, 4 Apr 2024 09:21:03 -0300 Subject: [PATCH 23/74] chore(site): add e2e to test add and remove user (#12851) --- site/e2e/helpers.ts | 11 ++++-- .../users/createUserWithPassword.spec.ts | 35 +++++++++++++++++++ site/e2e/tests/users/removeUser.spec.ts | 33 +++++++++++++++++ site/src/api/api.ts | 8 +++++ .../pages/CreateUserPage/CreateUserForm.tsx | 6 +++- .../CreateUserPage/CreateUserPage.test.tsx | 7 ++-- 6 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 site/e2e/tests/users/createUserWithPassword.spec.ts create mode 100644 site/e2e/tests/users/removeUser.spec.ts diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 519a8968f8..84b1b911c9 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -7,6 +7,7 @@ import capitalize from "lodash/capitalize"; import path from "path"; import * as ssh from "ssh2"; import { Duplex } from "stream"; +import * as API from "api/api"; import type { WorkspaceBuildParameter, UpdateTemplateMeta, @@ -565,7 +566,7 @@ const createTemplateVersionTar = async ( ); }; -const randomName = () => { +export const randomName = () => { return randomUUID().slice(0, 8); }; @@ -603,7 +604,7 @@ export const createServer = async ( return e; }; -const findSessionToken = async (page: Page): Promise => { +export const findSessionToken = async (page: Page): Promise => { const cookies = await page.context().cookies(); const sessionCookie = cookies.find((c) => c.name === "coder_session_token"); if (!sessionCookie) { @@ -825,3 +826,9 @@ export async function openTerminalWindow( return terminal; } + +export const setupApiCalls = async (page: Page) => { + const token = await findSessionToken(page); + API.setSessionToken(token); + API.setHost(`http://127.0.0.1:${coderPort}`); +}; diff --git a/site/e2e/tests/users/createUserWithPassword.spec.ts b/site/e2e/tests/users/createUserWithPassword.spec.ts new file mode 100644 index 0000000000..b8c95d35b3 --- /dev/null +++ b/site/e2e/tests/users/createUserWithPassword.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from "@playwright/test"; +import { randomName } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("create user with password", async ({ page, baseURL }) => { + await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" }); + await expect(page).toHaveTitle("Users - Coder"); + + await page.getByRole("button", { name: "Create user" }).click(); + await expect(page).toHaveTitle("Create User - Coder"); + + const name = randomName(); + const userValues = { + username: name, + email: `${name}@coder.com`, + loginType: "password", + password: "s3cure&password!", + }; + + await page.getByLabel("Username").fill(userValues.username); + await page.getByLabel("Email").fill(userValues.email); + await page.getByLabel("Login Type").click(); + await page.getByRole("option", { name: "Password", exact: false }).click(); + // Using input[name=password] due to the select element utilizing 'password' + // as the label for the currently active option. + const passwordField = page.locator("input[name=password]"); + await passwordField.fill(userValues.password); + await page.getByRole("button", { name: "Create user" }).click(); + await expect(page.getByText("Successfully created user.")).toBeVisible(); + + await expect(page).toHaveTitle("Users - Coder"); + await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible(); +}); diff --git a/site/e2e/tests/users/removeUser.spec.ts b/site/e2e/tests/users/removeUser.spec.ts new file mode 100644 index 0000000000..c6e60c25e6 --- /dev/null +++ b/site/e2e/tests/users/removeUser.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from "@playwright/test"; +import * as API from "api/api"; +import { randomName, setupApiCalls } from "../../helpers"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(async ({ page }) => await beforeCoderTest(page)); + +test("remove user", async ({ page, baseURL }) => { + await setupApiCalls(page); + const currentUser = await API.getAuthenticatedUser(); + const name = randomName(); + const user = await API.createUser({ + email: `${name}@coder.com`, + username: name, + password: "s3cure&password!", + login_type: "password", + disable_login: false, + organization_id: currentUser.organization_ids[0], + }); + + await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" }); + await expect(page).toHaveTitle("Users - Coder"); + + const userRow = page.locator("tr", { hasText: user.email }); + await userRow.getByRole("button", { name: "More options" }).click(); + await userRow.getByText("Delete", { exact: false }).click(); + + const dialog = page.getByTestId("dialog"); + await dialog.getByLabel("Name of the user to delete").fill(user.username); + await dialog.getByRole("button", { name: "Delete" }).click(); + + await expect(page.getByText("Successfully deleted the user.")).toBeVisible(); +}); diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 7048d9bca9..760f860ebe 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -72,6 +72,14 @@ if (token !== null && token.getAttribute("content") !== null) { } } +export const setSessionToken = (token: string) => { + axios.defaults.headers.common["Coder-Session-Token"] = token; +}; + +export const setHost = (host?: string) => { + axios.defaults.baseURL = host; +}; + const CONTENT_TYPE_JSON = { "Content-Type": "application/json", }; diff --git a/site/src/pages/CreateUserPage/CreateUserForm.tsx b/site/src/pages/CreateUserPage/CreateUserForm.tsx index dee0a4b7c2..3f44c71838 100644 --- a/site/src/pages/CreateUserPage/CreateUserForm.tsx +++ b/site/src/pages/CreateUserPage/CreateUserForm.tsx @@ -193,7 +193,11 @@ export const CreateUserForm: FC< type="password" /> - + ); diff --git a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/CreateUserPage/CreateUserPage.test.tsx index 2787b26244..83a1c0266b 100644 --- a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/CreateUserPage/CreateUserPage.test.tsx @@ -1,6 +1,5 @@ import { fireEvent, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { Language as FooterLanguage } from "components/FormFooter/FormFooter"; import { renderWithAuth, waitForLoaderToBeRemoved, @@ -35,9 +34,9 @@ const fillForm = async ({ await userEvent.type(emailField, email); await userEvent.type(loginTypeField, "password"); await userEvent.type(passwordField as HTMLElement, password); - const submitButton = await screen.findByText( - FooterLanguage.defaultSubmitLabel, - ); + const submitButton = screen.getByRole("button", { + name: "Create user", + }); fireEvent.click(submitButton); }; From 90efa1b846f2268288ae8fd87706a13c37a77d2a Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 4 Apr 2024 15:42:26 +0200 Subject: [PATCH 24/74] docs: describe multi-cloud architecture (#12857) --- docs/about/architecture.md | 106 +++++++++++++++++++++++ docs/images/architecture-multi-cloud.png | Bin 0 -> 193107 bytes 2 files changed, 106 insertions(+) create mode 100644 docs/images/architecture-multi-cloud.png diff --git a/docs/about/architecture.md b/docs/about/architecture.md index b180caf66d..943071276e 100644 --- a/docs/about/architecture.md +++ b/docs/about/architecture.md @@ -162,3 +162,109 @@ offer the fastest developer experience. - Session persistence (sticky sessions) can be disabled as _coderd_ instances are stateless. - WebSocket and long-lived connections must be supported. + +### Multi-cloud architecture + +By distributing Coder workspaces across different cloud providers, organizations +can mitigate the risk of downtime caused by provider-specific outages or +disruptions. Additionally, multi-cloud deployment enables organizations to +leverage the unique features and capabilities offered by each cloud provider, +such as region availability and pricing models. + +![Architecture Diagram](../images/architecture-multi-cloud.png) + +#### Components + +The deployment model comprises: + +- `coderd` instances deployed within a single region of the same cloud provider, + with replicas strategically distributed across availability zones. +- Workspace provisioners deployed in each cloud, communicating with `coderd` + instances. +- Workspace proxies running in the same locations as provisioners to optimize + user connections to workspaces for maximum speed. + +Due to the relatively large overhead of cross-regional communication, it is not +advised to set up multi-cloud control planes. It is recommended to keep coderd +replicas and the database within the same cloud-provider and region. + +Note: The _multi-cloud architecture_ follows the deployment principles outlined +in the _multi-region architecture_. However, it adapts component selection based +on the specific cloud provider. Developers can initiate workspaces based on the +nearest region and technical specifications provided by the cloud providers. + +##### Workload resources + +**Workspace provisioner** + +- _Security recommendation_: Create a long, random pre-shared key (PSK) and add + it to the regional secret store, so that local _provisionerd_ can access it. + Remember to distribute it using safe, encrypted communication channel. The PSK + must also be added to the _coderd_ configuration. + +**Workspace proxy** + +- _Security recommendation_: Use `coder` CLI to create + [authentication tokens for every workspace proxy](../admin/workspace-proxies.md#requirements), + and keep them in regional secret stores. Remember to distribute them using + safe, encrypted communication channel. + +**Managed database** + +- For AWS: _Amazon RDS for PostgreSQL_ +- For Azure: _Azure Database for PostgreSQL - Flexible Server_ +- For GCP: _Cloud SQL for PostgreSQL_ + +##### Workload supporting resources + +**Kubernetes platform (optional)** + +- For AWS: _Amazon Elastic Kubernetes Service_ +- For Azure: _Azure Kubernetes Service_ +- For GCP: _Google Kubernetes Engine_ + +See how to deploy +[Coder on Azure Kubernetes Service](https://github.com/ericpaulsen/coder-aks). + +Learn more about [security requirements](../install/kubernetes.md) for deploying +Coder on Kubernetes. + +**Load balancer** + +- For AWS: + - _AWS Network Load Balancer_ + - Level 4 load balancing + - For Kubernetes deployment: annotate service with + `service.beta.kubernetes.io/aws-load-balancer-type: "nlb"`, preserve the + client source IP with `externalTrafficPolicy: Local` + - _AWS Classic Load Balancer_ + - Level 7 load balancing + - For Kubernetes deployment: set `sessionAffinity` to `None` +- For Azure: + - _Azure Load Balancer_ + - Level 7 load balancing + - Azure Application Gateway + - Deploy Azure Application Gateway when more advanced traffic routing + policies are needed for Kubernetes applications. + - Take advantage of features such as WebSocket support and TLS termination + provided by Azure Application Gateway, enhancing the capabilities of + Kubernetes deployments on Azure. +- For GCP: + - _Cloud Load Balancing_ with SSL load balancer: + - Layer 4 load balancing, SSL enabled + - _Cloud Load Balancing_ with HTTPS load balancer: + - Layer 7 load balancing + - For Kubernetes deployment: annotate service (with ingress enabled) with + `kubernetes.io/ingress.class: "gce"`, leverage the `NodePort` service + type. + - Note: HTTP load balancer rejects DERP upgrade, Coder will fallback to + WebSockets + +**Single sign-on** + +- For AWS: + [AWS IAM Identity Center](https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html) +- For Azure: + [Microsoft Entra ID Sign-On](https://learn.microsoft.com/en-us/entra/identity/app-proxy/) +- For GCP: + [Google Cloud Identity Platform](https://cloud.google.com/architecture/identity/single-sign-on) diff --git a/docs/images/architecture-multi-cloud.png b/docs/images/architecture-multi-cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..4b40126c7b801fc29fbc0c7ee487e28e388cc31e GIT binary patch literal 193107 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