Compare commits

...

1027 Commits

Author SHA1 Message Date
Ben Potter 321546474b docs: add v2.9.0 changelog (#12562)
* docs: add v2.9.0 changelog

* added sharable ports screenshot

* moved autostop visibility improvements from backend to dashboard, added screenshot

* move experiments to bottom

* added activity bump screenshot

---------

Co-authored-by: Stephen Kirby <me@skirby.dev>
2024-03-12 12:12:35 -05:00
Cian Johnston 47cb584052 fix(support): sanitize agent env (#12554) 2024-03-12 15:23:11 +00:00
Steven Masley 597694fbdd chore: bump migration file (#12556) 2024-03-12 14:55:45 +00:00
Steven Masley e11d3ca0ee chore: move default everyone group to a migration (#12435) 2024-03-12 09:27:36 -05:00
Bruno Quaresma f3083226ab chore: add package manager (#12551)
Every time I run `pnpm` in the project it adds the package manager attribute on package.json so I just decided to push it since it does not look like an issue and we can make sure everyone is running the same pnpm version.
2024-03-12 10:44:23 -03:00
Cian Johnston 7b081c873e fix(site): warn when user leaves template editor with un-built changes (#12548) 2024-03-12 13:08:54 +00:00
Danny Kopping 90d00190ea chore: remove pr_number param from deploy-pr.sh (#12549)
Field was removed in https://github.com/coder/coder/pull/11259

Signed-off-by: Danny Kopping <danny@coder.com>
2024-03-12 12:01:31 +00:00
Cian Johnston edc465c449 fix(site): TemplateVersionEditor: allow triggering builds on non-dirtied template version (#12547) 2024-03-12 11:35:16 +00:00
Spike Curtis 51707446d0 fix: stop holding Pubsub mutex while calling pq.Listener (#12518)
fixes #11950

https://github.com/coder/coder/issues/11950#issuecomment-1987756088 explains the bug

We were also calling into `Unlisten()` and `Close()` while holding the mutex.  I don't believe that `Close()` depends on the notification loop being unblocked, but it's hard to be sure, and the safest thing to do is assume it could block.

So, I added a unit test that fakes out `pq.Listener` and sends a bunch of notifies every time we call into it to hopefully prevent regression where we hold the mutex while calling into these functions.

It also removes the use of a `context.Context` to stop the PubSub -- it must be explicitly `Closed()`.  This simplifies a bunch of the logic, and is how we use the pubsub anyway.
2024-03-12 09:44:12 +04:00
dependabot[bot] 6f00ccfa64 chore: bump storj.io/drpc from 0.0.33-0.20230420154621-9716137f6037 to 0.0.33 (#12526)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 03:43:01 +00:00
dependabot[bot] da146e9655 chore: bump golang.org/x/crypto from 0.20.0 to 0.21.0 (#12523)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 03:41:54 +00:00
dependabot[bot] 242e4c4c85 chore: bump golang.org/x/term from 0.17.0 to 0.18.0 (#12525)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 03:40:00 +00:00
Michael Brewer f6ed81bc3b feat(site): add microsoft teams icon (#12513)
* feat(site): add microsoft teams icon

Attribution below:
Vectors and icons by <a href="https://github.com/garudatechnologydevelopers/sketch-icons?ref=svgrepo.com" target="_blank">Garuda Technology</a> in MIT License via <a href="https://www.svgrepo.com/" target="_blank">SVG Repo</a>


closes #12496

* fix(site): correct order of icons
2024-03-12 03:18:10 +00:00
Cian Johnston d704ff4570 chore(codersdk): explain format of codersdk.UpdateWorkspaceAutostartRequest.Schedule (#12539) 2024-03-11 22:50:38 +00:00
Kayla Washburn-Love a546cb8b32 chore: add stories to Search (#12457) 2024-03-11 12:16:31 -06:00
Bruno Quaresma 83af8674e8 chore(site): add CreateTokenPage story (#12472) 2024-03-11 14:10:19 -03:00
Steven Masley e3051dff0c chore: add workspace id filter on api (#12483)
* chore: add workspace id filter on api
2024-03-11 11:37:15 -05:00
Cian Johnston 8f40ee3465 Revert "feat: make agent stats' cardinality configurable (#12468)" (#12533)
This reverts commit 21d1873d97.
2024-03-11 14:33:36 +00:00
elasticspoon 773862a9f5 feat(cli): make url optional for login command (#10925) (#12466)
Allow `coder login` to log into existing deployment if available.

Update help and error messages to indicate that `coder login` is
available as a command.

Fixes #10925
Fixes #9551
2024-03-11 16:14:19 +02:00
Cian Johnston bed61f7d2a fix(coderd): correctly handle tar dir entries with missing path separator (#12479)
* coderd: add test to reproduce trailing directory issue
* coderd: add trailing path separator to dir entries when converting to zip
* provisionersdk: add trailing path separator to directory entries
2024-03-11 14:06:41 +00:00
Danny Kopping 21d1873d97 feat: make agent stats' cardinality configurable (#12468)
Closes #12221
2024-03-11 16:04:08 +02:00
Cian Johnston 0647ec1960 fix(coderd): prevent nil err deref (#12475) 2024-03-11 14:03:58 +00:00
Garrett Delfosse dc69341583 fix: make public menu item selectable (#12484) 2024-03-11 10:00:40 -04:00
Alessandro Varesi 5e9bf31229 fix: devcontainer-docker bad default directory (#12453) 2024-03-11 16:56:41 +03:00
Michael Brewer cef632b1fb feat(site): add dotnet icon (#12512) 2024-03-11 16:54:34 +03:00
Bruno Quaresma cd64e981b4 chore(site): add stories to 404 page (#12470)
Related to https://github.com/coder/coder/issues/12263
2024-03-11 10:36:06 -03:00
Cian Johnston b1ecc53033 chore(coderd): improve tests for tar<->zip conversion (#12477)
* improve tests for tar<->zip conversion
* set mode and modtime correctly when converting from zip to tar (#12476)
2024-03-11 13:29:57 +00:00
Bruno Quaresma 0220c97ef9 chore(site): add TableToolbar stories (#12473)
Related to https://github.com/coder/coder/issues/12263
2024-03-11 10:21:47 -03:00
Bruno Quaresma b8dd6b3aa2 chore(site): add Form storybook (#12469)
Related to #12260
2024-03-11 10:21:18 -03:00
Cian Johnston 1f276a22b3 chore(dogfood): update keys (#12515) 2024-03-11 13:07:48 +00:00
Mathias Fredriksson bae0a747ed test(coderd): skip flaky dau test (#12517)
* test(coderd): skip flaky dau test

* chore(coderd/database/dbpurge): fix failing test (#12530)

---------

Co-authored-by: Cian Johnston <cian@coder.com>
2024-03-11 12:54:38 +00:00
Michael Brewer 5296611a3f feat(site): add confluence icon (#12500)
Attribution for this icon below, if needed

Vectors and icons by <a href="https://github.com/vscode-icons/vscode-icons?ref=svgrepo.com" target="_blank">Vscode Icons</a> in MIT License via <a href="https://www.svgrepo.com/" target="_blank">SVG Repo</a>
2024-03-11 11:34:02 +03:00
dependabot[bot] 2b4560cc4b chore: bump github.com/fergusstrange/embedded-postgres (#12400)
Bumps [github.com/fergusstrange/embedded-postgres](https://github.com/fergusstrange/embedded-postgres) from 1.25.0 to 1.26.0.
- [Release notes](https://github.com/fergusstrange/embedded-postgres/releases)
- [Commits](https://github.com/fergusstrange/embedded-postgres/compare/v1.25.0...v1.26.0)

---
updated-dependencies:
- dependency-name: github.com/fergusstrange/embedded-postgres
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 00:16:16 +03:00
dependabot[bot] 6588cee38a chore: bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 (#12460)
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.3.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.3)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 00:15:52 +03:00
Michael Smith 4d42c07c72 chore(site): update and refactor all custom hook tests that rely on React Router (#12219)
* chore: rename useTab to useSearchParamsKey and add test

* chore: mark old renderHookWithAuth as deprecated (temp)

* fix: update imports for useResourcesNav

* refactor: change API for useSearchParamsKey

* chore: let user pass in their own URLSearchParams value

* refactor: clean up comments for clarity

* fix: update import

* wip: commit progress on useWorkspaceDuplication revamp

* chore: migrate duplication test to new helper

* refactor: update code for clarity

* refactor: reorder test cases for clarity

* refactor: split off hook helper into separate file

* refactor: remove reliance on internal React Router state property

* refactor: move variables around for more clarity

* refactor: more updates for clarity

* refactor: reorganize test cases for clarity

* refactor: clean up test cases for useWorkspaceDupe

* refactor: clean up test cases for useWorkspaceDupe
2024-03-08 18:31:01 -05:00
Kayla Washburn-Love cf4f56dc2f chore: add stories for MoreMenu (#12464) 2024-03-08 12:01:48 -07:00
Kayla Washburn-Love 8d8220bb07 chore: add stories for Loader (#12445) 2024-03-08 11:35:14 -07:00
Ben Potter 1e17782ff6 docs: simplify install docs (#11946)
* docs: simplify install docs

* changes from feedback

* fmt

* fixups from feedback
2024-03-08 15:15:59 +00:00
Danny Kopping 7a92154e67 Install pnpm before calling pnpm exec in make stages (#12471)
Signed-off-by: Danny Kopping <danny@coder.com>
2024-03-08 14:33:28 +02:00
Bruno Quaresma 5b2acbc5b7 chore(site): add FileUpload stories (#12456)
Related to https://github.com/coder/coder/issues/12260
2024-03-08 09:08:44 -03:00
Bruno Quaresma 18d1c17db1 chore(site): add storybook for BuildAvatar and BuildIcon (#12455)
Related to https://github.com/coder/coder/issues/12260
2024-03-08 08:06:56 -03:00
Bruno Quaresma 060033e4ef fix(site): fix terminal size when displaying alerts (#12444)
Before - The terminal size does not fit the available space so the bottom is hidden.

https://github.com/coder/coder/assets/3165839/d08470b9-9fc6-476c-a551-8a3e13fc25bf

After - The terminal adjusts when there are alert changes.

https://github.com/coder/coder/assets/3165839/8cc32bfb-056f-47cb-97f2-3bb18c5fe906

Unfortunately, I don't think there is a sane way to automate tests for this but open to suggestions.

Close https://github.com/coder/coder/issues/7914
2024-03-08 07:38:40 -03:00
Dean Sheather d2a5b31b2b feat: add derp mesh health checking in workspace proxies (#12222) 2024-03-08 16:31:40 +10:00
Colin Adler 6b0b87eb27 fix: add --block-direct-connections to wsproxies (#12182) 2024-03-07 23:45:59 -06:00
Colin Adler 66154f937e fix(coderd): pass block endpoints into servertailnet (#12149) 2024-03-08 05:29:54 +00:00
Garrett Delfosse d2a74cf547 fix: display tooltip when selection is disabled (#12439) 2024-03-07 10:43:25 -05:00
Dean Sheather 586586e9dd fix: do not set max deadline for workspaces on template update (#12446)
* fix: do not set max deadline for workspaces on template update

When templates are updated and schedule data is changed, we update all
running workspaces to have up-to-date scheduling information that sticks
to the new policy.

When updating the max_deadline for existing running workspaces, if the
max_deadline was before now()+2h we would set the max_deadline to
now()+2h.

Builds that don't/shouldn't have a max_deadline have it set to 0, which
is always before now()+2h, and thus would always have the max_deadline
updated.

* test: add unit test to excercise template schedule bug
---------

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
2024-03-07 09:42:50 -06:00
Cian Johnston 17caf58b5e feat(support): add template info to support bundle (#12451)
Adds workspace build parameters, template, template version, and zipped template source to support bundle.
2024-03-07 14:43:46 +00:00
Bruno Quaresma db02c72ac6 chore(site): add storybook for terminal page (#12441) 2024-03-07 14:17:38 +00:00
Spike Curtis b96f6b48a4 fix: ensure ssh cleanup happens on cmd error
I noticed in my logs that sometimes `coder ssh` doesn't gracefully disconnect from the coordinator.

The cause is the `closerStack` construct we use in that function.  It has two paths to start closing things down:

1. explicit `close()` which we do in `defer`
2. context cancellation, which happens if the cli function returns an error

sometimes the ssh remote command returns an error, and this triggers context cancellation of the `closerStack`.  That is fine in and of itself, but we still want the explicit `close()` to wait until everything is closed before returning, since that's where we do cleanup, including the graceful disconnect.  Prior to this fix the `close()` just immediately exits if another goroutine is closing the stack.  Here we add a wait until everything is done.
2024-03-07 17:26:49 +04:00
Cian Johnston c8aa99a5b8 feat(coderd/database/dbfake): allow specifying fileID in TemplateVersionBuilder (#12450) 2024-03-07 12:36:11 +00:00
dependabot[bot] e4326947c4 chore: bump github.com/go-chi/httprate from 0.8.0 to 0.9.0 (#12401)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 16:57:21 +05:00
dependabot[bot] 14b1400968 chore: bump github.com/go-playground/validator/v10 from 10.18.0 to 10.19.0 (#12396)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 16:57:12 +05:00
dependabot[bot] de35755bd2 chore: bump github.com/hashicorp/hcl/v2 from 2.17.0 to 2.20.0 (#12398)
Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.17.0 to 2.20.0.
- [Release notes](https://github.com/hashicorp/hcl/releases)
- [Changelog](https://github.com/hashicorp/hcl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/hcl/compare/v2.17.0...v2.20.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/hcl/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 10:46:51 +00:00
Dean Sheather bd752a6d8b chore: embed static files in debug builds (#12449) 2024-03-07 09:23:28 +00:00
Cian Johnston 3e6e1e6f10 ci: add jnb wsproxy + update dogfood template (#12440)
Co-authored-by: Dean Sheather <dean@deansheather.com>
2024-03-06 18:54:26 +00:00
Dean Sheather 662be56d72 chore: rename migrations to fix main (#12442) 2024-03-06 18:28:53 +00:00
Steven Masley b5f866c1cb chore: add organization_id column to provisioner daemons (#12356)
* chore: add organization_id column to provisioner daemons
* Update upsert to include organization id on set
2024-03-06 12:04:50 -06:00
Dean Sheather 46a2ff1061 feat: allow setting port share protocol (#12383)
Co-authored-by: Garrett Delfosse <garrett@coder.com>
2024-03-06 09:23:57 -05:00
Steven Masley 23ff807a27 chore: remove autocreate orgs on CreateUser (#12434)
New users must be explictly given an organization to join.
Organizations should not be auto created as a side effect of
creating a new user.
2024-03-06 07:29:28 -06:00
Dean Sheather 842799847a chore: fix trivy scanning (#12421) 2024-03-05 19:04:16 -06:00
Michael Smith a92853c72d fix: ensure auto-workspace creation waits until all parameters are ready (#12419)
* fix: ensure auto-workspace creation waits until all parameters are ready

* refactor: move creation blocking logic to main callback

* fix: let creation start if experimental feature is off
2024-03-05 18:42:50 -05:00
Kayla Washburn-Love 0fe109d517 chore: sort imports in our typescript code (#12417) 2024-03-05 16:31:22 -07:00
Steven Masley 17c486c5e6 chore: ensure default org always exists (#12412)
* chore: ensure default org always exists

First user just joins the org created by the migration
2024-03-05 14:06:35 -06:00
Bruno Quaresma bc30c9c013 feat(site): warn user if they leave the editor without publishing (#12406) 2024-03-05 16:55:23 -03:00
Garrett Delfosse 61bd341a36 chore: change max share level on existing port shares (#12411) 2024-03-05 13:47:01 -05:00
Cian Johnston 5106d9fc47 feat(support): fetch data concurrently (#12385)
Modifies pkg support to fetch data concurrently
2024-03-05 17:41:42 +00:00
Bruno Quaresma fb88fa8603 feat(site): display error messages on ws and access url health pages (#12430)
Close https://github.com/coder/coder/issues/12408
2024-03-05 13:27:57 -03:00
Cian Johnston 4343998c37 chore(coderd): add tests for big oidc tokens (#12424)
- Adds two test cases for a 64k+ ID token and a 64k+ userinfo payload.
- Reformats the entire test cases array as instructed by CI
2024-03-05 14:46:00 +00:00
Marcin Tojek b1f9a6dc31 fix: use timestamptz instead of timestamp (#12425)
* fix: use timestampz instead of timestamp

* fix: timestamptz
2024-03-05 14:16:29 +00:00
Marcin Tojek 3e99c0373f fix: improve pagination parser (#12422) 2024-03-05 14:05:15 +00:00
Cian Johnston 61db293b33 feat(scripts/develop.sh): add --debug flag to develop.sh (#12423)
Adds a `--debug` flag to `scripts/develop.sh` that will start coder under `dlv debug` instead.
You can then use e.g. the following launch snippet to connect dlv:
```
    {
      "name": "Delve Remote",
      "type": "go",
      "request": "attach",
      "mode": "remote",
      "port": 12345,
    }
```

You can also run invididual CLI commands under dlv e.g.

```
debug=1 scripts/coder-dev.sh list
```

Also sets CGO_ENABLED=0 in develop.sh by default.
2024-03-05 13:29:08 +00:00
dependabot[bot] 8585863d0e chore: bump golang.org/x/crypto from 0.19.0 to 0.20.0 (#12403)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/crypto/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-05 15:49:17 +05:00
Marcin Tojek e4fa212164 fix: always return count of workspaces (#12407) 2024-03-05 09:24:43 +01:00
Dean Sheather 0016b0200b chore: add test for workspace proxy derp meshing (#12220)
- Reworks the proxy registration loop into a struct (so I can add a `RegisterNow` method)
- Changes the proxy registration loop interval to 15s (previously 30s)
- Adds test which tests bidirectional DERP meshing on all possible paths between 6 workspace proxy replicas

Related to https://github.com/coder/customers/issues/438
2024-03-04 23:40:15 -08:00
Steven Masley 5c6974e55f feat: implement provisioner auth middleware and proper org params (#12330)
* feat: provisioner auth in mw to allow ExtractOrg

Step to enable org scoped provisioner daemons

* chore: handle default org handling for provisioner daemons
2024-03-04 15:15:41 -06:00
Colin Adler 926fd7ffa6 chore: add v2.6.1, v2.7.3, v2.8.4 release notes (#12415) 2024-03-04 12:23:01 -06:00
Alex 320c2eac6f Entra External Auth for ADO (#12201) 2024-03-04 12:12:46 -06:00
Colin Adler 4439a920e4 Merge pull request from GHSA-7cc2-r658-7xpf
This fixes a vulnerability with the `CODER_OIDC_EMAIL_DOMAIN` option,
where users with a superset of the allowed email domain would be allowed
to login. For example, given `CODER_OIDC_EMAIL_DOMAIN=google.com`, a
user would be permitted entry if their email domain was
`colin-google.com`.
2024-03-04 12:52:03 -05:00
Garrett Delfosse 8f190b2016 fix: disallow out of range ports (#12414) 2024-03-04 12:25:06 -05:00
Kayla Washburn-Love 3a86ae569a refactor: use TableEmpty in user settings (#12389) 2024-03-04 09:45:40 -07:00
Mathias Fredriksson 4ce1448bbe fix(cli): generate correctly named file in DumpHandler (#12409) 2024-03-04 18:35:33 +02:00
Bruno Quaresma afcea74462 fix(site): retry and debug passing build parameters options (#12384) 2024-03-04 10:25:53 -03:00
Kayla Washburn-Love af4d0b148b chore: add stories for Popover (#12387) 2024-03-01 15:43:35 -07:00
Michael Brewer 722ff50e59 fix: add service banner to workspace page (#12381) 2024-03-01 10:53:03 -07:00
Kayla Washburn-Love 4f0b885c30 chore: add stories for UserAvatar (#12376) 2024-03-01 10:50:17 -07:00
Kayla Washburn-Love 7824bee25f chore: add stories for Stack (#12375) 2024-03-01 10:26:50 -07:00
Kayla Washburn-Love f4c888f33e chore: add stories for Latency component (#12374) 2024-03-01 10:26:38 -07:00
Kayla Washburn-Love f00935baa6 chore: add stories for TableEmpty and TableLoader (#12373) 2024-03-01 10:26:30 -07:00
Cian Johnston b1c2fea78b feat(cli): add support cmd (#12328)
Part of #12163

- Adds a command coder support bundle <workspace> that generates a 
  support bundle and writes it to coder-support-$(date +%s).zip.
- Note: this is hidden currently until the rest of the functionality is fleshed out.
2024-03-01 17:13:50 +00:00
Colin Adler e5d911462f fix(tailnet): enforce valid agent and client addresses (#12197)
This adds the ability for `TunnelAuth` to also authorize incoming wireguard node IPs, preventing agents from reporting anything other than their static IP generated from the agent ID.
2024-03-01 09:02:33 -06:00
Colin Adler 7fbca62e08 chore: fix Test_parseInsightsStartAndEndTime flake (#12377)
Fixes https://github.com/coder/coder/issues/10600
2024-02-29 18:20:25 -06:00
Stephen Kirby 5a53afda46 minor change to quiet hours docs (#12338) 2024-02-29 14:50:00 -06:00
Bruno Quaresma 26b483d95e fix(site): fix form layout for tablet viewports (#12369) 2024-02-29 16:24:06 -03:00
Steven Masley 4006974a98 fix: external auth device flow, check both queries for errors (#12367)
* fix: external auth device flow, check both queries for errors
* Minor style update

---------

Co-authored-by: BrunoQuaresma <bruno_nonato_quaresma@hotmail.com>
2024-02-29 13:00:16 -06:00
Cian Johnston 9f3591add8 chore(cli): use xerrors.Errorf instead of fmt.Errorf (#12368) 2024-02-29 18:58:48 +00:00
Steven Masley cbcf4ef2c4 chore: add faking 429 responses from fake idp (#12365)
Required to trigger error condition in fe.
See pull (#12367)
2024-02-29 09:45:53 -06:00
Cian Johnston eba8cd7c07 chore: consolidate various randomPort() implementations (#12362)
Consolidates our existing randomPort() implementations to package testutil
2024-02-29 12:51:44 +00:00
Cian Johnston 4f87ba46f9 chore: update provisioner tag documentation with suggestions from #12315 (#12347)
- Adds more testcases to TestAcquirer_MatchTags
- Adds functionality to generate a table from above test
- Update provisioner tag documentation with generated table
- Apply other feedback from #12315
2024-02-29 12:31:11 +00:00
Cian Johnston e57c101200 feat: add support package and accompanying tests (#12289) 2024-02-29 11:58:33 +00:00
Cian Johnston 2bf3c72948 chore: add test for enterprise server cli (#12353) 2024-02-29 10:25:50 +00:00
Cian Johnston b17fcd9cff ci: use linter version from Dockerfile (#12354) 2024-02-29 09:53:32 +00:00
Kayla Washburn-Love b24ad1bbf0 refactor: show parameter suggestions from user history below field (#12340) 2024-02-28 15:29:48 -07:00
Kyle Carberry b2a5e2f4c0 fix: Increase license key rows (#12352)
It was pretty hard to tell when you pasted something in
this box with only displaying a single line.

This should help!
2024-02-28 21:57:10 +00:00
Steven Masley 97f083810f chore: provide usage instruction for CLI argument failures (#12309)
* chore: add usage to # cli arg failures
2024-02-28 12:10:17 -06:00
Marcin Tojek 30d9d84758 fix: use flag to enable Prometheus (#12345) 2024-02-28 17:58:03 +01:00
Dean Sheather bedd2c5922 fix: avoid race between replicas on start (#12344)
DERP mesh key setup would do a SELECT and then an INSERT on failure, without a lock. During some testing with multiple replicas, I managed to cause a replica to crash due to them initializing simultaneously.

Fixes:

Encountered an error running "coder server"
create coder API: insert mesh key: pq: duplicate key value violates unique constraint "site_configs_key_key"

Co-authored-by: Cian Johnston <cian@coder.com>
2024-02-28 16:14:11 +00:00
Bruno Quaresma 76273bf369 feat(site): display client errors in DERP Region health page (#12318) 2024-02-28 13:30:38 +00:00
Cian Johnston 1465ee2ed1 fix(coderd): use database.IsQueryCanceledError instead of xerrors.Is(err, context.Canceled) (#12325) 2024-02-28 21:19:57 +10:00
Marcin Tojek eb4a1e2568 feat: enable Prometheus endpoint for external provisioner (#12320) 2024-02-28 09:21:56 +01:00
Michael Smith 087f973415 refactor(site): clean up clipboard functionality and define tests (#12296)
* refactor: clean up and update API for useClipboard

* wip: commit current progress on useClipboard test

* docs: clean up wording on showCopySuccess

* chore: make sure tests can differentiate between HTTP/HTTPS

* chore: add test ID to dummy input

* wip: commit progress on useClipboard test

* wip: commit more test progress

* refactor: rewrite code for clarity

* chore: finish clipboard tests

* fix: prevent double-firing for button click aliases

* refactor: clean up test setup

* fix: rename incorrect test file

* refactor: update code to display user errors

* refactor: redesign useClipboard to be easier to test

* refactor: clean up GlobalSnackbar

* feat: add functionality for notifying user of errors (with tests)

* refactor: clean up test code

* refactor: centralize cleanup steps
2024-02-27 21:05:37 -05:00
dependabot[bot] e183843a16 chore: bump google.golang.org/grpc from 1.61.0 to 1.62.0 (#12301)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 02:37:44 +05:00
Bruno Quaresma 0fc1a9164e feat(site): support zip upload for template files (#12323)
Related to #11687
2024-02-27 18:00:23 -03:00
Eric Paulsen 0f9c142ea6 docs: add k8s security reference (#12334)
* docs: add k8s security reference

* make fmt
2024-02-27 15:59:11 -05:00
Kayla Washburn-Love 30772b80c1 Revert "ci: bump the github-actions group with 1 update (#12303)" (#12327)
This reverts commit 5757321ba2.
2024-02-27 19:49:50 +00:00
Kayla Washburn-Love b2413a593c chore: reimplement activity status and autostop improvements (#12175) 2024-02-27 11:06:26 -07:00
Spike Curtis 4e7beee102 feat: show tailnet peer diagnostics after coder ping (#12314)
Beginnings of a solution to #12297 

Doesn't cover disco or definitively display whether we successfully connected to DERP, but shows some checklist diagnostics for connecting to an agent.

For this first PR, I just added it to `coder ping` to see how we like it, but could be incorporated into `coder ssh` _et al._ after a timeout.

```
$ coder ping dogfood2
p2p connection established in 147ms
pong from dogfood2 p2p via  95.217.xxx.yyy:42631  in 147ms
pong from dogfood2 p2p via  95.217.xxx.yyy:42631  in 140ms
pong from dogfood2 p2p via  95.217.xxx.yyy:42631  in 140ms
✔ preferred DERP region 999 (Council Bluffs, Iowa)
✔ sent local data to Coder networking coodinator
✔ received remote agent data from Coder networking coordinator
    preferred DERP 10013 (Europe Fly.io (Paris))
    endpoints: 95.217.xxx.yyy:42631, 95.217.xxx.yyy:37576, 172.17.0.1:37576, 172.20.0.10:37576
✔ Wireguard handshake 11s ago
```
2024-02-27 22:04:46 +04:00
Mathias Fredriksson 32691e67e6 test(agent/agentscripts): fix test flake in TestEnv (#12326) 2024-02-27 17:58:10 +00:00
Kayla Washburn-Love cbaf1c65ef chore: clean out site/out/assets/ when building to prevent "too much data" errors (#12313) 2024-02-27 10:45:57 -07:00
Cian Johnston b9e2d0a400 fix(coderd): mark provisioner daemon psk as secret (#12322)
* fix(coderd): mark provisioner daemon psk as secret

Marks provisioner daemon PSK with the secret annotation.
This ensures it will be scrubbed from API requests to
/api/v2/deployment/config.

* make gen
2024-02-27 16:33:32 +00:00
Steven Masley 19baca55da feat: implement create org commands from cli (#12308)
* feat: implement create org commands from cli
2024-02-27 10:13:08 -06:00
dependabot[bot] 5757321ba2 ci: bump the github-actions group with 1 update (#12303)
Bumps the github-actions group with 1 update: [chromaui/action](https://github.com/chromaui/action).


Updates `chromaui/action` from 10 to 11
- [Release notes](https://github.com/chromaui/action/releases)
- [Commits](https://github.com/chromaui/action/compare/v10...v11)

---
updated-dependencies:
- dependency-name: chromaui/action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-27 11:53:48 -03:00
Cian Johnston 1d65e36b89 ci: increase connection hard limit for fly.io wsproxies (#12319) 2024-02-27 13:57:24 +00:00
Cian Johnston 392fecee87 chore(docs): update external provisioners documentation (#12315) 2024-02-27 13:05:28 +00:00
Cian Johnston 96c9838ce3 fix(cli): scaletest: do not screenshot if verbose=false (#12317) 2024-02-27 12:35:48 +00:00
Bruno Quaresma 2ca8248315 chore(site): apply code conventions (#12316) 2024-02-27 12:24:07 +00:00
dependabot[bot] 5a0d9db6c3 chore: bump github.com/elastic/go-sysinfo from 1.12.0 to 1.13.1 (#12213)
Bumps [github.com/elastic/go-sysinfo](https://github.com/elastic/go-sysinfo) from 1.12.0 to 1.13.1.
- [Release notes](https://github.com/elastic/go-sysinfo/releases)
- [Commits](https://github.com/elastic/go-sysinfo/compare/v1.12.0...v1.13.1)

---
updated-dependencies:
- dependency-name: github.com/elastic/go-sysinfo
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-27 15:19:43 +05:00
Selina NN 431bf5cf3b Update artifactory-integration.md (#12311)
Update date year
2024-02-27 14:57:26 +05:00
Asher f74532ff50 feat: audit oauth2 app management (#12275)
* Audit oauth2 app management
* Use 201 for creating secrets
2024-02-26 23:52:08 +00:00
Steven Masley 6b866b3f48 feat: set sane default for gitea external auth (#12306)
* feat: external auth defaults for gitea

Add some sane defaults for gitea to make it easier to configure
2024-02-26 12:35:18 -06:00
Steven Masley 70ccefc357 feat: set organization context in coder organizations (#12265)
* feat: add coder organizations set to change org context

`coder organizations set <org>`
2024-02-26 11:39:26 -06:00
Steven Masley 748cf4b2c4 feat: implement global flag for org selection (#12276)
* feat: implement global flag for org selection

Any command can use '-z' to override org context
2024-02-26 11:38:49 -06:00
dependabot[bot] 5a41385400 chore: bump github.com/gohugoio/hugo from 0.122.0 to 0.123.3 (#12302)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 21:18:19 +05:00
Steven Masley d2998c6b7b feat: implement organization context in the cli (#12259)
* feat: implement organization context in the cli

`coder org show current`
2024-02-26 10:03:49 -06:00
Steven Masley f44c89d200 chore: enforce orgid in audit logs where required (#12283)
* chore: enforce orgid in audit logs where required
2024-02-26 08:27:33 -06:00
Cian Johnston 74b749b890 chore(coderd): add test to assert agent token invalid when workspace deleted (#12290) 2024-02-26 13:27:00 +00:00
Muhammad Atif Ali 7eed40bd99 chore(dogfood): bump jetbrains-gateway module to 1.0.6 (#12298) 2024-02-26 11:57:40 +00:00
Spike Curtis b0afffbafb feat: use v2 API for agent metadata updates (#12281)
Switches the agent to report metadata over the v2 API.

Fixes #10534
2024-02-26 09:50:19 +04:00
Gary Reynolds 7a245e61b1 chore(docs): inline OIDC flow diagram (#12255)
When viewing the Authentication page, the diagram showing the flow is a useful
resource for understanding the rest of the page.

Rather than linking to a specific version of the SVG, inline it as part of the
documentation.
2024-02-26 04:48:01 +00:00
Michael Brewer 245e280531 docs: add gitlab self-managed example (#12295) 2024-02-25 10:11:13 -05:00
Eric Paulsen fb198ac99c docs: add steps for postgres server verification (#12072)
* docs: add steps for postgres server verification

* make: fmt

* refactor to guide

* add manifest
2024-02-25 01:16:56 +00:00
dependabot[bot] 7e797e90ac chore: bump golang.org/x/tools from 0.17.0 to 0.18.0 (#12209)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-24 10:42:50 +05:00
Steven Masley c33c452663 fix: use default org over index [0] for new scim (#12284) 2024-02-23 15:31:36 -06:00
Colin Adler 0dd126e025 chore: upgrade github.com/hashicorp/hc-install to v0.6.3 (#12288)
Removes a dependency on a vulnerable version of github.com/cloudflare/circl
2024-02-23 13:41:58 -06:00
Bruno Quaresma 79480ca587 feat(site): display build logs on template creation (#12271) 2024-02-23 12:23:52 -07:00
Steven Masley 13359aa16f chore: drop github per user rate limit tracking (#12286)
* chore: drop github per user rate limit tracking

Rate limits for authenticated requests are per user.
This would be an excessive number of prometheus labels,
so we only track the unauthorized limit.
2024-02-23 11:17:52 -06:00
Marcin Tojek 90db6683c4 fix: refresh entitlements after creating first user (#12285) 2024-02-23 16:48:24 +00:00
Cian Johnston 2cb9bfd517 refactor(coderd): move healthcheck report structs to codersdk (#12279)
Moves healthcheck report-related structs from coderd/healthcheck to codersdk
This prevents an import cycle when adding a codersdk.Client method to hit /api/v2/debug/health.
2024-02-23 13:13:28 +00:00
Spike Curtis aa7a9f5cc4 feat: use v2 API for agent lifecycle updates (#12278)
Agent uses the v2 API to post lifecycle updates.

Part of #10534
2024-02-23 15:24:28 +04:00
Dean Sheather ee7828a166 chore: fix wsproxy test flake (#12280)
* chore: fix wsproxy test flake

* fixup! chore: fix wsproxy test flake
2024-02-23 21:19:54 +10:00
Spike Curtis 4cc132cea0 feat: switch agent to use v2 API for sending logs (#12068)
Changes the agent to use the new v2 API for sending logs, via the logSender component.

We keep the PatchLogs function around, but deprecate it so that we can test the v1 endpoint.
2024-02-23 11:27:15 +04:00
Spike Curtis af3fdc68c3 chore: refactor agent routines that use the v2 API (#12223)
In anticipation of needing the `LogSender` to run on a context that doesn't get immediately canceled when you `Close()` the agent, I've undertaken a little refactor to manage the goroutines that get run against the Tailnet and Agent API connection.

This handles controlling two contexts, one that gets canceled right away at the start of graceful shutdown, and another that stays up to allow graceful shutdown to complete.
2024-02-23 11:04:23 +04:00
Kayla Washburn-Love 66585f042f feat: support markdown in update messages (#12273) 2024-02-22 16:14:06 -07:00
Kayla Washburn-Love 7e6cb66a50 feat(site): allow creating a workspace without connecting optional external auth providers (#12251) 2024-02-22 10:27:36 -07:00
Kayla Washburn-Love b8a53230c7 chore: revert "refactor(site): verify external auth before display ws form (#11777)" (#12183) 2024-02-22 09:44:30 -07:00
Cian Johnston 53e8f9c0f9 fix(coderd): only allow untagged provisioners to pick up untagged jobs (#12269)
Alternative solution to #6442

Modifies the behaviour of AcquireProvisionerJob and adds a special case for 'un-tagged' jobs such that they can only be picked up by 'un-tagged' provisioners.

Also adds comprehensive test coverage for AcquireJob given various combinations of tags.
2024-02-22 15:04:31 +00:00
Marcin Tojek aa7a12a5ec docs: document Terraform variables (#12270) 2024-02-22 15:26:53 +01:00
Steven Masley d4d8424ce0 fix: fix GetOrganizationsByUserID error when multiple organizations exist (#12257)
* test: fetching user orgs fails if multi orgs in pg db
* fix: GetOrganizationsByUserID fixed if multi orgs exist
2024-02-22 08:14:48 -06:00
Spike Curtis da376549a3 fix: stop waiting for Agent in a goroutine in ssh test (#12268)
Fixes race seen here: https://github.com/coder/coder/runs/21852483781

What happens is that the agent connects, completes the test, and then disconnects before the Eventually condition runs.  The waiter then times out because it's looking for a connected agent.

Then, since it's a `require` in a goroutine, that causes the `tGo` cleanup to hang and the whole test suite to timeout after 10 minutes.

Anyway, `agenttest.New` doesn't block, and we don't actually need to wait for the agent to connect, since a successful SSH session is evidence that it connected.
2024-02-22 17:01:06 +04:00
dependabot[bot] a31a05e2cb chore: bump github.com/valyala/fasthttp from 1.51.0 to 1.52.0 (#12210)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 11:59:29 +05:00
dependabot[bot] 307a206605 chore: bump github.com/prometheus/client_model from 0.5.0 to 0.6.0 (#12212)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 11:59:17 +05:00
Asher 51d178d538 feat: add OAuth2 user settings page (#12237) 2024-02-21 14:16:55 -09:00
dependabot[bot] 3cbe14fdad chore: bump ip from 2.0.0 to 2.0.1 in /site (#12238)
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-21 14:06:43 -09:00
Asher 7eb2beccea fix: redirect from oauth2 authorization page (#12241)
At the moment it just says "you are not authorized", but we want to
automatically redirect to the login page.
2024-02-21 13:30:33 -09:00
Steven Masley c3a7b13690 chore: remove organization requirement from convertGroup() (#12195)
* feat: convertGroups() no longer requires organization info

Removing role information from some users in the api. This info is
excessive and not required. It is costly to always include
2024-02-21 15:58:11 -06:00
Steven Masley 3f65bd14cc fix: ignore surronding whitespace for cli config (#12250)
* fix: ignore surronding whitespace for cli config

Cli config files break if you edit them manually with any editor.
Editors drop a newline at the end, and we not break on this.
If a developer manually edits a file, it should still work
2024-02-21 13:03:41 -06:00
Kayla Washburn-Love 475c3650ca feat: add support for optional external auth providers (#12021) 2024-02-21 11:18:38 -07:00
Bruno Quaresma 78c9f82719 fix(site): fix error when typing long number on ttl (#12249) 2024-02-21 17:29:29 +00:00
Michael Smith 1d254f4680 fix: add tests and improve accessibility for useClickable (#12218) 2024-02-21 10:59:13 -05:00
Bruno Quaresma a827185b6d refactor: move auto fill feature into an experiment (#12230) 2024-02-21 11:48:34 -03:00
Marcin Tojek c230bcf5ca fix: previous parameter value is not a number (#12246) 2024-02-21 15:44:45 +01:00
Bruno Quaresma b4fb754b2d feat(site): show previous agent scripts logs (#12233) 2024-02-21 11:42:34 -03:00
Bruno Quaresma 0398e3c531 chore(site): fix storybook for agent row with port forward button (#12247) 2024-02-21 14:32:39 +00:00
Bruno Quaresma cc4cefbbee chore(site): fix storybook test (#12245) 2024-02-21 14:17:18 +00:00
Bruno Quaresma ebe05820c9 fix(site): fix web terminal bottom overflow (#12228) 2024-02-21 11:02:53 -03:00
dependabot[bot] 91c3df785f chore: bump github.com/aws/smithy-go from 1.19.0 to 1.20.0 (#12206)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-21 13:09:18 +05:00
dependabot[bot] 519cf5935f chore: bump github.com/prometheus/common from 0.46.0 to 0.47.0 (#12207)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-21 11:36:52 +05:00
Asher 3d742f64e6 fix: move oauth2 routes (#12240)
* fix: move oauth2 routes

From /login/oauth2/* to /oauth2/*.

/login/oauth2 causes /login to no longer get served by the frontend,
even if nothing is actually served on /login itself.

* Add forgotten comment on delete
2024-02-20 17:01:25 -09:00
Asher 4d39da294e feat: add oauth2 token exchange (#12196)
Co-authored-by: Steven Masley <stevenmasley@gmail.com>
2024-02-20 14:58:43 -09:00
Steven Masley 07cccf9033 feat: disable directory listings for static files (#12229)
* feat: disable directory listings for static files

Static file server handles serving static asset files (js, css, etc).
The default file server would also list all files in a directory.
This has been changed to only serve files.
2024-02-20 15:50:30 -06:00
Steven Masley 2dac34276a fix: add postgres triggers to remove deleted users from user_links (#12117)
* chore: add database test fixture to insert non-unique linked_ids
* chore: create unit test to exercise failed email change bug
* fix: add postgres triggers to keep user_links clear of deleted users
* Add migrations to prevent deleted users with links
* Force soft delete of users, do not allow un-delete
2024-02-20 13:19:38 -06:00
Garrett Delfosse b342bd7869 feat: add port sharing frontend (#12119) 2024-02-20 13:26:34 -05:00
Bruno Quaresma 0021c2f906 fix(site): fix parameters field size (#12231) 2024-02-20 13:54:07 -03:00
Marcin Tojek 57bf997369 feat: support custom validation errors for number-typed parameters (#12224) 2024-02-20 16:32:03 +01:00
Michael Smith 6414b7aade chore(site): refactor tests for global hooks   (#12216)
* refactor: clean up tests for debounce

* refactor: clean up tests for useCustomEvent

* refactor: clean up events file

* refactor: clean up tests for hookPolyfills
2024-02-20 09:19:43 -05:00
Michael Smith d6ae9d8548 revert: remove anti-flicker clipboard styling (#12227)
- These CSS changes were for making sure there weren't layout shifts
  when using the non-secure clipboard fallback, which could cause janky
  UI flickers. It seems to be breaking things for some users on HTTP-only
  connections, though.
2024-02-20 14:14:25 +00:00
Cian Johnston 643c3ee54b refactor(provisionerd): move provisionersdk.VersionCurrent -> provisionerdproto.VersionCurrent (#12225) 2024-02-20 12:44:19 +00:00
Cian Johnston c62a8b0bee fix(helm)!: remove prometheus-http port declaration from coderd service spec (#12214)
This PR removes the prometheus-http port entirely from the coder service specification (originally added in #10448). It also removes the Helm value coder.service.prometheusNodePort.

Rationale: some cloud providers will helpfully expose all ports on a LoadBalancer service for you. The net effect of this is that setting CODER_PROMETHEUS_ENABLE will end up exposing port 2112 on your coderd service to the internet, which is likely undesired behaviour.
2024-02-20 11:36:17 +00:00
Mathias Fredriksson b1c0b39d88 feat(agent): add script data dir for binaries and files (#12205)
The agent is extended with a `--script-data-dir` flag, defaulting to the
OS temp dir. This dir is used for storing `coder-script-data/bin` and
`coder-script/[script uuid]`. The former is a place for all scripts to
place executable binaries that will be available by other scripts, SSH
sessions, etc. The latter is a place for the script to store files.

Since we default to OS temp dir, files are ephemeral by default. In the
future, we may consider adding new env vars or changing the default
storage location. Workspace startup speed could potentially benefit from
scripts being able to skip steps that require downloading software. We
may also extend this with more env variables (e.g. persistent storage in
HOME).

Fixes #11131
2024-02-20 13:26:18 +02:00
Spike Curtis ab4cb66e00 feat: add WaitUntilEmpty to LogSender (#12159)
We'll need this to be able to tell when all outstanding logs have been sent, as part of graceful shutdown.
2024-02-20 11:11:31 +04:00
Spike Curtis 081e37d7d9 chore: move LogSender to agentsdk (#12158)
Moves the LogSender to agentsdk and deprecates LogsSender based on the v1 API.
2024-02-20 10:44:20 +04:00
Dean Sheather 9861830e87 fix: never send local endpoints if disabled (#12138) 2024-02-20 15:51:25 +10:00
Mathias Fredriksson c63f569174 refactor(agent/agentssh): move envs to agent and add agentssh config struct (#12204)
This commit refactors where custom environment variables are set in the
workspace and decouples agent specific configs from the `agentssh.Server`.
To reproduce all functionality, `agentssh.Config` is introduced.

The custom environment variables are now configured in `agent/agent.go`
and the agent retains control of the final state. This will allow for
easier extension in the future and keep other modules decoupled.
2024-02-19 16:30:00 +02:00
Colin Adler 817cc78b94 fix(examples): remove dead code comment (#12194) 2024-02-17 17:38:19 +00:00
Mathias Fredriksson 0442ee5fa8 fix(agent/reconnectingpty): fix screen startup speed by disabling messages (#12190) 2024-02-16 22:37:02 +02:00
Cian Johnston a2cbb0f87f fix(enterprise/coderd): check provisionerd API version on connection (#12191) 2024-02-16 18:43:07 +00:00
Steven Masley f17149c59d feat: set groupsync to use default org (#12146)
* fix: assign new oauth users to default org

This is not a final solution, as we eventually want to be able
to map to different orgs. This makes it so multi-org does not break oauth/oidc.
2024-02-16 11:09:19 -06:00
Kayla Washburn-Love dbaafc863c chore: update no-restricted-imports lint rule (#12180)
- prevent importing from the "monolith" lodash module. individual modules are better for tree shaking.
- prevent importing `useTheme` and types from @mui/material/styles. prefer importing from @emotion/react.
2024-02-16 09:54:40 -07:00
Steven Masley 75870c22ab fix: assign new oauth users to default org (#12145)
* fix: assign new oauth users to default org

This is not a final solution, as we eventually want to be able
to map to different orgs. This makes it so multi-org does not break oauth/oidc.
2024-02-16 08:47:26 -06:00
Steven Masley 2a8004b1b2 feat: use default org for PostUser (#12143)
Instead of assuming only 1 org exists, this uses the
is_default org to place a user in if not specified.
2024-02-16 08:28:36 -06:00
Marcin Tojek 0e1bad4f82 docs: fix header font (#12193) 2024-02-16 13:32:45 +00:00
Muhammad Atif Ali 799d71f6b2 docs: simplify docker installation docs (#12187) 2024-02-16 12:53:03 +00:00
Bruno Quaresma be1edc3995 fix(site): fix language detection for Dockerfile (#12188) 2024-02-16 12:50:36 +00:00
Marcin Tojek 41647ca984 docs: describe resource ordering in UI (#12185) 2024-02-16 13:33:57 +01:00
Bruno Quaresma df297627c2 fix(site): match activity bump text with template settings (#12170)
Close https://github.com/coder/coder/issues/12130
2024-02-16 09:33:15 -03:00
Muhammad Atif Ali 99dbeb4a85 ci: fix broken dogfood workflow (#12186) 2024-02-16 14:14:35 +03:00
Muhammad Atif Ali 8ca2add6dc chore(dogfood): revert to pre-artifactory state (#12169) 2024-02-16 13:47:15 +03:00
Colin Adler 97e4d51953 fix(cli/clibase): don't error on required flags with --help (#12181) 2024-02-15 23:41:46 +00:00
Michael Smith fbd436cc2c fix: improve clipboard support on HTTP connections and older browsers (#12178)
* fix: add future-proofing for clipboard copies on http connections

* docs: clean up comment formatting
2024-02-15 16:44:53 -05:00
Colin Adler 8a9f59a4bb fix(cli): avoid panic when external auth name isn't provided (#12177)
Fixes https://github.com/coder/coder/issues/10216
2024-02-15 15:17:16 -06:00
Colin Adler 4c3d44658d fix(codersdk): correctly log coordination error (#12176) 2024-02-15 20:47:12 +00:00
Steven Masley 2bf2f88b09 feat: implement 'is_default' org field (#12142)
The first organization created is now marked as "default". This is
to allow "single org" behavior as we move to a multi org codebase.

It is intentional that the user cannot change the default org at this
stage. Only 1 default org can exist, and it is always the first org.

Closes: https://github.com/coder/coder/issues/11961
2024-02-15 11:01:16 -06:00
dependabot[bot] a67362fdb1 chore: bump github.com/u-root/u-root from 0.12.0 to 0.13.0 (#12100)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 19:25:52 +03:00
Marcin Tojek 5aa5ff1bde chore: deprecate API workspace build resources (#12167) 2024-02-15 17:13:44 +01:00
Spike Curtis 2aff014e5d feat: add logSender for sending logs on agent v2 API (#12046)
Adds a new subcomponent of the agent for queueing up logs until they can be sent over the Agent API.

Subsequent PR will change the agent to use this instead of the HTTP API for posting logs.

Relates to #10534
2024-02-15 16:57:17 +04:00
Spike Curtis 627232eae9 fix: fix pgcoord to delete coordinator row last (#12155)
Fixes #12141
Fixes #11750

PGCoord shutdown was uncoordinated, so an update at an inopportune time during shutdown would be rejected because the coordinator row was already deleted.

This PR ensures that the PGCoord subcomponents that write updates are shut down before we take down the heartbeats, which is responsible for deleting the coordinator row.
2024-02-15 16:34:29 +04:00
Marcin Tojek 7a453608c9 feat: support order property of coder_agent (#12121) 2024-02-15 13:33:13 +01:00
Sulochan c66e665864 docs: add kubevirt coder template in list of community templates (#12113) 2024-02-15 13:18:10 +03:00
Marcin Tojek 8cc62fb221 fix(site): ignore fileInfo if file is missing (#12154) 2024-02-15 09:15:22 +00:00
Muhammad Atif Ali d9f99da327 chore(docs): update artifactory-integration guide (#12153) 2024-02-15 11:20:50 +03:00
Spike Curtis 2d0b9106c0 fix: change servertailnet to register the DERP dialer before setting DERP map (#12137)
I noticed a possible race where tailnet.Conn can try to dial the embedded region before we've set our custom dialer that send the DERP in-memory.  This closes that race and adds a test case for servertailnet with no STUN and an embedded relay
2024-02-15 10:51:12 +04:00
dependabot[bot] 1bb4aecf49 chore: bump golang.org/x/oauth2 from 0.16.0 to 0.17.0 (#12099)
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-14 13:37:30 -06:00
Cian Johnston d6b025db14 Revert "feat: add activity status and autostop reason to workspace overview (#11987)" (#12144)
Related to https://github.com/coder/coder/pull/11987

This reverts commit d37b131.
2024-02-14 17:14:49 +00:00
Spike Curtis 04991f425a fix: set node callback each time we reinit the coordinator in servertailnet (#12140)
I think this will resolve #12136 but lets get a proper test at the system level before closing.

Before this change, we only register the node callback at start of day for the server tailnet.  If the coordinator changes, like we know happens when we are licensed for the PGCoordinator, we close the connection to the old coord, and open a new one to the new coord.

The callback is designed to direct the updates to the new coordinator, but there is nothing that specifically triggers it to fire after we connect to the new coordinator.

If we have STUN, then period re-STUNs will generally get it to fire eventually, but without STUN it we could go indefinitely without a callback.

This PR changes the servertailnet to re-register the callback each time we reconnect to the coordinator.  Registering a callback (even if it's the same callback) triggers an immediate call with our node information, so the new coordinator will have it.
2024-02-14 20:45:31 +04:00
Spike Curtis 5a0d240bc3 feat: expose DERP server debug metrics (#12135)
Adds some debug endpoints for looking into the DERP server.

The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function.  I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default.  However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.

The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.

Example:

```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```

If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package.  That's for a later PR if at all.
2024-02-14 15:11:45 +04:00
Muhammad Atif Ali 53c55439be chore (examples/templates/incus): fix a typo (#12123) 2024-02-13 19:16:33 +00:00
Steven Masley 5d483a7ea1 fix: do not query user_link for deleted accounts (#12112) 2024-02-13 13:02:21 -06:00
Steven Masley 06f3ab1206 chore: add database test fixture to insert non-unique linked_ids (#12111)
* chore: add database test fixture to insert non-unique linked_ids
2024-02-13 12:06:47 -06:00
Kayla Washburn-Love d37b131426 feat: add activity status and autostop reason to workspace overview (#11987) 2024-02-13 10:50:17 -07:00
Muhammad Atif Ali e53d8bdb50 docs: update modules docs (#11911) 2024-02-13 15:35:09 +00:00
Cian Johnston 68641f9e2f chore(examples/templates/incus): fix incus group name in README (#12120) 2024-02-13 15:31:07 +00:00
dependabot[bot] e938690b1e chore: bump golang.org/x/mod from 0.14.0 to 0.15.0 (#12094)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 18:25:26 +03:00
Muhammad Danish 3c536aa880 ci: use repo secret for syncing winget-pkgs fork (#12108) 2024-02-13 18:25:13 +03:00
dependabot[bot] 28bbdee655 chore: bump github.com/go-playground/validator/v10 (#12096)
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.17.0 to 10.18.0.
- [Release notes](https://github.com/go-playground/validator/releases)
- [Commits](https://github.com/go-playground/validator/compare/v10.17.0...v10.18.0)

---
updated-dependencies:
- dependency-name: github.com/go-playground/validator/v10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 17:54:13 +03:00
dependabot[bot] 4760e85c15 chore: bump golang.org/x/net from 0.20.0 to 0.21.0 (#12097)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 17:53:16 +03:00
dependabot[bot] 9560d9a68b ci: bump the github-actions group with 2 updates (#12091)
Bumps the github-actions group with 2 updates: [crate-ci/typos](https://github.com/crate-ci/typos) and [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action).


Updates `crate-ci/typos` from 1.18.0 to 1.18.2
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.18.0...v1.18.2)

Updates `aquasecurity/trivy-action` from 0.16.1 to 0.17.0
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/d43c1f16c00cfd3978dde6c07f4bbcf9eb6993ca...84384bd6e777ef152729993b8145ea352e9dd3ef)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 17:52:12 +03:00
Garrett Delfosse 3ab3a62bef feat: add port-sharing backend (#11939) 2024-02-13 09:31:20 -05:00
Cian Johnston c939416702 chore(examples): add sample Incus template (#12114)
Adds sample incus template created for FOSDEM 2024; there's enough intricacy involved to make it worth persisting
2024-02-13 14:30:31 +00:00
Dean Sheather e1e352d8c1 feat: add template activity_bump property (#11734)
Allows template admins to configure the activity bump duration. Defaults to 1h.
2024-02-13 07:00:35 +00:00
Dean Sheather fead57f304 fix: allow access to unhealthy/initializing apps (#12086) 2024-02-13 16:30:49 +10:00
Cian Johnston ec25fb8bbc fix(docs/networking/stun): convert svg diagrams to png 2024-02-12 17:27:53 +00:00
Cian Johnston 2fabc9499a fix(docs): remove inline mermaid diagrams (#12107) 2024-02-12 15:56:37 +00:00
Cian Johnston 1cc51b009a chore(examples): remove deprecated startup_script_timeout and shutdown_script_timeout (#12104)
Removes deprecated startup_script_timeout and shutdown_script_timeout from our example templates.

Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-02-12 14:29:41 +00:00
Marcin Tojek 3e68650791 feat: support order property of coder_app resource (#12077) 2024-02-12 15:11:31 +01:00
Cian Johnston 1e9a3c952f chore(docs/networking/stun): fix diagram in section 2 (#12103) 2024-02-12 12:33:41 +00:00
Cian Johnston d1a522a8fc chore(docs): add requirements re ports and stun server to docs (#12026)
Adds documentation on port requirements and a short overview of STUN with some example scenarios.

Co-authored-by: Dean Sheather <dean@deansheather.com>
Co-authored-by: Spike Curtis <spike@coder.com>
2024-02-12 11:42:27 +00:00
Dean Sheather 2fc3064653 chore: add tests for app ID copy in app healths (#12088) 2024-02-12 05:49:48 +00:00
Colin Adler 06254a167f chore(docs): add v2.8.2 changelog (#12089) 2024-02-12 05:48:34 +00:00
Dean Sheather 429144da22 fix: copy app ID in healthcheck (#12087) 2024-02-12 05:01:16 +00:00
Eric Paulsen bb308851f5 docs: fix jetbrains reconnect faq (#12073)
* docs: fix jetbrains reconnect faq

* make: fmt

* add asher feedback
2024-02-09 23:44:33 +00:00
Bruno Quaresma 390217b396 feat(site): add create template from scratch (#12082) 2024-02-09 14:42:26 +00:00
Cian Johnston 2b307c7c4e fix(cli/server): do not redirect /healthz (#12080) 2024-02-09 13:44:47 +00:00
Spike Curtis 92b2e26a48 feat: send log limit exceeded in response, not error (#12078)
When we exceed the db-imposed limit of logs, we need to communicate that back to the agent.  In v1 we did it with a 4xx-level HTTP status, but with dRPC, the errors are delivered as strings, which feels fragile to me for something we want to gracefully handle.

So, this PR adds the log limit exceeded as a field on the response message, and fixes the API handler to set it as appropriate instead of an error.
2024-02-09 16:17:20 +04:00
Spike Curtis 1f5a6d59ba chore: consolidate websocketNetConn implementations (#12065)
Consolidates websocketNetConn from multiple packages in favor of a central one in codersdk
2024-02-09 11:39:08 +04:00
Colin Adler ec8e41f516 chore: add logging around agent app health reporting (#12071) 2024-02-08 23:37:44 -06:00
Marcin Tojek c0e169ebf9 feat: support custom order of agent metadata (#12066) 2024-02-08 17:29:34 +01:00
Mathias Fredriksson e659957b65 fix(cli/ssh): prevent reads/writes to stdin/stdout in stdio mode (#12045)
Fixes #11530
2024-02-08 13:09:42 +02:00
Spike Curtis 151aaadc23 fix: allow startup scripts larger than 32k (#12060)
Fixes #12057 and adds a regression test.
2024-02-07 22:26:42 +04:00
Bruno Quaresma 4d63a473b2 fix(site): fix infinity loading when template has no previous version (#12059) 2024-02-07 14:56:09 -03:00
Mathias Fredriksson 040ce40ed8 fix(dogfood): add ability to synchronize with startup script via done file (#12058) 2024-02-07 19:16:18 +02:00
Bruno Quaresma d8a8070986 fix(site): enable submit when auto start and stop are both disabled (#12055) 2024-02-07 14:06:48 -03:00
Bruno Quaresma 4b1bac31b6 feat(site): allow any file extension on template editor (#12000) 2024-02-07 13:24:28 -03:00
Marcin Tojek 4e7b208068 fix(site): e2e: print API backend calls (#12051) 2024-02-07 15:50:07 +01:00
Eric Paulsen 1abe0cfa1a docs: fix /audit & /insights params (#12043) 2024-02-07 08:38:54 -05:00
Spike Curtis 1cf4b62867 feat: change agent to use v2 API for reporting stats (#12024)
Modifies the agent to use the v2 API to report its statistics, using the `statsReporter` subcomponent.
2024-02-07 15:26:41 +04:00
Muhammad Atif Ali 70ad833b02 ci: fix GH_TOKEN in release.yaml (#12044) 2024-02-07 13:37:11 +03:00
Mathias Fredriksson f2aef0726b fix(agent/agentssh): allow scp to exit with zero status (#12028)
Fixes #11786
2024-02-07 10:22:31 +02:00
Josh Vawdrey d3ccb07361 feat(cli): support header and header-command in config-ssh (#10413)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-02-07 10:21:26 +02:00
Ben Potter d6cdaae8b1 docs: add v2.8.0 changelog (#12042)
* docs: add v2.8.0 changelog

* fmt
2024-02-07 00:14:17 +00:00
Cian Johnston 36808f19dc feat!: update terraform to version 1.6.x, relax max version constraint (#12027)
* feat(provisioner): relax max terraform version constraint

* feat!(scripts/Dockerfile.base): update bundled terraform to 1.6.x

* bump terraform version in Dogfood image

* fix over-zealous rename
2024-02-06 17:58:26 -06:00
Kayla Washburn-Love b8e32a37de fix: use replace when redirecting from /health (#12039)
`pushHistory` will break the back button, so we need to use `replaceHistory` instead
2024-02-06 14:27:32 -07:00
Marcin Tojek 3f04e98cfa feat(cli): pull templates in zip format (#12032) 2024-02-06 19:17:29 +01:00
Spike Curtis 213ae69bee fix: start timer before subscribing to avoid test race (#12031)
Fixes #12030

This is a good example of the kind of thing I'd like to address with a time-testing lib.  The problem is that there is a race between the watchdog starting it's timer and the test incrementing the time.  What would make this easier is if the time-testing library could wait for and assert the call to start the timer before incrementing the time.
2024-02-06 20:21:23 +04:00
Marcin Tojek b6806bca70 fix: nix: google-chrome installed conditionally (#12029) 2024-02-06 16:46:58 +01:00
Dean Sheather 98b86f3cd6 chore: add logs to pq notification dialer (#12020) 2024-02-06 15:21:48 +00:00
Spike Curtis e09cd2c6bd feat: add watchdog to pubsub (#12011)
adds a watchdog to our pubsub and runs it for Coder server.

If the watchdog times out, it triggers a graceful exit in `coder server` to give any provisioner jobs a chance to shut down.

c.f. #11950
2024-02-06 16:58:45 +04:00
Cian Johnston f1e5b4fbb8 ci: stop deploying legacy wsproxies (#12025) 2024-02-06 11:00:10 +00:00
Cian Johnston 26379877b2 fix(dogfood): stop overriding /etc/apt/sources.list with tsw mirrors (#11999) 2024-02-06 09:39:05 +00:00
Colin Adler c7f52b73bb feat(coderd): add prometheus metrics to servertailnet (#11988) 2024-02-05 23:57:18 -06:00
Spike Curtis c84a637116 fix: stop logging error on query canceled (#12017)
Fixes flake seen here: https://github.com/coder/coder/actions/runs/7782340530/job/21218566449
2024-02-06 08:43:34 +04:00
Kayla Washburn-Love b73e66e9a9 feat: show workspace name suggestions below the name field (#12001) 2024-02-05 10:40:15 -07:00
dependabot[bot] 52ec3edd5d ci: bump the github-actions group with 4 updates (#12019)
Bumps the github-actions group with 4 updates: [buildjet/cache](https://github.com/buildjet/cache), [crate-ci/typos](https://github.com/crate-ci/typos), [codecov/codecov-action](https://github.com/codecov/codecov-action) and [hmarr/auto-approve-action](https://github.com/hmarr/auto-approve-action).


Updates `buildjet/cache` from 3 to 4
- [Changelog](https://github.com/BuildJet/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/buildjet/cache/compare/v3...v4)

Updates `crate-ci/typos` from 1.17.2 to 1.18.0
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.17.2...v1.18.0)

Updates `codecov/codecov-action` from 3 to 4
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

Updates `hmarr/auto-approve-action` from 3 to 4
- [Release notes](https://github.com/hmarr/auto-approve-action/releases)
- [Commits](https://github.com/hmarr/auto-approve-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: buildjet/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: hmarr/auto-approve-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 14:46:05 +00:00
dependabot[bot] 1f0ba745e9 chore: bump github.com/bramvdbogaerde/go-scp (#12015)
Bumps [github.com/bramvdbogaerde/go-scp](https://github.com/bramvdbogaerde/go-scp) from 1.2.1-0.20221219230748-977ee74ac37b to 1.3.0.
- [Release notes](https://github.com/bramvdbogaerde/go-scp/releases)
- [Commits](https://github.com/bramvdbogaerde/go-scp/commits/v1.3.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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 14:50:02 +02:00
dependabot[bot] c1e01dfb7b chore: bump github.com/elastic/go-sysinfo from 1.11.0 to 1.12.0 (#12013)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 15:37:57 +03:00
Spike Curtis e5ba586e30 fix: fix graceful disconnect in DialWorkspaceAgent (#11993)
I noticed in testing that the CLI wasn't correctly sending the disconnect message when it shuts down, and thus agents are seeing this as a "lost" peer, rather than a "disconnected" one. 

What was happening is that we just used a single context for everything from the netconn to the RPCs, and when the context was canceled we failed to send the disconnect message due to canceled context.

So, this PR splits things into two contexts, with a graceful one set to last up to 1 second longer than the main one.
2024-02-05 14:01:37 +04:00
Spike Curtis bb99cb7d2b chore: move FakeCoordinator to tailnettest (#11992)
Moves FakeCoordinator to tailnettest since it's reused in testing multiple packages in this stack of PRs.
2024-02-05 13:49:32 +04:00
Spike Curtis 646ac942b2 chore: rename FakeCoordinator for export (#11991)
Part of a stack that fixes graceful disconnect from the CLI to tailnet.  I reuse FakeCoordinator in a test for graceful disconnects.
2024-02-05 13:33:31 +04:00
Eric Paulsen f57ce97b5a docs: add faq for gateway reconnects (#12007)
* docs: add faq for gateway reconnects

* make: fmt
2024-02-04 15:50:53 -06:00
Kayla Washburn-Love 1d14d4e58c fix: use dark background in terminal, even when a light theme is selected (#12004) 2024-02-02 15:05:52 -07:00
Jon Ayers 73c5993bea fix: only display xray results if vulns > 0 (#11989) 2024-02-02 11:02:46 -06:00
Cian Johnston 6593de3c73 fix(dogfood/flake.nix): add google-chrome (#11974) 2024-02-02 15:56:06 +00:00
Bruno Quaresma 9b930f8fad feat(site): show deprecation message on template page (#11996) 2024-02-02 14:13:35 +00:00
Bruno Quaresma 2e378b4894 fix(site): fix parameter input icon shrink (#11995) 2024-02-02 13:49:49 +00:00
Mathias Fredriksson aae228ac01 fix(dogfood): resolve module.git-clone.repo_dir containing ~/ (#11994) 2024-02-02 14:21:34 +02:00
Mathias Fredriksson bddea7bcf9 feat(cli/vscodessh): add support for --wait and scripts that block login (#10473) 2024-02-02 13:18:26 +02:00
Kayla Washburn-Love c6c71de353 fix: change build status colors (#11985) 2024-02-01 18:02:40 -07:00
dependabot[bot] efac9ced3e chore: bump github.com/moby/moby (#11975)
Bumps [github.com/moby/moby](https://github.com/moby/moby) from 24.0.1+incompatible to 25.0.2+incompatible.
- [Release notes](https://github.com/moby/moby/releases)
- [Commits](https://github.com/moby/moby/compare/v24.0.1...v25.0.2)

---
updated-dependencies:
- dependency-name: github.com/moby/moby
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 13:58:13 -06:00
Muhammad Atif Ali 21237d96a5 docs: update remote docker host docs (#11919)
* docs: update remote docker host docs

Adds a link to external provisioners as a method to use remote docker hosts

* `make fmt`

* Update docker.md

* fmt
2024-02-01 22:43:38 +03:00
Muhammad Atif Ali 9616b92f0e chore(dogfood): fix nix icon path (#11984) 2024-02-01 19:27:41 +00:00
Bruno Quaresma 96346525e0 fix(site): fix text overflow on batch ws deletion (#11981)
Before:
![image](https://github.com/coder/coder/assets/3165839/723a8fd7-8f63-4712-8af1-cd442455c723)

After:
<img width="674" alt="Screenshot 2024-02-01 at 13 48 56" src="https://github.com/coder/coder/assets/3165839/91c3099e-6a11-4beb-b46b-70a9a6c4abb4">
2024-02-01 14:02:08 -03:00
Marcin Tojek ad8e0db172 feat: add custom error message on signups disabled page (#11959) 2024-02-01 18:01:25 +01:00
Kayla Washburn-Love e070a55142 refactor: stabilize theme.roles (#11969) 2024-02-01 09:53:26 -07:00
Bruno Quaresma 6c9f60a9c5 refactor(site): only display quota if it is higher than 0 (#11979) 2024-02-01 13:49:48 -03:00
Steven Masley 79d5c238cc fix: always return a clean http client for promoauth (#11963)
* fix: add unit test to verify default client is not broken

* always return a clean http client
* No need to clone the tripper
2024-02-01 11:13:34 -05:00
Bruno Quaresma 1a94686928 refactor(site): add table chosmetic changes (#11977)
- Set default 14px as the default font size for the table content
- Add `xsmall` size for checkboxes
- Remove checkbox wrapper padding on the table heading

Before:
<img width="1512" alt="Screenshot 2024-02-01 at 10 22 10" src="https://github.com/coder/coder/assets/3165839/92b844ae-f2bf-476a-89fe-90b16f19c306">

After: 
<img width="1512" alt="Screenshot 2024-02-01 at 10 26 00" src="https://github.com/coder/coder/assets/3165839/0f87d098-4b13-4373-96d2-2c18ee2587f6">
2024-02-01 10:41:15 -03:00
Spike Curtis 1aa117b9ec chore: rename client Listen to ConnectRPC (#11916)
ConnectRPC seems more appropriate for this function
2024-02-01 14:44:11 +04:00
dependabot[bot] 1031ccb3c9 chore: bump github.com/opencontainers/runc from 1.1.5 to 1.1.12 (#11968)
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.5 to 1.1.12.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/v1.1.12/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.1.5...v1.1.12)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 13:15:01 +03:00
Spike Curtis d5a98cc6d7 fix: avoid race in TestPGPubsub_Metrics by using Eventually (#11973)
Annoyingly, prometheus Registry collects metrics async, which is causing our test to be racy.  They also don't export enough from the Metric interface for us to replicate a synchronous collect, so we have to use Eventually to test.
2024-02-01 12:10:19 +04:00
Spike Curtis 5a359d50dd feat: add metrics to PGPubsub (#11971)
Adds prometheus metrics to PGPubsub for monitoring its health and performance in production.

Related to #11950 --- additional diagnostics to help figure out what's happening
2024-02-01 11:25:03 +04:00
Asher e748312193 fix(dogfood): fix startup script looping (#11972)
Seems to be on account of the quotes interpreting a ~ literally.  We do
replace it with /home/coder but only if it matches ~/, not ~ alone.
2024-01-31 21:33:02 -09:00
Colin Adler 3ace7982aa fix: rewrite url to agent ip in single tailnet (#11810)
This restores previous behavior of being able to cache connections
across agents in single tailnet.
2024-02-01 00:25:52 -06:00
Spike Curtis 073d1f7078 chore: remove pingWebSocket since yamux runs keepalives (#11914)
Since we run yamux over the websocket, we don't need to ping at the websocket layer because yamux has a 30 second keepalive mechanism enabled in the default config.
2024-02-01 09:48:58 +04:00
Colin Adler 4ed1f5581a chore(coderd): add logging to agent rpc yamux conn (#11965) 2024-01-31 23:17:20 -06:00
Spike Curtis cc0dc103b6 chore: remove agentsdk client RPC() function (#11913)
The RPC() function isn't called, since Listen() was modified to do this job.

Listen() has the right signature, since it returns a drpc.Conn, rather than the Agent API.  That's because tailnet v2 and agent v2 are separate APIs served over the same connection.

It might be clearer to rename `Listen()` to `RPC()` but I'll save that for a different PR.
2024-02-01 08:22:12 +04:00
Spike Curtis eb03e4490a feat: add statsReporter for reporting stats on agent v2 API (#11920)
Adds a new statsReporter subcomponent of the agent, which in a later PR will be used to report stats over the v2 API.

Refactors the logic a bit so that we can handle starting and stopping stats reporting if the agent API connection drops and reconnects.
2024-02-01 08:21:01 +04:00
Spike Curtis b79785c86f feat: move agent v2 API connection monitoring to yamux layer (#11910)
Moves monitoring of the agent v2 API connection to the yamux layer.

Present behavior monitors this at the websocket layer, and closes the websocket on completion. This can cause yamux to hit unexpected errors since the connection is closed underneath it.

This might be the cause of yamux errors that some customers are seeing

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/tCz4CxRU9jhAJ7zH8RTi/53b8b5ef-e9e5-44a5-b559-99c37c136071.png)

In any case, it's more graceful to close yamux first and let yamux close the underlying websocket.  That should limit yamux error logging to truly unexpected/error cases.

The only downside is that the yamux `Close()` doesn't accept a reason, so if the agent becomes outdated and we close the API connection, the agent just sees the connection close without a reason.  I'm not sure we log this at the agent anyway, but it would be nice.  I think more accurate logging on Coderd are more important.

I've also added some logging when the monitor disconnects for reasons other than the context being canceled (e.g. agent outdated, failed pings).
2024-02-01 08:18:35 +04:00
Spike Curtis 13e214f7f1 feat: add logging to agent yamux session (#11912)
Log yamux errors and warnings in the agent.
2024-02-01 08:18:13 +04:00
Michael Smith b0a855caa4 fix: improve click UX and styling for Auth Token page (#11863)
* wip: commit progress for clipboard update

* wip: push more progress

* chore: finish initial version of useClipboard revamp

* refactor: update API query to use newer RQ patterns

* fix: update importers of useClipboard

* fix: increase clickable area of CodeExample

* fix: update styles for CliAuthPageView

* fix: resolve issue with ref re-routing

* docs: update comments for clarity

* wip: commit progress on clipboard tests

* chore: add extra test case for referential stability

* wip: disable test stub to avoid breaking CI

* wip: add test case for tab-switching

* feat: finish changes

* fix: improve styling for strong text

* fix: make sure period doesn't break onto separate line

* fix: make center styling more friendly to screen readers

* refactor: clean up mocking implementation

* fix: resolve security concern for clipboard text

* fix: update CodeExample to obscure text when appropriate

* fix: apply secret changes to relevant code examples

* refactor: simplify code for obfuscating text

* fix: partially revert clipboard changes

* fix: clean up page styling further

* fix: remove duplicate property identifier

* refactor: rename variables for clarity

* fix: simplify/revert CopyButton component design

* fix: update how dummy input is hidden from page

* fix: remove unused onClick handler prop

* fix: resolve unused import

* fix: opt code examples out of secret behavior
2024-01-31 21:25:30 -05:00
Colin Adler c7f51a9d70 chore(site): update time until shutdown tooltip language (#11964) 2024-01-31 16:21:35 -06:00
Kayla Washburn-Love d2e6405322 chore: add inactive role to experimental theme (#11967) 2024-01-31 15:16:17 -07:00
Bruno Quaresma 4df913372f feat(site): display xray scan result in the agent (#11955) 2024-01-31 19:16:01 -03:00
Steven Masley ac64155282 fix: strip timezone information from a date in dau response (#11962)
* fix: strip timezone information from a date in dau response

Timezone information is lost, so do not forward it to the client.

* fix: timezone offset should be flipped
* Make tests deterministic
2024-01-31 16:01:50 -06:00
Kayla Washburn-Love 76e73287a5 refactor: add modules/templates and modules/workspaces (#11947) 2024-01-31 12:09:36 -07:00
Muhammad Atif Ali 4604db072a fix(dogfood): fix startup script on workspace creation (#11958) 2024-01-31 18:01:22 +03:00
Muhammad Atif Ali d2b4d58e96 chore(dogfood): use better names for image options (#11957) 2024-01-31 14:36:01 +00:00
Muhammad Atif Ali 215a9d1b30 chore: experiment building dogfood image with nix (#11680) 2024-01-31 14:27:11 +00:00
Marcin Tojek 13cbca679e feat: support template bundles as zip archives (#11839) 2024-01-31 14:49:55 +01:00
Mathias Fredriksson b25deaae20 fix(coderd/database): fix limit in GetUserWorkspaceBuildParameters (#11954) 2024-01-31 13:56:36 +02:00
Spike Curtis a34cada09a feat: add logging to pgPubsub (#11953)
Should be helpful for #11950

Adds a logger to pgPubsub and logs various events, most especially connection and disconnection from postgres.
2024-01-31 15:49:16 +04:00
Spike Curtis 1c8b803785 feat: add logging to pgcoord subscribe/unsubscribe (#11952)
Adds logging to unsubscribing from peer and tunnel updates in pgcoordinator, since #11950 seems to be problem with these subscriptions
2024-01-31 12:15:58 +04:00
Jon Ayers 0c30dde9b5 feat: add customizable upgrade message on client/server version mismatch (#11587) 2024-01-30 17:11:37 -06:00
Ammar Bandukwala adbb025e74 feat: add user-level parameter autofill (#11731)
This PR solves #10478 by auto-filling previously used template values in create and update workspace flows.

I decided against explicit user values in settings for these reasons:

* Autofill is far easier to implement
* Users benefit from autofill _by default_ — we don't need to teach them new concepts
* If we decide that autofill creates more harm than good, we can remove it without breaking compatibility
2024-01-30 16:02:21 -06:00
Kayla Washburn-Love aeb4112513 chore: update storybook (#11936) 2024-01-30 14:23:40 -07:00
Spike Curtis 520b12e1a2 fix: close MultiAgentConn when coordinator closes (#11941)
Fixes an issue where a MultiAgentConn isn't closed properly when the coordinator it is connected to is closed.

Since servertailnet checks whether the conn is closed before reinitializing, it is important that we check this, otherwise servertailnet can get stuck if the coordinator closes (e.g. when we switch from AGPL to PGCoordinator after decoding a license).
2024-01-31 00:38:19 +04:00
Colin Adler 2fd1a726aa fix: only delete expired agents on success (#11940) 2024-01-30 14:11:45 -06:00
Colin Adler 27f3b7a814 fix: add timeout to listening ports request (#11935)
This can potentially hang for 15m if the agent is unreachable.
2024-01-30 13:53:52 -06:00
Bruno Quaresma 7f1c808ff9 feat(site): simplify create template form by removing advanced settings (#11918) 2024-01-30 16:40:59 -03:00
Kayla Washburn-Love 619bdd1e7a refactor: redesign Paywall component (#11907) 2024-01-30 10:26:19 -07:00
Kayla Washburn-Love 20dcefa156 add an interaction test to InfoTooltip (#11905) 2024-01-30 10:20:11 -07:00
Bruno Quaresma e26ba1affd feat(site): do not show popover on update deadline (#11921) 2024-01-30 14:11:15 -03:00
Bruno Quaresma dcab6fa5a4 feat(site): display user avatar (#11893)
* add owner API to workspace and workspace build responses
* display user avatar in workspace top bar

Co-authored-by: Cian Johnston <cian@coder.com>
2024-01-30 17:07:06 +00:00
Mathias Fredriksson 83eea2d323 feat(scaletest/templates): add support for concurrent scenarios (#11753) 2024-01-30 14:54:54 +02:00
Bruno Quaresma 4b27c77969 fix(site): fix parameters' request upon template variables update (#11898)
Fix https://github.com/coder/coder/issues/11870
2024-01-30 08:03:53 -03:00
Mathias Fredriksson 60653bbacb fix(cli): allow template name length of 32 in template push and create (#11915) 2024-01-30 12:47:10 +02:00
Muhammad Atif Ali 86e33257af chore(docs): fix a typo (#11895) 2024-01-30 12:00:25 +03:00
Spike Curtis 0fc177203e feat: use agent v2 API to update app health (#11889)
Use the Agent v2 API to update App Health
2024-01-30 11:35:12 +04:00
Spike Curtis 2599850e54 feat: use agent v2 API to post startup (#11877)
Uses the v2 Agent API to post startup information.
2024-01-30 11:23:28 +04:00
Spike Curtis da8bb1c198 feat: use agent v2 API to fetch manifest (#11832)
Agent uses the v2 API to obtain the manifest, instead of the HTTP API.
2024-01-30 10:11:28 +04:00
Spike Curtis 9cf4e7f15a fix: prevent agent_test.go from failing on error logs (#11909)
We're failing tests on error logs like this: https://github.com/coder/coder/actions/runs/7706053882/job/21000984583

Unfortunately, the error we hit, when the underlying connection is closed, is unexported, so we can't specifically ignore it.

Part of the issue is that agent.Close() doesn't wait for these goroutines to complete before returning, so the test harness proceeds to close the connection. This looks to our product code like the network connection failing.  It would be possible to fix this, but just doesn't seem worth it for the extra insurance of catching other error logs in these tests.
2024-01-30 10:04:01 +04:00
Spike Curtis d3983e4dba feat: add logging to client tailnet yamux (#11908)
Adds logging to yamux when used for tailnet client connections, e.g. CLI and wsproxy.  This could be useful for debugging connection issues with tailnet v2 API.
2024-01-30 09:58:59 +04:00
Spike Curtis 0eff646c31 chore: move proto to sdk conversion to agentsdk (#11831)
`agentsdk` depends on `agent/proto` because it needs to get the version to dial.

Therefore, the conversion routines need to live in `agentsdk` so that we can convert to and from the Manifest.

I briefly considered refactoring the agent to only reference `proto.Manifest`, but decided against it because we might have multiple protocol versions in the future, its useful to have a protocol-independent data structure.
2024-01-30 09:04:56 +04:00
Spike Curtis 1e8a9c09fe chore: remove legacy wsconncache (#11816)
Fixes #8218

Removes `wsconncache` and related "is legacy?" functions and API calls that were used by it.

The only leftover is that Agents still use the legacy IP, so that back level clients or workspace proxies can dial them correctly.

We should eventually remove this: #11819
2024-01-30 07:56:36 +04:00
Spike Curtis 13e24f21e4 feat: use Agent v2 API for Service Banner (#11806)
Agent uses the v2 API for the service banner, rather than the v1 HTTP API.

One of several for #10534
2024-01-30 07:44:47 +04:00
Jon Ayers 4f5a2f0a9b feat: add backend for jfrog xray support (#11829) 2024-01-29 19:30:02 -06:00
dependabot[bot] 46d92dac57 ci: bump the github-actions group with 5 updates (#11890)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 00:38:59 +03:00
dependabot[bot] 5937027c86 chore: bump github.com/gohugoio/hugo from 0.121.2 to 0.122.0 (#11883)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 00:36:23 +03:00
dependabot[bot] 4dc6a302f2 chore: bump google.golang.org/grpc from 1.60.1 to 1.61.0 (#11885)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 00:36:12 +03:00
dependabot[bot] 3b65a1508c chore: bump github.com/google/uuid from 1.5.0 to 1.6.0 (#11886)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 00:36:01 +03:00
dependabot[bot] 71b79eace4 chore: bump alpine from 3.19.0 to 3.19.1 in /scripts (#11887)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 00:35:44 +03:00
Eric Paulsen d8a3ebef31 docs: fix example template README 404s and semantics (#11903)
* docs: fix example template README 404s and semantics

* make: gen
2024-01-29 21:34:12 +00:00
Kayla Washburn-Love f572e18144 fix: fix cliui prompt styling (#11899) 2024-01-29 13:56:43 -07:00
Spike Curtis 207328ca50 feat: use appearance.Fetcher in agentapi (#11770)
This PR updates the Agent API to use the appearance.Fetcher, which is set by entitlement code in Enterprise coderd.

This brings the agentapi into compliance with the Enterprise feature.
2024-01-29 21:22:50 +04:00
Garrett Delfosse f54278cdfe fix: respect wait flag on ping (#11896) 2024-01-29 11:50:35 -05:00
Colin Adler bc14e926d8 feat: add option to speedtest to dump a pcap of network traffic (#11848) 2024-01-29 09:57:31 -06:00
Spike Curtis b2bc3fff33 fix: wait for new template version before promoting (#11874)
Fixes a test flake due to not waiting for the correct template version prior to promoting it.
2024-01-29 19:29:56 +04:00
Steven Masley 04a23261e6 chore: ensure github uids are unique (#11826) 2024-01-29 09:13:46 -06:00
Steven Masley d66e6e78ee fix: always attempt external auth refresh when fetching (#11762) (#11830)
* fix: always attempt external auth refresh when fetching
* refactor validate to check expiry when considering "valid"
2024-01-29 08:55:15 -06:00
Cian Johnston eeef56a655 feat(cli): show workspace favorite status in list output (#11878) 2024-01-29 14:14:12 +00:00
Cian Johnston 9abf6ec170 feat(site): show favorite workspaces in ui (#11875)
* Add Star beside workspace name to indicate favorite status in WorkspacesList
* Add button in workspace top row to toggle workspace favorite status
2024-01-29 13:39:31 +00:00
Bruno Quaresma acd22b2c65 fix(site): fix capitalized username (#11891)
Fix #11888
2024-01-29 10:24:19 -03:00
Mathias Fredriksson 3e89ba23e5 test(scaletest): fix websocket error during close (#11879)
Fixes #11735
2024-01-29 13:42:30 +02:00
Muhammad Atif Ali 8398b4188b ci: fix winget-release workflow (#11865) 2024-01-29 13:49:20 +03:00
Spike Curtis bc4ae53261 chore: refactor Appearance to an interface callable by AGPL code (#11769)
The new Agent API needs an interface for ServiceBanners, so this PR creates it and refactors the AGPL and Enterprise code to achieve it.

Before we depended on the fact that the HTTP endpoint was missing to serve an empty ServiceBanner on AGPL deployments, but that won't work with dRPC, so we need a real interface to call.
2024-01-29 12:17:31 +04:00
Marcin Tojek aacb4a2b4c feat: use map instead of slice in metrics aggregator (#11815) 2024-01-29 09:12:41 +01:00
Spike Curtis 37e9479815 fix: fix TestServiceBanners/Agent (#11768)
The original test is bugged in that it

1. creates a new AGPL coderd with a new database, so no appearance is set in the DB.
2. overwrites the agentClient so the assertion after removing the license is against the AGPL coderd
2024-01-29 11:56:33 +04:00
Spike Curtis f9fdd44510 feat: change codersdk to use tailnet v2 for DERPMap updates (#11736)
fixes #10533


refactors `codersdk` workspace agent dialer to use a single websocket connection to the tailnet v2 API for both coordination and DERPMap updates, rather than separate websockets (and the v1 API for DERPMaps).
2024-01-29 11:26:50 +04:00
Muhammad Atif Ali 699a4b8dd4 chore(dogfood): use built-in VS Code Desktop button over the module (#11869) 2024-01-29 00:37:22 +03:00
Eric Paulsen be4d5221ba docs: add guide for azure federation (#11864)
* docs: add guide for azure federation

* make: fmt

* refactor: arm secrets and semantics
2024-01-28 15:51:11 -05:00
Muhammad Atif Ali 2f9bf1ebe1 ci: validate template before pushing (#11867) 2024-01-27 10:02:10 +03:00
Spike Curtis 4825b7ccd2 fix: use new context after t.Parallel in TestOAuthAppSecrets
c.f. https://coder.com/blog/go-testing-contexts-and-t-parallel

fixes flakes like https://github.com/coder/coder/runs/20856469613
2024-01-27 08:45:43 +04:00
Muhammad Atif Ali de6d4794dc chore(dogfood): replace repo_dir with base_repo_dir in git-clone module (#11835)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-01-27 01:45:07 +00:00
Kayla Washburn-Love bb8ce7bc02 chore: move components/Resources to modules/resources (#11852) 2024-01-26 15:11:07 -07:00
Kayla Washburn-Love 8f46beef72 chore: remove most usage of PropsWithChildren (#11859) 2024-01-26 12:04:22 -07:00
Kayla Washburn-Love c2b6e204f3 fix: type error from theme update (#11844) 2024-01-26 10:56:19 -07:00
Kayla Washburn-Love c85fc3c8fe refactor: add more fill variants to the experimental theme (#11827) 2024-01-26 10:28:59 -07:00
Cian Johnston 42e997d39e fix(coderd/rbac): do not cache context cancellation errors (#11840)
#7439 added global caching of RBAC results.
Calls are cached based on hash(subject, object, action).
We often use dbauthz.AsSystemRestricted to handle "internal" authz calls, and these are often repeated with similar arguments and are likely to get cached.
So a transient error doing an authz check on a system function will be cached for up to a minute.
I'm just starting off with excluding context.Canceled but there's likely a whole suite of different errors we want to also exclude from the global cache.
2024-01-26 16:19:55 +00:00
Spike Curtis d6baa3cab0 fix: stop running tests that exec sh scripts in parallel (#11834)
Ok, so my last attempt at a fix here failed

https://github.com/coder/coder/actions/runs/7666229961/job/20893608286

I have a new theory: it's not the `terraform` binary that's busy, it's actually `fake_cancel.sh` and it gets marked busy when we `exec` it from the script we write.

Use of `exec` also replaces the executing code in place, rather than starting a new process/shell, so that's why the error we get says `terraform` is busy.
2024-01-26 19:22:35 +04:00
Bruno Quaresma 0ba035a16d refactor(site): improve parameters field (#11802) 2024-01-26 11:31:52 -03:00
Kira Pilot 4c71cccbc3 fix(site): disable autostart and autostop according to template settings (#11809)
* fix (site): disable autostart and autostop according to template settings

* checking form values again; wrote tests

* fixed closure and label bugs

* fix broken query key

* tweaks
2024-01-26 09:06:01 -05:00
Mathias Fredriksson 52c08a98bb test(scaletest): fix worksapcebuild retry (#11836) 2024-01-26 15:49:19 +02:00
Mathias Fredriksson 02124758fb feat(cli/exp): extend scaletest create-workspaces with --retry option (#11825)
Part of #11801
2024-01-26 11:29:48 +00:00
Cian Johnston fdf9f03097 fix(enterprise/cli): add ID to default columns in licenses list output (#11823) 2024-01-26 09:55:16 +00:00
Dean Sheather 29707099d7 chore: add agentapi tests (#11269) 2024-01-26 07:04:19 +00:00
Muhammad Atif Ali 541154b74b docs: simplify JFrog integration docs (#11787) 2024-01-25 19:50:06 -05:00
Steven Masley 005c014f13 chore: instrument additional github api calls (#11824)
* chore: instrument additional githubapi calls

This only affects github as a login source, not external auth.
2024-01-25 18:34:46 -06:00
Bruno Quaresma e371716b38 refactor(site): add minor workspace improvements (#11822) 2024-01-25 21:05:29 -03:00
Kayla Washburn-Love 73a6899f2c chore: miscellaneous cleanup (#11785) 2024-01-25 14:22:52 -07:00
Ammar Bandukwala 79568bf628 Revert "fix: always attempt external auth refresh when fetching (#11762)"
This reverts commit 0befc0826a.
2024-01-25 14:22:47 -06:00
Steven Masley 0befc0826a fix: always attempt external auth refresh when fetching (#11762)
* fix: always attempt external auth refresh when fetching
* refactor validate to check expiry when considering "valid"
2024-01-25 10:54:56 -06:00
Bruno Quaresma fd7f85bc5e fix(site): fix proxy settings link (#11817) 2024-01-25 12:16:24 +00:00
Cian Johnston 8eae4f83bf fix(coderd/provisionerdserver): fix test flake in TestHeartbeat (#11808) 2024-01-25 12:05:57 +00:00
Muhammad Atif Ali 979a920832 docs: use coder modules in offline deployments (#11788)
* docs: use coder modules in offline deployments

* fix typos

* Update offline installation instructions with Artifactory support for Coder modules

* Review suggestions
2024-01-25 08:01:56 +03:00
Ben Potter 6b0e1291d2 docs: add v2.7.3 changelog (#11811)
* docs: add v2.7.1 changelog

* docs: add v2.7.2 changelog
2024-01-24 16:53:08 -06:00
Kayla Washburn-Love 3d76e1b55c chore: clean up package.json and tsconfig (#11757) 2024-01-24 13:53:44 -07:00
Cian Johnston ecae6f9135 fix(enterprise/tailnet): handle query canceled error in sendBeat() (#11794) 2024-01-24 18:42:05 +00:00
Bruno Quaresma 8bc91b489e refactor(site): increase form fields gap (#11803) 2024-01-24 14:16:42 -03:00
Marcin Tojek 560e8cc1ae fix: check update permission to start workspace (#11798) 2024-01-24 17:18:03 +01:00
Cian Johnston 4616ccf462 fix(coderd): alter return signature of convertWorkspace, add check for requesterID (#11796) 2024-01-24 14:13:14 +00:00
Cian Johnston 70dc282b7d feat(cli): add favorite/unfavorite commands (#11793) 2024-01-24 14:05:39 +00:00
Cian Johnston f92336c4d5 feat(coderd): allow workspace owners to mark workspaces as favorite (#11791)
- Adds column `favorite` to workspaces table
- Adds API endpoints to favorite/unfavorite workspaces
- Modifies sorting order to return owners' favorite workspaces first
2024-01-24 13:39:19 +00:00
Bruno Quaresma 6145da8a9e refactor(site): verify external auth before display ws form (#11777) 2024-01-24 09:45:22 -03:00
Spike Curtis 5cbb76b47a fix: stop spamming DERP map updates for equivalent maps (#11792)
Fixes 2 related issues:

1. wsconncache had incorrect logic to test whether to send DERPMap updates, sending if the maps were equivalent, instead of if they were _not equivalent_.
2. configmaps used a bugged check to test equality between DERPMaps, since it contains a map and the map entries are serialized in random order. Instead, we avoid comparing the protobufs and instead depend on the existing function that compares `tailcfg.DERPMap`. This also has the effect of reducing the number of times we convert to and from protobuf.
2024-01-24 16:27:15 +04:00
Spike Curtis f5dbc718a7 fix: accept agent RPC connection without version query parameter (#11790)
Fixes an issue where Coder v2.7.1 agents connect to /api/v2/workspaceagents/me/rpc without a version query parameter
2024-01-24 09:10:16 +04:00
Colin Adler 13beb04521 fix: disable keepalives in workspaceapps transport (#11789)
Connection caching causes requests to hit the wrong workspaces. See
comment.

Fixes https://github.com/coder/coder/issues/11767
2024-01-24 14:46:59 +10:00
Muhammad Atif Ali 1e2634d2d0 chore(dogfood): use versioning for coder modules (#11774) 2024-01-24 01:33:25 +00:00
Kayla Washburn-Love 31a6a5dc6d chore: add stories for DropdownArrow (#11764) 2024-01-23 16:02:57 -07:00
Jon Ayers 383eed93f8 fix: use correct logger for lifecycle_executor (#11763) 2024-01-23 14:33:55 -06:00
Bruno Quaresma e828daba6e refactor(site): simplify create workspace form (#11771)
This is the first PR of a series of PRs trying to simplify and improve the create workspace flow.
- Use the existent template header and remove the selected template card
- Move the owner field to the general section so we don't have "anemic" sections with single fields

Before:
<img width="1512" alt="Screenshot 2024-01-23 at 10 22 45" src="https://github.com/coder/coder/assets/3165839/6a2ba6b4-9ffb-4576-9282-7901691f45ee">

Now:
<img width="1512" alt="Screenshot 2024-01-23 at 10 22 56" src="https://github.com/coder/coder/assets/3165839/84301548-4af9-4de0-96ff-2a6363fc8cf7">
2024-01-23 15:39:23 -03:00
Steven Masley d6ba0dfecb feat: add "updated" search param to workspaces (#11714)
* feat: add "updated" search param to workspaces
* rego -> sql needs to specify which <table>.organization_id
2024-01-23 11:52:06 -06:00
Steven Masley 081fbef097 fix: code-server path based forwarding, defer to code-server (#11759)
Do not attempt to construct a path based port forward url.
Always defer to code server, as it has it's own proxy method.
2024-01-23 11:36:44 -06:00
Marcin Tojek 77a4792ecd fix(cli): ssh: auto-update workspace (#11773) 2024-01-23 18:01:44 +01:00
Bruno Quaresma 369821ea19 feat(site): generates unique workspace names by default (#11772) 2024-01-23 15:55:29 +00:00
Bruno Quaresma 910f17f4e7 refactor(site): refactor external auth component (#11758)
Recommended improvements:
- Rename component for clarity 
- Simplify interface for contextual relevance 
- Handle polling errors based on section, not every button

Before:
<img width="1511" alt="Screenshot 2024-01-22 at 15 24 26" src="https://github.com/coder/coder/assets/3165839/cfb8c0bc-f5a2-4708-bd97-fdfc46bd1eee">

Now:
<img width="1512" alt="Screenshot 2024-01-22 at 15 24 41" src="https://github.com/coder/coder/assets/3165839/5aaad448-1bb2-45ea-9250-cd374a072be2">
2024-01-23 12:26:12 -03:00
Spike Curtis 059e533544 feat: agent uses Tailnet v2 API for DERPMap updates (#11698)
Switches the Agent to use Tailnet v2 API to get DERPMap updates.

Subsequent PRs will do the same for the CLI (`codersdk`) and `wsproxy`.
2024-01-23 14:42:07 +04:00
Spike Curtis 3e0e7f8739 feat: check agent API version on connection (#11696)
fixes #10531

Adds a check for `version` on connection to the Agent API websocket endpoint.  This is primarily for future-proofing, so that up-level agents get a sensible error if they connect to a back-level Coderd.

It also refactors the location of the `CurrentVersion` variables, to be part of the `proto` packages, since the versions refer to the APIs defined therein.
2024-01-23 14:27:49 +04:00
Spike Curtis eb12fd7d92 feat: make ServerTailnet set peers lost when it reconnects to the coordinator (#11682)
Adds support to `ServerTailnet` to set all peers lost before attempting to reconnect to the coordinator. In practice, this only really affects `wsproxy` since coderd has a local connection to the coordinator that only goes down if we're shutting down or change licenses.
2024-01-23 13:17:56 +04:00
dependabot[bot] f86186eef2 chore: bump github.com/hashicorp/terraform-json from 0.20.0 to 0.21.0 (#11738)
Bumps [github.com/hashicorp/terraform-json](https://github.com/hashicorp/terraform-json) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/hashicorp/terraform-json/releases)
- [Commits](https://github.com/hashicorp/terraform-json/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-json
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 09:46:11 +03:00
Ben Potter 18d43405c0 chore: change SSH wording on workspace page (#11702) 2024-01-23 09:45:44 +03:00
dependabot[bot] ca38bfd2fc ci: bump the github-actions group with 2 updates (#11745)
Bumps the github-actions group with 2 updates: [crate-ci/typos](https://github.com/crate-ci/typos) and [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign).


Updates `crate-ci/typos` from 1.17.1 to 1.17.2
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.17.1...v1.17.2)

Updates `toshimaru/auto-author-assign` from 2.0.1 to 2.1.0
- [Release notes](https://github.com/toshimaru/auto-author-assign/releases)
- [Changelog](https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/toshimaru/auto-author-assign/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: toshimaru/auto-author-assign
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 09:43:02 +03:00
Stephen Kirby 91a8b1b886 chore: fix broken docs links (#11760)
* fixed broken install/packages.md link

* fixed broken docs links

* fixed admin/auth link

* fixed example-guide links

* replaced mitchell tweet with nix docs

* make fmt

* replaced private image with imgur
2024-01-22 18:45:34 -06:00
Asher 3014777d2a feat: add endpoints to oauth2 provider applications (#11718)
These will show up when configuring the application along with the
client ID and everything else.  Should make it easier to configure the
application, otherwise you will have to go look up the URLs in the
docs (which are not yet written).

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
2024-01-22 13:25:25 -09:00
Steven Masley 8e0a153725 chore: implement device auth flow for fake idp (#11707)
* chore: implement device auth flow for fake idp
2024-01-22 20:46:05 +00:00
Asher 16c6cefde8 chore: pass lifetime directly into api key generate (#11715)
Rather than passing all the deployment values.  This is to make it
easier to generate API keys as part of the oauth flow.

I also added and fixed a test for when the lifetime is set and the
default and expiration are unset.

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
2024-01-22 11:42:55 -09:00
Bruno Quaresma a31d19d538 refactor(site): apply cosmetic changes and remove ExternalAuth from settings page (#11756) 2024-01-22 16:07:43 -03:00
Asher 7589df325b fix: display error when fetching OAuth2 provider apps (#11713) 2024-01-22 09:56:36 -09:00
Kayla Washburn-Love 69e963b1a2 refactor: move dashboard functionality to modules/dashboard/ (#11721) 2024-01-22 11:44:33 -07:00
Bruno Quaresma 14f114b224 chore(site): add test for sensitive value (#11755) 2024-01-22 15:03:15 -03:00
Kayla Washburn-Love f74ef142d0 refactor: reorganize auth components and hooks (#11717) 2024-01-22 10:43:32 -07:00
Bruno Quaresma f02561a599 chore(site): minor refactor to the resource metadata code (#11746) 2024-01-22 12:55:46 -03:00
Spike Curtis 5388a1b6d7 fix: use TSMP ping for reachability, not latency (#11749)
Use TSMP ping for reachability, but leave Disco ping for when we call Ping() since we often use that to determine whether we have a direct connection.

Also adds unit tests to make sure Ping() returns direct connection vs DERP correctly.
2024-01-22 17:37:15 +04:00
Ben Potter 66f119bde8 docs: add v2.7.1 changelog (#11747) 2024-01-22 07:09:18 -06:00
Spike Curtis 7ffd99cfe2 fix: use DiscoPing (partially reverts #11306) (#11744) 2024-01-22 12:40:21 +00:00
Spike Curtis 3d85cdfa11 feat: set peers lost when disconnected from coordinator (#11681)
Adds support to Coordination to call SetAllPeersLost() when it is closed. This ensure that when we disconnect from a Coordinator, we set all peers lost.

This covers CoderSDK (CLI client) and Agent.  Next PR will cover MultiAgent (notably, `wsproxy`).
2024-01-22 15:26:20 +04:00
Danny Kopping 9f6b38ce9c chore: use correct anchor link on scale.md (#11728) 2024-01-22 10:34:38 +00:00
Dean Sheather 15a90f028e chore: collect more template telemetry to gauge feature usage
We don't have visibility into some feature usage, so this adds a lot of fields missing from `database.Template` to `telemetry.Template`. Deprecation message is not collected, just whether it's set or not.
2024-01-22 18:55:27 +10:00
Spike Curtis b7b936547d feat: add setAllPeersLost to the configMaps subcomponent (#11665)
adds setAllPeersLost to the configMaps subcomponent of tailnet.Conn --- we'll call this when we disconnect from a coordinator so we'll eventually clean up peers if they disconnect while we are retrying the coordinator connection (or we don't succeed in reconnecting to the coordinator).
2024-01-22 12:12:15 +04:00
Spike Curtis f01cab9894 feat: use tailnet v2 API for coordination (#11638)
This one is huge, and I'm sorry.

The problem is that once I change `tailnet.Conn` to start doing v2 behavior, I kind of have to change it everywhere, including in CoderSDK (CLI), the agent, wsproxy, and ServerTailnet.

There is still a bit more cleanup to do, and I need to add code so that when we lose connection to the Coordinator, we mark all peers as LOST, but that will be in a separate PR since this is big enough!
2024-01-22 11:07:50 +04:00
Muhammad Atif Ali 5a2cf7cd14 chore(docs): remove tabs from appearance settings (#11726) 2024-01-20 13:27:28 +00:00
dependabot[bot] 83013792b1 chore: bump vite from 4.5.1 to 4.5.2 in /site (#11723)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 12:19:26 +00:00
Muhammad Atif Ali 4bed9611a8 fix(docs): fix tabs for support links (#11724) 2024-01-20 15:12:07 +03:00
Ben Potter 77de24c94f docs: add v2.7.0 changelog (#11719)
* docs: add v2.7.0 changelog

* some modifications
2024-01-19 23:11:35 +00:00
Kayla Washburn-Love 80eac73ed1 chore: remove useLocalStorage hook (#11712) 2024-01-19 16:04:19 -07:00
Asher fa99f6a200 chore: make yarn use the right version of node (#11716)
Otherwise if for example you try to run `yarn storybook` it complains
that the version of Node is wrong.

`pnpm storybook` works fine and that is probably what we should
actually use, but as long as we are installing Yarn and not restricting
its use we might as well make it use the right version of Node.
2024-01-19 12:59:38 -09:00
Kayla Washburn-Love 76911f1375 chore: fix TemplateVersionEditor story (#11709) 2024-01-19 12:13:30 -07:00
Steven Masley ca48b8783b fix: update template with noop returned undefined template (#11688)
* fix: doing a noop patch to templates resulted in 404

The patch response did not include the template. The UI required the
template to be returned to form the new page path

null is more explicit, and harder to make occur by mistake.
2024-01-19 18:54:25 +00:00
Kayla Washburn-Love 75d70a9542 chore: add a story for WorkspaceOutdatedTooltip (#11695) 2024-01-19 11:41:18 -07:00
Muhammad Atif Ali 6090007708 docs: update docs to set SupportLinks (#11699) 2024-01-19 20:10:10 +03:00
Steven Masley d67c9d1bb5 fix: set request header before do (#11706) 2024-01-19 16:14:08 +00:00
Steven Masley ccfd1a561b chore: improve device handling error message (#11606) 2024-01-19 09:41:52 -06:00
Mathias Fredriksson 593a1e9f60 feat(cli/exp): add target workspace/users to scaletest commands (#11701) 2024-01-19 15:32:46 +00:00
Marcin Tojek 4b059c4c93 fix: make workspace tooltips actionable (#11700) 2024-01-19 15:17:02 +01:00
Mathias Fredriksson 200a87e7d4 feat(cli/ssh): allow multiple remote forwards and allow missing local file (#11648) 2024-01-19 15:21:10 +02:00
Mathias Fredriksson 73e6bbff7e feat(cli/exp): add app testing to scaletest workspace-traffic (#11633) 2024-01-19 15:20:19 +02:00
Bruno Quaresma 1f63a11396 refactor(site): refactor resource and agents (#11647) 2024-01-19 09:06:33 -03:00
Marcin Tojek 89fd29478d feat: expose support links as env variables (#11697) 2024-01-19 11:20:36 +01:00
Garrett Delfosse bf0a6fcc32 feat: manage provisioner tags in template editor (#11600) 2024-01-18 17:35:20 -05:00
Kayla Washburn-Love 9ed3487f67 feat: batch workspace updates (#11583) 2024-01-18 15:14:25 -07:00
Bruno Quaresma 156aaba335 feat(site): show version files diff based on active version (#11686) 2024-01-18 16:08:17 -03:00
Steven Masley 6bb1a34a37 fix: allow ports in wildcard url configuration (#11657)
* fix: allow ports in wildcard url configuration

This just forwards the port to the ui that generates urls.
Our existing parsing + regex already supported ports for
subdomain app requests.
2024-01-18 09:44:05 -06:00
Spike Curtis 1f0e6ba6c6 fix: use raw syscalls to write binary we execute (#11684)
Fixes flake seen here, I think

https://github.com/coder/coder/actions/runs/7565915337/job/20602500818

golang's file processing is complex, and in at least some cases it can return from a file.Close() call without having actually closed the file descriptor.

If we're holding open the file descriptor of an executable we just wrote, and try to execute it, it will fail with "text file busy" which is what we have seen.

So, to be extra sure, I've avoided the standard library and directly called the syscalls to open, write, and close the file we intend to use in the test.

I've also added some more logging so if it's some issue of multiple tests writing to the same location, the we might have a chance to see it.
2024-01-18 16:21:11 +04:00
Marcin Tojek c5d73b86d6 feat: change owner name using account form (#11683) 2024-01-18 12:32:01 +01:00
Muhammad Atif Ali 1ea70ba573 ci: build a multi-arch image on each commit to main (#11544) 2024-01-18 10:57:35 +00:00
Spike Curtis 8910ac715c feat: add tailnet v2 support to wsproxy coordinate endpoint (#11637)
wsproxy also needs to be updated to use tailnet v2 because the `tailnet.Conn` stores peers by ID, and the peerID was not being carried by the JSON protocol.  This adds a query param to the endpoint to conditionally switch to the new protocol.
2024-01-18 10:10:36 +04:00
Spike Curtis 07427e06f7 chore: add setBlockEndpoints to nodeUpdater (#11636)
nodeUpdater also needs block endpoints, so that it can stop sending nodes with endpoints.
2024-01-18 10:02:15 +04:00
Spike Curtis 5b4de667d6 chore: add setCallback to nodeUpdater (#11635)
we need to be able to (re-)set the node callback when we lose and regain connection to a coordinator over the network.
2024-01-18 09:51:09 +04:00
Spike Curtis e725f9d7d4 chore: stop passing addresses on configMaps constructor (#11634)
moving this out of the constructor so that setting this when creating a new `tailnet.Conn` triggers configuring the engine.
2024-01-18 09:43:28 +04:00
Spike Curtis a514df71ed chore: add setDERPMap to configMaps (#11590)
Add setDERPMap
2024-01-18 09:34:30 +04:00
Spike Curtis 25e289e1f6 chore: add setAddresses to nodeUpdater (#11571)
Adds setAddresses to nodeUpdater
2024-01-18 09:24:16 +04:00
Spike Curtis 387723a596 fix: close pg PubSub listener to avoid race (#11640)
Fixes flake as seen here: https://github.com/coder/coder/runs/20528529187
2024-01-18 09:18:59 +04:00
Asher 72d9ec07aa fix: detect JetBrains running on local ipv6 (#11676) 2024-01-17 14:08:15 -09:00
Jon Ayers 552e9fe22f fix: avoid returning 500 on apps when workspace stopped (#11656) 2024-01-17 12:06:59 -06:00
Bruno Quaresma 1be119b08f fix(site): fix search menu for creating workspace and templates filter (#11674) 2024-01-17 17:54:56 +00:00
Steven Masley b246f08d84 chore: move app URL parsing to its own package (#11651)
* chore: move app url parsing to it's own package
2024-01-17 10:41:42 -06:00
Bruno Quaresma 1aee8da4b6 fix(site): fix sidebar scroll (#11671) 2024-01-17 16:05:05 +00:00
dependabot[bot] fa6176c2ff chore: bump github.com/u-root/u-root from 0.11.0 to 0.12.0 (#11625)
* chore: bump github.com/u-root/u-root from 0.11.0 to 0.12.0

Bumps [github.com/u-root/u-root](https://github.com/u-root/u-root) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/u-root/u-root/releases)
- [Changelog](https://github.com/u-root/u-root/blob/main/RELEASES)
- [Commits](https://github.com/u-root/u-root/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: github.com/u-root/u-root
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* `go mod tidy`

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-01-18 01:36:47 +10:00
Marcin Tojek e83f13d8c5 fix: typo in whitespace (#11667) 2024-01-17 12:36:15 +00:00
Marcin Tojek 5eb3e1cdaa feat: expose owner_name in coder_workspace resource (#11639) 2024-01-17 13:20:45 +01:00
Spike Curtis b173195e0d Revert "fix: detect JetBrains running on local ipv6 (#11653)" (#11664)
This reverts commit 2d61d5332a.
2024-01-17 15:38:39 +04:00
Spike Curtis 2aa3cbbd03 chore: add logging to nodeUpdater (#11569)
Add debug logging for nodeUpdater and configMaps
2024-01-17 14:15:45 +04:00
Spike Curtis bad2ce562e fix: stop asserting fuzz bytes written in test
Fixes a flake seen here: https://github.com/coder/coder/actions/runs/7541558190/job/20528545916

```
=== FAIL: enterprise/provisionerd TestRemoteConnector_Fuzz (0.06s)
    t.go:84: 2024-01-16 12:32:27.024 [info]  connector: failed provisioner authentication  remote_addr=[::1]:45138 ...
        error= failed to receive jobID:
                   github.com/coder/coder/v2/enterprise/provisionerd.(*remoteConnector).authenticate
                       /home/runner/actions-runner/_work/coder/coder/enterprise/provisionerd/remoteprovisioners.go:438
                 - bufio.Scanner: token too long
    t.go:84: 2024-01-16 12:32:27.024 [debu]  connector: closed connection  remote_addr=[::1]:45138  error=<nil>
    remoteprovisioners_test.go:209: 
            Error Trace:    /home/runner/actions-runner/_work/coder/coder/enterprise/provisionerd/remoteprovisioners_test.go:209
            Error:          "2992256" is not less than "2097152"
            Test:           TestRemoteConnector_Fuzz
            Messages:       should not allow more than 1 MiB
```

This was an attempt to test that malicious actors can't abuse our authentication protocol to make us allocate a bunch of memory.
However, the test asserted on the number of bytes sent by the fuzzer, not the number of bytes read (& allocated) by the service.  The former is affected by network queue sizes and is thus flaky without actively managing the socket queues, which I don't think we want to do.

In actual practise, the thing that matters is how much memory the bufio Scanner allocates. By inspection, the scanner will allocate up to 64k, and testing this is true devolves into testing the go standard library, which I don't think is worth doing.

So... let's just drop the assertion because 

a) its flaky, 

b) it doesn't test what we actually want to test, 

c) the behavior we actually care about is part of the standard library.
2024-01-17 12:59:45 +04:00
Spike Curtis 38d9ce5267 chore: add setStatus support to nodeUpdater (#11568)
Add support for the wgengine Status callback to nodeUpdater
2024-01-17 09:06:34 +04:00
Spike Curtis f6dc707511 chore: add DERPForcedWebsocket to nodeUpdater (#11567)
Add support for DERPForcedWebsocket to nodeUpdater
2024-01-17 08:55:45 +04:00
Asher 2d61d5332a fix: detect JetBrains running on local ipv6 (#11653) 2024-01-16 15:53:41 -09:00
Colin Adler be43d6247d feat: add additional fields to first time setup trial flow (#11533)
* feat: add additional fields to first time setup trial flow

* trial generator typo
2024-01-16 18:19:16 -06:00
Jon Ayers 1196f83ebd feat: automatically activate dormant workspaces when manually started (#11655) 2024-01-16 16:42:04 -06:00
Stephen Kirby d74aae7a4a removed alpha tags from workspace actions features in template settings (#11654) 2024-01-16 16:23:19 -06:00
Muhammad Atif Ali 417270a6d7 chore(docs): remove the template_update_policies experiment from docs (#11615) 2024-01-17 00:18:57 +03:00
Jon Ayers 6ebcee3b49 docs: add workspace cleanup docs (#11146)
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
Co-authored-by: kirby <kirby@coder.com>
2024-01-16 15:12:56 -06:00
Mathias Fredriksson 385d58caf6 fix(agent/agentssh): allow remote forwarding a socket multiple times (#11631)
* fix(agent/agentssh): allow remote forwarding a socket multiple times

Fixes #11198
Fixes https://github.com/coder/customers/issues/407
2024-01-16 21:26:13 +02:00
Steven Masley 08b4eb3124 fix: refresh all oauth links on external auth page (#11646)
* fix: refresh all oauth links on external auth page
2024-01-16 11:03:55 -06:00
Cian Johnston d583acad00 fix(coderd): workspaceapps: update last_used_at when workspace app reports stats (#11603)
- Adds a new query BatchUpdateLastUsedAt
- Adds calls to BatchUpdateLastUsedAt in app stats handler upon flush
- Passes a stats flush channel to apptest setup scaffolding and updates unit tests to assert modifications to LastUsedAt.
2024-01-16 14:06:39 +00:00
Muhammad Atif Ali 5bfbf9f9e6 chore(docs/install/docker.md): shorten headings length (#11630) 2024-01-16 07:19:58 +00:00
Steven Masley 5087f7b5f6 chore: improve fake IDP script (#11602)
* chore: testIDP using static defaults for easier reuse
2024-01-15 10:01:41 -06:00
Marcin Tojek f915bdf26c feat: support links with custom icons (#11629) 2024-01-15 16:56:01 +01:00
dependabot[bot] 5c310ec334 chore: bump github.com/prometheus/common from 0.45.0 to 0.46.0 (#11618)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 18:19:16 +03:00
dependabot[bot] 288f879f72 ci: bump the github-actions group with 1 update (#11616)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 14:48:19 +00:00
dependabot[bot] af013fc3a1 chore: bump github.com/go-playground/validator/v10 from 10.16.0 to 10.17.0 (#11626)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 17:45:49 +03:00
dependabot[bot] 476d72e63d chore: bump github.com/andybalholm/brotli from 1.0.6 to 1.1.0 (#11621)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 17:44:48 +03:00
dependabot[bot] ecefb8c0c1 chore: bump golang.org/x/tools from 0.16.1 to 0.17.0 (#11622)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 17:44:17 +03:00
Cian Johnston 244ca88645 ci: set CODER_VERBOSE=true for fly.io wsproxies (#11405) 2024-01-15 13:14:38 +00:00
dependabot[bot] 054420bb33 chore: bump github.com/go-logr/logr from 1.3.0 to 1.4.1 (#11475)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 14:08:28 +03:00
sharkymark f65b2efb95 chore: replace remote with cloud when referencing development environments; add Slack as an enterprise option for community sharing (#11375) 2024-01-15 05:31:35 -05:00
Eric Paulsen c799f0ff43 docs: add steps to configure supportLinks in Helm chart (#11612) 2024-01-15 05:29:59 -05:00
Eric Paulsen e1493b220a fix: guide naming (#11613) 2024-01-15 05:29:43 -05:00
Muhammad Atif Ali 8b10d21a70 chore(docs): fix a minor punctuation error (#11610) 2024-01-14 08:03:07 +00:00
Eric Paulsen e70a97a722 docs: add guide for template ImagePullSecret (#11608)
* docs: add guide for template imagepullsecret

* add: manifest

* make: fmt
2024-01-12 18:44:26 -06:00
Kayla Washburn-Love 4c3f05b8aa fix: show error when creating a new group fails (#11560) 2024-01-12 16:06:02 -07:00
Steven Masley 905292053a fix: improve wsproxy error when proxyurl is set to a primary (#11586)
* coder error first
2024-01-12 20:32:02 +00:00
Steven Masley 03ee63931c chore: remove duplicate validate calls on same oauth token (#11598)
* chore: remove duplicate validate calls on same oauth token
2024-01-12 14:27:22 -06:00
Bruno Quaresma 8181c9f349 refactor(site): make cosmetic changes on agent logs (#11601) 2024-01-12 17:09:36 -03:00
Bruno Quaresma 68e5a51d90 feat(site): display builds logs by default (#11597) 2024-01-12 16:39:23 -03:00
Bruno Quaresma ec166cf423 fix(site): remove search menu vertical padding (#11599) 2024-01-12 19:33:21 +00:00
Bruno Quaresma f3edc42b76 fix(site): fix workspace resource width on ultra wide screens (#11596) 2024-01-12 16:09:12 -03:00
Bruno Quaresma 130d5d68a0 refactor(site): refactor workspace notifications (#11520) 2024-01-12 15:55:31 -03:00
Stephen Kirby bdefd4e2e6 chore: convert faq headers to dropdowns (#11585)
* changed FAQs from headers to twists

* added dropdowns and mild formatting

* make fmt
2024-01-12 12:49:41 -06:00
Bruno Quaresma 162c91ec2a fix(site): fix resource selection when workspace has no prev resources (#11594) 2024-01-12 15:45:06 -03:00
Marcin Tojek cb77f04104 feat: load variables from tfvars files (#11549) 2024-01-12 15:08:23 +01:00
Bruno Quaresma aeb1ab8ad8 fix(site): fix resource selection when workspace resources change (#11581) 2024-01-12 10:14:31 -03:00
Cian Johnston 0e96115d5d fix(coderd): correctly show warning when no provisioner daemons are registered (#11591) 2024-01-12 11:22:59 +00:00
Steven Masley f5a9f5ca3d chore: handle errors in wsproxy server for cli using buildinfo (#11584)
Cli errors are pretty formatted. This handles nested pretty types. Before it found the first error it could understand and return that. Now it will print the full error stack with more information.

To prevent information loss, a "[Trace=...]" was added to capture some extra error context for debugging.
2024-01-11 16:55:34 -06:00
Jon Ayers aecdafdcf2 fix: fix template edit overriding with flag defaults (#11564) 2024-01-11 16:18:46 -06:00
Kayla Washburn-Love eb8d85f432 feat: treat deprecation messages as markdown (#11562) 2024-01-11 14:15:29 -07:00
Cian Johnston 95fd0bb22b feat(site): remove experiment deployment_health_page (#11572) 2024-01-11 21:03:10 +00:00
Cian Johnston 26f5ce63a8 feat(site): add docs links on health page (#11582)
* feat(site): add docs links on health page

* apply suggestions
2024-01-11 20:32:25 +00:00
Garrett Delfosse 5b122d108e fix: publish workspace update on quota failure (#11559) 2024-01-11 14:59:40 -05:00
Kayla Washburn-Love 05eac64be4 feat: add a character counter for fields with length limits (#11558)
- refactors`getFormHelpers` to accept an options object
- adds a `maxLength` option which will display a message and character counter for fields with length limits
- set `maxLength` option for template description fields
2024-01-11 12:15:43 -07:00
Garrett Delfosse f9f94b5d01 fix: remove cancel button if user cannot cancel job (#11553) 2024-01-11 13:48:44 -05:00
Kayla Washburn-Love 8c3a4f2d7f chore: move some components into pages/ (#11536) 2024-01-11 11:30:15 -07:00
Steven Masley e3ad9580e9 chore: allow running fake idp with coderd dev (#11555)
* chore: allow running fake idp with coderd dev
2024-01-11 18:10:57 +00:00
sharkymark c91b885a4a chore: add optional coder_app to faq (#11351)
Merging since Mark is out.

* chore: add optional coder_app to faq

* applied Atif's suggestions

* make fmt again

---------

Co-authored-by: kirby <kirby@coder.com>
Co-authored-by: Stephen Kirby <58410745+stirby@users.noreply.github.com>
2024-01-11 12:07:22 -06:00
Steven Masley fcd299109c chore: update language about autostop on templates page (#11552)
* chore: update language about autostop on templates page
2024-01-11 12:01:07 -06:00
Steven Masley 8b61ff3e0e fix: apply appropriate artifactory defaults for external auth (#11580) 2024-01-11 11:58:27 -06:00
Cian Johnston f3d091fa01 fix(site): improve rendering of provisioner tags (#11575)
* fix(site): improve rendering of provisioner tags

* fixup! fix(site): improve rendering of provisioner tags

* Update site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx

* fixup! Update site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx
2024-01-11 17:42:21 +00:00
Colin Adler 4a0808259a fix: ensure wsproxy MultiAgent is closed when websocket dies (#11414)
The `SingleTailnet` behavior only checked to see if the `MultiAgent` was
closed, but the websocket error was not being propogated into the
`MultiAgent`, causing it to never be swapped for a new working one.

Fixes https://github.com/coder/coder/issues/11401

Before:
```
Coder Workspace Proxy v0.0.0-devel+85ff030 - Your Self-Hosted Remote Development Platform
Started HTTP listener at http://0.0.0.0:3001

View the Web UI: http://127.0.0.1:3001

==> Logs will stream in below (press ctrl+c to gracefully exit):
2024-01-04 20:11:56.376 [warn]  net.workspace-proxy.servertailnet: broadcast server node to agents ...
    error= write message:
               github.com/coder/coder/v2/enterprise/wsproxy/wsproxysdk.(*remoteMultiAgentHandler).writeJSON
                   /home/coder/coder/enterprise/wsproxy/wsproxysdk/wsproxysdk.go:524
             - failed to write msg: WebSocket closed: failed to read frame header: EOF
```

After:
```
Coder Workspace Proxy v0.0.0-devel+12f1878 - Your Self-Hosted Remote Development Platform
Started HTTP listener at http://0.0.0.0:3001

View the Web UI: http://127.0.0.1:3001

==> Logs will stream in below (press ctrl+c to gracefully exit):
2024-01-04 20:26:38.545 [warn]  net.workspace-proxy.servertailnet: multiagent closed, reinitializing
2024-01-04 20:26:38.546 [erro]  net.workspace-proxy.servertailnet: reinit multi agent ...
    error= dial coordinate websocket:
               github.com/coder/coder/v2/enterprise/wsproxy/wsproxysdk.(*Client).DialCoordinator
                   /home/coder/coder/enterprise/wsproxy/wsproxysdk/wsproxysdk.go:454
             - failed to WebSocket dial: failed to send handshake request: Get "http://127.0.0.1:3000/api/v2/workspaceproxies/me/coordinate": dial tcp 127.0.0.1:3000: connect: connection refused
2024-01-04 20:26:38.587 [erro]  net.workspace-proxy.servertailnet: reinit multi agent ...
    error= dial coordinate websocket:
               github.com/coder/coder/v2/enterprise/wsproxy/wsproxysdk.(*Client).DialCoordinator
                   /home/coder/coder/enterprise/wsproxy/wsproxysdk/wsproxysdk.go:454
             - failed to WebSocket dial: failed to send handshake request: Get "http://127.0.0.1:3000/api/v2/workspaceproxies/me/coordinate": dial tcp 127.0.0.1:3000: connect: connection refusedhandshake request: Get "http://127.0.0.1:3000/api/v2/workspaceproxies/me/coordinate": dial tcp 127.0.0.1:3000: connect: connection refused
2024-01-04 20:26:40.446 [info]  net.workspace-proxy.servertailnet: successfully reinitialized multiagent  agents=0  took=1.900892615s
```
2024-01-11 11:37:09 -06:00
Bruno Quaresma d708ac7c04 fix(site): remove refetch on windows focus (#11574)
It causes the sign-in page to reload whenever a user enters a page or changes the window's focus. This is happening because when the "user" fetch is made, the server returns an error, making the react-query mark the data as stale and try to load it whenever possible.
2024-01-11 11:06:36 -03:00
Bruno Quaresma 3695b74ab6 fix(site): fix loading indicator alignment (#11573) 2024-01-11 10:53:36 -03:00
Cian Johnston 8a12ee7831 fix(site): show wsproxy errors in context in WorkspaceProxyPage (#11556)
* Shows the overall report error at the top of the page, if present.
* Shows workspaceproxy errors above warnings inside the corresponding element, if present.
* Improves unregistered proxy status
2024-01-11 10:47:02 +00:00
Spike Curtis 8701dbc874 chore: add nodeUpdater to tailnet (#11539)
Adds a nodeUpdater component, which serves a similar role to configMaps, but tracks information from tailscale going out to the coordinator as node updates.  This first PR just handles netInfo, subsequent PRs will
handle DERP forced websockets, endpoints, and addresses.
2024-01-11 09:29:42 +04:00
Spike Curtis 7005fb1b2f chore: add support for blockEndpoints to configMaps (#11512)
Adds support for setting blockEndpoints on the configMaps
2024-01-11 09:18:31 +04:00
Spike Curtis 617ecbfb1f chore: add support for peer updates to tailnet.configMaps (#11487)
Adds support to configMaps to handle peer updates including lost and disconnected peers
2024-01-11 09:11:43 +04:00
bamhm182 4e5367c4a4 chore: update Digital Ocean example template (#11528) (#11535)
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-01-11 00:00:25 +00:00
Jon Ayers 9b437032e9 feat: remove workspace_actions experiment (#11427) 2024-01-10 16:19:39 -06:00
Steven Masley 04afb88e6f fix: return a more sophisticated error for device failure on 429 (#11554)
* fix: return a more sophisticated error for device failure on 429
2024-01-10 11:29:44 -06:00
Mathias Fredriksson b1d53a68c2 fix(agent/agentssh): fix X11 forwarding by improving Xauthority management (#11550)
Fixes #11531
2024-01-10 19:04:44 +02:00
Steven Masley 89ab659114 chore: add oauth2 prometheus metrics for to documentation (#11534) 2024-01-10 15:46:37 +00:00
Steven Masley 3f9da674c6 chore: instrument github oauth2 limits (#11532)
* chore: instrument github oauth2 limits

Rate limit information for github oauth2 providers instrumented in prometheus
2024-01-10 15:29:33 +00:00
Steven Masley 50b78e3325 chore: instrument external oauth2 requests (#11519)
* chore: instrument external oauth2 requests

External requests made by oauth2 configs are now instrumented into prometheus metrics.
2024-01-10 09:13:30 -06:00
Garrett Delfosse aa7fe075a8 fix: correct flag name (#11525) 2024-01-10 09:36:26 -05:00
Garrett Delfosse 0727535342 fix: correct app url format in comment (#11523) 2024-01-10 09:36:10 -05:00
Muhammad Atif Ali 6e5c2efca1 chore(docs): remove provider logos from 1-click install (#11548)
* docs: remove cloud logos from 1-click install

They were looking good and are not adding much value.

* Delete docs/images/install/render.png

* Delete docs/images/install/ec2.svg

* Delete docs/images/install/eks.svg

* Delete docs/images/install/fly.io.svg

* Delete docs/images/install/gce.svg

* Delete docs/images/install/heroku.svg

* Delete docs/images/install/railway.svg
2024-01-10 13:28:40 +00:00
Spike Curtis cae095fdb6 fix: stop logging errors on canceled cleanup queries (#11547)
Fixes flake seen here: https://github.com/coder/coder/actions/runs/7474259128/job/20340051975
2024-01-10 16:20:29 +04:00
Muhammad Atif Ali 9682db593e chore(docs): reorganize installation docs (#11465) 2024-01-10 15:00:19 +03:00
Spike Curtis dfe8efc186 fix: use background context for inmem provisionerd (#11545)
This test case fails with an error log, showing "context canceled" when trying to send an acquired job to an in-mem provisionerd.

https://github.com/coder/coder/runs/20331469006

In this case, we don't want to supress this error, since it could mean that we acquired a job, locked it in the database, then failed to send it to a provisioner.
(We also don't want to mark the job as failed because we don't know whether the job made it to the provisionerd or not --- in the failed test you can see that the job is actually processed just fine).

The reason we got context canceled is because the API was shutting down --- we don't want provisionerdserver to abruptly stop processing job stuff as the API shuts down as this will leave jobs in a bad state.  This PR fixes up the use of contexts with provisionerdserver and the associated drpc service calls.
2024-01-10 15:29:57 +04:00
Muhammad Atif Ali c125206b24 docs(faqs): add FAQ regarding unsupported base image for VS Code Server (#11543) 2024-01-10 12:16:44 +03:00
Cian Johnston 5ecb0db4f2 chore(coderd): fix test flake in TestAgentWebsocketMonitor_SendPings (#11518) 2024-01-10 08:45:46 +00:00
Cian Johnston 5ed3c413cd chore(coderd): fix test flake in TestWorkspaceUpdateAutomaticUpdates_OK (#11521) 2024-01-10 08:45:32 +00:00
dependabot[bot] 61cd9f087b chore: bump follow-redirects from 1.15.2 to 1.15.4 in /site (#11540)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-10 10:30:05 +03:00
Spike Curtis 89e3bbe0f5 chore: add configMaps component to tailnet (#11400)
Work in progress on a subcomponent of the Conn which will handle configuring the wireguard engine on changes.  I've implemented setAddresses as the simplest case and added unit tests of the reconfiguration loop.

Besides making the code easier to test and understand, the goal is for this component to handle disconnect and loss updates about peers, and thereby, implement the v2 Tailnet API.

Further PRs will handle peer updates, status updates, and net info updates.

Then, after the subcomponent is implemented and tested, I will refactor Conn to use it instead of the current monolithic architecture.
2024-01-10 10:58:53 +04:00
Asher d837d66e29 chore: update sqlc to 1.25.0 (#11538)
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-01-10 09:19:41 +03:00
Asher 0912cfc2d6 chore: update flake to include new mockgen (#11537)
It looks like we updated mockgen to use Uber's fork, but the flake still
pointed to a nixos-unstable commit containing the old mockgen resulting 
in an error like:

missing go.sum entry for module providing package github.com/golang/mock/mockgen/model
2024-01-09 15:37:51 -09:00
Kayla Washburn 97bd74b468 chore: add additional stories to storybook (#11524)
add stories for ActiveUserChart, CopyableValue, and CopyButton
2024-01-09 14:03:40 -07:00
Kayla Washburn 8a48485014 refactor: clean up Welcome component (#11526) 2024-01-09 14:03:33 -07:00
Garrett Delfosse 4fa07124cd feat: display application name over sign in form (#11500) 2024-01-09 12:51:16 -05:00
Garrett Delfosse 30d5ac060b fix: carry tags to new templateversions (#11502) 2024-01-09 12:47:44 -05:00
Cian Johnston 952706e905 fix(site): HealthPage/WorkspaceProxyPage: adjust border colour for unhealthy regions (#11516) 2024-01-09 17:36:41 +00:00
Kayla Washburn e77b1a5ffd chore: miscellaneous cleanup (#11027) 2024-01-09 10:14:19 -07:00
Cian Johnston 9f4f953350 fix(coderd/healthcheck): ignore deleted wsproxies in wsproxy healthcheck (#11515) 2024-01-09 16:36:26 +00:00
Marcin Tojek e5b9d63901 docs: escape enum pipe (#11513) 2024-01-09 13:39:38 +00:00
Marcin Tojek 525e6e5dc8 docs: remove empty page (#11511) 2024-01-09 12:52:45 +01:00
Marcin Tojek b8373e6fab fix: nix: force node version v18 (#11510) 2024-01-09 12:27:56 +01:00
Spike Curtis fdd60d316e fix: fix MetricsAggregator check for metric sameness (#11508)
Fixes #11451

A refactor of the Agent API passes metrics as protobufs, which include pointers to label name/value pairs.  The aggregator tested for sameness by doing a shallow compare of label values, which for different stats reports would compare unequal because the pointers would be different.

This fix does a deep compare.

While testing I also noted that we neglect to compare template names. This is unlikely to have caused any issue in practice, since the combination of username/workspace is unique, but in the context of comparing metric labels we should do the comparison.

If a user creates a workspace, deletes it, then recreates from a different template, we could in principle have reported incorrect stats for the old template.
2024-01-09 15:21:30 +04:00
Spike Curtis 21093c00f0 fix: stop logging error on canceled query (#11506)
Fixes flake seen here: https://github.com/coder/coder/actions/runs/7447779208/job/20260756050
2024-01-09 14:38:56 +04:00
Cian Johnston 0c953b4b8c fix(enterprise/coderd): make primary workspace proxy always be updatd now (#11499) 2024-01-09 10:03:08 +00:00
Steven Masley fb29af664b fix: relax csrf to exclude path based apps (#11430)
* fix: relax csrf to exclude path based apps
* add unit test to verify path based apps are not CSRF blocked
2024-01-08 22:33:57 +00:00
Kayla Washburn 9f5a59d5c5 feat(site): improve icon compatibility across themes (#11457) 2024-01-08 14:12:40 -07:00
Garrett Delfosse 427afe13e0 fix: generate new random username to prevent flake (#11501) 2024-01-08 19:09:14 +00:00
Cian Johnston 220e95dd5c feat(site): add healthcheck page for provisioner daemons (#11494)
Part of #10676

- Adds a health section for provisioner daemons (mostly cannibalized from the Workspace Proxy section)
- Adds a corresponding storybook entry for provisioner daemons health section
- Fixed an issue where dismissing the provisioner daemons warnings would result in a 500 error
- Adds provisioner daemon error codes to docs
2024-01-08 17:14:09 +00:00
dependabot[bot] 6096af77c8 chore: bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 (#11495)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.3 to 1.3.7.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.3...v1.3.7)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 20:10:59 +03:00
Bruno Quaresma a613a0a4da refactor(site): improve settings option (#11489) 2024-01-08 13:16:16 -03:00
Bruno Quaresma 61450863ff feat(site): move resources into the sidebar (#11456) 2024-01-08 13:14:25 -03:00
dependabot[bot] 359a642e7e chore: bump github.com/prometheus/client_golang from 1.17.0 to 1.18.0 (#11474)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 17:07:56 +03:00
Cian Johnston 93cf5dcd47 fix(coderd/healthcheck): add daemon-specific warnings to healthcheck output (#11490)
- Sorts provisioner daemons by name ascending in output
- Adds daemon-specific warnings to healthcheck output
- Reword some messages
2024-01-08 13:55:00 +00:00
dependabot[bot] f4393d0c3f chore: bump github.com/hashicorp/terraform-json from 0.18.0 to 0.20.0 (#11483)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 13:20:14 +00:00
Bruno Quaresma 0186241880 fix(site): display github login config (#11488) 2024-01-08 10:17:09 -03:00
dependabot[bot] efb1ee31c0 chore: bump github.com/unrolled/secure from 1.13.0 to 1.14.0 (#11476)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 16:15:53 +03:00
dependabot[bot] 4c7a93dd7e chore: bump github.com/coreos/go-oidc/v3 from 3.7.0 to 3.9.0 (#11479)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 15:32:05 +03:00
dependabot[bot] a6c746e4e0 chore: bump github.com/aws/smithy-go from 1.17.0 to 1.19.0 (#11484)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 15:31:43 +03:00
dependabot[bot] 2c9589d883 chore: bump github.com/google/uuid from 1.4.0 to 1.5.0 (#11485)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 15:31:23 +03:00
dependabot[bot] 58f5f324b0 chore: bump github.com/gohugoio/hugo from 0.120.3 to 0.121.2 (#11473)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 15:31:09 +03:00
Muhammad Atif Ali acec1f7716 chore: increase dependabot PRs limit for go (#11472) 2024-01-08 14:54:28 +03:00
dependabot[bot] 5337a70561 chore: bump google.golang.org/protobuf from 1.31.0 to 1.32.0 (#11468)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 13:43:33 +03:00
Cian Johnston 04fd96a014 feat(coderd): add provisioner_daemons to /debug/health endpoint (#11393)
Adds a healthcheck for provisioner daemons to /debug/health endpoint.
2024-01-08 09:29:04 +00:00
Michael Smith 31f8fac1b9 fix: make ProxyMenu more accessible to screen readers (#11312)
* wip: commit progress on latency update

* chore: add stories and clean up tests

* refactor: clean up code

* fix: make sure headers aren't treated as interactive elements

* refactor: clean up tests

* fix: clean up stories

* docs: add clarifying comment

* fix: update stories again

* fix: clean up/extend prop definitions

* refactor: quick cleanup

* fix: apply Kira's feedback

* refactor: clean up abbr markup to account for pronunciation

* fix: more cleanup

* fix: refine screen reader output for VoiceOver

* refactor: clean up and redefine tests

* feature: add finishing touches
2024-01-07 18:37:01 -05:00
dependabot[bot] 8a9fe2bf00 chore: bump golang.org/x/term from 0.15.0 to 0.16.0 (#11463)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/term/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 12:28:39 +00:00
dependabot[bot] 64f239c844 chore: bump github.com/go-chi/httprate from 0.7.4 to 0.8.0 (#11461)
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.7.4 to 0.8.0.
- [Commits](https://github.com/go-chi/httprate/compare/v0.7.4...v0.8.0)

---
updated-dependencies:
- dependency-name: github.com/go-chi/httprate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 12:27:29 +00:00
dependabot[bot] ceb0ec43ad chore: bump google.golang.org/grpc from 1.59.0 to 1.60.1 (#11444)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.59.0 to 1.60.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.59.0...v1.60.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 12:18:00 +00:00
Muhammad Atif Ali a2f86e5e5e chore(dogfood): install corepack (#11459) 2024-01-06 03:11:16 +03:00
Muhammad Atif Ali 965b1e69e2 ci: add variable to template push in dogfood.yaml (#11458) 2024-01-05 21:35:49 +00:00
Garrett Delfosse b21da38bea chore: deprecate template create command in favor of template push (#11390) 2024-01-05 21:04:14 +00:00
Garrett Delfosse 3d54bc06f6 feat: display current version on coder list (#11450)
* feat: display current version on coder list

* fix make gen

* update golden
2024-01-05 15:33:08 -05:00
Muhammad Atif Ali 31f7b39513 chore(dogfood): update dogfood template to use artifactory (#11452)
* chore(dogfood): update to use artifactory

* Update main.tf
2024-01-05 23:25:51 +03:00
Steven Masley da7859c445 chore: change language on autostop (#11454)
* chore: change language on autostop
2024-01-05 11:40:25 -06:00
Bruno Quaresma c428395d71 feat(site): move history into sidebar (#11413) 2024-01-05 13:32:05 -03:00
Steven Masley f0132b543d fix: fix workspace proxy command app link href (#11423)
* fix: workspace proxy command app link href
2024-01-05 10:27:06 -06:00
dependabot[bot] 46b90ce898 chore: bump github.com/golang-migrate/migrate/v4 from 4.16.0 to 4.17.0 (#11446)
Bumps [github.com/golang-migrate/migrate/v4](https://github.com/golang-migrate/migrate) from 4.16.0 to 4.17.0.
- [Release notes](https://github.com/golang-migrate/migrate/releases)
- [Changelog](https://github.com/golang-migrate/migrate/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang-migrate/migrate/compare/v4.16.0...v4.17.0)

---
updated-dependencies:
- dependency-name: github.com/golang-migrate/migrate/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-05 09:42:53 -06:00
dependabot[bot] f3efa0803b ci: bump the github-actions group with 3 updates (#11447)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-05 14:11:09 +00:00
dependabot[bot] 45e989a519 chore: bump golang.org/x/sync from 0.5.0 to 0.6.0 (#11445)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-05 17:09:11 +03:00
dependabot[bot] bf00e61f10 chore: bump github.com/jedib0t/go-pretty/v6 from 6.4.0 to 6.5.0 (#11442)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-05 16:51:19 +03:00
Muhammad Atif Ali 118ab7d4de ci: ungroup go dependencies (#11441) 2024-01-05 13:40:10 +00:00
Bruno Quaresma 9389c2b283 refactor(site): only show derp tags if they are true (#11439) 2024-01-05 09:45:34 -03:00
Cian Johnston 4d2fe2685a chore(coderd): extract api version validation to util package (#11407) 2024-01-05 10:22:07 +00:00
Spike Curtis 58873fa7e2 chore: remove unused context/cancel in tailnet Conn (#11399)
Spotted during code read; unused fields
2024-01-05 08:15:42 +04:00
Spike Curtis 64638b381d feat: promote PG Coordinator out of experimental (#11398)
Promotes PG Coordinator out of experimental to GA
2024-01-05 08:03:36 +04:00
Eric Paulsen e816dc0e60 fix: gcp federation guide formatting (#11432) 2024-01-05 03:31:05 +00:00
Eric Paulsen 138d31621f docs: add guide for Google to AWS federation (#11429)
* feat: add docs for Google to AWS federation

* make: fmt
2024-01-04 20:13:29 -05:00
Steven Masley dd05a6b13a chore: mockgen archived, moved to new location (#11415)
* chore: mockgen archived, moved to new location
2024-01-04 18:35:56 -06:00
dependabot[bot] bb3510631b chore: bump the offlinedocs group in /offlinedocs with 1 update (#11428)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-05 02:03:26 +03:00
Steven Masley c6366e5b73 chore: prevent nil derefs in non-critical paths (#11411)
* chore: prevent nil derefs in non-critical paths

---------

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-01-04 14:51:48 -06:00
Steven Masley 85ff030ab4 chore: update LastConnectedReplicaID in dbmem (#11412) 2024-01-04 19:18:54 +00:00
Cian Johnston 4f433e7f3d ci: broaden scope of needs.changes.db (#11386)
* Broadens scope of needs.changes.db to include anything under the path coderd/database.
* Removes dependency of test-go-pg on sqlc-vet.
2024-01-04 16:24:54 +00:00
Garrett Delfosse 5981abd689 fix: handle unescaped userinfo in postgres url (#11396)
* fix: handle unescaped userinfo in postgres url

* add tests

* fix tests
2024-01-04 08:46:00 -05:00
Muhammad Atif Ali f0db302df2 chore: add lxc logo (#11404) 2024-01-04 16:31:16 +03:00
Cian Johnston 4355894b2b fix(coderd/database): revert addition of v prefix to provisioner_daemons.api_version (#11403)
"Reverts" #11385 by adding an inverse migration.
2024-01-04 11:47:31 +00:00
Spike Curtis f9ebe8c719 fix: send end of logs when dbfake completes job (#11402) 2024-01-04 15:18:00 +04:00
Spike Curtis 48cd4c3a10 feat: promote single-tailnet out of experimental (#11366) 2024-01-04 09:27:36 +04:00
Kayla Washburn ffa7722c31 feat: select group avatars with the emoji picker (#11395) 2024-01-03 15:47:24 -07:00
Ben Potter ee2daedae0 chore: template update policies are GA (#11397) 2024-01-03 15:49:29 -06:00
Dean Sheather 06f519d7f1 docs: add template autostop requirement docs (#11235)
* chore: template autostop requirement docs

* fixup! chore: template autostop requirement docs

* fixes from feedback.

* fmt

---------

Co-authored-by: Ben <me@bpmct.net>
2024-01-03 14:25:25 -06:00
Kyle Carberry 30afe43f8a fix: create tempdir prior to cleanup (#11394)
See https://github.com/coder/coder/actions/runs/7399827933/job/20132407700

Seems like this happened because the test was being cleaned up
while the tempdir was being made.
2024-01-03 19:18:15 +00:00
Bruno Quaresma 4edd21ae9e fix(site): fix loading spinner on template version status badge (#11392) 2024-01-03 16:29:04 +00:00
Bruno Quaresma f5196c06e1 fix(site): fix insights picker and disable animation (#11391) 2024-01-03 16:25:01 +00:00
Cian Johnston f80a1cf3c8 fix(coderd/database): add missing v prefix to provisioner_daemons.api_version (#11385) 2024-01-03 14:11:02 +00:00
Cian Johnston 068e730046 chore(coderd/database/dbfake): fix pq test flake in TestStart_Starting (#11384) 2024-01-03 12:27:50 +00:00
Bruno Quaresma d74e7ca20f docs: update FE fetching data docs (#11376) 2024-01-03 12:27:33 +00:00
Spike Curtis 5d76210b0d fix: change coder start to be a no-op if workspace is started
Fixes #11380
2024-01-03 13:24:37 +04:00
Cian Johnston 1ef96022b0 feat(coderd): add provisioner build version and api_version on serve (#11369)
* assert provisioner daemon version and api_version in unit tests
* add build info in HTTP header, extract codersdk.BuildVersionHeader
* add api_version to codersdk.ProvisionerDaemon
* testutil.MustString -> testutil.MustRandString
2024-01-03 09:01:57 +00:00
Muhammad Atif Ali 9031b498ea ci: use depot.dev to build dogfood image (#11378) 2024-01-02 23:27:37 +03:00
Garrett Delfosse 227234ded5 fix: correct wording on logo url field (#11377) 2024-01-02 14:53:18 -05:00
Bruno Quaresma ac899be74c chore(site): move workspace topbar component (#11374) 2024-01-02 16:02:11 -03:00
Mathias Fredriksson df3c310379 feat(cli): add coder open vscode (#11191)
Fixes #7667
2024-01-02 20:46:18 +02:00
Bruno Quaresma 099be249a7 fix(site): fix external auth button loading state (#11373) 2024-01-02 18:30:05 +00:00
Kayla Washburn 6308a78365 chore: clean up light theme code (#11319) 2024-01-02 15:19:20 -03:00
Bruno Quaresma c37c0e7d1b refactor(site): simplify workspace topbar (#11370) 2024-01-02 15:17:42 -03:00
Bruno Quaresma 62a20e86fd chore(site): ignore deletion date on chromatic (#11372) 2024-01-02 18:17:24 +00:00
Bruno Quaresma a1341ee9ac fix(site): fix pill spinner size (#11368) 2024-01-02 15:05:20 -03:00
Bruno Quaresma 467a1a3e71 fix(site): fix workspace topbar back button (#11371) 2024-01-02 18:01:06 +00:00
Kayla Washburn a24c3b4dc7 chore: cleanup inline prop type definitions (#11317) 2024-01-02 10:39:00 -07:00
Bruno Quaresma cf17fabcc6 feat(site): refactor workspace header to be more slim (#11327) 2024-01-02 12:42:51 -03:00
Muhammad Atif Ali 608937c79c chore(site): update node to version 18.19.0 (#11344) 2024-01-02 12:41:24 +00:00
Bruno Quaresma 8717fdfc20 refactor(site): refactor pill component API (#11329)
Refactor the Pill API to make it easier to extend and reuse.
2024-01-02 09:28:51 -03:00
Spike Curtis c9b7d61769 chore: refactor agent connection updates (#11301)
Refactors the code that handles monitoring an agent websocket with pings and updating the connection times in the DB.

Consolidates v1 and v2 agent APIs under the same code for this.

One substantive change (not _just_ a refactor) is that I've made it so that we actually disconnect if the agent fails to respond to our pings, rather than the old behavior where we would update the database, but not actually tear down the websocket.
2024-01-02 16:04:37 +04:00
Spike Curtis 520c3a8ff7 fix: use TSMP for pings and checking reachability (#11306)
We're seeing some flaky tests related to agent connectivity - https://github.com/coder/coder/actions/runs/7286675441/job/19856270998

I'm pretty sure what happened in this one is that the client opened a connection while the wgengine was in the process of reconfiguring the wireguard device, so the fact that the peer became "active" as a result of traffic being sent was not noticed.

The test calls `AwaitReachable()` but this only tests the disco layer, so it doesn't wait for wireguard to come up.

I think we should be using TSMP for pinging and reachability, since this operates at the IP layer, and therefore requires that wireguard comes up before being successful.

This should also help with the problems we have seen where a TCP connection starts before wireguard is up and the initial round trip has to wait for the 5 second wireguard handshake retry.

fixes: #11294
2024-01-02 15:53:52 +04:00
Muhammad Atif Ali 58e40f6cd6 chore: update nfpm to v2.35.1 (#11310) 2024-01-02 10:27:46 +00:00
Spike Curtis 4071f1713b feat: add logging to agent stats and JetBrains tracking (#11364)
Adds logging so we can hope to diagnose #11363
2024-01-02 13:34:49 +04:00
dependabot[bot] 893a8ea583 chore: bump golang.org/x/tools from 0.15.0 to 0.16.1 (#11357)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 12:26:45 +03:00
dependabot[bot] a439507c6a ci: bump the github-actions group with 1 update (#11355)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 12:26:29 +03:00
Steven Masley 8f49f10134 chore: put overrides and renames in pkg context in sqlc.yaml (#11347)
* chore: Put overrides and renames in pkg context in sqlc.yaml

---------

Co-authored-by: Andrew Benton <andrewmbenton@gmail.com>
2024-01-02 08:56:38 +00:00
Spike Curtis 36636bb6a5 feat: add tailnet to agent RPC service (#11304)
Adds tailnet.DRPCService to the agent API

Supports #10531 but we still need to add version negotiation to the websocket endpoint
2024-01-02 10:10:20 +04:00
Spike Curtis 25f2abf9ab chore: remove tailnet from agent API and rename client API to tailnet (#11303)
Refactors our DRPC service definitions slightly.

In the previous version, I inserted the RPCs from the tailnet proto directly into the Agent service.  This makes things hard to deal with because DRPC then generates a new set of methods with new interfaces with the `DRPCAgent_` prefixed.  Since you can't have a single method that takes different argument types, we couldn't reuse the implementation of those RFCs without a lot of extra classes and pass-thru methods.

Instead, the "right" way to do it is to integrate at the DRPC layer.  So, we have two DRPC services available over the Agent websocket, and register them both on the DRPC `mux`.

Since the tailnet proto RPC service is now for both clients and agents, I renamed some things to clarify and shorten.

This PR also removes the `TailnetAPI` implementation from the `agentapi` package, and the next PR in the stack replaces it with the implementation from the `tailnet` package.
2024-01-02 10:02:45 +04:00
Spike Curtis 65290997c1 chore: disable failing metrics check until it can be fixed (#11361) 2024-01-02 05:39:48 +00:00
Spike Curtis f28f340c7b fix: test for expiry 3 months on Azure certs (#11362) 2024-01-02 09:30:36 +04:00
Spike Curtis d257f8163d feat: implement DERP streaming on tailnet Client API (#11302)
Implements DERPMap streaming from client API.

In a subsequent PR I plan to remove the implementation in coderd/agentapi in favor of the tailnet one
2024-01-02 08:07:57 +04:00
dependabot[bot] 055a160431 chore: bump the offlinedocs group in /offlinedocs with 1 update (#11358)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 05:49:33 +03:00
sharkymark 3582284977 chore: update images in appearance docs and correct inconsistencies (#11346)
* chore: update images in appearance docs and correct inconsistencies

* fix: spelling

---------

Co-authored-by: Eric <ericpaulsen@coder.com>
2023-12-28 15:06:54 -06:00
Myne 6fcc49f030 fix(examples/templates/nomad-docker): ignore NOMAD_NAMESPACE and NOMAD_REGION when Coder is running in nomad (#11341) 2023-12-28 10:24:18 +03:00
Muhammad Atif Ali e9437e2662 chore(site): update miscellaneous svg icons (#11343) 2023-12-27 23:05:03 +03:00
Muhammad Atif Ali e3a1bdb60d chore(dogfood): update nodejs installation method (#11339) 2023-12-26 16:53:41 +03:00
Muhammad Atif Ali 0ebd656cd1 refactor: refactor JFrog docs and template (#11336) 2023-12-25 07:26:34 +00:00
Muhammad Atif Ali 5a558b69c3 chore(examples/jfrog): always install the latest JFrog extension (#11335) 2023-12-24 13:59:04 +03:00
Muhammad Atif Ali b69ccab390 fix(docs): add missing scoped token resource to JFrog docs (#11334) 2023-12-24 13:30:52 +03:00
Muhammad Atif Ali ed3ecfc923 chore: build dogfood image on PRs and skip pushing to registry (#11311) 2023-12-24 11:43:38 +03:00
Muhammad Atif Ali efe8c67774 ci: fix close reason type for stale issues
The action was faking because we were incorrectly using `not planned` instead of `not_planned`.
2023-12-23 18:43:13 +03:00
Mathias Fredriksson be3889af07 test(site/e2e): catch missing agent defaults in fillResource (#11105) 2023-12-23 11:52:27 +00:00
Yonatan Arbel 8271cb01c0 docs: fix broken link to JFrog module (#11322) 2023-12-22 14:42:59 +03:00
Cian Johnston 19abde12fb chore(coderd): fix test flake with auditor (#11316) 2023-12-22 09:50:49 +00:00
Michael Smith 167c15238a fix: prevent UI from jumping around when selecting workspaces (#11321) 2023-12-21 22:36:42 +00:00
Ben Potter b3e3521274 docs: add v2.6.0 changelog (#11320)
* docs: add v2.6.0 changelog

* fmt
2023-12-21 22:33:13 +00:00
Kayla Washburn 029c92fede fix: fix name for external auth connections (#11318) 2023-12-21 15:27:16 -07:00
Kayla Washburn db71c0fa54 refactor: remove theme "color palettes" (#11314) 2023-12-21 14:45:54 -07:00
Asher 5cfa34b31e feat: add OAuth2 applications (#11197)
* Add database tables for OAuth2 applications

These are applications that will be able to use OAuth2 to get an API key
from Coder.

* Add endpoints for managing OAuth2 applications

These let you add, update, and remove OAuth2 applications.

* Add frontend for managing OAuth2 applications
2023-12-21 21:38:42 +00:00
Kayla Washburn e044d3b752 fix: add additional theme colors (#11313) 2023-12-21 12:59:39 -07:00
Jon Ayers 0b7d68dc3f chore: remove template_update_policies experiment (#11250) 2023-12-21 13:39:33 -06:00
Muhammad Atif Ali 5b071f4d94 feat(examples/templates): add GCP VM devcontainer template (#11246) 2023-12-21 13:01:10 +00:00
Spike Curtis 52b87a28b0 fix: stop printing warnings on external provisioner daemon command (#11309)
fixes #11307
2023-12-21 16:55:34 +04:00
Spike Curtis db9104c02e fix: avoid panic on nil connection (#11305)
Related to https://github.com/coder/coder/actions/runs/7286675441/job/19855871305

Fixes a panic if the listener returns an error, which can obfuscate the underlying problem and cause unrelated tests to be marked failed.
2023-12-21 14:26:11 +04:00
Steven Masley fe867d02e0 fix: correct perms for forbidden error in TemplateScheduleStore.Load (#11286)
* chore: TemplateScheduleStore.Load() throwing forbidden error
* fix: workspace agent scope to include template
2023-12-20 11:38:49 -06:00
Kira Pilot 20dff2aa5d added react query dev tools (#11293) 2023-12-20 10:08:51 -05:00
Ben Potter 19e4a86711 docs: add guidelines for debugging group sync (#11296)
* docs: add guidelines for debugging group sync

* fmt
2023-12-20 12:52:07 +00:00
Bruno Quaresma e2e56d7d4f refactor(site): move workspace schedule controls to its own component (#11281) 2023-12-20 08:46:18 -03:00
Cian Johnston bfc588955c ci: make test-go-pg depend on sqlc-vet (#11288) 2023-12-20 08:47:47 +00:00
Muhammad Atif Ali 3ffe7f55aa feat(examples/templates): add aws vm devcontainer template (#11248)
* feat(examples/templates): add aws vm devcontainer template

* Create README.md

* add code-server

* fix code-server

* `make fmt`

* Add files via upload

* Update README.md

* fix typo and persist workspace

* always land in the repo directory
2023-12-20 08:24:45 +03:00
Kayla Washburn 97f7a35a47 feat: add light theme (#11266) 2023-12-19 17:03:00 -07:00
Bruno Quaresma e0d34ca6f7 fix(site): fix error when loading workspaces with dormant (#11291) 2023-12-19 20:42:07 -03:00
Steven Masley 24080b121c feat: enable csrf token header (#11283)
* feat: enable csrf token header

* Exempt external auth requets
* ensure dev server bypasses CSRF
* external auth is just get requests
* Add some more routes
* Extra assurance nothing breaks
2023-12-19 15:42:05 -06:00
Steven Masley fbda21a9f2 feat: move moons experiment to ga (released) (#11285)
* feat: release moons experiment as ga
2023-12-19 14:40:22 -06:00
Steven Masley e8be092af0 chore: add sqlc push action on releases (#11171)
* add sqlc push action on releases
* Make sqlc push optional
2023-12-19 20:31:55 +00:00
Steven Masley c1451ca4da chore: implement yaml parsing for external auth configs (#11268)
* chore: yaml parsing for external auth configs
* Also unmarshal and check the output again
2023-12-19 18:09:45 +00:00
dependabot[bot] 016b3ef5a2 chore: bump golang.org/x/crypto from 0.15.0 to 0.17.0 (#11274)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 20:52:43 +03:00
Cian Johnston d2d7628522 fix(enterprise/cli): add CODER_PROVISIONER_DAEMON_LOG_* options (#11279)
- Extracts cli.BuildLogger to clilog package
- Updates existing usage of cli.BuildLogger and removes it
- Use clilog to initialize provisionerd logger
2023-12-19 16:49:50 +00:00
Bruno Quaresma 7c4fbe5bae refactor(site): make HelpTooltip easier to reuse and compose (#11242) 2023-12-19 10:43:23 -03:00
Spike Curtis f2606a78dd fix: avoid converting nil node
fixes: #11276
2023-12-19 13:38:15 +04:00
Stephen Kirby 83e1349c2c moved docker installation warning to install/docker (#11273) 2023-12-18 18:19:20 -06:00
MarkE 280d38d4b8 added UI as Dashboard synonym (#11271) 2023-12-18 17:13:07 -06:00
Kayla Washburn 3ab4800a18 chore: clean up lint (#11270) 2023-12-18 14:59:39 -07:00
Bruno Quaresma e84d89353f fix(site): fix template editor filetree navigation (#11260)
Close https://github.com/coder/coder/issues/11203
2023-12-18 14:21:24 -03:00
Cian Johnston ff61475239 fix(coderd/provisionerdserver): use s.timeNow (#11267) 2023-12-18 17:11:50 +00:00
Steven Masley c35b560c87 chore: fix flake, use time closer to actual test (#11240)
* chore: fix flake, use time closer to actual test

The tests were queued, and the autostart time was being set
to the time the table was created, not when the test was actually
being run. This diff was causing failures in CI
2023-12-18 10:55:46 -06:00
Cian Johnston 213b768785 feat(coderd): insert provisioner daemons (#11207)
* Adds UpdateProvisionerDaemonLastSeenAt
* Adds heartbeat to provisioner daemons
* Inserts provisioner daemons to database upon start
* Ensures TagOwner is an empty string and not nil
* Adds COALESCE() in idx_provisioner_daemons_name_owner_key
2023-12-18 16:44:52 +00:00
Steven Masley a6901ae2c5 chore: fix race in cron close behavior (TestAgent_WriteVSCodeConfigs) (#11243)
* chore: add unit test to excercise flake
* Implement a *fix for cron stop() before run()

This fix still has a race condition. I do not see a clean solution
without modifying the cron libary. The cron library uses a boolean
to indicate running, and that boolean needs to be set to "true"
before we call "Close()". Or "Close()" should prevent "Run()"
from doing anything.

In either case, this solves the issue for a niche unit test bug
in which the test finishes, calling Close(), before there was
an oppertunity to start the go routine. It probably isn't worth
a lot of time investment, and this fix will suffice
2023-12-18 09:26:40 -06:00
Jon Ayers 56cbd47082 chore: fix TestWorkspaceAutobuild/DormancyThresholdOK flake (#11251) 2023-12-18 09:23:06 -06:00
Muhammad Atif Ali 45e9d93d37 chore: remove unused input from deploy-pr workflow (#11259) 2023-12-18 17:32:53 +03:00
Muhammad Atif Ali 5647e87207 ci: drop chocolatey from ci (#11245) 2023-12-18 17:31:35 +03:00
Dean Sheather 307186325f fix: avoid db import in slim builds (#11258) 2023-12-19 00:09:22 +10:00
dependabot[bot] 28a0242c27 ci: bump the github-actions group with 4 updates (#11256)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 13:30:18 +00:00
Dean Sheather e46431078c feat: add AgentAPI using DRPC (#10811)
Co-authored-by: Spike Curtis <spike@coder.com>
2023-12-18 22:53:28 +10:00
Cian Johnston eb781751b8 ci: update flux to 2.2.1 (#11253) 2023-12-18 09:29:46 +00:00
Muhammad Atif Ali 838ab8de7e docs: fix a broken link (#11254) 2023-12-18 09:28:55 +00:00
Ben Potter 2e86b76fb8 docs: improve structure for example templates (#9842)
Co-authored-by: Kyle Carberry <kyle@carberry.com>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
2023-12-17 17:05:13 +03:00
Steven Masley 3f6096b0d7 chore: unit test to enforce authorized queries match args (#11211)
* chore: unit test to enforce authorized queries match args
* Also check querycontext arguments
2023-12-15 20:31:07 +00:00
Garrett Delfosse 7924bb2a56 feat!: move workspace renames behind flag, disable by default (#11189) 2023-12-15 13:38:47 -05:00
Steven Masley e63de9a259 chore: enforcement of dbauthz tests was broken (#11218)
* chore: enforcement of dbauthz tests was broken

Implemented missing tests to catch back up

---------

Co-authored-by: Cian Johnston <cian@coder.com>
2023-12-15 18:30:21 +00:00
Stephen Kirby 0801760956 docs: add guides section (#11199)
* setup manifest

* added okta guide from steven M

* improved index by adding children

* changed icon to notes.svg

* added meta guide, fixed profile photo fmt
2023-12-15 11:10:41 -06:00
Ravindra Shinde a495952349 Upgrade code-server version to 4.19.1 (#11233) 2023-12-15 14:21:07 +00:00
Marcin Tojek 58c2ce17da refactor(cli): load template variables (#11234) 2023-12-15 14:55:24 +01:00
Cian Johnston fa91992976 ci: add audit docs gen dependency on db gen (#11231)
Audit docs gen depends on queries.sql.go so adding an explicit dependency
2023-12-15 11:49:19 +00:00
Marcin Tojek 89d8a293f0 fix: tar: do not archive .tfvars (#11208) 2023-12-15 11:15:12 +01:00
Spike Curtis 211e59bf65 feat: add tailnet v2 API support to coordinate endpoint (#11228)
closes #10532

Adds v2 support to the /coordinate endpoint via a query parameter.

v1 already has test cases, and we haven't implemented v2 at the client yet, so the only new test case is an unsupported version.
2023-12-15 14:10:24 +04:00
Cian Johnston a41cbb0f03 chore(dogfood): align Terraform version to that of dockerfile.base (#11227) 2023-12-15 10:02:59 +00:00
Dean Sheather 1e49190e12 feat: add server flag to disable user custom quiet hours (#11124) 2023-12-15 19:33:51 +10:00
Spike Curtis a58e4febb9 feat: add tailnet v2 Service and Client (#11225)
Part of #10532

Adds a tailnet ClientService that accepts a net.Conn and serves v1 or v2 of the tailnet API.

Also adds a DRPCService that implements the DRPC interface for the v2 API.  This component is within the ClientService, but needs to be reusable and exported so that we can also embed it in the Agent API.

Finally, includes a NewDRPCClient function that takes a net.Conn and runs dRPC in yamux over it on the client side.
2023-12-15 12:48:39 +04:00
Spike Curtis 9a4e1100fa chore: move drpc transport tools to codersdk/drpc (#11224)
Part of #10532

DRPC transport over yamux and in-mem pipes was previously only used on the provisioner APIs, but now will also be used in tailnet.  Moved to subpackage of codersdk to avoid import loops.
2023-12-15 12:41:39 +04:00
Dean Sheather b36071c6bb feat: allow templates to specify max_ttl or autostop_requirement (#10920) 2023-12-15 18:27:56 +10:00
Spike Curtis 30f032d282 feat: add tailnet ValidateVersion (#11223)
Part of #10532

Adds a method to validate a requested version of the tailnet API
2023-12-15 11:49:30 +04:00
Spike Curtis ad3fed72bc chore: rename Coordinator to CoordinatorV1 (#11222)
Renames the tailnet.Coordinator to represent both v1 and v2 APIs, so that we can use this interface for the main atomic pointer.

Part of #10532
2023-12-15 11:38:12 +04:00
Spike Curtis 545cb9a7cc fix: wait for coordinator in Test_agentIsLegacy (#11214)
Fixes flake https://github.com/coder/coder/runs/19639217635

AGPL coordinator used to process node updates for single_tailnet synchronously, but it's been refactored to process async, so in this test we need to wait for it to be processed.
2023-12-15 07:21:18 +04:00
Ben Potter e6e65fdc64 docs: add v2.5.1 changelog (#11220)
* docs: add v2.5.1 changelog

* fix typo
2023-12-14 17:35:36 -06:00
Colin Adler 4672700ef6 chore: add additional fields to license telemetry (#11173)
This sends the email the license was issued to, and whether or not it's a trial in the telemetry payload. It's a bit janky since the license parsing is all enterprise licensed.
2023-12-14 15:52:52 -06:00
Jon Ayers 06394a5b8c Revert "fix: prevent data race when mutating tags (#11200)" (#11216)
This reverts commit 82f7b0cef4.
2023-12-14 12:37:55 -06:00
Kayla Washburn 81ed112cd3 fix: fix auto theme (#11215) 2023-12-14 11:31:42 -07:00
Spike Curtis fad457420b fix: copy StringMap on insert and query in dbmem (#11206)
Addresses the issue in #11185 for the StringMap datatype.

There are other slice data types in our database package that also need to be fixed, but that'll be a different PR
2023-12-14 22:23:29 +04:00
Bruno Quaresma 32c93a887e fix(site): fix initial body background color 2023-12-14 18:15:25 +00:00
Bruno Quaresma 43411d20ba fix(site): fix pending color on dark blue theme (#11212) 2023-12-14 15:08:53 -03:00
Kayla Washburn 133dc66143 feat: add a theme picker (#11140) 2023-12-14 10:38:44 -07:00
Bruno Quaresma 0cd4842d18 fix(site): fix pending indicator color (#11209) 2023-12-14 11:30:40 -03:00
Cian Johnston df7ed18e1b chore(coderd/autobuild): wait for active template version and inactive template version (#11210) 2023-12-14 13:58:57 +00:00
Cian Johnston 5b0e6bfa2a feat(coderd/database): add api_version to provisioner_daemons table (#11204)
Adds column api_version to the provisioner_daemons table.
This is distinct from the coderd version, and is used to handle breaking changes in the provisioner daemon API.
2023-12-14 12:52:41 +00:00
Muhammad Atif Ali b779655f01 ci: fix syntax for ipv6 address in fly.io wsproxies (#11205) 2023-12-14 15:26:43 +03:00
Jon Ayers 82f7b0cef4 fix: prevent data race when mutating tags (#11200) 2023-12-14 08:56:59 +00:00
Colin Adler eb81fcf1e1 fix: lower amount of cached timezones for deployment daus (#11196)
Updates https://github.com/coder/customers/issues/384

This should help alleviate some pressure, but doesn't really fix the
root cause. See above issue for more details.
2023-12-13 16:50:29 -06:00
Stephen Kirby a3432b4265 docs: add faqs from sharkymark (#11168)
* added sharkymark FAQs page

* make fmt

* fixed typos for link

* changed FAQs icon to (i)

* satisfied review

* make fmt

* added docs links for coder_app, CODER_ACCESS_URL

* removed mentions of mark

* fixed some minor code formatting issues

* fixed numbered bullets rendering, make fmt
2023-12-13 15:56:11 -06:00
Muhammad Atif Ali c3eb68a585 Update CODER_WILDCARD_ACCESS_URL in fly-wsproxies configuration files (#11195) 2023-12-13 21:43:53 +00:00
Muhammad Atif Ali d82ed008f2 ci: revert fly proxies to shared cpu type (#11194) 2023-12-13 21:15:56 +00:00
Muhammad Atif Ali 3924b294fb ci: bump memory to 1024 MB for fly.io proxies (#11193)
* Update paris-coder.toml

* Update sao-paulo-coder.toml

* Update sydney-coder.toml
2023-12-13 20:03:46 +00:00
Muhammad Atif Ali 12f728189c ci: add wildcard support to fly.io wsproxies (#11188)
* ci: add wildcard support to fly.io wsproxies

* Update sao-paulo-coder.toml

* Update sydney-coder.toml

* Update paris-coder.toml

* Apply suggestions from code review

Co-authored-by: Dean Sheather <dean@deansheather.com>

* Update .github/fly-wsproxies/sao-paulo-coder.toml

Co-authored-by: Dean Sheather <dean@deansheather.com>

* Update sao-paulo-coder.toml

* Update sydney-coder.toml

---------

Co-authored-by: Dean Sheather <dean@deansheather.com>
2023-12-13 22:44:04 +03:00
Steven Masley b7bdb17460 feat: add metrics to workspace agent scripts (#11132)
* push startup script metrics to agent
2023-12-13 11:45:43 -06:00
Steven Masley 41ed581460 chore: include build version header on subdomain apps (#11172)
Idk why this was not the case before, this is very helpful to have
2023-12-13 11:45:27 -06:00
Marcin Tojek fd43985e94 fix: nix: switch to go1.21.5 (#11183) 2023-12-13 14:41:18 +01:00
Muhammad Atif Ali c60c75c833 ci: do not rebuild but use artifacts from the build job (#11180) 2023-12-13 12:46:22 +00:00
Marcin Tojek f2a91157a9 fix: update nix to include sqlc v1.24.0 (#11182) 2023-12-13 13:35:02 +01:00
Cian Johnston 4f7ae6461b feat(coderd/database): add UpsertProvisionerDaemons query (#11178)
Co-authored-by: Marcin Tojek <marcin@coder.com>
2023-12-13 12:31:40 +00:00
Marcin Tojek ef4d1b68e1 test: insights metrics: verify plugin usage (#11156) 2023-12-13 10:46:52 +01:00
Dean Sheather 8b8a763ca9 chore: use flux 2.2.0 (#11174) 2023-12-13 09:26:48 +00:00
Spike Curtis bf3b35b1e2 fix: stop logging context Canceled as error (#11177)
fixes #11166 and a related log that could have the same problem
2023-12-13 13:08:30 +04:00
Spike Curtis 43ba3146a9 feat: add test case for BlockDirect + listening ports (#11152)
Adds a test case for #10391 with single tailnet out of experimental
2023-12-13 12:28:09 +04:00
Steven Masley 6800fc8477 chore: bump go (->v1.21.5) and sqlc (->v1.24.0) to new versions (#11170) 2023-12-12 18:50:23 -06:00
Steven Masley 6b4d908e7e chore: makefile set sqlc-vet to .Phony (#11169) 2023-12-12 22:55:13 +00:00
Steven Masley e52d848d05 chore: validate queries using sqlc-vet in github actions (#11163) 2023-12-12 15:53:26 -06:00
Steven Masley dba0dfa859 chore: correct 500 -> 404 on workspace agent mw (#11129)
* chore: correct 500 -> 404
2023-12-12 15:14:32 -06:00
Steven Masley 0181e036f6 chore: remove unused query failing to prepare (#11167) 2023-12-12 15:02:15 -06:00
Ammar Bandukwala 19c0cfdabf chore(provisionersdk): add test for not following symlinks (#11165) 2023-12-12 14:44:50 -06:00
Cian Johnston 2471f3b9a8 ci: set flux version to 2.1.2 (#11164) 2023-12-12 20:17:01 +00:00
Kayla Washburn f67c5cf72b fix: only show orphan option while deleting failed workspaces (#11161) 2023-12-12 11:18:04 -07:00
Kayla Washburn 689da5b7c1 feat(site): improve bulk delete flow (#11093) 2023-12-12 10:14:28 -07:00
sempie 007b2b8db0 docs: add text to docs mentioning appearance settings for oidc sign-on page (#11159)
* add text to docs mentioning appearance settings for oidc sign-on page
2023-12-12 11:33:44 -05:00
Ben Potter cab8ffa54a docs: add v2.5.0 changelog (#11139)
* docs: add v2.5.0 changelog

* fix typos

* Apply suggestions from code review

* changes from feedback

* more fixes

* Update docs/changelogs/v2.5.0.md

Co-authored-by: Muhammad Atif Ali <atif@coder.com>

* Update docs/changelogs/v2.5.0.md

* fmt

* updates

---------

Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2023-12-12 09:52:11 -06:00
Mathias Fredriksson b32a0a9af6 fix(go.mod): switch to sftp fork to fix file upload permissions (#11157)
Fixes #6685
Upstream https://github.com/pkg/sftp/pull/567
Related https://github.com/mutagen-io/mutagen/issues/459
2023-12-12 17:42:03 +02:00
Jon Ayers 41dbe7de4e fix: use correct permission when determining orphan deletion privileges (#11143) 2023-12-12 08:24:04 -06:00
Cian Johnston 8afbc8f7f5 chore(site): update test entities (#11155) 2023-12-12 13:03:37 +00:00
Spike Curtis edeb9bb42a fix: appease linter on darwin (#11154)
Fixing up some linting errors that show up on Darwin, but not in CI.
2023-12-12 17:02:28 +04:00
Cian Johnston 2883cad6ad fix(coderd/autobuild): wait for template version job in TestExecutorInactiveWorkspace (#11150) 2023-12-12 12:43:02 +00:00
Muhammad Atif Ali dde21cebcc chore(dogfood): use go 1.20.11 to match CI (#11153) 2023-12-12 11:45:28 +00:00
Cian Johnston b02796655e fix(coderd/database): remove column updated_at from provisioner_daemons table (#11108) 2023-12-12 11:19:28 +00:00
Cian Johnston 197cd935cf chore(Makefile): use linter version from dogfood Dockerfile (#11147)
* chore(Makefile): use golangci-lint version from dogfood Dockerfile

* chore(dogfood/Dockerfile): update golangci-lint to latest version

* chore(coderd): address linter complaints
2023-12-12 10:02:32 +00:00
Cian Johnston d07fa9c62f ci: offlinedocs: install protoc (#11148) 2023-12-12 10:00:16 +00:00
Jon Ayers 45c07317c0 docs: add documentation for template update policies (#11145) 2023-12-11 19:05:25 -06:00
Michael Smith 3ce7b2ebe6 fix: remove URL desyncs when trying to search users table (#11144)
* fix: remove URL search params desync

* refactor: clean up payload definition for clarity
2023-12-12 00:45:03 +00:00
Jon Ayers ba3b835339 fix: prevent editing build parameters if template requires active version (#11117)
Co-authored-by: McKayla Washburn <mckayla@hey.com>
2023-12-11 15:54:16 -07:00
Garrett Delfosse b7ea330aea fix: ensure we are talking to coder on first user check (#11130) 2023-12-11 14:27:32 -05:00
Stephen Kirby e37bbe6208 fixed small typo in docs/admin/configure (#11135) 2023-12-11 12:49:28 -06:00
Kayla Washburn 6775a86785 chore: make "users"."avatar_url" NOT NULL (#11112) 2023-12-11 10:09:51 -07:00
Mathias Fredriksson 3e5d292135 feat: add support for coder_env (#11102)
Fixes #10166
2023-12-11 16:10:18 +02:00
Muhammad Atif Ali 4612c28d99 ci: update tj-actions/branch-names action in dogfood.yaml (#11120) 2023-12-11 16:49:53 +03:00
dependabot[bot] 486d1fb697 chore: bump alpine from 3.18.5 to 3.19.0 in /scripts (#11126)
Bumps alpine from 3.18.5 to 3.19.0.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 23:24:16 +10:00
dependabot[bot] 6823194683 ci: bump the github-actions group with 7 updates (#11123)
Bumps the github-actions group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [crate-ci/typos](https://github.com/crate-ci/typos) | `1.16.23` | `1.16.24` |
| [google-github-actions/setup-gcloud](https://github.com/google-github-actions/setup-gcloud) | `1` | `2` |
| [google-github-actions/get-gke-credentials](https://github.com/google-github-actions/get-gke-credentials) | `1` | `2` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `2` | `3` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `4` | `5` |
| [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) | `0.14.0` | `0.16.0` |
| [actions/stale](https://github.com/actions/stale) | `8.0.0` | `9.0.0` |


Updates `crate-ci/typos` from 1.16.23 to 1.16.24
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.16.23...v1.16.24)

Updates `google-github-actions/setup-gcloud` from 1 to 2
- [Release notes](https://github.com/google-github-actions/setup-gcloud/releases)
- [Changelog](https://github.com/google-github-actions/setup-gcloud/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/setup-gcloud/compare/v1...v2)

Updates `google-github-actions/get-gke-credentials` from 1 to 2
- [Release notes](https://github.com/google-github-actions/get-gke-credentials/releases)
- [Changelog](https://github.com/google-github-actions/get-gke-credentials/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/get-gke-credentials/compare/v1...v2)

Updates `docker/setup-buildx-action` from 2 to 3
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

Updates `docker/build-push-action` from 4 to 5
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

Updates `aquasecurity/trivy-action` from 0.14.0 to 0.16.0
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/2b6a709cf9c4025c5438138008beaddbb02086f0...91713af97dc80187565512baba96e4364e983601)

Updates `actions/stale` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8.0.0...v9.0.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: google-github-actions/setup-gcloud
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: google-github-actions/get-gke-credentials
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 23:21:07 +10:00
Muhammad Atif Ali 2c7ad1c094 ci: ungroup Dockerfile dependabot changes (#11125) 2023-12-11 16:16:28 +03:00
Spike Curtis 8d9157dc35 fix: use provisionerd context when failing job on canceled acquire (#11118)
Spotted during code read. We need to use the provisionerd auth context when failing a job due to a lost provisioner daemon.
2023-12-11 14:52:44 +04:00
Spike Curtis 50575e1a9a fix: use fake local network for port-forward tests (#11119)
Fixes #10979

Testing code that listens on a specific port has created a long battle with flakes.  Previous attempts to deal with this include opening a listener on a port chosen by the OS, then closing the listener, noting the port and starting the test with that port.
This still flakes, notably in macOS which has a proclivity to reuse ports quickly.

Instead of fighting with the chaos that is an OS networking stack, this PR fakes the host networking in tests.

I've taken a small step here, only faking out the Listen() calls that port-forward makes, but I think over time we should be transitioning all networking the CLI does to an abstract interface so we can fake it.  This allows us to run in parallel without flakes and
presents an opportunity to test error paths as well.
2023-12-11 14:51:56 +04:00
Jon Ayers 37f6b38d53 fix: return 403 when rebuilding workspace with require_active_version (#11114) 2023-12-08 23:03:46 -06:00
Bruno Quaresma 8488afa8df chore(site): enable react-query cache (#11113) 2023-12-08 23:58:29 +00:00
Kayla Washburn d8e95001e8 chore: add theme_preference column to users table (#11069) 2023-12-08 21:59:53 +00:00
Kayla Washburn ebd6c1b573 feat(site): bring back dark blue (#11071) 2023-12-08 14:38:35 -07:00
Garrett Delfosse 716759aacf fix: provide helpful error when no login url specified (#11110) 2023-12-08 14:44:40 -05:00
Eric Paulsen 167c759149 docs: add license and template insights prom metrics (#11109)
* docs: add license and template insights prom metrics

* add: coderd_insights_applications_usage_seconds
2023-12-08 14:17:14 -05:00
Garrett Delfosse d8467c11ad fix: handle no memory limit in coder stat mem (#11107) 2023-12-08 12:46:53 -05:00
Spike Curtis 6d66cb246d feat: display 'Deprecated' warning for agents using old API version (#11058)
Fixes #10340
2023-12-08 20:20:44 +04:00
Steven Masley 78517cab52 feat: add group allowlist for oidc (#11070)
* feat: group allow list in OIDC settings
2023-12-08 10:14:19 -06:00
Steven Masley cb89bc1729 feat: restart stopped workspaces on ssh command (#11050)
* feat: autostart workspaces on ssh & port forward

This is opt out by default. VScode ssh does not have this behavior
2023-12-08 10:01:13 -06:00
Bruno Quaresma 1f7c63cf1b fix(site): hide ws proxy on menu when disabled (#11101) 2023-12-08 11:47:09 -03:00
Bruno Quaresma 9d8578e0e3 refactor(site): apply minor naming improvements (#11080)
Minor naming and logic improvements to improve readability
2023-12-08 11:46:18 -03:00
Bruno Quaresma 2c7394bb3d refactor(site): change a few names related to workspace actions (#11079) 2023-12-08 13:41:58 +00:00
Cian Johnston 2b19a2369f chore(coderd): move provisionerd tags to provisionersdk (#11100) 2023-12-08 12:10:25 +00:00
Cian Johnston 4ca4736411 ci: reconcile provisionerd as well (#11085) 2023-12-08 09:55:43 +00:00
Marcin Tojek 918a82436e fix: insights: remove time-dependent tests (#11099) 2023-12-08 09:51:18 +00:00
Jon Ayers 02696f2df9 chore: fix flake in TestExecutorAutostopTemplateDisabled (#11096) 2023-12-08 09:02:54 +00:00
Spike Curtis b4ca1d6579 feat: include server agent API version in buildinfo (#11057)
First part of #10340 -- we need this version to compare with agents to tell if they are on a deprecated Agent API version
2023-12-08 12:50:25 +04:00
Muhammad Atif Ali f0969f99ad revert: "chore(dogfood): remove agent_name from jetbrains-ide module" (#11095) 2023-12-08 01:14:37 +00:00
Jon Ayers e73a202aed feat: show dormant workspaces by default (#11053) 2023-12-07 18:09:35 -06:00
Muhammad Atif Ali be31b2e4d7 chore(dogfood): remove agent_name from jetbrains-ide module
This is no more needed.
Depends on https://github.com/coder/modules/pull/99
2023-12-08 02:34:21 +03:00
Jon Ayers ce49a55f56 chore: update build_reason 'autolock' -> 'dormancy' (#11074) 2023-12-07 17:11:57 -06:00
Steven Masley 8221544514 chore: check if process is nil (#11090)
* chore: check if process is nil

We check if process is nil in the ports_supported file.
Just matching that defensive check, not sure if it can be nil.
2023-12-07 22:23:42 +00:00
Asher dbbf8acc26 fix: track JetBrains connections (#10968)
* feat: implement jetbrains agentssh tracking

Based on tcp forwarding instead of ssh connections

* Add JetBrains tracking to bottom bar
2023-12-07 12:15:54 -09:00
Cian Johnston 51687c74c8 fix(coderd/healthcheck): do not return null regions in RegionsResponse (#11088) 2023-12-07 21:10:12 +00:00
Garrett Delfosse 228cbec99b fix: stop updating agent stats from deleted workspaces (#11026)
Co-authored-by: Steven Masley <stevenmasley@gmail.com>
2023-12-07 13:55:29 -05:00
Cian Johnston 1e349f0d50 feat(cli): allow specifying name of provisioner daemon (#11077)
- Adds a --name argument to provisionerd start
- Plumbs through name to integrated and external provisioners
- Defaults to hostname if not specified for external, hostname-N for integrated
- Adds cliutil.Hostname
2023-12-07 16:59:13 +00:00
Garrett Delfosse 8aea6040c8 fix: use unique workspace owners over unique users (#11044) 2023-12-07 10:53:15 -05:00
Kira Pilot 091fdd6761 fix: redirect unauthorized git users to login screen (#10995)
* fix: redirect to login screen if unauthorized git user

* consolidated language

* fix redirect
2023-12-07 09:19:31 -05:00
Barton Ip 5d2e87f1a7 docs: add warning about Sysbox before installation (#10619)
* Add warning about Sysbox before installation

* Formatting tings
2023-12-07 16:58:50 +03:00
Spike Curtis b34ecf1e9e fix: fix deadlock of mappingQuery on context canceled
Fixes #11078

replace bare channel send with SendCtx so that we properly shut down when context is canceled.
2023-12-07 17:19:18 +04:00
Marcin Tojek 941e3873a8 fix: implement fake DeleteOldWorkspaceAgentStats (#11076) 2023-12-07 14:08:16 +01:00
Bruno Quaresma c0d68a4c2c fix(site): fix clickable props on the workspace table row (#11072) 2023-12-06 19:50:39 +00:00
dependabot[bot] 567ecca61b chore: bump vite from 4.5.0 to 4.5.1 in /site (#11052)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.1/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 22:40:24 +03:00
Bruno Quaresma 667ee41165 refactor(site): improve minor queries and visuals on external auth (#11066) 2023-12-06 16:17:31 -03:00
Bruno Quaresma 8a6bfc9d28 feat(site): do not show health warning when the warning is dismissed (#11068) 2023-12-06 16:06:58 -03:00
Steven Masley 2947b827fb chore: use httpError to allow better error elevation (#11065) 2023-12-06 10:27:40 -06:00
Bruno Quaresma dd01bde9b6 fix(site): fix template editor route (#11063) 2023-12-06 15:59:00 +00:00
Bruno Quaresma 44f9613bf2 feat(site): dismiss health section warnings (#11059) 2023-12-06 12:50:35 -03:00
Bruno Quaresma 2bc11d2e63 fix(site): fetch health data only if has permissions (#11062) 2023-12-06 15:47:58 +00:00
Bruno Quaresma 43488b44ce chore(site): refactor pagination text (#11061) 2023-12-06 12:19:29 -03:00
Steven Masley b376b2cd13 feat: add user/settings page for managing external auth (#10945)
Also add support for unlinking on the coder side to allow reflow.
2023-12-06 08:41:45 -06:00
Marcin Tojek f6891bc465 fix: implement fake DeleteOldWorkspaceAgentLogs (#11042) 2023-12-06 14:31:43 +01:00
Bruno Quaresma 088fd0b904 chore(site): ignore updated at on chromatic (#11060) 2023-12-06 10:19:33 -03:00
Spike Curtis 2c86d0bed0 feat: support v2 Tailnet API in AGPL coordinator (#11010)
Fixes #10529
2023-12-06 15:04:28 +04:00
Cian Johnston 38ed816207 fix(coderd/debug): fix caching issue with dismissed sections (#11051) 2023-12-06 08:38:03 +00:00
Kira Pilot 53453c06a1 fix: display app templates correctly in build preview (#10994)
* fix: appropriately display display_app apps in template build preview

* added display apps to build preview

* added test, consolidated names

* handling empty state
2023-12-05 16:01:40 -05:00
Steven Masley 81a3b36884 feat: add endpoints to list all authed external apps (#10944)
* feat: add endpoints to list all authed external apps

Listing the apps allows users to auth to external apps without going through the create workspace flow.
2023-12-05 14:03:44 -06:00
Cian Johnston feaa9894a4 fix(site/src/api/typesGenerated): generate HealthSection enums (#11049)
Relates to #8971

- Introduces a codersdk.HealthSection enum type
- Refactors existing references using strings to use new HealthSection type
2023-12-05 20:00:27 +00:00
Cian Johnston f66e802fae fix(coderd/debug): putDeploymentHealthSettings: use 204 instead of 304 if not modified (#11048) 2023-12-05 19:06:56 +00:00
Bruno Quaresma 876d448d69 fix(site): fix padding for loader (#11046) 2023-12-05 17:18:31 +00:00
Eric Paulsen 3dcbf63cbe add: document suspended users not consuming seat (#11045) 2023-12-05 12:05:05 -05:00
Bruno Quaresma 0f47b58bfb feat(site): refactor health pages (#11025) 2023-12-05 13:58:51 -03:00
Cian Johnston 2e4e0b2d2c fix(scripts/apitypings): force health.Message and health.Severity to correct types (#11043)
* Force typegen types for some fields of derp health report
* Explicitly allocate slices for RegionReport.{Errors,Warnings} to avoid nulls in API response
2023-12-05 16:31:48 +00:00
Cian Johnston a235644046 fix(codersdk): make codersdk.ProvisionerDaemon.UpdatedAt a codersdk.NullTime (#11037) 2023-12-05 15:40:45 +00:00
Michael Smith fab343a2e9 fix: increase default staleTime for paginated data (#11041)
* fix: update default staleTime for paginated data

* fix: swap cacheTime for staleTime in app-wide query client

* fix: revert cacheTime change

* fix: update debug limit

* fix: apply staleTime to prefetches

* refactor: cleanup code
2023-12-05 14:41:06 +00:00
Muhammad Atif Ali f0b4badf74 ci: add arm64 and amd64 portable binaries to winget (#11030)
* ci: add arm64 and amd64 portable binaries to winget 

This PR updates `release.yaml` workflow to automate updates for `arm64` and `x64` zip installers to winget. This has recently been merged into [winget](https://github.com/microsoft/winget-pkgs/pull/129175).

Thanks to @mdanish-kh for the upstream PR.

* fixup!

* remove extra `--urls` flags

* remove architecture override.

`wingetcreate` does not need architecture override as it now supports parsing the URL for `amd64` and correctly marking it as x64 architecture. 

Reference: 
1. https://github.com/microsoft/winget-create/blob/08baf0e61e62dabcb2487397984fc69fad6a7499/src/WingetCreateCore/Common/PackageParser.cs#L594C56-L594C61
2. PR: https://github.com/microsoft/winget-create/pull/445
2. This has been available since version https://github.com/microsoft/winget-create/releases/tag/v1.5.3.0

* fixup!

* Update release.yaml
2023-12-05 17:06:39 +03:00
Cian Johnston 5fad611020 feat(coderd): add last_seen_at and version to provisioner_daemons table (#11033)
Related to #10676

- Adds columns last_seen_at and version to provisioner_daemons table
- Adds the above to codersdk.ProvisionerDaemons struct
2023-12-05 13:54:38 +00:00
Michael Smith dd1f8331de fix: disable prefetches for audits table (#11040) 2023-12-05 08:49:11 -05:00
Cian Johnston 1b2ed5bc9b ci: add missing go tools to offlinedocs build step (#11034) 2023-12-05 12:03:29 +00:00
Mathias Fredriksson e300b036be feat(scaletest): add greedy agent test to runner (#10559) 2023-12-05 12:37:10 +02:00
Spike Curtis dca8125263 fix: update tailscale to include fix to prevent race (#11032)
fixes #10876
2023-12-05 14:30:19 +04:00
Dean Sheather 695f57f7ff fix: use header flags in wsproxy server (#10985) 2023-12-05 14:13:42 +04:00
Dean Sheather b07b40b346 chore: revert nix dogfood image (#11022)
The nix image isn't used because it doesn't work, and we haven't been
updating our "pre-nix" tag since the changes were made. Reverts back to
being a regular Dockerfile.
2023-12-05 09:02:57 +00:00
Cian Johnston d70f9ea26c chore(docs): apply async suggestions from #10915 (#10976) 2023-12-05 09:01:03 +00:00
Bruno Quaresma dff53d0787 fix(site): fix filter font size (#11028) 2023-12-04 18:17:43 -03:00
Kayla Washburn 185400db11 refactor: remove usage of <Box> and sx (#10702) 2023-12-04 12:09:04 -07:00
Garrett Delfosse 1e6ea6133c fix: pass in time parameter to prevent flakes (#11023)
Co-authored-by: Dean Sheather <dean@deansheather.com>
2023-12-04 12:20:22 -05:00
Marcin Tojek a42b6c185d fix(site): e2e: use click instead of check (#11024) 2023-12-04 18:02:46 +01:00
dependabot[bot] b8e9262c51 chore: bump the scripts-docker group in /scripts with 1 update (#11020)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 10:38:58 -06:00
Garrett Delfosse ccd5e1a749 fix: use database for user creation to prevent flake (#10992) 2023-12-04 11:05:17 -05:00
Steven Masley 2f54f769be feat: allow IDP to return single string for roles/groups claim (#10993)
* feat: allow IDP to return single string instead of array for roles/groups claim

This is to support ADFS
2023-12-04 10:01:45 -06:00
dependabot[bot] 3883d7181d chore: bump the offlinedocs group in /offlinedocs with 6 updates (#11014)
* chore: bump the offlinedocs group in /offlinedocs with 6 updates

Bumps the offlinedocs group in /offlinedocs with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [fs-extra](https://github.com/jprichardson/node-fs-extra) | `11.1.1` | `11.2.0` |
| [react-markdown](https://github.com/remarkjs/react-markdown) | `8.0.3` | `9.0.1` |
| [rehype-raw](https://github.com/rehypejs/rehype-raw) | `6.1.1` | `7.0.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `18.18.1` | `18.19.2` |
| [eslint](https://github.com/eslint/eslint) | `8.53.0` | `8.55.0` |
| [typescript](https://github.com/Microsoft/TypeScript) | `5.1.6` | `5.3.2` |


Updates `fs-extra` from 11.1.1 to 11.2.0
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/11.1.1...11.2.0)

Updates `react-markdown` from 8.0.3 to 9.0.1
- [Release notes](https://github.com/remarkjs/react-markdown/releases)
- [Changelog](https://github.com/remarkjs/react-markdown/blob/main/changelog.md)
- [Commits](https://github.com/remarkjs/react-markdown/compare/8.0.3...9.0.1)

Updates `rehype-raw` from 6.1.1 to 7.0.0
- [Release notes](https://github.com/rehypejs/rehype-raw/releases)
- [Commits](https://github.com/rehypejs/rehype-raw/compare/6.1.1...7.0.0)

Updates `@types/node` from 18.18.1 to 18.19.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 8.53.0 to 8.55.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.53.0...v8.55.0)

Updates `typescript` from 5.1.6 to 5.3.2
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.1.6...v5.3.2)

---
updated-dependencies:
- dependency-name: fs-extra
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: offlinedocs
- dependency-name: react-markdown
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: offlinedocs
- dependency-name: rehype-raw
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: offlinedocs
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: offlinedocs
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: offlinedocs
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: offlinedocs
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: install react-gfm v4 and update type signatures

* fix: update link-nesting for a11y/hydration issue

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Parkreiner <michaelsmith@coder.com>
2023-12-04 10:11:01 -05:00
dependabot[bot] 2443a9f861 ci: bump the github-actions group with 2 updates (#11018)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 16:57:44 +03:00
sharkymark 676e215a91 chore: path app IDEs (#11007) 2023-12-04 11:22:22 +00:00
Mathias Fredriksson 70cede8f7a test(agent): improve TestAgent_Dial tests (#11013)
Refs #11008
2023-12-04 13:11:30 +02:00
Muhammad Atif Ali b212bd4ac5 chore: deploy workspace proxies on fly.io (#10983)
Co-authored-by: Dean Sheather <dean@deansheather.com>
2023-12-04 12:12:22 +03:00
Spike Curtis dbadae5a9c Revert "chore(helm): gitignore and rm helm chart tarballs from vcs (#10951)" (#11009)
This reverts commit 7f62085a02.
2023-12-04 06:59:06 +00:00
Spike Curtis 0536b58b48 fix: parse username/workspace correctly on coder state push --build (#10974)
Fixes the same issue as #10884 but for state push
2023-12-04 09:58:35 +04:00
Szabolcs Fruhwald baf3bf6b9c feat: add workspace_id, owner_name to agent manifest (#10199)
Co-authored-by: Kyle Carberry <kyle@carberry.com>
Co-authored-by: Atif Ali <atif@coder.com>
2023-12-04 00:41:54 +03:00
Michael Smith 28eca2e53f fix: create centralized PaginationContainer component (#10967)
* chore: add Pagination component, add new test, and update other pagination tests

* fix: add back temp spacing for WorkspacesPageView

* chore: update AuditPage to use Pagination

* chore: update UsersPage to use Pagination

* refactor: move parts of Pagination into WorkspacesPageView

* fix: handle empty states for pagination labels better

* docs: rewrite comment for clarity

* refactor: rename components/properties for clarity

* fix: rename component files for clarity

* chore: add story for PaginationContainer

* chore: rename story for clarity

* fix: handle undefined case better

* fix: update imports for PaginationContainer mocks

* fix: update story values for clarity

* fix: update scroll logic to go to the bottom instead of the top

* fix: update mock setup for test

* fix: update stories

* fix: remove scrolling functionality

* fix: remove deprecated property

* refactor: rename prop

* fix: remove debounce flake
2023-12-02 17:37:59 -05:00
Dean Sheather d9a169556a chore: run deploy job on regular runner 2023-12-02 10:08:33 -08:00
Colin Adler 6b3c4c00a2 fix: UpdateWorkspaceDormantDeletingAt interval out of range (#11000) 2023-12-02 11:47:08 -06:00
Colin Adler 49ed66c7ad chore: remove ALTER TYPE .. ADD VALUE from migration 65 (#10998)
Follow up of Follow up of https://github.com/coder/coder/pull/10966
2023-12-02 11:40:23 -06:00
Colin Adler cbcf7561e5 chore: remove ALTER TYPE .. ADD VALUE from migration 46 (#10997)
Follow up of https://github.com/coder/coder/pull/10966
2023-12-02 11:38:12 -06:00
Colin Adler 427572199e chore: remove ALTER TYPE .. ADD VALUE from migration 18 (#10996)
Follow up of https://github.com/coder/coder/pull/10966
2023-12-02 11:35:25 -06:00
Dean Sheather c82e878b50 chore: disable legacy dogfood deploy (#10999) 2023-12-03 02:20:19 +10:00
Colin Adler 8e684c8195 feat: run all migrations in a transaction (#10966)
Updates coder/customers#365

This PR updates our migration framework to run all migrations in a single transaction. This is the same behavior we had in v1 and ensures that failed migrations don't bring the whole deployment down. If a migration fails now, it will automatically be rolled back to the previous version, allowing the deployment to continue functioning.
2023-12-01 16:11:10 -06:00
Garrett Delfosse 60d0aa6930 fix: handle 404 on unknown top level routes (#10964) 2023-12-01 12:35:44 -05:00
Bruno Quaresma 2aa79369a2 refactor(site): improve health check page sidebar (#10960) 2023-12-01 12:43:51 -03:00
Cian Johnston 432925df31 ci: make offlinedocs required (#10980) 2023-12-01 14:37:47 +00:00
Mathias Fredriksson 6fe84025aa chore(Makefile): exclude .terraform directories (#10988) 2023-12-01 15:13:51 +02:00
Marcin Tojek 13b89f79df feat: purge old provisioner daemons (#10949) 2023-12-01 12:43:05 +00:00
Dean Sheather 153abd5003 chore: fix build job pt.3 (#10986) 2023-12-01 12:25:06 +00:00
Dean Sheather 122cbaa134 chore: fix build job (#10984) 2023-12-01 12:08:10 +00:00
Dean Sheather 15875a76ae chore: add new deploy job for new dogfood (#10852) 2023-12-01 03:16:49 -08:00
Cian Johnston 9ad96288b2 fix(helm/provisioner): run helm dependency update (#10982) 2023-12-01 10:30:00 +00:00
Cian Johnston 7f62085a02 chore(helm): gitignore and rm helm chart tarballs from vcs (#10951) 2023-12-01 09:52:54 +00:00
Cian Johnston d49bcc93fe fix(docs): remove anchor links from headings in admin/healthcheck.md (#10975)
Relates to #8965

* Fixes offlinedocs that broke from change in feat(coderd/healthcheck): add access URL error codes and healthcheck doc #10915 by removing the offending anchor links from the page subheadings.
* Makes offlinedocs also conditional on changes to docs
2023-12-01 09:49:18 +00:00
Spike Curtis b267497c6d fix: parse username/workspace correctly on coder state pull --build (#10973)
fixes #10884
2023-12-01 13:03:49 +04:00
Spike Curtis 46d95cb0f0 fix: wait for dial goroutine to complete (#10959)
Fixes flake seen here: https://github.com/coder/coder/runs/19170327767

The goroutine that attempts to dial the socket didn't complete before the test did.  Here we add an explicit wait for it to complete in each run of the loop.
2023-12-01 11:37:32 +04:00
Spike Curtis 812fb95273 fix: prevent connIO from panicking in race between Close and Enqueue (#10948)
Spotted during a code read.  ConnIO unlocks the mutex before attempting to write to the response channel, which could allow another goroutine to call Close() and close the channel, causing a panic.

Fix is to hold the mutex.  This won't cause a deadlock because the `select{}` has a `default` case, so we won't block even if the receiver isn't keeping up.
2023-12-01 10:23:29 +04:00
Spike Curtis 612e67a53b feat: add cleanup of lost tailnet peers and tunnels to PGCoordinator (#10939)
Adds the "lost" peer cleanup queries to PGCoordinator, including tests.
2023-12-01 10:13:29 +04:00
dependabot[bot] d9ccd97d36 chore: bump @adobe/css-tools from 4.3.1 to 4.3.2 in /site (#10970)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 09:09:33 +03:00
Spike Curtis 571d358e4b feat: add queries to clean lost connections in PGCoordinator (#10938)
Adds cleanup queries to clean out "lost" peer and tunnel state after 24 hours.  We leave this state in the database so that anything trying to connect to the peer can see that it was lost, but clean it up after 24 hours to ensure our table doesn't grow without bounds.
2023-12-01 10:02:30 +04:00
Spike Curtis 0cab6e7763 feat: support graceful disconnect in PGCoordinator (#10937)
Adds support for graceful disconnect to PGCoordinator.  When peers gracefully disconnect, they send a disconnect message.  This triggers the peer to be disconnected from all tunneled peers.

The Multi-Agent Client supports graceful disconnect, since it is in memory and we know that when it is closed, we really mean to disconnect.

The v1 agent and client Websocket connections do not support graceful disconnect, since the v1 protocol doesn't have this feature.  That means that if a v1 peer connects to a v2 peer, when the v1 peer's coordinator connection is closed, the v2 peer will
see it as "lost" since we don't know whether the v1 peer meant to disconnect, or it just lost connectivity to the coordinator.
2023-12-01 09:55:25 +04:00
Jon Ayers 967db2801b chore: refactor ResolveAutostart tests to use dbfake (#10603) 2023-11-30 19:33:04 -06:00
Jon Ayers 12a4b114de fix: fix TestWorkspaceAutobuild/InactiveTTLOK flake (#10965) 2023-11-30 18:29:41 -06:00
Michael Smith d016f93de8 feat: add usePaginatedQuery hook (#10803)
* wip: commit current progress on usePaginatedQuery

* chore: add cacheTime to users query

* chore: update cache logic for UsersPage usersQuery

* wip: commit progress on Pagination

* chore: add function overloads to prepareQuery

* wip: commit progress on usePaginatedQuery

* docs: add clarifying comment about implementation

* chore: remove optional prefetch property from query options

* chore: redefine queryKey

* refactor: consolidate how queryKey/queryFn are called

* refactor: clean up pagination code more

* fix: remove redundant properties

* refactor: clean up code

* wip: commit progress on usePaginatedQuery

* wip: commit current pagination progress

* docs: clean up comments for clarity

* wip: get type signatures compatible (breaks runtime logic slightly)

* refactor: clean up type definitions

* chore: add support for custom onInvalidPage functions

* refactor: clean up type definitions more for clarity reasons

* chore: delete Pagination component (separate PR)

* chore: remove cacheTime fixes (to be resolved in future PR)

* docs: add clarifying/intellisense comments for DX

* refactor: link users queries to same queryKey implementation

* docs: remove misleading comment

* docs: more comments

* chore: update onInvalidPage params for more flexibility

* fix: remove explicit any

* refactor: clean up type definitions

* refactor: rename query params for consistency

* refactor: clean up input validation for page changes

* refactor/fix: update hook to be aware of async data

* chore: add contravariance to dictionary

* refactor: increase type-safety of usePaginatedQuery

* docs: more comments

* chore: move usePaginatedQuery file

* fix: add back cacheTime

* chore: swap in usePaginatedQuery for users table

* chore: add goToFirstPage to usePaginatedQuery

* fix: make page redirects work properly

* refactor: clean up clamp logic

* chore: swap in usePaginatedQuery for Audits table

* refactor: move dependencies around

* fix: remove deprecated properties from hook

* refactor: clean up code more

* docs: add todo comment

* chore: update testing fixtures

* wip: commit current progress for tests

* fix: update useEffectEvent to sync via layout effects

* wip: commit more progress on tests

* wip: stub out all expected test cases

* wip: more test progress

* wip: more test progress

* wip: commit more test progress

* wip: AHHHHHHHH

* chore: finish two more test cases

* wip: add in all tests (still need to investigate prefetching

* refactor: clean up code slightly

* fix: remove math bugs when calculating pages

* fix: wrap up all testing and clean up cases

* docs: update comments for clarity

* fix: update error-handling for invalid page handling

* fix: apply suggestions
2023-11-30 17:44:03 -05:00
Jon Ayers 329aa45c16 fix: fix TestWorkspaceAutobuild/DormantNoAutostart flake (#10963) 2023-11-30 15:45:27 -06:00
Steven Masley 0a16bda786 chore: add external auth providers to oidctest (#10958)
* implement external auth in oidctest
* Refactor more external tests to new oidctest
2023-11-30 14:05:15 -06:00
Mathias Fredriksson 99151183bc feat(scaletest): replace bash with dd in ssh/rpty traffic and use pseudorandomness (#10821)
Fixes #10795
Refs #8556
2023-11-30 19:30:12 +02:00
Cian Johnston 433be7b16d chore(docs/admin/healthcheck): remove GHFM tips (#10954) 2023-11-30 16:33:41 +00:00
Cian Johnston 07895006d9 refactor(coderd/healthcheck): make Warnings an object with { Code, Message } (#10950)
- Adds health.Message { code string, mesasge string }
- Refactors existing warnings []string to be of type []health.Message instead
2023-11-30 14:49:50 +00:00
Cian Johnston 4f9292859d feat(coderd/healthcheck): add access URL error codes and healthcheck doc (#10915)
Relates to #8965

- Added error codes for separate code paths in health checks
- Prefixed errors and warnings with error code prefixes
- Added a docs page with details on each code, cause and solution

Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2023-11-30 12:15:40 +00:00
dependabot[bot] 5b2f43619b chore: bump the react group in /site with 4 updates (#10869)
* chore: bump the react group in /site with 3 updates

Bumps the react group in /site with 3 updates: [react-helmet-async](https://github.com/staylor/react-helmet-async), [react-markdown](https://github.com/remarkjs/react-markdown) and [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom).


Updates `react-helmet-async` from 1.3.0 to 2.0.1
- [Release notes](https://github.com/staylor/react-helmet-async/releases)
- [Commits](https://github.com/staylor/react-helmet-async/commits)

Updates `react-markdown` from 8.0.7 to 9.0.1
- [Release notes](https://github.com/remarkjs/react-markdown/releases)
- [Changelog](https://github.com/remarkjs/react-markdown/blob/main/changelog.md)
- [Commits](https://github.com/remarkjs/react-markdown/compare/8.0.7...9.0.1)

Updates `react-router-dom` from 6.16.0 to 6.20.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.20.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-helmet-async
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: react-markdown
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix lint

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Atif Ali <atif@coder.com>
2023-11-29 23:11:59 +03:00
Garrett Delfosse d41f9f8b47 fix: do not allow selection of unsuccessful versions (#10941) 2023-11-29 13:01:17 -05:00
Bruno Quaresma 2e8ab2aeaf chore(site): enable react-query cache (#10943) 2023-11-29 17:53:11 +00:00
Bruno Quaresma e4d7b0b664 docs: update FE guide (#10942) 2023-11-29 17:27:36 +00:00
Marcin Tojek 2b574e2b2d feat: add dismissed property to the healthcheck section (#10940) 2023-11-29 16:37:40 +00:00
Kira Pilot d374becdeb fix: redirect to new url after template name update (#10926)
* fix: updating template name routes to correct URL

* added e2e test
2023-11-29 10:54:21 -05:00
Kira Pilot 88f4490ad6 fix: clear workspace name validation on field dirty (#10927) 2023-11-29 10:53:45 -05:00
Steven Masley cb6c0f3cbb chore: refactor oidc group and role sync to methods (#10918)
The 'userOIDC' method body was getting unwieldy.
I think there is a good way to redesign the flow, but
I do not want to undertake that at this time.
The easy win is just to move some LoC to other methods
and cleanup the main method.
2023-11-29 09:24:00 -06:00
Spike Curtis 2b71e38b31 feat: add status to tailnet mapping query (#10936)
Adds the `status` column to the mapping query so that we can add graceful disconnect logic around it
2023-11-29 16:53:01 +04:00
Mathias Fredriksson f431aa53d2 chore(go.mod): update github.com/coder/ssh (#10934) 2023-11-29 13:19:49 +02:00
Spike Curtis 2dc565d5de chore: remove New----Builder from dbfake function names (#10882)
Drop "New" and "Builder" from the function names, in favor of the top-level resource created.  This shortens tests and gives a nice syntax.  Since everything is a builder, the prefix and suffix don't add much value and just make things harder to read.

I've also chosen to leave `Do()` as the function to insert into the database.  Even though it's a builder pattern, I fear `.Build()` might be confusing with Workspace Builds.  One other idea is `Insert()` but if we later add dbfake functions that update, this might be inconsistent.
2023-11-29 11:06:04 +04:00
Jon Ayers 48d69c9e60 fix: update autostart context to include querying users (#10929) 2023-11-28 17:56:49 -06:00
Bruno Quaresma e9c12c30cf feat(site): refactor template version editor layout (#10912) 2023-11-28 16:42:31 -03:00
Garrett Delfosse afbda2235c fix: insert replica when removed by cleanup (#10917) 2023-11-28 14:15:09 -05:00
Spike Curtis 52901e1219 feat: implement HTMLDebug for PGCoord with v2 API (#10914)
Implements HTMLDebug for the PGCoordinator with the new v2 API and related DB tables.
2023-11-28 22:37:20 +04:00
Eric Paulsen 18c4a98865 fix: numerical validation grammer (#10924) 2023-11-28 10:14:53 -08:00
Marcin Tojek 19b6d194fc feat: manage health settings using Coder API (#10861) 2023-11-28 18:15:17 +01:00
Dean Sheather 452668c893 chore: avoid dbmock test errors in dbgen (#10923) 2023-11-28 17:04:25 +00:00
Spike Curtis 14bd489af6 feat: add queries for PGCoord HTMLDebug (#10913)
Adds queries for implementing HTMLDebug on the new PGCoordinator
2023-11-28 20:19:32 +04:00
Dean Sheather 3416f6dfb5 chore: update port-forwarding documentation (#10916) 2023-11-28 23:54:19 +10:00
Bruno Quaresma 6808daef0f chore(site): use variable font for Inter (#10903) 2023-11-27 21:35:29 +00:00
Garrett Delfosse 74c5261013 fix: add spacing for yes/no prompts (#10907) 2023-11-27 16:12:07 -05:00
Michael Smith 1f6e39c0b0 fix: hide groups in account page if not enabled (#10898) 2023-11-27 14:06:00 -05:00
Bruno Quaresma a4d74b8b44 chore(site): remove paperLight background value (#10857)
I noticed we have been overusing colors in the UI, so simplifying is better for the "look and feel" and maintaining the styles over time. 

![image](https://github.com/coder/coder/assets/3165839/f70c831d-eba8-4521-820a-6257ae0bedf1)

If you want to have a better sense of what it looks like, I recommend you go to the Chromatic snapshot.
2023-11-27 15:52:20 -03:00
dependabot[bot] c634a38bd7 ci: bump the github-actions group with 1 update (#10890)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-27 21:47:26 +03:00
Muhammad Atif Ali 4cb94d1347 chore: update dependabot to use single groups (#10870)
* chore: update dependabot.yaml to use single groups

This will hopefully reduce @dependabot spamming PRs.

* Update dependabot.yaml
2023-11-27 21:27:58 +03:00
Kira Pilot 54c3fc63d9 fix: docuemnt workspace filter query param correctly (#10894) 2023-11-27 12:57:24 -05:00
Steven Masley 20525c8b2e chore: add script to analyze which releases have migrations (#10823)
* chore: add script to analyze which releases have migrations
2023-11-27 10:53:32 -06:00
Steven Masley abb2c7656a chore: add claims to oauth link in db for debug (#10827)
* chore: add claims to oauth link in db for debug
2023-11-27 10:47:23 -06:00
Cian Johnston 0534f8f59b fix(provisionersdk): use mtime instead of atime for session cleanup (#10893)
See #10892

- Updates provisionersdk session cleanup to use mtime instead of atime.
- Also runs go mod tidy.
2023-11-27 16:21:59 +00:00
Dean Sheather f28df8e7b8 chore: update wgtunnel to avoid panic (#10877) 2023-11-28 02:19:40 +10:00
Cian Johnston 0babc3c555 fix(provisioner/terraform/cleanup): use mtime instead of atime (#10892)
- Updates plugin staleness check to check mtime instead of atime, as atime has been shown to be unreliable
- Updates existing unit test to use a real filesystem as Afero's in-memory FS doesn't support atimes at all
2023-11-27 15:19:41 +00:00
Bruno Quaresma 707d0e97d9 fix(site): fixsidebar styles (#10891) 2023-11-27 09:55:20 -03:00
Mathias Fredriksson f441ad66e1 fix(codersdk): keep workspace agent connection open after dial context (#10863) 2023-11-27 14:29:57 +02:00
Spike Curtis 3a0a4ddfcd chore: convert dbfake.ProvisionerJobResources to builder (#10881)
Convert to builder for consistency with rest of the package.  This will make it easier to use, and means we can drop "Builder" from function arguments since they are all builders in the package.
2023-11-27 14:46:31 +04:00
Spike Curtis 4548ad7cef chore: remove dbfake.Workspace (#10880)
Remove dbfake.Workspace and use builder instead.
2023-11-27 14:39:16 +04:00
Spike Curtis 78283a7fb9 chore: remove dbfake.WorkspaceWithAgent (#10879)
Replace dbfake.WorkspaceWithAgent() with the builder pattern and remove this function.
2023-11-27 14:30:15 +04:00
Spike Curtis 82d5130b07 chore: convert dbfake.Workspace and .WorkspaceWithAgent to a builder pattern (#10878)
Converts dbfake Workspace and WorkspaceWithAgent to builder pattern.
2023-11-27 14:16:31 +04:00
Cian Johnston b73397e08c fix(site): add workspace proxy section to health page (#10862)
* Adds workspace proxy section to health page
* Conditionally places workspace proxy warnings in errors or warnings based on calculated severity
* Adds some more stories we were missing for HealthPage
2023-11-27 09:26:02 +00:00
Spike Curtis 6c67add2d9 fix: detect and retry reverse port forward on used port (#10844)
Fixes #10799

The flake happens when we try to remote forward, but the port we've chosen is not free.  In the flaked example, it's actually the SSH listener that occupies the port we try to remote forward, leading to confusing reads (c.f. the linked issue).

This fix simplies the tests considerably by using the Go ssh client, rather than shelling out to OpenSSH.  This avoids using a pseudoterminal, avoids the need for starting any local OS listeners to communicate the forwarding (go SSH just returns in-process listeners), and avoids an OS listener to wire OpenSSH up to the agentConn.

With the simplied logic, we can immediately tell if a remote forward on a random port fails, so we can do this in a loop until success or timeout.

I've also simplified and fixed up the other forwarding tests. Since we set up forwarding in-process with Go ssh, we can remove a lot of the `require.Eventually` logic.
2023-11-27 09:42:45 +04:00
Dean Sheather d5ddcbdda0 chore: fix flake in templates_test.go (#10875) 2023-11-27 15:29:10 +10:00
lbi22 7029ccfbdf feat: add support for custom permissions in Helm chart rbac.yaml file (#10590)
Co-authored-by: Dean Sheather <dean@deansheather.com>
Co-authored-by: Atif Ali <atif@coder.com>
2023-11-27 14:12:46 +10:00
Ben Potter 3530d39740 docs: fix typo in additional-clusters.md (#10868) 2023-11-26 12:53:33 +00:00
Cian Johnston dd161b172e feat: allow auditors to read template insights (#10860)
- Adds a template_insights pseudo-resource
- Grants auditor and template admin roles read access on template_insights
- Updates existing RBAC checks to check for read template_insights, falling back to template update permissions where necessary
- Updates TemplateLayout to show Insights tab if can read template_insights or can update template
2023-11-24 17:21:32 +00:00
Mathias Fredriksson e73901cf56 fix(coderd): remove nil ptr deref in watchWorkspace (#10859)
Fixes #10849
2023-11-24 15:16:21 +00:00
Cian Johnston 411ce46442 feat(coderd/healthcheck): add health check for proxy (#10846)
Adds a health check for workspace proxies:
- Healthy iff all proxies are healthy and the same version,
- Warning if some proxies are unhealthy,
- Error if all proxies are unhealthy, or do not all have the same version.
2023-11-24 15:06:51 +00:00
Marcin Tojek b501046cf9 test: increase test coverage around health severity (#10858) 2023-11-24 15:42:17 +01:00
Mathias Fredriksson 61be4dfe5a fix: improve exit codes for agent/agentssh and cli/ssh (#10850) 2023-11-24 14:35:56 +02:00
Mathias Fredriksson dbdcad0d09 test(agent/agentssh): fix flake in signal test (#10855) 2023-11-24 13:47:40 +02:00
Marcin Tojek 34841cf2b7 fix: healthcheck warnings should be empty array (#10856) 2023-11-24 12:37:07 +01:00
Mathias Fredriksson 2c6e0f7d0a feat(agent/agentssh): handle session signals (#10842) 2023-11-23 19:55:36 +02:00
Marcin Tojek a7c27cad26 feat: add database support for dismissed healthchecks (#10845) 2023-11-23 16:18:12 +00:00
Cian Johnston f342d10c31 fix(enterprise/coderd/proxyhealth): properly defer healthCheckDuration observe (#10848) 2023-11-23 15:23:40 +00:00
Marcin Tojek 78df68348a feat: include health severity in reports (#10817) 2023-11-23 16:08:41 +01:00
sharkymark e311e9ec24 chore: correct disabling direct and STUN; add vs code remote required URLs (#10830)
* chore: correct disabling direct and STUN; add vs code remote required URLs

* chore: offline docs
2023-11-22 20:04:56 -06:00
Michael Smith 491e0e3abf fix: display explicit 'retry' button(s) when a workspace fails (#10720)
* refactor: remove workspace error enums

* fix: add in retry button for failed workspaces

* fix: make handleBuildRetry auto-detect debug permissions

* chore: consolidate retry messaging

* chore: update renderWorkspacePage to accept parameters

* chore: make workspace test helpers take explicit workspace parameter

* refactor: update how parameters for tests are defined

* fix: update old tests to be correctly parameterized
2023-11-22 16:03:09 -05:00
dependabot[bot] 65c726eb50 chore: bump eslint from 8.52.0 to 8.53.0 in /offlinedocs (#10686)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-22 21:35:05 +03:00
Colin Adler 7f39ff854e fix: skip autostart for suspended/dormant users (#10771) 2023-11-22 11:14:32 -06:00
Zubarev Alexander 614c17924c fix(docs): disable CODER_DERP_SERVER_STUN_ADDRESSES correctly (#10840) 2023-11-22 11:14:01 -06:00
Mathias Fredriksson 6ecba0fda7 fix(coderd): prevent logging error for query cancellation in watchWorkspaceAgentMetadata (#10843) 2023-11-22 15:32:31 +00:00
Bruno Quaresma d58239b9ec chore(site): ignore chromatic changes on syntax highlight (#10839) 2023-11-22 09:51:46 -03:00
Bruno Quaresma ddf5569b10 fix(site): fix tabs (#10838) 2023-11-22 09:33:02 -03:00
Mathias Fredriksson a20ec6659d fix(site): use correct default insights time for day interval (#10837) 2023-11-22 12:30:04 +00:00
Spike Curtis 89c13c2212 fix: enable FeatureHighAvailability if it is licensed (#10834)
fixes #10810

The tailnet coordinators don't depend on replicasync, so we can still enable HA coordinators even if the relay URL is unset.

The in-memory, non-HA coordinator probably has lower latency than the PG Coordinator, since we have to query the database, so enterprise customers might want to disable it for single-replica deployments, but this PR default-enables the HA coordinator.  We could add support later to disable it if anyone complains. Latency setting up connections matters, but I don't believe the coordinator contributes significantly at this point for reasonable postgres round-trip-time.
2023-11-22 14:46:55 +04:00
Marcin Tojek 8dd003ba5e fix: preserve order of node reports in healthcheck (#10835) 2023-11-22 11:15:11 +01:00
dependabot[bot] 60c01555b9 chore: bump react-icons from 4.11.0 to 4.12.0 in /offlinedocs (#10687)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-22 13:00:59 +03:00
Dean Sheather a9c0c01629 chore: fix flake in listening ports test (#10833) 2023-11-22 09:30:51 +00:00
Spike Curtis f20cc66c04 fix: give SSH stdio sessions a chance to close before closing netstack (#10815)
Man, graceful shutdown is hard.  Even after my changes, we were still hitting a graceful shutdown race: https://github.com/coder/coder/runs/18886842123

The problem was that while we attempt a graceful shutdown at the SSH layer by closing the session for writing, we were not giving it a chance to complete before continuing to tear down the stack of closers, including one that closes the netstack, and thus drop the TCP connection before it closes.
2023-11-22 13:11:21 +04:00
Spike Curtis b25e5dc90b chore: remove dbfake.WorkspaceBuild in favor of builder pattern (#10814)
I'd like to convert dbfake into a builder pattern to prevent a proliferation of XXXWithYYY methods.  This is one step of the way by removing the Non-builder function.
2023-11-22 13:04:58 +04:00
dependabot[bot] b73d9d788b chore: bump github.com/go-jose/go-jose/v3 from 3.0.0 to 3.0.1 (#10828)
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/v3/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-22 08:21:59 +03:00
Jon Ayers 8d1cfbce8f fix: update workspace cleanup flag names for template cmds (#10805) 2023-11-21 18:20:01 -06:00
Jon Ayers 51b58cfc98 fix: only update last_used_at when connection count > 0 (#10808) 2023-11-21 18:10:41 -06:00
Jon Ayers 782fe84c7c feat: disable start/restart if active version required (#10809) 2023-11-21 18:06:30 -06:00
Marcin Tojek 214123d476 test: skip flaky HealthyWithNodeDegraded (#10826) 2023-11-21 20:46:58 +01:00
Muhammad Atif Ali 1c2f9e3199 chore: refactoring to move the notes at top 2023-11-21 22:03:21 +03:00
Kayla Washburn 8cd8901db5 refactor: avoid @emotion/css when possible (#10807) 2023-11-21 11:29:43 -07:00
Kayla Washburn 26b5390f4b refactor: remove usage of styled and withStyles (#10806) 2023-11-21 10:43:01 -07:00
Jon Ayers ad3eb4bb75 Revert "docs: add documentation for template update policies (#10804)" (#10822)
This reverts commit e6dc9eeffc.
2023-11-21 17:10:08 +00:00
dependabot[bot] d0ac4cb4b1 chore: bump prettier from 3.0.0 to 3.1.0 in /site (#10695)
* chore: bump prettier from 3.0.0 to 3.1.0 in /site

Bumps [prettier](https://github.com/prettier/prettier) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.0.0...3.1.0)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* prettier

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kira Pilot <kira.pilot23@gmail.com>
2023-11-21 11:48:40 -05:00
Kayla Washburn e51eeb67ce refactor: improve settings sidebar components (#10801) 2023-11-21 09:38:55 -07:00
dependabot[bot] 7fa70ce159 chore: bump github.com/aws/smithy-go from 1.16.0 to 1.17.0 (#10788)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-21 16:33:15 +03:00
dependabot[bot] 4590149810 chore: bump google.golang.org/api from 0.150.0 to 0.151.0 (#10787)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-21 16:32:49 +03:00
Spike Curtis 5d5b5aa074 chore: use dbfake for ssh tests rather than provisionerd (#10812)
Refactors SSH tests to skip provisionerd and instead use dbfake to insert workspaces and builds.  This should make tests faster and more reliable.

dbfake.WorkspaceBuild is refactored to use a "builder" pattern with "fluent" options, as the number of options and variants was starting to get out of hand.
2023-11-21 16:22:08 +04:00
Marcin Tojek 048dc0450f feat: ensure coder remains healthy with single degraded DERP server (#10813) 2023-11-21 12:58:25 +01:00
Cian Johnston abafc0863c feat(coderd): store workspace proxy version in the database (#10790)
Stores workspace proxy version in database upon registration.
2023-11-21 11:21:25 +00:00
Steven Masley 7060069034 fix: prevent change in defaults if user unsets in template edit (#10793)
* fix: template edit not change defaults if user unset
2023-11-20 18:14:30 -06:00
Jon Ayers e6dc9eeffc docs: add documentation for template update policies (#10804)
Co-authored-by: Ben Potter <ben@coder.com>
2023-11-20 16:30:24 -06:00
Kira Pilot ace188bfc2 fix: clarify language in orphan section of delete modal (#10764)
* fix: clarify language in orphan section of delete modal

* tinted title

* Update site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx

Co-authored-by: Muhammad Atif Ali <atif@coder.com>

* prettier

---------

Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2023-11-20 15:04:51 -05:00
Steven Masley 5229d7fd3a feat: implement deprecated flag for templates to prevent new workspaces (#10745)
* feat: implement deprecated flag for templates to prevent new workspaces
* Add deprecated filter to template fetching
* Add deprecated to template table
* Add deprecated notice to template page
* Add ui to deprecate a template
2023-11-20 19:16:18 +00:00
Marcin Tojek d8df87d5ae fix: insights metrics comparison (#10800)
* fix: insights metrics comparison

* links
2023-11-20 18:37:46 +01:00
Mathias Fredriksson 6b3f599438 fix(site): correctly interpret timezone based on offset in formatOffset (#10797)
Fixes #10784
2023-11-20 19:30:09 +02:00
Kayla Washburn 9b6433e3a7 chore: remove theme experiment (#10798)
Co-authored-by: Kyle Carberry <kyle@carberry.com>
2023-11-20 09:53:20 -07:00
Spike Curtis 92ef0baff3 fix: remove pty match for TestSSH/RemoteForward (#10789)
Fixes #10578
2023-11-20 20:50:09 +04:00
Michael Smith df4f34ac15 fix: prevent alt text from appearing if OIDC icon fail to load (#10792)
* fix: update alt text issue
2023-11-20 10:51:25 -05:00
Bruno Quaresma fbec79f35d refactor(site): refactor login screen (#10768) 2023-11-20 11:19:50 -03:00
Bruno Quaresma 2895c108c2 chore(site): remove Typography component (#10769)
* Remove Typography from NavbarView

* Remove Typography from EmptyState

* Remove Typography from Paywall

* Fix font size

* Remove Typography from CliAuthPage

* Remove Typography from Single SignOn

* Remove Typography from file dialog

* Remove from not found

* Remove from Section

* Remove from global snackbar

* Remove Typography component

* Add eslint role
2023-11-20 10:15:40 -03:00
Spike Curtis 5173bce5cc fix: stop redirecting DERP and replicasync http requests (#10752)
Fixes an issue where setting CODER_REDIRECT_TO_ACCESS_URL breaks use of multiple Coder server replicas for DERP traffic.
2023-11-20 14:46:59 +04:00
Spike Curtis 5c48cb4447 feat: modify PG Coordinator to work with new v2 Tailnet API (#10573)
re: #10528

Refactors PG Coordinator to work with the Tailnet v2 API, including wrappers for the existing v1 API.

The debug endpoint functions, but doesn't return sensible data, that will be in another stacked PR.
2023-11-20 14:31:04 +04:00
Muhammad Atif Ali a8c25180db fix(docs): fix a broken link (#10783) 2023-11-20 12:49:07 +03:00
JounQin 148eb90bda docs: migrate all deprecated CODER_ADDRESS to CODER_HTTP_ADDRESS (#10780)
Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
2023-11-19 17:54:02 +00:00
JounQin 9b864ed700 docs: align CODER_HTTP_ADDRESS with document (#10779) 2023-11-19 15:38:39 +00:00
Ammar Bandukwala cfe35f54b4 feat(cli/agent): preserve old logs (#10776)
See https://github.com/coder/coder/pull/7815 for background.
2023-11-18 10:53:56 -06:00
Eric Paulsen 328a383f15 fix: set ignore_changes on EC2 example templates (#10773) 2023-11-18 01:07:27 -05:00
Colin Adler 3aef070959 fix: return non-null warning arrays in healthcheck (#10774) 2023-11-17 22:25:44 +00:00
Cian Johnston 2c3ebc50cb fix(site): handle null warnings in health page (#10775) 2023-11-17 22:10:13 +00:00
1943 changed files with 116397 additions and 40850 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Development environments on your infrastructure",
"image": "codercom/oss-dogfood:pre-nix",
"image": "codercom/oss-dogfood:latest",
"features": {
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
+6
View File
@@ -0,0 +1,6 @@
# Ignore all files and folders
**
# Include flake.nix and flake.lock
!flake.nix
!flake.lock
+3
View File
@@ -6,9 +6,12 @@ coderd/apidoc/swagger.json linguist-generated=true
coderd/database/dump.sql linguist-generated=true
peerbroker/proto/*.go linguist-generated=true
provisionerd/proto/*.go linguist-generated=true
provisionerd/proto/version.go linguist-generated=false
provisionersdk/proto/*.go linguist-generated=true
*.tfplan.json linguist-generated=true
*.tfstate.json linguist-generated=true
*.tfstate.dot linguist-generated=true
*.tfplan.dot linguist-generated=true
site/e2e/provisionerGenerated.ts linguist-generated=true
site/src/api/typesGenerated.ts linguist-generated=true
site/src/pages/SetupPage/countries.tsx linguist-generated=true
+1 -1
View File
@@ -4,7 +4,7 @@ description: |
inputs:
version:
description: "The Go version to use."
default: "1.20.11"
default: "1.21.5"
runs:
using: "composite"
steps:
+1 -1
View File
@@ -17,7 +17,7 @@ runs:
- name: Setup Node
uses: buildjet/setup-node@v3
with:
node-version: 18.17.0
node-version: 18.19.0
# See https://github.com/actions/setup-node#caching-global-packages-data
cache: "pnpm"
cache-dependency-path: ${{ inputs.directory }}/pnpm-lock.yaml
+1 -1
View File
@@ -7,4 +7,4 @@ runs:
- name: Setup sqlc
uses: sqlc-dev/setup-sqlc@v4
with:
sqlc-version: "1.20.0"
sqlc-version: "1.25.0"
+7 -31
View File
@@ -38,19 +38,12 @@ updates:
commit-message:
prefix: "chore"
labels: []
open-pull-requests-limit: 15
ignore:
# Ignore patch updates for all dependencies
- dependency-name: "*"
update-types:
- version-update:semver-patch
groups:
otel:
patterns:
- "go.nhat.io/otelsql"
- "go.opentelemetry.io/otel*"
golang-x:
patterns:
- "golang.org/x/*"
# Update our Dockerfile.
- package-ecosystem: "docker"
@@ -66,10 +59,6 @@ updates:
# We need to coordinate terraform updates with the version hardcoded in
# our Go code.
- dependency-name: "terraform"
groups:
scripts-docker:
patterns:
- "*"
- package-ecosystem: "npm"
directory: "/site/"
@@ -94,26 +83,9 @@ updates:
- version-update:semver-major
open-pull-requests-limit: 15
groups:
react:
site:
patterns:
- "react*"
- "@types/react*"
xterm:
patterns:
- "xterm*"
mui:
patterns:
- "@mui*"
storybook:
patterns:
- "@storybook*"
- "storybook*"
eslint:
patterns:
- "eslint*"
- "@eslint*"
- "@typescript-eslint/eslint-plugin"
- "@typescript-eslint/parser"
- "*"
- package-ecosystem: "npm"
directory: "/offlinedocs/"
@@ -136,6 +108,10 @@ updates:
- dependency-name: "@types/node"
update-types:
- version-update:semver-major
groups:
offlinedocs:
patterns:
- "*"
# Update dogfood.
- package-ecosystem: "terraform"
+34
View File
@@ -0,0 +1,34 @@
app = "jnb-coder"
primary_region = "jnb"
[experimental]
entrypoint = ["/bin/sh", "-c", "CODER_DERP_SERVER_RELAY_URL=\"http://[${FLY_PRIVATE_IP}]:3000\" /opt/coder wsproxy server"]
auto_rollback = true
[build]
image = "ghcr.io/coder/coder-preview:main"
[env]
CODER_ACCESS_URL = "https://jnb.fly.dev.coder.com"
CODER_HTTP_ADDRESS = "0.0.0.0:3000"
CODER_PRIMARY_ACCESS_URL = "https://dev.coder.com"
CODER_WILDCARD_ACCESS_URL = "*--apps.jnb.fly.dev.coder.com"
CODER_VERBOSE = "true"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
# Ref: https://fly.io/docs/reference/configuration/#http_service-concurrency
[http_service.concurrency]
type = "requests"
soft_limit = 50
hard_limit = 100
[[vm]]
cpu_kind = "shared"
cpus = 2
memory_mb = 512
+34
View File
@@ -0,0 +1,34 @@
app = "paris-coder"
primary_region = "cdg"
[experimental]
entrypoint = ["/bin/sh", "-c", "CODER_DERP_SERVER_RELAY_URL=\"http://[${FLY_PRIVATE_IP}]:3000\" /opt/coder wsproxy server"]
auto_rollback = true
[build]
image = "ghcr.io/coder/coder-preview:main"
[env]
CODER_ACCESS_URL = "https://paris.fly.dev.coder.com"
CODER_HTTP_ADDRESS = "0.0.0.0:3000"
CODER_PRIMARY_ACCESS_URL = "https://dev.coder.com"
CODER_WILDCARD_ACCESS_URL = "*--apps.paris.fly.dev.coder.com"
CODER_VERBOSE = "true"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
# Ref: https://fly.io/docs/reference/configuration/#http_service-concurrency
[http_service.concurrency]
type = "requests"
soft_limit = 50
hard_limit = 100
[[vm]]
cpu_kind = "shared"
cpus = 2
memory_mb = 512
@@ -0,0 +1,34 @@
app = "sao-paulo-coder"
primary_region = "gru"
[experimental]
entrypoint = ["/bin/sh", "-c", "CODER_DERP_SERVER_RELAY_URL=\"http://[${FLY_PRIVATE_IP}]:3000\" /opt/coder wsproxy server"]
auto_rollback = true
[build]
image = "ghcr.io/coder/coder-preview:main"
[env]
CODER_ACCESS_URL = "https://sao-paulo.fly.dev.coder.com"
CODER_HTTP_ADDRESS = "0.0.0.0:3000"
CODER_PRIMARY_ACCESS_URL = "https://dev.coder.com"
CODER_WILDCARD_ACCESS_URL = "*--apps.sao-paulo.fly.dev.coder.com"
CODER_VERBOSE = "true"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
# Ref: https://fly.io/docs/reference/configuration/#http_service-concurrency
[http_service.concurrency]
type = "requests"
soft_limit = 50
hard_limit = 100
[[vm]]
cpu_kind = "shared"
cpus = 2
memory_mb = 512
+34
View File
@@ -0,0 +1,34 @@
app = "sydney-coder"
primary_region = "syd"
[experimental]
entrypoint = ["/bin/sh", "-c", "CODER_DERP_SERVER_RELAY_URL=\"http://[${FLY_PRIVATE_IP}]:3000\" /opt/coder wsproxy server"]
auto_rollback = true
[build]
image = "ghcr.io/coder/coder-preview:main"
[env]
CODER_ACCESS_URL = "https://sydney.fly.dev.coder.com"
CODER_HTTP_ADDRESS = "0.0.0.0:3000"
CODER_PRIMARY_ACCESS_URL = "https://dev.coder.com"
CODER_WILDCARD_ACCESS_URL = "*--apps.sydney.fly.dev.coder.com"
CODER_VERBOSE = "true"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
# Ref: https://fly.io/docs/reference/configuration/#http_service-concurrency
[http_service.concurrency]
type = "requests"
soft_limit = 50
hard_limit = 100
[[vm]]
cpu_kind = "shared"
cpus = 2
memory_mb = 512
+3 -4
View File
@@ -88,10 +88,9 @@ provider "kubernetes" {
data "coder_workspace" "me" {}
resource "coder_agent" "main" {
os = "linux"
arch = "amd64"
startup_script_timeout = 180
startup_script = <<-EOT
os = "linux"
arch = "amd64"
startup_script = <<-EOT
set -e
# install and start code-server
+250 -145
View File
@@ -31,10 +31,12 @@ jobs:
runs-on: ubuntu-latest
outputs:
docs-only: ${{ steps.filter.outputs.docs_count == steps.filter.outputs.all_count }}
docs: ${{ steps.filter.outputs.docs }}
go: ${{ steps.filter.outputs.go }}
ts: ${{ steps.filter.outputs.ts }}
k8s: ${{ steps.filter.outputs.k8s }}
ci: ${{ steps.filter.outputs.ci }}
db: ${{ steps.filter.outputs.db }}
offlinedocs-only: ${{ steps.filter.outputs.offlinedocs_count == steps.filter.outputs.all_count }}
offlinedocs: ${{ steps.filter.outputs.offlinedocs }}
steps:
@@ -44,7 +46,7 @@ jobs:
fetch-depth: 1
# For pull requests it's not necessary to checkout the code
- name: check changed files
uses: dorny/paths-filter@v2
uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -56,6 +58,9 @@ jobs:
- "examples/web-server/**"
- "examples/monitoring/**"
- "examples/lima/**"
db:
- "**.sql"
- "coderd/database/**"
go:
- "**.sql"
- "**.go"
@@ -121,12 +126,13 @@ jobs:
- name: Get golangci-lint cache dir
run: |
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.2
linter_ver=$(egrep -o 'GOLANGCI_LINT_VERSION=\S+' dogfood/Dockerfile | cut -d '=' -f 2)
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$linter_ver
dir=$(golangci-lint cache status | awk '/Dir/ { print $2 }')
echo "LINT_CACHE_DIR=$dir" >> $GITHUB_ENV
- name: golangci-lint cache
uses: buildjet/cache@v3
uses: buildjet/cache@v4
with:
path: |
${{ env.LINT_CACHE_DIR }}
@@ -136,7 +142,7 @@ jobs:
# Check for any typos
- name: Check for typos
uses: crate-ci/typos@v1.16.23
uses: crate-ci/typos@v1.18.2
with:
config: .github/workflows/typos.toml
@@ -183,7 +189,7 @@ jobs:
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 github.com/golang/mock/mockgen@v1.6.0
go install go.uber.org/mock/mockgen@v0.4.0
- name: Install Protoc
run: |
@@ -216,11 +222,11 @@ jobs:
uses: ./.github/actions/setup-node
- name: Setup Go
uses: buildjet/setup-go@v4
uses: buildjet/setup-go@v5
with:
# This doesn't need caching. It's super fast anyways!
cache: false
go-version: 1.20.11
go-version: 1.21.5
- name: Install shfmt
run: go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0
@@ -300,7 +306,7 @@ jobs:
api-key: ${{ secrets.DATADOG_API_KEY }}
- name: Check code coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
# This action has a tendency to error out unexpectedly, it has
# the `fail_ci_if_error` option that defaults to `false`, but
# that is no guarantee, see:
@@ -314,7 +320,8 @@ jobs:
test-go-pg:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
needs: changes
needs:
- changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
# This timeout must be greater than the timeout set by `go test` in
# `make test-postgres` to ensure we receive a trace of running
@@ -347,7 +354,7 @@ jobs:
api-key: ${{ secrets.DATADOG_API_KEY }}
- name: Check code coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
# This action has a tendency to error out unexpectedly, it has
# the `fail_ci_if_error` option that defaults to `false`, but
# that is no guarantee, see:
@@ -388,100 +395,6 @@ jobs:
with:
api-key: ${{ secrets.DATADOG_API_KEY }}
deploy:
name: "deploy"
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-16vcpu-ubuntu-2204' || 'ubuntu-latest' }}
timeout-minutes: 30
needs: changes
if: |
github.ref == 'refs/heads/main' && !github.event.pull_request.head.repo.fork
&& needs.changes.outputs.docs-only == 'false'
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
with:
workload_identity_provider: projects/573722524737/locations/global/workloadIdentityPools/github/providers/github
service_account: coder-ci@coder-dogfood.iam.gserviceaccount.com
- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v1
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Install goimports
run: go install golang.org/x/tools/cmd/goimports@latest
- name: Install nfpm
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.16.0
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Build Release
run: |
set -euo pipefail
go mod download
version="$(./scripts/version.sh)"
make gen/mark-fresh
make -j \
build/coder_"$version"_windows_amd64.zip \
build/coder_"$version"_linux_amd64.{tar.gz,deb}
- name: Install Release
run: |
set -euo pipefail
regions=(
# gcp-region-id instance-name systemd-service-name
"us-central1-a coder coder"
"australia-southeast1-b coder-sydney coder-workspace-proxy"
"europe-west3-c coder-europe coder-workspace-proxy"
"southamerica-east1-b coder-brazil coder-workspace-proxy"
)
deb_pkg="./build/coder_$(./scripts/version.sh)_linux_amd64.deb"
if [ ! -f "$deb_pkg" ]; then
echo "deb package not found: $deb_pkg"
ls -l ./build
exit 1
fi
gcloud config set project coder-dogfood
for region in "${regions[@]}"; do
echo "::group::$region"
set -- $region
set -x
gcloud config set compute/zone "$1"
gcloud compute scp "$deb_pkg" "${2}:/tmp/coder.deb"
gcloud compute ssh "$2" -- /bin/sh -c "set -eux; sudo dpkg -i --force-confdef /tmp/coder.deb; sudo systemctl daemon-reload; sudo service '$3' restart"
set +x
echo "::endgroup::"
done
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: coder
path: |
./build/*.zip
./build/*.tar.gz
./build/*.deb
retention-days: 7
test-js:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
needs: changes
@@ -500,7 +413,7 @@ jobs:
working-directory: site
- name: Check code coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
# This action has a tendency to error out unexpectedly, it has
# the `fail_ci_if_error` option that defaults to `false`, but
# that is no guarantee, see:
@@ -538,7 +451,7 @@ jobs:
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 github.com/golang/mock/mockgen@v1.6.0
go install go.uber.org/mock/mockgen@v0.4.0
- name: Install Protoc
run: |
@@ -564,7 +477,7 @@ jobs:
- 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@v3
uses: actions/upload-artifact@v4
with:
name: failed-test-videos
path: ./site/test-results/**/*.webm
@@ -572,7 +485,7 @@ jobs:
- name: Upload pprof dumps
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: debug-pprof-dumps
path: ./site/test-results/**/debug-pprof-*.txt
@@ -599,12 +512,13 @@ jobs:
# the check to pass. This is desired in PRs, but not in mainline.
- name: Publish to Chromatic (non-mainline)
if: github.ref != 'refs/heads/main' && github.repository_owner == 'coder'
uses: chromaui/action@v1
uses: chromaui/action@v10
env:
NODE_OPTIONS: "--max_old_space_size=4096"
STORYBOOK: true
with:
buildScriptName: "storybook:build"
# Do a fast, testing build for change previews
buildScriptName: "storybook:ci"
exitOnceUploaded: true
# This will prevent CI from failing when Chromatic detects visual changes
exitZeroOnChanges: true
@@ -618,6 +532,8 @@ jobs:
# Run TurboSnap to trace file dependencies to related stories
# and tell chromatic to only take snapshots of relevent stories
onlyChanged: true
# Avoid uploading single files, because that's very slow
zip: true
# This is a separate step for mainline only that auto accepts and changes
# instead of holding CI up. Since we squash/merge, this is defensive to
@@ -627,7 +543,7 @@ jobs:
# infinitely "in progress" in mainline unless we re-review each build.
- name: Publish to Chromatic (mainline)
if: github.ref == 'refs/heads/main' && github.repository_owner == 'coder'
uses: chromaui/action@v1
uses: chromaui/action@v10
env:
NODE_OPTIONS: "--max_old_space_size=4096"
STORYBOOK: true
@@ -635,6 +551,7 @@ jobs:
autoAcceptChanges: true
# This will prevent CI from failing when Chromatic detects visual changes
exitZeroOnChanges: true
# Do a full build with documentation for mainline builds
buildScriptName: "storybook:build"
projectToken: 695c25b6cb65
workingDir: "./site"
@@ -642,12 +559,15 @@ jobs:
# Run TurboSnap to trace file dependencies to related stories
# and tell chromatic to only take snapshots of relevent stories
onlyChanged: true
# Avoid uploading single files, because that's very slow
zip: true
offlinedocs:
name: offlinedocs
needs: changes
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
if: needs.changes.outputs.offlinedocs == 'true' || needs.changes.outputs.ci == 'true'
if: needs.changes.outputs.offlinedocs == 'true' || needs.changes.outputs.ci == 'true' || needs.changes.outputs.docs == 'true'
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -660,12 +580,26 @@ jobs:
with:
directory: offlinedocs
- 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: Setup Go
uses: ./.github/actions/setup-go
- name: Install go tools
run: |
go install github.com/golang/mock/mockgen@v1.6.0
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: Setup sqlc
uses: ./.github/actions/setup-sqlc
@@ -696,6 +630,7 @@ jobs:
- test-js
- test-e2e
- offlinedocs
- sqlc-vet
# Allow this job to run even if the needed jobs fail, are skipped or
# cancelled.
if: always()
@@ -710,6 +645,8 @@ jobs:
echo "- test-go-pg: ${{ needs.test-go-pg.result }}"
echo "- test-go-race: ${{ needs.test-go-race.result }}"
echo "- test-js: ${{ needs.test-js.result }}"
echo "- test-e2e: ${{ needs.test-e2e.result }}"
echo "- offlinedocs: ${{ needs.offlinedocs.result }}"
echo
# We allow skipped jobs to pass, but not failed or cancelled jobs.
@@ -720,29 +657,23 @@ jobs:
echo "Required checks have passed"
build-main-image:
# This build and publihes ghcr.io/coder/coder-preview:main for each merge commit to main branch.
# We are only building this for amd64 plateform. (>95% pulls are for amd64)
build:
# This builds and publishes ghcr.io/coder/coder-preview:main for each commit
# to main branch. We are only building this for amd64 platform. (>95% pulls
# are for amd64)
needs: changes
if: github.ref == 'refs/heads/main' && needs.changes.outputs.docs-only == 'false'
if: needs.changes.outputs.docs-only == 'false' && !github.event.pull_request.head.repo.fork
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
outputs:
IMAGE: ghcr.io/coder/coder-preview:${{ steps.build-docker.outputs.tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Setup sqlc
uses: ./.github/actions/setup-sqlc
- name: GHCR Login
uses: docker/login-action@v3
with:
@@ -750,35 +681,209 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Linux amd64 Docker image
id: build_and_push
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Install nfpm
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.35.1
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Build
run: |
set -euxo pipefail
go mod download
make gen/mark-fresh
export DOCKER_IMAGE_NO_PREREQUISITES=true
version="$(./scripts/version.sh)"
export CODER_IMAGE_BUILD_BASE_TAG="$(CODER_IMAGE_BASE=coder-base ./scripts/image_tag.sh --version "$version")"
make -j build/coder_linux_amd64
./scripts/build_docker.sh \
--arch amd64 \
--target ghcr.io/coder/coder-preview:main \
--version $version \
--push \
build/coder_linux_amd64
# Tag image with new package tag and push
tag=$(echo "$version" | sed 's/+/-/g')
docker tag ghcr.io/coder/coder-preview:main ghcr.io/coder/coder-preview:main-$tag
docker push ghcr.io/coder/coder-preview:main-$tag
version="$(./scripts/version.sh)"
tag="main-$(echo "$version" | sed 's/+/-/g')"
echo "tag=$tag" >> $GITHUB_OUTPUT
make gen/mark-fresh
make -j \
build/coder_linux_{amd64,arm64,armv7} \
build/coder_"$version"_windows_amd64.zip \
build/coder_"$version"_linux_amd64.{tar.gz,deb}
- name: Build Linux Docker images
id: build-docker
env:
CODER_IMAGE_BASE: ghcr.io/coder/coder-preview
CODER_IMAGE_TAG_PREFIX: main
DOCKER_CLI_EXPERIMENTAL: "enabled"
run: |
set -euxo pipefail
# build Docker images for each architecture
version="$(./scripts/version.sh)"
tag="main-$(echo "$version" | sed 's/+/-/g')"
echo "tag=$tag" >> $GITHUB_OUTPUT
# build images for each architecture
make -j build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# only push if we are on main branch
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
# build and push multi-arch manifest, this depends on the other images
# being pushed so will automatically push them
make -j push/build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# Define specific tags
tags=("$tag" "main" "latest")
# Create and push a multi-arch manifest for each tag
# we are adding `latest` tag and keeping `main` for backward
# compatibality
for t in "${tags[@]}"; do
./scripts/build_docker_multiarch.sh \
--push \
--target "ghcr.io/coder/coder-preview:$t" \
--version $version \
$(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag)
done
fi
- name: Prune old images
uses: vlaurin/action-ghcr-prune@v0.5.0
if: github.ref == 'refs/heads/main'
uses: vlaurin/action-ghcr-prune@v0.6.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
organization: coder
container: coder-preview
keep-younger-than: 7 # days
keep-tags: latest
keep-tags-regexes: ^pr
prune-tags-regexes: ^main-
prune-tags-regexes: |
^main-
^v
prune-untagged: true
- name: Upload build artifacts
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
with:
name: coder
path: |
./build/*.zip
./build/*.tar.gz
./build/*.deb
retention-days: 7
deploy:
name: "deploy"
runs-on: ubuntu-latest
timeout-minutes: 30
needs:
- changes
- build
if: |
github.ref == 'refs/heads/main' && !github.event.pull_request.head.repo.fork
&& needs.changes.outputs.docs-only == 'false'
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/573722524737/locations/global/workloadIdentityPools/github/providers/github
service_account: coder-ci@coder-dogfood.iam.gserviceaccount.com
- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Set up Flux CLI
uses: fluxcd/flux2/action@main
with:
# Keep this up to date with the version of flux installed in dogfood cluster
version: "2.2.1"
- name: Get Cluster Credentials
uses: "google-github-actions/get-gke-credentials@v2"
with:
cluster_name: dogfood-v2
location: us-central1-a
project_id: coder-dogfood-v2
- name: Reconcile Flux
run: |
set -euxo pipefail
flux --namespace flux-system reconcile source git flux-system
flux --namespace flux-system reconcile source git coder-main
flux --namespace flux-system reconcile kustomization flux-system
flux --namespace flux-system reconcile kustomization coder
flux --namespace flux-system reconcile source chart coder-coder
flux --namespace flux-system reconcile source chart coder-coder-provisioner
flux --namespace coder reconcile helmrelease coder
flux --namespace coder reconcile helmrelease coder-provisioner
# Just updating Flux is usually not enough. The Helm release may get
# redeployed, but unless something causes the Deployment to update the
# pods won't be recreated. It's important that the pods get recreated,
# since we use `imagePullPolicy: Always` to ensure we're running the
# latest image.
- name: Rollout Deployment
run: |
set -euxo pipefail
kubectl --namespace coder rollout restart deployment/coder
kubectl --namespace coder rollout status deployment/coder
kubectl --namespace coder rollout restart deployment/coder-provisioner
kubectl --namespace coder rollout status deployment/coder-provisioner
deploy-wsproxies:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' && !github.event.pull_request.head.repo.fork
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup flyctl
uses: superfly/flyctl-actions/setup-flyctl@master
- name: Deploy workspace proxies
run: |
flyctl deploy --image "$IMAGE" --app paris-coder --config ./.github/fly-wsproxies/paris-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_PARIS" --yes
flyctl deploy --image "$IMAGE" --app sydney-coder --config ./.github/fly-wsproxies/sydney-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_SYDNEY" --yes
flyctl deploy --image "$IMAGE" --app sao-paulo-coder --config ./.github/fly-wsproxies/sao-paulo-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_SAO_PAULO" --yes
flyctl deploy --image "$IMAGE" --app jnb-coder --config ./.github/fly-wsproxies/jnb-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_JNB" --yes
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
IMAGE: ${{ needs.build.outputs.IMAGE }}
TOKEN_PARIS: ${{ secrets.FLY_PARIS_CODER_PROXY_SESSION_TOKEN }}
TOKEN_SYDNEY: ${{ secrets.FLY_SYDNEY_CODER_PROXY_SESSION_TOKEN }}
TOKEN_SAO_PAULO: ${{ secrets.FLY_SAO_PAULO_CODER_PROXY_SESSION_TOKEN }}
TOKEN_JNB: ${{ secrets.FLY_JNB_CODER_PROXY_SESSION_TOKEN }}
# sqlc-vet runs a postgres docker container, runs Coder migrations, and then
# runs sqlc-vet to ensure all queries are valid. This catches any mistakes
# in migrations or sqlc queries that makes a query unable to be prepared.
sqlc-vet:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.db == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
# We need golang to run the migration main.go
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Setup sqlc
uses: ./.github/actions/setup-sqlc
- name: Setup and run sqlc vet
run: |
make sqlc-vet
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
pull-requests: write
steps:
- name: auto-approve dependabot
uses: hmarr/auto-approve-action@v3
uses: hmarr/auto-approve-action@v4
if: github.actor == 'dependabot[bot]'
cla:
+54 -23
View File
@@ -5,29 +5,28 @@ on:
branches:
- main
paths:
- "flake.nix"
- "flake.lock"
- "dogfood/**"
- ".github/workflows/dogfood.yaml"
# Uncomment these lines when testing with CI.
# pull_request:
# paths:
# - "flake.nix"
# - "flake.lock"
# - "dogfood/**"
# - ".github/workflows/dogfood.yaml"
- "flake.lock"
- "flake.nix"
pull_request:
paths:
- "dogfood/**"
- ".github/workflows/dogfood.yaml"
- "flake.lock"
- "flake.nix"
workflow_dispatch:
jobs:
deploy_image:
runs-on: buildjet-4vcpu-ubuntu-2204
build_image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v6.5
uses: tj-actions/branch-names@v8
- name: "Branch name to Docker tag name"
id: docker-tag-name
@@ -37,46 +36,78 @@ jobs:
tag=${tag//\//--}
echo "tag=${tag}" >> $GITHUB_OUTPUT
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v7
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: Run the Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@v2
- run: nix build .#devEnvImage && ./result | docker load
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Tag and Push
run: |
docker tag codercom/oss-dogfood:latest codercom/oss-dogfood:${{ steps.docker-tag-name.outputs.tag }}
docker push codercom/oss-dogfood -a
- name: Build and push Non-Nix image
uses: depot/build-push-action@v1
with:
project: b4q6ltmpzh
token: ${{ secrets.DEPOT_TOKEN }}
buildx-fallback: true
context: "{{defaultContext}}:dogfood"
pull: true
save: true
push: ${{ github.ref == 'refs/heads/main' }}
tags: "codercom/oss-dogfood:${{ steps.docker-tag-name.outputs.tag }},codercom/oss-dogfood:latest"
- name: Build and push Nix image
uses: depot/build-push-action@v1
with:
project: b4q6ltmpzh
token: ${{ secrets.DEPOT_TOKEN }}
buildx-fallback: true
context: "."
file: "dogfood/Dockerfile.nix"
pull: true
save: true
push: ${{ github.ref == 'refs/heads/main' }}
tags: "codercom/oss-dogfood-nix:${{ steps.docker-tag-name.outputs.tag }},codercom/oss-dogfood-nix:latest"
deploy_template:
needs: deploy_image
needs: build_image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: ./.github/actions/setup-tf
- name: Terraform init and validate
run: |
cd dogfood
terraform init -upgrade
terraform validate
- name: Get short commit SHA
if: github.ref == 'refs/heads/main'
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Get latest commit title
if: github.ref == 'refs/heads/main'
id: message
run: echo "pr_title=$(git log --format=%s -n 1 ${{ github.sha }})" >> $GITHUB_OUTPUT
- name: "Get latest Coder binary from the server"
if: github.ref == 'refs/heads/main'
run: |
curl -fsSL "https://dev.coder.com/bin/coder-linux-amd64" -o "./coder"
chmod +x "./coder"
- name: "Push template"
if: github.ref == 'refs/heads/main'
run: |
./coder templates push $CODER_TEMPLATE_NAME --directory $CODER_TEMPLATE_DIR --yes --name=$CODER_TEMPLATE_VERSION --message="$CODER_TEMPLATE_MESSAGE"
env:
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Assign author
uses: toshimaru/auto-author-assign@v2.0.1
uses: toshimaru/auto-author-assign@v2.1.0
+7 -10
View File
@@ -9,10 +9,6 @@ on:
- main
workflow_dispatch:
inputs:
pr_number:
description: "PR number"
type: number
required: true
experiments:
description: "Experiments to enable"
required: false
@@ -123,7 +119,7 @@ jobs:
echo "NEW=$NEW" >> $GITHUB_OUTPUT
- name: Check changed files
uses: dorny/paths-filter@v2
uses: dorny/paths-filter@v3
id: filter
with:
base: ${{ github.ref }}
@@ -167,7 +163,7 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: Find Comment
uses: peter-evans/find-comment@v2
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ needs.get_info.outputs.PR_NUMBER }}
@@ -177,7 +173,7 @@ jobs:
- name: Comment on PR
id: comment_id
uses: peter-evans/create-or-update-comment@v3
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ needs.get_info.outputs.PR_NUMBER }}
@@ -355,6 +351,7 @@ jobs:
- name: Install/Upgrade Helm chart
run: |
set -euo pipefail
helm dependency update --skip-refresh ./helm/coder
helm upgrade --install "pr${{ env.PR_NUMBER }}" ./helm/coder \
--namespace "pr${{ env.PR_NUMBER }}" \
--values ./pr-deploy-values.yaml \
@@ -419,7 +416,7 @@ jobs:
# Create template
cd ./.github/pr-deployments/template
coder templates create -y --variable namespace=pr${{ env.PR_NUMBER }} kubernetes
coder templates push -y --variable namespace=pr${{ env.PR_NUMBER }} kubernetes
# Create workspace
coder create --template="kubernetes" kube --parameter cpu=2 --parameter memory=4 --parameter home_disk_size=2 -y
@@ -444,7 +441,7 @@ jobs:
echo "Slack notification sent"
- name: Find Comment
uses: peter-evans/find-comment@v2
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ env.PR_NUMBER }}
@@ -453,7 +450,7 @@ jobs:
direction: last
- name: Comment on PR
uses: peter-evans/create-or-update-comment@v3
uses: peter-evans/create-or-update-comment@v4
env:
STATUS: ${{ needs.get_info.outputs.NEW == 'true' && 'Created' || 'Updated' }}
with:
+38 -71
View File
@@ -103,7 +103,7 @@ jobs:
- name: Install nfpm
run: |
set -euo pipefail
wget -O /tmp/nfpm.deb https://github.com/goreleaser/nfpm/releases/download/v2.18.1/nfpm_amd64.deb
wget -O /tmp/nfpm.deb https://github.com/goreleaser/nfpm/releases/download/v2.35.1/nfpm_2.35.1_amd64.deb
sudo dpkg -i /tmp/nfpm.deb
rm /tmp/nfpm.deb
@@ -281,13 +281,13 @@ jobs:
CODER_GPG_RELEASE_KEY_BASE64: ${{ secrets.GPG_RELEASE_KEY_BASE64 }}
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_ID_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Setup GCloud SDK
uses: "google-github-actions/setup-gcloud@v1"
uses: "google-github-actions/setup-gcloud@v2"
- name: Publish Helm Chart
if: ${{ !inputs.dry_run }}
@@ -306,7 +306,7 @@ jobs:
- name: Upload artifacts to actions (if dry-run)
if: ${{ inputs.dry_run }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: |
@@ -321,7 +321,7 @@ jobs:
- name: Start Packer builds
if: ${{ !inputs.dry_run }}
uses: peter-evans/repository-dispatch@v2
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.CDRCI_GITHUB_TOKEN }}
repository: coder/packages
@@ -408,6 +408,11 @@ jobs:
if: ${{ !inputs.dry_run }}
steps:
- name: Sync fork
run: gh repo sync cdrci/winget-pkgs -b master
env:
GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
with:
@@ -434,27 +439,26 @@ jobs:
$release_assets = gh release view --repo coder/coder "v${version}" --json assets | `
ConvertFrom-Json
# Get the installer URL from the release assets.
$installer_url = $release_assets.assets | `
# Get the installer URLs from the release assets.
$amd64_installer_url = $release_assets.assets | `
Where-Object name -Match ".*_windows_amd64_installer.exe$" | `
Select -ExpandProperty url
$amd64_zip_url = $release_assets.assets | `
Where-Object name -Match ".*_windows_amd64.zip$" | `
Select -ExpandProperty url
$arm64_zip_url = $release_assets.assets | `
Where-Object name -Match ".*_windows_arm64.zip$" | `
Select -ExpandProperty url
echo "Installer URL: ${installer_url}"
echo "amd64 Installer URL: ${amd64_installer_url}"
echo "amd64 zip URL: ${amd64_zip_url}"
echo "arm64 zip URL: ${arm64_zip_url}"
echo "Package version: ${version}"
# The URL "|X64" suffix forces the architecture as it cannot be
# sniffed properly from the URL. wingetcreate checks both the URL and
# binary magic bytes for the architecture and they need to both match,
# but they only check for `x64`, `win64` and `_64` in the URL. Our URL
# contains `amd64` which doesn't match sadly.
#
# wingetcreate will still do the binary magic bytes check, so if we
# accidentally change the architecture of the installer, it will fail
# submission.
.\wingetcreate.exe update Coder.Coder `
--submit `
--version "${version}" `
--urls "${installer_url}|X64" `
--urls "${amd64_installer_url}" "${amd64_zip_url}" "${arm64_zip_url}" `
--token "$env:WINGET_GH_TOKEN"
env:
@@ -481,65 +485,28 @@ jobs:
# different repo.
GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }}
publish-chocolatey:
name: Publish to Chocolatey
runs-on: windows-latest
# publish-sqlc pushes the latest schema to sqlc cloud.
# At present these pushes cannot be tagged, so the last push is always the latest.
publish-sqlc:
name: "Publish to schema sqlc cloud"
runs-on: "ubuntu-latest"
needs: release
if: ${{ !inputs.dry_run }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 1
# Same reason as for release.
- name: Fetch git tags
run: git fetch --tags --force
# We need golang to run the migration main.go
- name: Setup Go
uses: ./.github/actions/setup-go
# From https://chocolatey.org
- name: Install Chocolatey
- name: Setup sqlc
uses: ./.github/actions/setup-sqlc
- name: Push schema to sqlc cloud
# Don't block a release on this
continue-on-error: true
run: |
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
- name: Build chocolatey package
run: |
cd scripts/chocolatey
# The package version is the same as the tag minus the leading "v".
# The version in this output already has the leading "v" removed but
# we do it again to be safe.
$version = "${{ needs.release.outputs.version }}".Trim('v')
$release_assets = gh release view --repo coder/coder "v${version}" --json assets | `
ConvertFrom-Json
# Get the URL for the Windows ZIP from the release assets.
$zip_url = $release_assets.assets | `
Where-Object name -Match ".*_windows_amd64.zip$" | `
Select -ExpandProperty url
echo "ZIP URL: ${zip_url}"
echo "Package version: ${version}"
echo "Downloading ZIP..."
Invoke-WebRequest $zip_url -OutFile assets.zip
echo "Extracting ZIP..."
Expand-Archive assets.zip -DestinationPath assets/
# No need to specify nuspec if there's only one in the directory.
choco pack --version=$version binary_path=assets/coder.exe
choco apikey --api-key $env:CHOCO_API_KEY --source https://push.chocolatey.org/
# No need to specify nupkg if there's only one in the directory.
choco push --source https://push.chocolatey.org/
env:
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
# We need a GitHub token for the gh CLI to function under GitHub Actions
GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }}
make sqlc-push
+20 -18
View File
@@ -28,21 +28,21 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: go, javascript
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go, javascript
# Workaround to prevent CodeQL from building the dashboard.
- name: Remove Makefile
run: |
rm Makefile
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
- name: Send Slack notification on failure
if: ${{ failure() }}
@@ -75,7 +75,7 @@ jobs:
- name: Install yq
run: go run github.com/mikefarah/yq/v4@v4.30.6
- name: Install mockgen
run: go install github.com/golang/mock/mockgen@v1.6.0
run: go install go.uber.org/mock/mockgen@v0.4.0
- name: Install protoc-gen-go
run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30
- name: Install protoc-gen-go-drpc
@@ -113,16 +113,8 @@ jobs:
make -j "$image_job"
echo "image=$(cat "$image_job")" >> $GITHUB_OUTPUT
- name: Run Prisma Cloud image scan
uses: PaloAltoNetworks/prisma-cloud-scan@v1
with:
pcc_console_url: ${{ secrets.PRISMA_CLOUD_URL }}
pcc_user: ${{ secrets.PRISMA_CLOUD_ACCESS_KEY }}
pcc_pass: ${{ secrets.PRISMA_CLOUD_SECRET_KEY }}
image_name: ${{ steps.build.outputs.image }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@2b6a709cf9c4025c5438138008beaddbb02086f0
uses: aquasecurity/trivy-action@84384bd6e777ef152729993b8145ea352e9dd3ef
with:
image-ref: ${{ steps.build.outputs.image }}
format: sarif
@@ -130,18 +122,28 @@ jobs:
severity: "CRITICAL,HIGH"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
category: "Trivy"
- name: Upload Trivy scan results as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: trivy
path: trivy-results.sarif
retention-days: 7
# Prisma cloud scan runs last because it fails the entire job if it
# detects vulnerabilities. :|
- name: Run Prisma Cloud image scan
uses: PaloAltoNetworks/prisma-cloud-scan@v1
with:
pcc_console_url: ${{ secrets.PRISMA_CLOUD_URL }}
pcc_user: ${{ secrets.PRISMA_CLOUD_ACCESS_KEY }}
pcc_pass: ${{ secrets.PRISMA_CLOUD_SECRET_KEY }}
image_name: ${{ steps.build.outputs.image }}
- name: Send Slack notification on failure
if: ${{ failure() }}
run: |
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
actions: write
steps:
- name: stale
uses: actions/stale@v8.0.0
uses: actions/stale@v9.0.0
with:
stale-issue-label: "stale"
stale-pr-label: "stale"
@@ -68,7 +68,7 @@ jobs:
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not planned'
state_reason: 'not_planned'
});
}
} else {
+3
View File
@@ -14,6 +14,7 @@ darcula = "darcula"
Hashi = "Hashi"
trialer = "trialer"
encrypter = "encrypter"
hel = "hel" # as in helsinki
[files]
extend-exclude = [
@@ -29,4 +30,6 @@ extend-exclude = [
"**/*_test.go",
"**/*.test.tsx",
"**/pnpm-lock.yaml",
"tailnet/testdata/**",
"site/src/pages/SetupPage/countries.tsx",
]
+2
View File
@@ -82,6 +82,8 @@ helm/**/templates/*.yaml
# Testdata shouldn't be formatted.
scripts/apitypings/testdata/**/*.ts
enterprise/tailnet/testdata/*.golden.html
tailnet/testdata/*.golden.html
# Generated files shouldn't be formatted.
site/e2e/provisionerGenerated.ts
+2
View File
@@ -8,6 +8,8 @@ helm/**/templates/*.yaml
# Testdata shouldn't be formatted.
scripts/apitypings/testdata/**/*.ts
enterprise/tailnet/testdata/*.golden.html
tailnet/testdata/*.golden.html
# Generated files shouldn't be formatted.
site/e2e/provisionerGenerated.ts
+8 -3
View File
@@ -18,10 +18,11 @@
"coderdenttest",
"coderdtest",
"codersdk",
"contravariance",
"cronstrue",
"databasefake",
"dbmem",
"dbgen",
"dbmem",
"dbtype",
"DERP",
"derphttp",
@@ -59,6 +60,7 @@
"idtoken",
"Iflag",
"incpatch",
"initialisms",
"ipnstate",
"isatty",
"Jobf",
@@ -112,18 +114,19 @@
"Signup",
"slogtest",
"sourcemapped",
"spinbutton",
"Srcs",
"stdbuf",
"stretchr",
"STTY",
"stuntest",
"tanstack",
"tailbroker",
"tailcfg",
"tailexchange",
"tailnet",
"tailnettest",
"Tailscale",
"tanstack",
"tbody",
"TCGETS",
"tcpip",
@@ -140,6 +143,7 @@
"tios",
"tmpdir",
"tokenconfig",
"Topbar",
"tparallel",
"trialer",
"trimprefix",
@@ -167,9 +171,10 @@
"workspaceapps",
"workspacebuilds",
"workspacename",
"wsconncache",
"wsjson",
"xerrors",
"xlarge",
"xsmall",
"yamux"
],
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
+89 -15
View File
@@ -50,7 +50,7 @@ endif
# Note, all find statements should be written with `.` or `./path` as
# the search path so that these exclusions match.
FIND_EXCLUSIONS= \
-not \( \( -path '*/.git/*' -o -path './build/*' -o -path './vendor/*' -o -path './.coderv2/*' -o -path '*/node_modules/*' -o -path '*/out/*' -o -path './coderd/apidoc/*' -o -path '*/.next/*' \) -prune \)
-not \( \( -path '*/.git/*' -o -path './build/*' -o -path './vendor/*' -o -path './.coderv2/*' -o -path '*/node_modules/*' -o -path '*/out/*' -o -path './coderd/apidoc/*' -o -path '*/.next/*' -o -path '*/.terraform/*' \) -prune \)
# Source files used for make targets, evaluated on use.
GO_SRC_FILES := $(shell find . $(FIND_EXCLUSIONS) -type f -name '*.go' -not -name '*_test.go')
# All the shell files in the repo, excluding ignored files.
@@ -361,6 +361,8 @@ $(foreach chart,$(charts),build/$(chart)_helm_$(VERSION).tgz): build/%_helm_$(VE
site/out/index.html: site/package.json $(shell find ./site $(FIND_EXCLUSIONS) -type f \( -name '*.ts' -o -name '*.tsx' \))
cd site
# prevents this directory from getting to big, and causing "too much data" errors
rm -rf out/assets/
../scripts/pnpm_install.sh
pnpm build
@@ -380,32 +382,44 @@ install: build/coder_$(VERSION)_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT)
cp "$<" "$$output_file"
.PHONY: install
fmt: fmt/prettier fmt/terraform fmt/shfmt fmt/go
BOLD := $(shell tput bold)
GREEN := $(shell tput setaf 2)
RESET := $(shell tput sgr0)
fmt: fmt/eslint fmt/prettier fmt/terraform fmt/shfmt fmt/go
.PHONY: fmt
fmt/go:
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/go$(RESET)"
# VS Code users should check out
# https://github.com/mvdan/gofumpt#visual-studio-code
go run mvdan.cc/gofumpt@v0.4.0 -w -l .
.PHONY: fmt/go
fmt/eslint:
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/eslint$(RESET)"
cd site
pnpm run lint:fix
.PHONY: fmt/eslint
fmt/prettier:
echo "--- prettier"
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/prettier$(RESET)"
cd site
# Avoid writing files in CI to reduce file write activity
ifdef CI
pnpm run format:check
else
pnpm run format:write
pnpm run format
endif
.PHONY: fmt/prettier
fmt/terraform: $(wildcard *.tf)
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/terraform$(RESET)"
terraform fmt -recursive
.PHONY: fmt/terraform
fmt/shfmt: $(SHELL_SRC_FILES)
echo "--- shfmt"
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/shfmt$(RESET)"
# Only do diff check in CI, errors on diff.
ifdef CI
shfmt -d $(SHELL_SRC_FILES)
@@ -428,7 +442,8 @@ lint/ts:
lint/go:
./scripts/check_enterprise_imports.sh
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.2
linter_ver=$(shell egrep -o 'GOLANGCI_LINT_VERSION=\S+' dogfood/Dockerfile | cut -d '=' -f 2)
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$$linter_ver
golangci-lint run
.PHONY: lint/go
@@ -474,7 +489,10 @@ gen: \
site/.eslintignore \
site/e2e/provisionerGenerated.ts \
site/src/theme/icons.json \
examples/examples.gen.json
examples/examples.gen.json \
tailnet/tailnettest/coordinatormock.go \
tailnet/tailnettest/coordinateemock.go \
tailnet/tailnettest/multiagentmock.go
.PHONY: gen
# Mark all generated files as fresh so make thinks they're up-to-date. This is
@@ -501,6 +519,9 @@ gen/mark-fresh:
site/e2e/provisionerGenerated.ts \
site/src/theme/icons.json \
examples/examples.gen.json \
tailnet/tailnettest/coordinatormock.go \
tailnet/tailnettest/coordinateemock.go \
tailnet/tailnettest/multiagentmock.go \
"
for file in $$files; do
echo "$$file"
@@ -528,6 +549,9 @@ coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $
coderd/database/dbmock/dbmock.go: coderd/database/db.go coderd/database/querier.go
go generate ./coderd/database/dbmock/
tailnet/tailnettest/coordinatormock.go tailnet/tailnettest/multiagentmock.go tailnet/tailnettest/coordinateemock.go: tailnet/coordinator.go tailnet/multiagent.go
go generate ./tailnet/tailnettest/
tailnet/proto/tailnet.pb.go: tailnet/proto/tailnet.proto
protoc \
--go_out=. \
@@ -562,7 +586,8 @@ provisionerd/proto/provisionerd.pb.go: provisionerd/proto/provisionerd.proto
site/src/api/typesGenerated.ts: $(wildcard scripts/apitypings/*) $(shell find ./codersdk $(FIND_EXCLUSIONS) -type f -name '*.go')
go run ./scripts/apitypings/ > $@
pnpm run format:write:only "$@"
./scripts/pnpm_install.sh
pnpm exec prettier --write "$@"
site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go provisionersdk/proto/provisioner.pb.go
cd site
@@ -571,7 +596,8 @@ site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go provisio
site/src/theme/icons.json: $(wildcard scripts/gensite/*) $(wildcard site/static/icon/*)
go run ./scripts/gensite/ -icons "$@"
pnpm run format:write:only "$@"
./scripts/pnpm_install.sh
pnpm exec prettier --write "$@"
examples/examples.gen.json: scripts/examplegen/main.go examples/examples.go $(shell find ./examples/templates)
go run ./scripts/examplegen/main.go > examples/examples.gen.json
@@ -581,21 +607,34 @@ coderd/rbac/object_gen.go: scripts/rbacgen/main.go coderd/rbac/object.go
docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/metrics
go run scripts/metricsdocgen/main.go
pnpm run format:write:only ./docs/admin/prometheus.md
./scripts/pnpm_install.sh
pnpm exec prettier --write ./docs/admin/prometheus.md
docs/cli.md: scripts/clidocgen/main.go examples/examples.gen.json $(GO_SRC_FILES)
CI=true BASE_PATH="." go run ./scripts/clidocgen
pnpm run format:write:only ./docs/cli.md ./docs/cli/*.md ./docs/manifest.json
./scripts/pnpm_install.sh
pnpm exec prettier --write ./docs/cli.md ./docs/cli/*.md ./docs/manifest.json
docs/admin/audit-logs.md: scripts/auditdocgen/main.go enterprise/audit/table.go coderd/rbac/object_gen.go
docs/admin/audit-logs.md: coderd/database/querier.go scripts/auditdocgen/main.go enterprise/audit/table.go coderd/rbac/object_gen.go
go run scripts/auditdocgen/main.go
pnpm run format:write:only ./docs/admin/audit-logs.md
./scripts/pnpm_install.sh
pnpm exec prettier --write ./docs/admin/audit-logs.md
coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen $(FIND_EXCLUSIONS) -type f) $(wildcard coderd/*.go) $(wildcard enterprise/coderd/*.go) $(wildcard codersdk/*.go) $(wildcard enterprise/wsproxy/wsproxysdk/*.go) $(DB_GEN_FILES) .swaggo docs/manifest.json coderd/rbac/object_gen.go
./scripts/apidocgen/generate.sh
pnpm run format:write:only ./docs/api ./docs/manifest.json ./coderd/apidoc/swagger.json
./scripts/pnpm_install.sh
pnpm exec prettier --write ./docs/api ./docs/manifest.json ./coderd/apidoc/swagger.json
update-golden-files: cli/testdata/.gen-golden helm/coder/tests/testdata/.gen-golden helm/provisioner/tests/testdata/.gen-golden scripts/ci-report/testdata/.gen-golden enterprise/cli/testdata/.gen-golden coderd/.gen-golden provisioner/terraform/testdata/.gen-golden
update-golden-files: \
cli/testdata/.gen-golden \
helm/coder/tests/testdata/.gen-golden \
helm/provisioner/tests/testdata/.gen-golden \
scripts/ci-report/testdata/.gen-golden \
enterprise/cli/testdata/.gen-golden \
enterprise/tailnet/testdata/.gen-golden \
tailnet/testdata/.gen-golden \
coderd/.gen-golden \
provisioner/terraform/testdata/.gen-golden
.PHONY: update-golden-files
cli/testdata/.gen-golden: $(wildcard cli/testdata/*.golden) $(wildcard cli/*.tpl) $(GO_SRC_FILES) $(wildcard cli/*_test.go)
@@ -606,6 +645,14 @@ enterprise/cli/testdata/.gen-golden: $(wildcard enterprise/cli/testdata/*.golden
go test ./enterprise/cli -run="TestEnterpriseCommandHelp" -update
touch "$@"
tailnet/testdata/.gen-golden: $(wildcard tailnet/testdata/*.golden.html) $(GO_SRC_FILES) $(wildcard tailnet/*_test.go)
go test ./tailnet -run="TestDebugTemplate" -update
touch "$@"
enterprise/tailnet/testdata/.gen-golden: $(wildcard enterprise/tailnet/testdata/*.golden.html) $(GO_SRC_FILES) $(wildcard enterprise/tailnet/*_test.go)
go test ./enterprise/tailnet -run="TestDebugTemplate" -update
touch "$@"
helm/coder/tests/testdata/.gen-golden: $(wildcard helm/coder/tests/testdata/*.yaml) $(wildcard helm/coder/tests/testdata/*.golden) $(GO_SRC_FILES) $(wildcard helm/coder/tests/*_test.go)
go test ./helm/coder/tests -run=TestUpdateGoldenFiles -update
touch "$@"
@@ -690,6 +737,33 @@ test:
gotestsum --format standard-quiet -- -v -short -count=1 ./...
.PHONY: test
# sqlc-cloud-is-setup will fail if no SQLc auth token is set. Use this as a
# dependency for any sqlc-cloud related targets.
sqlc-cloud-is-setup:
if [[ "$(SQLC_AUTH_TOKEN)" == "" ]]; then
echo "ERROR: 'SQLC_AUTH_TOKEN' must be set to auth with sqlc cloud before running verify." 1>&2
exit 1
fi
.PHONY: sqlc-cloud-is-setup
sqlc-push: sqlc-cloud-is-setup test-postgres-docker
echo "--- sqlc push"
SQLC_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/$(shell go run scripts/migrate-ci/main.go)" \
sqlc push -f coderd/database/sqlc.yaml && echo "Passed sqlc push"
.PHONY: sqlc-push
sqlc-verify: sqlc-cloud-is-setup test-postgres-docker
echo "--- sqlc verify"
SQLC_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/$(shell go run scripts/migrate-ci/main.go)" \
sqlc verify -f coderd/database/sqlc.yaml && echo "Passed sqlc verify"
.PHONY: sqlc-verify
sqlc-vet: test-postgres-docker
echo "--- sqlc vet"
SQLC_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/$(shell go run scripts/migrate-ci/main.go)" \
sqlc vet -f coderd/database/sqlc.yaml && echo "Passed sqlc vet"
.PHONY: sqlc-vet
# When updating -timeout for this test, keep in sync with
# test-go-postgres (.github/workflows/coder.yaml).
# Do add coverage flags so that test caching works.
+5 -5
View File
@@ -7,7 +7,7 @@
</a>
<h1>
Self-Hosted Remote Development Environments
Self-Hosted Cloud Development Environments
</h1>
<a href="https://coder.com#gh-light-mode-only">
@@ -31,9 +31,9 @@
</div>
[Coder](https://coder.com) enables organizations to set up development environments in the cloud. Environments are defined with Terraform, connected through a secure high-speed Wireguard® tunnel, and are automatically shut down when not in use to save on costs. Coder gives engineering teams the flexibility to use the cloud for workloads that are most beneficial to them.
[Coder](https://coder.com) enables organizations to set up development environments in their public or private cloud infrastructure. Cloud development environments are defined with Terraform, connected through a secure high-speed Wireguard® tunnel, and are automatically shut down when not in use to save on costs. Coder gives engineering teams the flexibility to use the cloud for workloads that are most beneficial to them.
- Define development environments in Terraform
- Define cloud development environments in Terraform
- EC2 VMs, Kubernetes Pods, Docker Containers, etc.
- Automatically shutdown idle resources to save on costs
- Onboard developers in seconds instead of days
@@ -44,7 +44,7 @@
## Quickstart
The most convenient way to try Coder is to install it on your local machine and experiment with provisioning development environments using Docker (works on Linux, macOS, and Windows).
The most convenient way to try Coder is to install it on your local machine and experiment with provisioning cloud development environments using Docker (works on Linux, macOS, and Windows).
```
# First, install Coder
@@ -100,7 +100,7 @@ Browse our docs [here](https://coder.com/docs/v2) or visit a specific section be
Feel free to [open an issue](https://github.com/coder/coder/issues/new) if you have questions, run into bugs, or have a feature request.
[Join our Discord](https://discord.gg/coder) to provide feedback on in-progress features, and chat with the community using Coder!
[Join our Discord](https://discord.gg/coder) or [Slack](https://cdr.co/join-community) to provide feedback on in-progress features, and chat with the community using Coder!
## Contributing
+796 -404
View File
File diff suppressed because it is too large Load Diff
+488 -374
View File
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,10 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/coder/coder/v2/agent/agentproc (interfaces: Syscaller)
//
// Generated by this command:
//
// mockgen -destination ./syscallermock.go -package agentproctest github.com/coder/coder/v2/agent/agentproc Syscaller
//
// Package agentproctest is a generated GoMock package.
package agentproctest
@@ -8,7 +13,7 @@ import (
reflect "reflect"
syscall "syscall"
gomock "github.com/golang/mock/gomock"
gomock "go.uber.org/mock/gomock"
)
// MockSyscaller is a mock of Syscaller interface.
@@ -44,7 +49,7 @@ func (m *MockSyscaller) GetPriority(arg0 int32) (int, error) {
}
// GetPriority indicates an expected call of GetPriority.
func (mr *MockSyscallerMockRecorder) GetPriority(arg0 interface{}) *gomock.Call {
func (mr *MockSyscallerMockRecorder) GetPriority(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPriority", reflect.TypeOf((*MockSyscaller)(nil).GetPriority), arg0)
}
@@ -58,7 +63,7 @@ func (m *MockSyscaller) Kill(arg0 int32, arg1 syscall.Signal) error {
}
// Kill indicates an expected call of Kill.
func (mr *MockSyscallerMockRecorder) Kill(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockSyscallerMockRecorder) Kill(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Kill", reflect.TypeOf((*MockSyscaller)(nil).Kill), arg0, arg1)
}
@@ -72,7 +77,7 @@ func (m *MockSyscaller) SetPriority(arg0 int32, arg1 int) error {
}
// SetPriority indicates an expected call of SetPriority.
func (mr *MockSyscallerMockRecorder) SetPriority(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockSyscallerMockRecorder) SetPriority(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPriority", reflect.TypeOf((*MockSyscaller)(nil).SetPriority), arg0, arg1)
}
+4 -4
View File
@@ -7,18 +7,18 @@ import (
"github.com/spf13/afero"
)
func (p *Process) Niceness(sc Syscaller) (int, error) {
func (*Process) Niceness(Syscaller) (int, error) {
return 0, errUnimplemented
}
func (p *Process) SetNiceness(sc Syscaller, score int) error {
func (*Process) SetNiceness(Syscaller, int) error {
return errUnimplemented
}
func (p *Process) Cmd() string {
func (*Process) Cmd() string {
return ""
}
func List(fs afero.Fs, syscaller Syscaller) ([]*Process, error) {
func List(afero.Fs, Syscaller) ([]*Process, error) {
return nil, errUnimplemented
}
+1 -1
View File
@@ -5,9 +5,9 @@ import (
"syscall"
"testing"
"github.com/golang/mock/gomock"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/agent/agentproc"
+1
View File
@@ -10,6 +10,7 @@ type Syscaller interface {
Kill(pid int32, sig syscall.Signal) error
}
// nolint: unused // used on some but no all platforms
const defaultProcDir = "/proc"
type Process struct {
+3 -3
View File
@@ -17,14 +17,14 @@ var errUnimplemented = xerrors.New("unimplemented")
type nopSyscaller struct{}
func (nopSyscaller) SetPriority(pid int32, priority int) error {
func (nopSyscaller) SetPriority(int32, int) error {
return errUnimplemented
}
func (nopSyscaller) GetPriority(pid int32) (int, error) {
func (nopSyscaller) GetPriority(int32) (int, error) {
return 0, errUnimplemented
}
func (nopSyscaller) Kill(pid int32, sig syscall.Signal) error {
func (nopSyscaller) Kill(int32, syscall.Signal) error {
return errUnimplemented
}
+99 -13
View File
@@ -13,12 +13,15 @@ import (
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/robfig/cron/v3"
"github.com/spf13/afero"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
@@ -39,13 +42,19 @@ var (
parser = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.DowOptional)
)
type ScriptLogger interface {
Send(ctx context.Context, log ...agentsdk.Log) error
Flush(context.Context) error
}
// Options are a set of options for the runner.
type Options struct {
LogDir string
Logger slog.Logger
SSHServer *agentssh.Server
Filesystem afero.Fs
PatchLogs func(ctx context.Context, req agentsdk.PatchLogs) error
DataDirBase string
LogDir string
Logger slog.Logger
SSHServer *agentssh.Server
Filesystem afero.Fs
GetScriptLogger func(logSourceID uuid.UUID) ScriptLogger
}
// New creates a runner for the provided scripts.
@@ -57,6 +66,12 @@ func New(opts Options) *Runner {
cronCtxCancel: cronCtxCancel,
cron: cron.New(cron.WithParser(parser)),
closed: make(chan struct{}),
dataDir: filepath.Join(opts.DataDirBase, "coder-script-data"),
scriptsExecuted: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "agent",
Subsystem: "scripts",
Name: "executed_total",
}, []string{"success"}),
}
}
@@ -71,6 +86,31 @@ type Runner struct {
cron *cron.Cron
initialized atomic.Bool
scripts []codersdk.WorkspaceAgentScript
dataDir string
// scriptsExecuted includes all scripts executed by the workspace agent. Agents
// execute startup scripts, and scripts on a cron schedule. Both will increment
// this counter.
scriptsExecuted *prometheus.CounterVec
}
// DataDir returns the directory where scripts data is stored.
func (r *Runner) DataDir() string {
return r.dataDir
}
// ScriptBinDir returns the directory where scripts can store executable
// binaries.
func (r *Runner) ScriptBinDir() string {
return filepath.Join(r.dataDir, "bin")
}
func (r *Runner) RegisterMetrics(reg prometheus.Registerer) {
if reg == nil {
// If no registry, do nothing.
return
}
reg.MustRegister(r.scriptsExecuted)
}
// Init initializes the runner with the provided scripts.
@@ -84,13 +124,18 @@ func (r *Runner) Init(scripts []codersdk.WorkspaceAgentScript) error {
r.scripts = scripts
r.Logger.Info(r.cronCtx, "initializing agent scripts", slog.F("script_count", len(scripts)), slog.F("log_dir", r.LogDir))
err := r.Filesystem.MkdirAll(r.ScriptBinDir(), 0o700)
if err != nil {
return xerrors.Errorf("create script bin dir: %w", err)
}
for _, script := range scripts {
if script.Cron == "" {
continue
}
script := script
_, err := r.cron.AddFunc(script.Cron, func() {
err := r.run(r.cronCtx, script)
err := r.trackRun(r.cronCtx, script)
if err != nil {
r.Logger.Warn(context.Background(), "run agent script on schedule", slog.Error(err))
}
@@ -109,7 +154,18 @@ func (r *Runner) StartCron() {
// has exited by the time the `cron.Stop()` context returns, so we need to
// track it manually.
err := r.trackCommandGoroutine(func() {
r.cron.Run()
// Since this is run async, in quick unit tests, it is possible the
// Close() function gets called before we even start the cron.
// In these cases, the Run() will never end.
// So if we are closed, we just return, and skip the Run() entirely.
select {
case <-r.cronCtx.Done():
// The cronCtx is canceled before cron.Close() happens. So if the ctx is
// canceled, then Close() will be called, or it is about to be called.
// So do nothing!
default:
r.cron.Run()
}
})
if err != nil {
r.Logger.Warn(context.Background(), "start cron failed", slog.Error(err))
@@ -131,7 +187,7 @@ func (r *Runner) Execute(ctx context.Context, filter func(script codersdk.Worksp
}
script := script
eg.Go(func() error {
err := r.run(ctx, script)
err := r.trackRun(ctx, script)
if err != nil {
return xerrors.Errorf("run agent script %q: %w", script.LogSourceID, err)
}
@@ -141,6 +197,17 @@ func (r *Runner) Execute(ctx context.Context, filter func(script codersdk.Worksp
return eg.Wait()
}
// trackRun wraps "run" with metrics.
func (r *Runner) trackRun(ctx context.Context, script codersdk.WorkspaceAgentScript) error {
err := r.run(ctx, script)
if err != nil {
r.scriptsExecuted.WithLabelValues("false").Add(1)
} else {
r.scriptsExecuted.WithLabelValues("true").Add(1)
}
return err
}
// run executes the provided script with the timeout.
// If the timeout is exceeded, the process is sent an interrupt signal.
// If the process does not exit after a few seconds, it is forcefully killed.
@@ -166,7 +233,18 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
if !filepath.IsAbs(logPath) {
logPath = filepath.Join(r.LogDir, logPath)
}
logger := r.Logger.With(slog.F("log_path", logPath))
scriptDataDir := filepath.Join(r.DataDir(), script.LogSourceID.String())
err := r.Filesystem.MkdirAll(scriptDataDir, 0o700)
if err != nil {
return xerrors.Errorf("%s script: create script temp dir: %w", scriptDataDir, err)
}
logger := r.Logger.With(
slog.F("log_source_id", script.LogSourceID),
slog.F("log_path", logPath),
slog.F("script_data_dir", scriptDataDir),
)
logger.Info(ctx, "running agent script", slog.F("script", script.Script))
fileWriter, err := r.Filesystem.OpenFile(logPath, os.O_CREATE|os.O_RDWR, 0o600)
@@ -196,20 +274,27 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
cmd.WaitDelay = 10 * time.Second
cmd.Cancel = cmdCancel(cmd)
send, flushAndClose := agentsdk.LogsSender(script.LogSourceID, r.PatchLogs, logger)
// Expose env vars that can be used in the script for storing data
// and binaries. In the future, we may want to expose more env vars
// for the script to use, like CODER_SCRIPT_DATA_DIR for persistent
// storage.
cmd.Env = append(cmd.Env, "CODER_SCRIPT_DATA_DIR="+scriptDataDir)
cmd.Env = append(cmd.Env, "CODER_SCRIPT_BIN_DIR="+r.ScriptBinDir())
scriptLogger := r.GetScriptLogger(script.LogSourceID)
// If ctx is canceled here (or in a writer below), we may be
// discarding logs, but that's okay because we're shutting down
// anyway. We could consider creating a new context here if we
// want better control over flush during shutdown.
defer func() {
if err := flushAndClose(ctx); err != nil {
if err := scriptLogger.Flush(ctx); err != nil {
logger.Warn(ctx, "flush startup logs failed", slog.Error(err))
}
}()
infoW := agentsdk.LogsWriter(ctx, send, script.LogSourceID, codersdk.LogLevelInfo)
infoW := agentsdk.LogsWriter(ctx, scriptLogger.Send, script.LogSourceID, codersdk.LogLevelInfo)
defer infoW.Close()
errW := agentsdk.LogsWriter(ctx, send, script.LogSourceID, codersdk.LogLevelError)
errW := agentsdk.LogsWriter(ctx, scriptLogger.Send, script.LogSourceID, codersdk.LogLevelError)
defer errW.Close()
cmd.Stdout = io.MultiWriter(fileWriter, infoW)
cmd.Stderr = io.MultiWriter(fileWriter, errW)
@@ -284,6 +369,7 @@ func (r *Runner) Close() error {
return nil
}
close(r.closed)
// Must cancel the cron ctx BEFORE stopping the cron.
r.cronCtxCancel()
<-r.cron.Stop().Done()
r.cmdCloseWait.Wait()
+123 -22
View File
@@ -2,13 +2,16 @@ package agentscripts_test
import (
"context"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
"go.uber.org/goleak"
"cdr.dev/slog/sloggers/slogtest"
@@ -16,6 +19,7 @@ import (
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/testutil"
)
func TestMain(m *testing.M) {
@@ -24,21 +28,75 @@ func TestMain(m *testing.M) {
func TestExecuteBasic(t *testing.T) {
t.Parallel()
logs := make(chan agentsdk.PatchLogs, 1)
runner := setup(t, func(ctx context.Context, req agentsdk.PatchLogs) error {
logs <- req
return nil
ctx := testutil.Context(t, testutil.WaitShort)
fLogger := newFakeScriptLogger()
runner := setup(t, func(uuid2 uuid.UUID) agentscripts.ScriptLogger {
return fLogger
})
defer runner.Close()
err := runner.Init([]codersdk.WorkspaceAgentScript{{
Script: "echo hello",
LogSourceID: uuid.New(),
Script: "echo hello",
}})
require.NoError(t, err)
require.NoError(t, runner.Execute(context.Background(), func(script codersdk.WorkspaceAgentScript) bool {
return true
}))
log := <-logs
require.Equal(t, "hello", log.Logs[0].Output)
log := testutil.RequireRecvCtx(ctx, t, fLogger.logs)
require.Equal(t, "hello", log.Output)
}
func TestEnv(t *testing.T) {
t.Parallel()
fLogger := newFakeScriptLogger()
runner := setup(t, func(uuid2 uuid.UUID) agentscripts.ScriptLogger {
return fLogger
})
defer runner.Close()
id := uuid.New()
script := "echo $CODER_SCRIPT_DATA_DIR\necho $CODER_SCRIPT_BIN_DIR\n"
if runtime.GOOS == "windows" {
script = `
cmd.exe /c echo %CODER_SCRIPT_DATA_DIR%
cmd.exe /c echo %CODER_SCRIPT_BIN_DIR%
`
}
err := runner.Init([]codersdk.WorkspaceAgentScript{{
LogSourceID: id,
Script: script,
}})
require.NoError(t, err)
ctx := testutil.Context(t, testutil.WaitLong)
done := testutil.Go(t, func() {
err := runner.Execute(ctx, func(script codersdk.WorkspaceAgentScript) bool {
return true
})
assert.NoError(t, err)
})
defer func() {
select {
case <-ctx.Done():
case <-done:
}
}()
var log []agentsdk.Log
for {
select {
case <-ctx.Done():
require.Fail(t, "timed out waiting for logs")
case l := <-fLogger.logs:
t.Logf("log: %s", l.Output)
log = append(log, l)
}
if len(log) >= 2 {
break
}
}
require.Contains(t, log[0].Output, filepath.Join(runner.DataDir(), id.String()))
require.Contains(t, log[1].Output, runner.ScriptBinDir())
}
func TestTimeout(t *testing.T) {
@@ -46,35 +104,78 @@ func TestTimeout(t *testing.T) {
runner := setup(t, nil)
defer runner.Close()
err := runner.Init([]codersdk.WorkspaceAgentScript{{
Script: "sleep infinity",
Timeout: time.Millisecond,
LogSourceID: uuid.New(),
Script: "sleep infinity",
Timeout: time.Millisecond,
}})
require.NoError(t, err)
require.ErrorIs(t, runner.Execute(context.Background(), nil), agentscripts.ErrTimeout)
}
func setup(t *testing.T, patchLogs func(ctx context.Context, req agentsdk.PatchLogs) error) *agentscripts.Runner {
// TestCronClose exists because cron.Run() can happen after cron.Close().
// If this happens, there used to be a deadlock.
func TestCronClose(t *testing.T) {
t.Parallel()
runner := agentscripts.New(agentscripts.Options{})
runner.StartCron()
require.NoError(t, runner.Close(), "close runner")
}
func setup(t *testing.T, getScriptLogger func(logSourceID uuid.UUID) agentscripts.ScriptLogger) *agentscripts.Runner {
t.Helper()
if patchLogs == nil {
if getScriptLogger == nil {
// noop
patchLogs = func(ctx context.Context, req agentsdk.PatchLogs) error {
return nil
getScriptLogger = func(uuid uuid.UUID) agentscripts.ScriptLogger {
return noopScriptLogger{}
}
}
fs := afero.NewMemMapFs()
logger := slogtest.Make(t, nil)
s, err := agentssh.NewServer(context.Background(), logger, prometheus.NewRegistry(), fs, 0, "")
s, err := agentssh.NewServer(context.Background(), logger, prometheus.NewRegistry(), fs, nil)
require.NoError(t, err)
s.AgentToken = func() string { return "" }
s.Manifest = atomic.NewPointer(&agentsdk.Manifest{})
t.Cleanup(func() {
_ = s.Close()
})
return agentscripts.New(agentscripts.Options{
LogDir: t.TempDir(),
Logger: logger,
SSHServer: s,
Filesystem: fs,
PatchLogs: patchLogs,
LogDir: t.TempDir(),
DataDirBase: t.TempDir(),
Logger: logger,
SSHServer: s,
Filesystem: fs,
GetScriptLogger: getScriptLogger,
})
}
type noopScriptLogger struct{}
func (noopScriptLogger) Send(context.Context, ...agentsdk.Log) error {
return nil
}
func (noopScriptLogger) Flush(context.Context) error {
return nil
}
type fakeScriptLogger struct {
logs chan agentsdk.Log
}
func (f *fakeScriptLogger) Send(ctx context.Context, logs ...agentsdk.Log) error {
for _, log := range logs {
select {
case <-ctx.Done():
return ctx.Err()
case f.logs <- log:
// OK!
}
}
return nil
}
func (*fakeScriptLogger) Flush(context.Context) error {
return nil
}
func newFakeScriptLogger() *fakeScriptLogger {
return &fakeScriptLogger{make(chan agentsdk.Log, 100)}
}
+165 -89
View File
@@ -32,7 +32,6 @@ import (
"github.com/coder/coder/v2/agent/usershell"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/pty"
)
@@ -47,10 +46,36 @@ const (
MagicSessionTypeEnvironmentVariable = "CODER_SSH_SESSION_TYPE"
// MagicSessionTypeVSCode is set in the SSH config by the VS Code extension to identify itself.
MagicSessionTypeVSCode = "vscode"
// MagicSessionTypeJetBrains is set in the SSH config by the JetBrains extension to identify itself.
// MagicSessionTypeJetBrains is set in the SSH config by the JetBrains
// extension to identify itself.
MagicSessionTypeJetBrains = "jetbrains"
// MagicProcessCmdlineJetBrains is a string in a process's command line that
// uniquely identifies it as JetBrains software.
MagicProcessCmdlineJetBrains = "idea.vendor.name=JetBrains"
)
// Config sets configuration parameters for the agent SSH server.
type Config struct {
// MaxTimeout sets the absolute connection timeout, none if empty. If set to
// 3 seconds or more, keep alive will be used instead.
MaxTimeout time.Duration
// MOTDFile returns the path to the message of the day file. If set, the
// file will be displayed to the user upon login.
MOTDFile func() string
// ServiceBanner returns the configuration for the Coder service banner.
ServiceBanner func() *codersdk.ServiceBannerConfig
// UpdateEnv updates the environment variables for the command to be
// executed. It can be used to add, modify or replace environment variables.
UpdateEnv func(current []string) (updated []string, err error)
// WorkingDirectory sets the working directory for commands and defines
// where users will land when they connect via SSH. Default is the home
// directory of the user.
WorkingDirectory func() string
// X11SocketDir is the directory where X11 sockets are created. Default is
// /tmp/.X11-unix.
X11SocketDir string
}
type Server struct {
mu sync.RWMutex // Protects following.
fs afero.Fs
@@ -62,14 +87,10 @@ type Server struct {
// a lock on mu but protected by closing.
wg sync.WaitGroup
logger slog.Logger
srv *ssh.Server
x11SocketDir string
logger slog.Logger
srv *ssh.Server
Env map[string]string
AgentToken func() string
Manifest *atomic.Pointer[agentsdk.Manifest]
ServiceBanner *atomic.Pointer[codersdk.ServiceBannerConfig]
config *Config
connCountVSCode atomic.Int64
connCountJetBrains atomic.Int64
@@ -78,7 +99,7 @@ type Server struct {
metrics *sshServerMetrics
}
func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prometheus.Registry, fs afero.Fs, maxTimeout time.Duration, x11SocketDir string) (*Server, error) {
func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prometheus.Registry, fs afero.Fs, config *Config) (*Server, error) {
// Clients' should ignore the host key when connecting.
// The agent needs to authenticate with coderd to SSH,
// so SSH authentication doesn't improve security.
@@ -90,28 +111,54 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
if err != nil {
return nil, err
}
if x11SocketDir == "" {
x11SocketDir = filepath.Join(os.TempDir(), ".X11-unix")
if config == nil {
config = &Config{}
}
if config.X11SocketDir == "" {
config.X11SocketDir = filepath.Join(os.TempDir(), ".X11-unix")
}
if config.UpdateEnv == nil {
config.UpdateEnv = func(current []string) ([]string, error) { return current, nil }
}
if config.MOTDFile == nil {
config.MOTDFile = func() string { return "" }
}
if config.ServiceBanner == nil {
config.ServiceBanner = func() *codersdk.ServiceBannerConfig { return &codersdk.ServiceBannerConfig{} }
}
if config.WorkingDirectory == nil {
config.WorkingDirectory = func() string {
home, err := userHomeDir()
if err != nil {
return ""
}
return home
}
}
forwardHandler := &ssh.ForwardedTCPHandler{}
unixForwardHandler := &forwardedUnixHandler{log: logger}
unixForwardHandler := newForwardedUnixHandler(logger)
metrics := newSSHServerMetrics(prometheusRegistry)
s := &Server{
listeners: make(map[net.Listener]struct{}),
fs: fs,
conns: make(map[net.Conn]struct{}),
sessions: make(map[ssh.Session]struct{}),
logger: logger,
x11SocketDir: x11SocketDir,
listeners: make(map[net.Listener]struct{}),
fs: fs,
conns: make(map[net.Conn]struct{}),
sessions: make(map[ssh.Session]struct{}),
logger: logger,
config: config,
metrics: metrics,
}
srv := &ssh.Server{
ChannelHandlers: map[string]ssh.ChannelHandler{
"direct-tcpip": ssh.DirectTCPIPHandler,
"direct-tcpip": func(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
// Wrapper is designed to find and track JetBrains Gateway connections.
wrapped := NewJetbrainsChannelWatcher(ctx, s.logger, newChan, &s.connCountJetBrains)
ssh.DirectTCPIPHandler(srv, conn, wrapped, ctx)
},
"direct-streamlocal@openssh.com": directStreamLocalHandler,
"session": ssh.DefaultSessionHandler,
},
@@ -142,7 +189,7 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
},
ReversePortForwardingCallback: func(ctx ssh.Context, bindHost string, bindPort uint32) bool {
// Allow reverse port forwarding all!
s.logger.Debug(ctx, "local port forward",
s.logger.Debug(ctx, "reverse port forward",
slog.F("bind_host", bindHost),
slog.F("bind_port", bindPort))
return true
@@ -164,14 +211,16 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
},
}
// The MaxTimeout functionality has been substituted with the introduction of the KeepAlive feature.
// In cases where very short timeouts are set, the SSH server will automatically switch to the connection timeout for both read and write operations.
if maxTimeout >= 3*time.Second {
// The MaxTimeout functionality has been substituted with the introduction
// of the KeepAlive feature. In cases where very short timeouts are set, the
// SSH server will automatically switch to the connection timeout for both
// read and write operations.
if config.MaxTimeout >= 3*time.Second {
srv.ClientAliveCountMax = 3
srv.ClientAliveInterval = maxTimeout / time.Duration(srv.ClientAliveCountMax)
srv.ClientAliveInterval = config.MaxTimeout / time.Duration(srv.ClientAliveCountMax)
srv.MaxTimeout = 0
} else {
srv.MaxTimeout = maxTimeout
srv.MaxTimeout = config.MaxTimeout
}
s.srv = srv
@@ -237,8 +286,29 @@ func (s *Server) sessionHandler(session ssh.Session) {
err := s.sessionStart(logger, session, extraEnv)
var exitError *exec.ExitError
if xerrors.As(err, &exitError) {
logger.Info(ctx, "ssh session returned", slog.Error(exitError))
_ = session.Exit(exitError.ExitCode())
code := exitError.ExitCode()
if code == -1 {
// If we return -1 here, it will be transmitted as an
// uint32(4294967295). This exit code is nonsense, so
// instead we return 255 (same as OpenSSH). This is
// also the same exit code that the shell returns for
// -1.
//
// For signals, we could consider sending 128+signal
// instead (however, OpenSSH doesn't seem to do this).
code = 255
}
logger.Info(ctx, "ssh session returned",
slog.Error(exitError),
slog.F("process_exit_code", exitError.ExitCode()),
slog.F("exit_code", code),
)
// TODO(mafredri): For signal exit, there's also an "exit-signal"
// request (session.Exit sends "exit-status"), however, since it's
// not implemented on the session interface and not used by
// OpenSSH, we'll leave it for now.
_ = session.Exit(code)
return
}
if err != nil {
@@ -270,8 +340,8 @@ func (s *Server) sessionStart(logger slog.Logger, session ssh.Session, extraEnv
s.connCountVSCode.Add(1)
defer s.connCountVSCode.Add(-1)
case MagicSessionTypeJetBrains:
s.connCountJetBrains.Add(1)
defer s.connCountJetBrains.Add(-1)
// Do nothing here because JetBrains launches hundreds of ssh sessions.
// We instead track JetBrains in the single persistent tcp forwarding channel.
case "":
s.connCountSSHSession.Add(1)
defer s.connCountSSHSession.Add(-1)
@@ -311,10 +381,10 @@ func (s *Server) sessionStart(logger slog.Logger, session ssh.Session, extraEnv
if isPty {
return s.startPTYSession(logger, session, magicTypeLabel, cmd, sshPty, windowSize)
}
return s.startNonPTYSession(session, magicTypeLabel, cmd.AsExec())
return s.startNonPTYSession(logger, session, magicTypeLabel, cmd.AsExec())
}
func (s *Server) startNonPTYSession(session ssh.Session, magicTypeLabel string, cmd *exec.Cmd) error {
func (s *Server) startNonPTYSession(logger slog.Logger, session ssh.Session, magicTypeLabel string, cmd *exec.Cmd) error {
s.metrics.sessionsTotal.WithLabelValues(magicTypeLabel, "no").Add(1)
cmd.Stdout = session
@@ -338,6 +408,17 @@ func (s *Server) startNonPTYSession(session ssh.Session, magicTypeLabel string,
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "no", "start_command").Add(1)
return xerrors.Errorf("start: %w", err)
}
sigs := make(chan ssh.Signal, 1)
session.Signals(sigs)
defer func() {
session.Signals(nil)
close(sigs)
}()
go func() {
for sig := range sigs {
s.handleSignal(logger, sig, cmd.Process, magicTypeLabel)
}
}()
return cmd.Wait()
}
@@ -348,6 +429,7 @@ type ptySession interface {
Context() ssh.Context
DisablePTYEmulation()
RawCommand() string
Signals(chan<- ssh.Signal)
}
func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTypeLabel string, cmd *pty.Cmd, sshPty ssh.Pty, windowSize <-chan ssh.Window) (retErr error) {
@@ -359,7 +441,7 @@ func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTy
session.DisablePTYEmulation()
if isLoginShell(session.RawCommand()) {
serviceBanner := s.ServiceBanner.Load()
serviceBanner := s.config.ServiceBanner()
if serviceBanner != nil {
err := showServiceBanner(session, serviceBanner)
if err != nil {
@@ -370,15 +452,10 @@ func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTy
}
if !isQuietLogin(s.fs, session.RawCommand()) {
manifest := s.Manifest.Load()
if manifest != nil {
err := showMOTD(s.fs, session, manifest.MOTDFile)
if err != nil {
logger.Error(ctx, "agent failed to show MOTD", slog.Error(err))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "motd").Add(1)
}
} else {
logger.Warn(ctx, "metadata lookup failed, unable to show MOTD")
err := showMOTD(s.fs, session, s.config.MOTDFile())
if err != nil {
logger.Error(ctx, "agent failed to show MOTD", slog.Error(err))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "motd").Add(1)
}
}
@@ -403,13 +480,36 @@ func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTy
}
}
}()
sigs := make(chan ssh.Signal, 1)
session.Signals(sigs)
defer func() {
session.Signals(nil)
close(sigs)
}()
go func() {
for win := range windowSize {
resizeErr := ptty.Resize(uint16(win.Height), uint16(win.Width))
// If the pty is closed, then command has exited, no need to log.
if resizeErr != nil && !errors.Is(resizeErr, pty.ErrClosed) {
logger.Warn(ctx, "failed to resize tty", slog.Error(resizeErr))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "resize").Add(1)
for {
if sigs == nil && windowSize == nil {
return
}
select {
case sig, ok := <-sigs:
if !ok {
sigs = nil
continue
}
s.handleSignal(logger, sig, process, magicTypeLabel)
case win, ok := <-windowSize:
if !ok {
windowSize = nil
continue
}
resizeErr := ptty.Resize(uint16(win.Height), uint16(win.Width))
// If the pty is closed, then command has exited, no need to log.
if resizeErr != nil && !errors.Is(resizeErr, pty.ErrClosed) {
logger.Warn(ctx, "failed to resize tty", slog.Error(resizeErr))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "resize").Add(1)
}
}
}
}()
@@ -452,6 +552,18 @@ func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTy
return nil
}
func (s *Server) handleSignal(logger slog.Logger, ssig ssh.Signal, signaler interface{ Signal(os.Signal) error }, magicTypeLabel string) {
ctx := context.Background()
sig := osSignalFrom(ssig)
logger = logger.With(slog.F("ssh_signal", ssig), slog.F("signal", sig.String()))
logger.Info(ctx, "received signal from client")
err := signaler.Signal(sig)
if err != nil {
logger.Warn(ctx, "signaling the process failed", slog.Error(err))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "signal").Add(1)
}
}
func (s *Server) sftpHandler(logger slog.Logger, session ssh.Session) {
s.metrics.sftpConnectionsTotal.Add(1)
@@ -481,7 +593,7 @@ func (s *Server) sftpHandler(logger slog.Logger, session ssh.Session) {
defer server.Close()
err = server.Serve()
if errors.Is(err, io.EOF) {
if err == nil || errors.Is(err, io.EOF) {
// Unless we call `session.Exit(0)` here, the client won't
// receive `exit-status` because `(*sftp.Server).Close()`
// calls `Close()` on the underlying connection (session),
@@ -513,11 +625,6 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
return nil, xerrors.Errorf("get user shell: %w", err)
}
manifest := s.Manifest.Load()
if manifest == nil {
return nil, xerrors.Errorf("no metadata was provided")
}
// OpenSSH executes all commands with the users current shell.
// We replicate that behavior for IDE support.
caller := "-c"
@@ -562,7 +669,7 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
}
cmd := pty.CommandContext(ctx, name, args...)
cmd.Dir = manifest.Directory
cmd.Dir = s.config.WorkingDirectory()
// If the metadata directory doesn't exist, we run the command
// in the users home directory.
@@ -576,21 +683,7 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
cmd.Dir = homedir
}
cmd.Env = append(os.Environ(), env...)
executablePath, err := os.Executable()
if err != nil {
return nil, xerrors.Errorf("getting os executable: %w", err)
}
// Set environment variables reliable detection of being inside a
// Coder workspace.
cmd.Env = append(cmd.Env, "CODER=true")
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
// Git on Windows resolves with UNIX-style paths.
// If using backslashes, it's unable to find the executable.
unixExecutablePath := strings.ReplaceAll(executablePath, "\\", "/")
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, unixExecutablePath))
// Specific Coder subcommands require the agent token exposed!
cmd.Env = append(cmd.Env, fmt.Sprintf("CODER_AGENT_TOKEN=%s", s.AgentToken()))
// Set SSH connection environment variables (these are also set by OpenSSH
// and thus expected to be present by SSH clients). Since the agent does
@@ -601,26 +694,9 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CLIENT=%s %s %s", srcAddr, srcPort, dstPort))
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CONNECTION=%s %s %s %s", srcAddr, srcPort, dstAddr, dstPort))
// This adds the ports dialog to code-server that enables
// proxying a port dynamically.
cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", manifest.VSCodePortProxyURI))
// Hide Coder message on code-server's "Getting Started" page
cmd.Env = append(cmd.Env, "CS_DISABLE_GETTING_STARTED_OVERRIDE=true")
// Load environment variables passed via the agent.
// These should override all variables we manually specify.
for envKey, value := range manifest.EnvironmentVariables {
// Expanding environment variables allows for customization
// of the $PATH, among other variables. Customers can prepend
// or append to the $PATH, so allowing expand is required!
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envKey, os.ExpandEnv(value)))
}
// Agent-level environment variables should take over all!
// This is used for setting agent-specific variables like "CODER_AGENT_TOKEN".
for envKey, value := range s.Env {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envKey, value))
cmd.Env, err = s.config.UpdateEnv(cmd.Env)
if err != nil {
return nil, xerrors.Errorf("apply env: %w", err)
}
return cmd, nil
+10 -1
View File
@@ -37,7 +37,7 @@ func Test_sessionStart_orphan(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
logger := slogtest.Make(t, nil)
s, err := NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), 0, "")
s, err := NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), nil)
require.NoError(t, err)
defer s.Close()
@@ -114,6 +114,11 @@ type testSSHContext struct {
context.Context
}
var (
_ gliderssh.Context = testSSHContext{}
_ ptySession = &testSession{}
)
func newTestSession(ctx context.Context) (toClient *io.PipeReader, fromClient *io.PipeWriter, s ptySession) {
toClient, fromPty := io.Pipe()
toPty, fromClient := io.Pipe()
@@ -144,6 +149,10 @@ func (s *testSession) Write(p []byte) (n int, err error) {
return s.fromPty.Write(p)
}
func (*testSession) Signals(_ chan<- gliderssh.Signal) {
// Not implemented, but will be called.
}
func (testSSHContext) Lock() {
panic("not implemented")
}
+153 -16
View File
@@ -3,8 +3,10 @@
package agentssh_test
import (
"bufio"
"bytes"
"context"
"fmt"
"net"
"runtime"
"strings"
@@ -15,15 +17,14 @@ import (
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
"go.uber.org/goleak"
"golang.org/x/crypto/ssh"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/coder/v2/testutil"
)
func TestMain(m *testing.M) {
@@ -35,14 +36,10 @@ func TestNewServer_ServeClient(t *testing.T) {
ctx := context.Background()
logger := slogtest.Make(t, nil)
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), 0, "")
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), nil)
require.NoError(t, err)
defer s.Close()
// The assumption is that these are set before serving SSH connections.
s.AgentToken = func() string { return "" }
s.Manifest = atomic.NewPointer(&agentsdk.Manifest{})
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
@@ -57,8 +54,8 @@ func TestNewServer_ServeClient(t *testing.T) {
var b bytes.Buffer
sess, err := c.NewSession()
sess.Stdout = &b
require.NoError(t, err)
sess.Stdout = &b
err = sess.Start("echo hello")
require.NoError(t, err)
@@ -80,13 +77,11 @@ func TestNewServer_ExecuteShebang(t *testing.T) {
ctx := context.Background()
logger := slogtest.Make(t, nil)
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), 0, "")
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), nil)
require.NoError(t, err)
t.Cleanup(func() {
_ = s.Close()
})
s.AgentToken = func() string { return "" }
s.Manifest = atomic.NewPointer(&agentsdk.Manifest{})
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -113,14 +108,10 @@ func TestNewServer_CloseActiveConnections(t *testing.T) {
ctx := context.Background()
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), 0, "")
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), nil)
require.NoError(t, err)
defer s.Close()
// The assumption is that these are set before serving SSH connections.
s.AgentToken = func() string { return "" }
s.Manifest = atomic.NewPointer(&agentsdk.Manifest{})
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
@@ -139,6 +130,7 @@ func TestNewServer_CloseActiveConnections(t *testing.T) {
defer wg.Done()
c := sshClient(t, ln.Addr().String())
sess, err := c.NewSession()
assert.NoError(t, err)
sess.Stdin = pty.Input()
sess.Stdout = pty.Output()
sess.Stderr = pty.Output()
@@ -159,6 +151,151 @@ func TestNewServer_CloseActiveConnections(t *testing.T) {
wg.Wait()
}
func TestNewServer_Signal(t *testing.T) {
t.Parallel()
t.Run("Stdout", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
logger := slogtest.Make(t, nil)
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), nil)
require.NoError(t, err)
defer s.Close()
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
done := make(chan struct{})
go func() {
defer close(done)
err := s.Serve(ln)
assert.Error(t, err) // Server is closed.
}()
defer func() {
err := s.Close()
require.NoError(t, err)
<-done
}()
c := sshClient(t, ln.Addr().String())
sess, err := c.NewSession()
require.NoError(t, err)
r, err := sess.StdoutPipe()
require.NoError(t, err)
// Perform multiple sleeps since the interrupt signal doesn't propagate to
// the process group, this lets us exit early.
sleeps := strings.Repeat("sleep 1 && ", int(testutil.WaitMedium.Seconds()))
err = sess.Start(fmt.Sprintf("echo hello && %s echo bye", sleeps))
require.NoError(t, err)
sc := bufio.NewScanner(r)
for sc.Scan() {
t.Log(sc.Text())
if strings.Contains(sc.Text(), "hello") {
break
}
}
require.NoError(t, sc.Err())
err = sess.Signal(ssh.SIGKILL)
require.NoError(t, err)
// Assumption, signal propagates and the command exists, closing stdout.
for sc.Scan() {
t.Log(sc.Text())
require.NotContains(t, sc.Text(), "bye")
}
require.NoError(t, sc.Err())
err = sess.Wait()
exitErr := &ssh.ExitError{}
require.ErrorAs(t, err, &exitErr)
wantCode := 255
if runtime.GOOS == "windows" {
wantCode = 1
}
require.Equal(t, wantCode, exitErr.ExitStatus())
})
t.Run("PTY", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
logger := slogtest.Make(t, nil)
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), nil)
require.NoError(t, err)
defer s.Close()
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
done := make(chan struct{})
go func() {
defer close(done)
err := s.Serve(ln)
assert.Error(t, err) // Server is closed.
}()
defer func() {
err := s.Close()
require.NoError(t, err)
<-done
}()
c := sshClient(t, ln.Addr().String())
pty := ptytest.New(t)
sess, err := c.NewSession()
require.NoError(t, err)
r, err := sess.StdoutPipe()
require.NoError(t, err)
// Note, we request pty but don't use ptytest here because we can't
// easily test for no text before EOF.
sess.Stdin = pty.Input()
sess.Stderr = pty.Output()
err = sess.RequestPty("xterm", 80, 80, nil)
require.NoError(t, err)
// Perform multiple sleeps since the interrupt signal doesn't propagate to
// the process group, this lets us exit early.
sleeps := strings.Repeat("sleep 1 && ", int(testutil.WaitMedium.Seconds()))
err = sess.Start(fmt.Sprintf("echo hello && %s echo bye", sleeps))
require.NoError(t, err)
sc := bufio.NewScanner(r)
for sc.Scan() {
t.Log(sc.Text())
if strings.Contains(sc.Text(), "hello") {
break
}
}
require.NoError(t, sc.Err())
err = sess.Signal(ssh.SIGKILL)
require.NoError(t, err)
// Assumption, signal propagates and the command exists, closing stdout.
for sc.Scan() {
t.Log(sc.Text())
require.NotContains(t, sc.Text(), "bye")
}
require.NoError(t, sc.Err())
err = sess.Wait()
exitErr := &ssh.ExitError{}
require.ErrorAs(t, err, &exitErr)
wantCode := 255
if runtime.GOOS == "windows" {
wantCode = 1
}
require.Equal(t, wantCode, exitErr.ExitStatus())
})
}
func sshClient(t *testing.T, addr string) *ssh.Client {
conn, err := net.Dial("tcp", addr)
require.NoError(t, err)
+76 -35
View File
@@ -2,11 +2,14 @@ package agentssh
import (
"context"
"errors"
"fmt"
"io/fs"
"net"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh"
@@ -33,22 +36,29 @@ type forwardedStreamLocalPayload struct {
type forwardedUnixHandler struct {
sync.Mutex
log slog.Logger
forwards map[string]net.Listener
forwards map[forwardKey]net.Listener
}
type forwardKey struct {
sessionID string
addr string
}
func newForwardedUnixHandler(log slog.Logger) *forwardedUnixHandler {
return &forwardedUnixHandler{
log: log,
forwards: make(map[forwardKey]net.Listener),
}
}
func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server, req *gossh.Request) (bool, []byte) {
h.log.Debug(ctx, "handling SSH unix forward")
h.Lock()
if h.forwards == nil {
h.forwards = make(map[string]net.Listener)
}
h.Unlock()
conn, ok := ctx.Value(ssh.ContextKeyConn).(*gossh.ServerConn)
if !ok {
h.log.Warn(ctx, "SSH unix forward request from client with no gossh connection")
return false, nil
}
log := h.log.With(slog.F("remote_addr", conn.RemoteAddr()))
log := h.log.With(slog.F("session_id", ctx.SessionID()), slog.F("remote_addr", conn.RemoteAddr()))
switch req.Type {
case "streamlocal-forward@openssh.com":
@@ -62,14 +72,22 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
addr := reqPayload.SocketPath
log = log.With(slog.F("socket_path", addr))
log.Debug(ctx, "request begin SSH unix forward")
key := forwardKey{
sessionID: ctx.SessionID(),
addr: addr,
}
h.Lock()
_, ok := h.forwards[addr]
_, ok := h.forwards[key]
h.Unlock()
if ok {
log.Warn(ctx, "SSH unix forward request for socket path that is already being forwarded (maybe to another client?)",
slog.F("socket_path", addr),
)
return false, nil
// In cases where `ExitOnForwardFailure=yes` is set, returning false
// here will cause the connection to be closed. To avoid this, and
// to match OpenSSH behavior, we silently ignore the second forward
// request.
log.Warn(ctx, "SSH unix forward request for socket path that is already being forwarded on this session, ignoring")
return true, nil
}
// Create socket parent dir if not exists.
@@ -83,12 +101,20 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
return false, nil
}
ln, err := net.Listen("unix", addr)
// Remove existing socket if it exists. We do not use os.Remove() here
// so that directories are kept. Note that it's possible that we will
// overwrite a regular file here. Both of these behaviors match OpenSSH,
// however, which is why we unlink.
err = unlink(addr)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Warn(ctx, "remove existing socket for SSH unix forward request", slog.Error(err))
return false, nil
}
lc := &net.ListenConfig{}
ln, err := lc.Listen(ctx, "unix", addr)
if err != nil {
log.Warn(ctx, "listen on Unix socket for SSH unix forward request",
slog.F("socket_path", addr),
slog.Error(err),
)
log.Warn(ctx, "listen on Unix socket for SSH unix forward request", slog.Error(err))
return false, nil
}
log.Debug(ctx, "SSH unix forward listening on socket")
@@ -99,7 +125,7 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
//
// This is also what the upstream TCP version of this code does.
h.Lock()
h.forwards[addr] = ln
h.forwards[key] = ln
h.Unlock()
log.Debug(ctx, "SSH unix forward added to cache")
@@ -115,9 +141,7 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
c, err := ln.Accept()
if err != nil {
if !xerrors.Is(err, net.ErrClosed) {
log.Warn(ctx, "accept on local Unix socket for SSH unix forward request",
slog.Error(err),
)
log.Warn(ctx, "accept on local Unix socket for SSH unix forward request", slog.Error(err))
}
// closed below
log.Debug(ctx, "SSH unix forward listener closed")
@@ -131,10 +155,7 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
go func() {
ch, reqs, err := conn.OpenChannel("forwarded-streamlocal@openssh.com", payload)
if err != nil {
h.log.Warn(ctx, "open SSH unix forward channel to client",
slog.F("socket_path", addr),
slog.Error(err),
)
h.log.Warn(ctx, "open SSH unix forward channel to client", slog.Error(err))
_ = c.Close()
return
}
@@ -144,12 +165,11 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
}
h.Lock()
ln2, ok := h.forwards[addr]
if ok && ln2 == ln {
delete(h.forwards, addr)
if ln2, ok := h.forwards[key]; ok && ln2 == ln {
delete(h.forwards, key)
}
h.Unlock()
log.Debug(ctx, "SSH unix forward listener removed from cache", slog.F("path", addr))
log.Debug(ctx, "SSH unix forward listener removed from cache")
_ = ln.Close()
}()
@@ -162,13 +182,22 @@ func (h *forwardedUnixHandler) HandleSSHRequest(ctx ssh.Context, _ *ssh.Server,
h.log.Warn(ctx, "parse cancel-streamlocal-forward@openssh.com (SSH unix forward) request payload from client", slog.Error(err))
return false, nil
}
log.Debug(ctx, "request to cancel SSH unix forward", slog.F("path", reqPayload.SocketPath))
h.Lock()
ln, ok := h.forwards[reqPayload.SocketPath]
h.Unlock()
if ok {
_ = ln.Close()
log.Debug(ctx, "request to cancel SSH unix forward", slog.F("socket_path", reqPayload.SocketPath))
key := forwardKey{
sessionID: ctx.SessionID(),
addr: reqPayload.SocketPath,
}
h.Lock()
ln, ok := h.forwards[key]
delete(h.forwards, key)
h.Unlock()
if !ok {
log.Warn(ctx, "SSH unix forward not found in cache")
return true, nil
}
_ = ln.Close()
return true, nil
default:
@@ -209,3 +238,15 @@ func directStreamLocalHandler(_ *ssh.Server, _ *gossh.ServerConn, newChan gossh.
Bicopy(ctx, ch, dconn)
}
// unlink removes files and unlike os.Remove, directories are kept.
func unlink(path string) error {
// Ignore EINTR like os.Remove, see ignoringEINTR in os/file_posix.go
// for more details.
for {
err := syscall.Unlink(path)
if !errors.Is(err, syscall.EINTR) {
return err
}
}
}
+97
View File
@@ -0,0 +1,97 @@
package agentssh
import (
"context"
"strings"
"sync"
"github.com/gliderlabs/ssh"
"go.uber.org/atomic"
gossh "golang.org/x/crypto/ssh"
"cdr.dev/slog"
)
// localForwardChannelData is copied from the ssh package.
type localForwardChannelData struct {
DestAddr string
DestPort uint32
OriginAddr string
OriginPort uint32
}
// JetbrainsChannelWatcher is used to track JetBrains port forwarded (Gateway)
// channels. If the port forward is something other than JetBrains, this struct
// is a noop.
type JetbrainsChannelWatcher struct {
gossh.NewChannel
jetbrainsCounter *atomic.Int64
logger slog.Logger
}
func NewJetbrainsChannelWatcher(ctx ssh.Context, logger slog.Logger, newChannel gossh.NewChannel, counter *atomic.Int64) gossh.NewChannel {
d := localForwardChannelData{}
if err := gossh.Unmarshal(newChannel.ExtraData(), &d); err != nil {
// If the data fails to unmarshal, do nothing.
logger.Warn(ctx, "failed to unmarshal port forward data", slog.Error(err))
return newChannel
}
// If we do get a port, we should be able to get the matching PID and from
// there look up the invocation.
cmdline, err := getListeningPortProcessCmdline(d.DestPort)
if err != nil {
logger.Warn(ctx, "failed to inspect port",
slog.F("destination_port", d.DestPort),
slog.Error(err))
return newChannel
}
// If this is not JetBrains, then we do not need to do anything special. We
// attempt to match on something that appears unique to JetBrains software.
if !strings.Contains(strings.ToLower(cmdline), strings.ToLower(MagicProcessCmdlineJetBrains)) {
return newChannel
}
logger.Debug(ctx, "discovered forwarded JetBrains process",
slog.F("destination_port", d.DestPort))
return &JetbrainsChannelWatcher{
NewChannel: newChannel,
jetbrainsCounter: counter,
logger: logger.With(slog.F("destination_port", d.DestPort)),
}
}
func (w *JetbrainsChannelWatcher) Accept() (gossh.Channel, <-chan *gossh.Request, error) {
c, r, err := w.NewChannel.Accept()
if err != nil {
return c, r, err
}
w.jetbrainsCounter.Add(1)
// nolint: gocritic // JetBrains is a proper noun and should be capitalized
w.logger.Debug(context.Background(), "JetBrains watcher accepted channel")
return &ChannelOnClose{
Channel: c,
done: func() {
w.jetbrainsCounter.Add(-1)
// nolint: gocritic // JetBrains is a proper noun and should be capitalized
w.logger.Debug(context.Background(), "JetBrains watcher channel closed")
},
}, r, err
}
type ChannelOnClose struct {
gossh.Channel
// once ensures close only decrements the counter once.
// Because close can be called multiple times.
once sync.Once
done func()
}
func (c *ChannelOnClose) Close() error {
c.once.Do(c.done)
return c.Channel.Close()
}
@@ -0,0 +1,51 @@
//go:build linux
package agentssh
import (
"errors"
"fmt"
"os"
"github.com/cakturk/go-netstat/netstat"
"golang.org/x/xerrors"
)
func getListeningPortProcessCmdline(port uint32) (string, error) {
acceptFn := func(s *netstat.SockTabEntry) bool {
return s.LocalAddr != nil && uint32(s.LocalAddr.Port) == port
}
tabs4, err4 := netstat.TCPSocks(acceptFn)
tabs6, err6 := netstat.TCP6Socks(acceptFn)
// In the common case, we want to check ipv4 listening addresses. If this
// fails, we should return an error. We also need to check ipv6. The
// assumption is, if we have an err4, and 0 ipv6 addresses listed, then we are
// interested in the err4 (and vice versa). So return both errors (at least 1
// is non-nil) if the other list is empty.
if (err4 != nil && len(tabs6) == 0) || (err6 != nil && len(tabs4) == 0) {
return "", xerrors.Errorf("inspect port %d: %w", port, errors.Join(err4, err6))
}
var proc *netstat.Process
if len(tabs4) > 0 {
proc = tabs4[0].Process
} else if len(tabs6) > 0 {
proc = tabs6[0].Process
}
if proc == nil {
// Either nothing is listening on this port or we were unable to read the
// process details (permission issues reading /proc/$pid/* potentially).
// Or, perhaps /proc/net/tcp{,6} is not listing the port for some reason.
return "", nil
}
// The process name provided by go-netstat does not include the full command
// line so grab that instead.
pid := proc.Pid
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil {
return "", xerrors.Errorf("read /proc/%d/cmdline: %w", pid, err)
}
return string(data), nil
}
@@ -0,0 +1,9 @@
//go:build !linux
package agentssh
func getListeningPortProcessCmdline(uint32) (string, error) {
// We are not worrying about other platforms at the moment because Gateway
// only supports Linux anyway.
return "", nil
}
+45
View File
@@ -0,0 +1,45 @@
//go:build !windows
package agentssh
import (
"os"
"github.com/gliderlabs/ssh"
"golang.org/x/sys/unix"
)
func osSignalFrom(sig ssh.Signal) os.Signal {
switch sig {
case ssh.SIGABRT:
return unix.SIGABRT
case ssh.SIGALRM:
return unix.SIGALRM
case ssh.SIGFPE:
return unix.SIGFPE
case ssh.SIGHUP:
return unix.SIGHUP
case ssh.SIGILL:
return unix.SIGILL
case ssh.SIGINT:
return unix.SIGINT
case ssh.SIGKILL:
return unix.SIGKILL
case ssh.SIGPIPE:
return unix.SIGPIPE
case ssh.SIGQUIT:
return unix.SIGQUIT
case ssh.SIGSEGV:
return unix.SIGSEGV
case ssh.SIGTERM:
return unix.SIGTERM
case ssh.SIGUSR1:
return unix.SIGUSR1
case ssh.SIGUSR2:
return unix.SIGUSR2
// Unhandled, use sane fallback.
default:
return unix.SIGKILL
}
}
+15
View File
@@ -0,0 +1,15 @@
package agentssh
import (
"os"
"github.com/gliderlabs/ssh"
)
func osSignalFrom(sig ssh.Signal) os.Signal {
switch sig {
// Signals are not supported on Windows.
default:
return os.Kill
}
}
+197 -5
View File
@@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
@@ -31,9 +32,9 @@ func (s *Server) x11Callback(ctx ssh.Context, x11 ssh.X11) bool {
return false
}
err = s.fs.MkdirAll(s.x11SocketDir, 0o700)
err = s.fs.MkdirAll(s.config.X11SocketDir, 0o700)
if err != nil {
s.logger.Warn(ctx, "failed to make the x11 socket dir", slog.F("dir", s.x11SocketDir), slog.Error(err))
s.logger.Warn(ctx, "failed to make the x11 socket dir", slog.F("dir", s.config.X11SocketDir), slog.Error(err))
s.metrics.x11HandlerErrors.WithLabelValues("socker_dir").Add(1)
return false
}
@@ -56,7 +57,7 @@ func (s *Server) x11Handler(ctx ssh.Context, x11 ssh.X11) bool {
return false
}
// We want to overwrite the socket so that subsequent connections will succeed.
socketPath := filepath.Join(s.x11SocketDir, fmt.Sprintf("X%d", x11.ScreenNumber))
socketPath := filepath.Join(s.config.X11SocketDir, fmt.Sprintf("X%d", x11.ScreenNumber))
err := os.Remove(socketPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
s.logger.Warn(ctx, "failed to remove existing X11 socket", slog.Error(err))
@@ -141,7 +142,7 @@ func addXauthEntry(ctx context.Context, fs afero.Fs, host string, display string
}
// Open or create the Xauthority file
file, err := fs.OpenFile(xauthPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)
file, err := fs.OpenFile(xauthPath, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return xerrors.Errorf("failed to open Xauthority file: %w", err)
}
@@ -153,7 +154,105 @@ func addXauthEntry(ctx context.Context, fs afero.Fs, host string, display string
return xerrors.Errorf("failed to decode auth cookie: %w", err)
}
// Write Xauthority entry
// Read the Xauthority file and look for an existing entry for the host,
// display, and auth protocol. If an entry is found, overwrite the auth
// cookie (if it fits). Otherwise, mark the entry for deletion.
type deleteEntry struct {
start, end int
}
var deleteEntries []deleteEntry
pos := 0
updated := false
for {
entry, err := readXauthEntry(file)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return xerrors.Errorf("failed to read Xauthority entry: %w", err)
}
nextPos := pos + entry.Len()
cookieStartPos := nextPos - len(entry.authCookie)
if entry.family == 0x0100 && entry.address == host && entry.display == display && entry.authProtocol == authProtocol {
if !updated && len(entry.authCookie) == len(authCookieBytes) {
// Overwrite the auth cookie
_, err := file.WriteAt(authCookieBytes, int64(cookieStartPos))
if err != nil {
return xerrors.Errorf("failed to write auth cookie: %w", err)
}
updated = true
} else {
// Mark entry for deletion.
if len(deleteEntries) > 0 && deleteEntries[len(deleteEntries)-1].end == pos {
deleteEntries[len(deleteEntries)-1].end = nextPos
} else {
deleteEntries = append(deleteEntries, deleteEntry{
start: pos,
end: nextPos,
})
}
}
}
pos = nextPos
}
// In case the magic cookie changed, or we've previously bloated the
// Xauthority file, we may have to delete entries.
if len(deleteEntries) > 0 {
// Read the entire file into memory. This is not ideal, but it's the
// simplest way to delete entries from the middle of the file. The
// Xauthority file is small, so this should be fine.
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return xerrors.Errorf("failed to seek Xauthority file: %w", err)
}
data, err := io.ReadAll(file)
if err != nil {
return xerrors.Errorf("failed to read Xauthority file: %w", err)
}
// Delete the entries in reverse order.
for i := len(deleteEntries) - 1; i >= 0; i-- {
entry := deleteEntries[i]
// Safety check: ensure the entry is still there.
if entry.start > len(data) || entry.end > len(data) {
continue
}
data = append(data[:entry.start], data[entry.end:]...)
}
// Write the data back to the file.
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return xerrors.Errorf("failed to seek Xauthority file: %w", err)
}
_, err = file.Write(data)
if err != nil {
return xerrors.Errorf("failed to write Xauthority file: %w", err)
}
// Truncate the file.
err = file.Truncate(int64(len(data)))
if err != nil {
return xerrors.Errorf("failed to truncate Xauthority file: %w", err)
}
}
// Return if we've already updated the entry.
if updated {
return nil
}
// Ensure we're at the end (append).
_, err = file.Seek(0, io.SeekEnd)
if err != nil {
return xerrors.Errorf("failed to seek Xauthority file: %w", err)
}
// Append Xauthority entry.
family := uint16(0x0100) // FamilyLocal
err = binary.Write(file, binary.BigEndian, family)
if err != nil {
@@ -198,3 +297,96 @@ func addXauthEntry(ctx context.Context, fs afero.Fs, host string, display string
return nil
}
// xauthEntry is an representation of an Xauthority entry.
//
// The Xauthority file format is as follows:
//
// - 16-bit family
// - 16-bit address length
// - address
// - 16-bit display length
// - display
// - 16-bit auth protocol length
// - auth protocol
// - 16-bit auth cookie length
// - auth cookie
type xauthEntry struct {
family uint16
address string
display string
authProtocol string
authCookie []byte
}
func (e xauthEntry) Len() int {
// 5 * uint16 = 10 bytes for the family/length fields.
return 2*5 + len(e.address) + len(e.display) + len(e.authProtocol) + len(e.authCookie)
}
func readXauthEntry(r io.Reader) (xauthEntry, error) {
var entry xauthEntry
// Read family
err := binary.Read(r, binary.BigEndian, &entry.family)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read family: %w", err)
}
// Read address
var addressLength uint16
err = binary.Read(r, binary.BigEndian, &addressLength)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read address length: %w", err)
}
addressBytes := make([]byte, addressLength)
_, err = r.Read(addressBytes)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read address: %w", err)
}
entry.address = string(addressBytes)
// Read display
var displayLength uint16
err = binary.Read(r, binary.BigEndian, &displayLength)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read display length: %w", err)
}
displayBytes := make([]byte, displayLength)
_, err = r.Read(displayBytes)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read display: %w", err)
}
entry.display = string(displayBytes)
// Read auth protocol
var authProtocolLength uint16
err = binary.Read(r, binary.BigEndian, &authProtocolLength)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read auth protocol length: %w", err)
}
authProtocolBytes := make([]byte, authProtocolLength)
_, err = r.Read(authProtocolBytes)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read auth protocol: %w", err)
}
entry.authProtocol = string(authProtocolBytes)
// Read auth cookie
var authCookieLength uint16
err = binary.Read(r, binary.BigEndian, &authCookieLength)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read auth cookie length: %w", err)
}
entry.authCookie = make([]byte, authCookieLength)
_, err = r.Read(entry.authCookie)
if err != nil {
return xauthEntry{}, xerrors.Errorf("failed to read auth cookie: %w", err)
}
return entry, nil
}
+254
View File
@@ -0,0 +1,254 @@
package agentssh
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_addXauthEntry(t *testing.T) {
t.Parallel()
type testEntry struct {
address string
display string
authProtocol string
authCookie string
}
tests := []struct {
name string
authFile []byte
wantAuthFile []byte
entries []testEntry
}{
{
name: "add entry",
authFile: nil,
wantAuthFile: []byte{
// w/unix:0 MIT-MAGIC-COOKIE-1 00
//
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0001 00 GIC-COOKIE-1...
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x00,
},
entries: []testEntry{
{
address: "w",
display: "0",
authProtocol: "MIT-MAGIC-COOKIE-1",
authCookie: "00",
},
},
},
{
name: "add two entries",
authFile: []byte{},
wantAuthFile: []byte{
// w/unix:0 MIT-MAGIC-COOKIE-1 00
// w/unix:1 MIT-MAGIC-COOKIE-1 11
//
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0001 0001 GIC-COOKIE-1....
// 00000020: 0000 0177 0001 3100 124d 4954 2d4d 4147 ...w..1..MIT-MAG
// 00000030: 4943 2d43 4f4f 4b49 452d 3100 0111 IC-COOKIE-1...
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x31,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x11,
},
entries: []testEntry{
{
address: "w",
display: "0",
authProtocol: "MIT-MAGIC-COOKIE-1",
authCookie: "00",
},
{
address: "w",
display: "1",
authProtocol: "MIT-MAGIC-COOKIE-1",
authCookie: "11",
},
},
},
{
name: "update entry with new auth cookie length",
authFile: []byte{
// w/unix:0 MIT-MAGIC-COOKIE-1 00
// w/unix:1 MIT-MAGIC-COOKIE-1 11
//
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0001 0001 GIC-COOKIE-1....
// 00000020: 0000 0177 0001 3100 124d 4954 2d4d 4147 ...w..1..MIT-MAG
// 00000030: 4943 2d43 4f4f 4b49 452d 3100 0111 IC-COOKIE-1...
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x31,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x11,
},
wantAuthFile: []byte{
// The order changed, due to new length of auth cookie resulting
// in remove + append, we verify that the implementation is
// behaving as expected (changing the order is not a requirement,
// simply an implementation detail).
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x31,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x11,
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x02, 0xff, 0xff,
},
entries: []testEntry{
{
address: "w",
display: "0",
authProtocol: "MIT-MAGIC-COOKIE-1",
authCookie: "ffff",
},
},
},
{
name: "update entry",
authFile: []byte{
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0001 0001 GIC-COOKIE-1....
// 00000020: 0000 0177 0001 3100 124d 4954 2d4d 4147 ...w..1..MIT-MAG
// 00000030: 4943 2d43 4f4f 4b49 452d 3100 0111 IC-COOKIE-1...
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x31,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x11,
},
wantAuthFile: []byte{
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0001 0001 GIC-COOKIE-1....
// 00000020: 0000 0177 0001 3100 124d 4954 2d4d 4147 ...w..1..MIT-MAG
// 00000030: 4943 2d43 4f4f 4b49 452d 3100 0111 IC-COOKIE-1...
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0xff,
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x31,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x01, 0x11,
},
entries: []testEntry{
{
address: "w",
display: "0",
authProtocol: "MIT-MAGIC-COOKIE-1",
authCookie: "ff",
},
},
},
{
name: "clean up old entries",
authFile: []byte{
// w/unix:0 MIT-MAGIC-COOKIE-1 80507df050756cdefa504b65adb3bcfb
// w/unix:0 MIT-MAGIC-COOKIE-1 267b37f6cbc11b97beb826bb1aab8570
// w/unix:0 MIT-MAGIC-COOKIE-1 516e22e2b11d1bd0115dff09c028ca5c
//
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0010 8050 GIC-COOKIE-1...P
// 00000020: 7df0 5075 6cde fa50 4b65 adb3 bcfb 0100 }.Pul..PKe......
// 00000030: 0001 7700 0130 0012 4d49 542d 4d41 4749 ..w..0..MIT-MAGI
// 00000040: 432d 434f 4f4b 4945 2d31 0010 267b 37f6 C-COOKIE-1..&{7.
// 00000050: cbc1 1b97 beb8 26bb 1aab 8570 0100 0001 ......&....p....
// 00000060: 7700 0130 0012 4d49 542d 4d41 4749 432d w..0..MIT-MAGIC-
// 00000070: 434f 4f4b 4945 2d31 0010 516e 22e2 b11d COOKIE-1..Qn"...
// 00000080: 1bd0 115d ff09 c028 ca5c ...]...(.\
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x10, 0x80, 0x50,
0x7d, 0xf0, 0x50, 0x75, 0x6c, 0xde, 0xfa, 0x50,
0x4b, 0x65, 0xad, 0xb3, 0xbc, 0xfb, 0x01, 0x00,
0x00, 0x01, 0x77, 0x00, 0x01, 0x30, 0x00, 0x12,
0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41, 0x47, 0x49,
0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b, 0x49, 0x45,
0x2d, 0x31, 0x00, 0x10, 0x26, 0x7b, 0x37, 0xf6,
0xcb, 0xc1, 0x1b, 0x97, 0xbe, 0xb8, 0x26, 0xbb,
0x1a, 0xab, 0x85, 0x70, 0x01, 0x00, 0x00, 0x01,
0x77, 0x00, 0x01, 0x30, 0x00, 0x12, 0x4d, 0x49,
0x54, 0x2d, 0x4d, 0x41, 0x47, 0x49, 0x43, 0x2d,
0x43, 0x4f, 0x4f, 0x4b, 0x49, 0x45, 0x2d, 0x31,
0x00, 0x10, 0x51, 0x6e, 0x22, 0xe2, 0xb1, 0x1d,
0x1b, 0xd0, 0x11, 0x5d, 0xff, 0x09, 0xc0, 0x28,
0xca, 0x5c,
},
wantAuthFile: []byte{
// w/unix:0 MIT-MAGIC-COOKIE-1 516e5bc892b7162b844abd1fc1a7c16e
//
// 00000000: 0100 0001 7700 0130 0012 4d49 542d 4d41 ....w..0..MIT-MA
// 00000010: 4749 432d 434f 4f4b 4945 2d31 0010 516e GIC-COOKIE-1..Qn
// 00000020: 5bc8 92b7 162b 844a bd1f c1a7 c16e [....+.J.....n
0x01, 0x00, 0x00, 0x01, 0x77, 0x00, 0x01, 0x30,
0x00, 0x12, 0x4d, 0x49, 0x54, 0x2d, 0x4d, 0x41,
0x47, 0x49, 0x43, 0x2d, 0x43, 0x4f, 0x4f, 0x4b,
0x49, 0x45, 0x2d, 0x31, 0x00, 0x10, 0x51, 0x6e,
0x5b, 0xc8, 0x92, 0xb7, 0x16, 0x2b, 0x84, 0x4a,
0xbd, 0x1f, 0xc1, 0xa7, 0xc1, 0x6e,
},
entries: []testEntry{
{
address: "w",
display: "0",
authProtocol: "MIT-MAGIC-COOKIE-1",
authCookie: "516e5bc892b7162b844abd1fc1a7c16e",
},
},
},
}
homedir, err := os.UserHomeDir()
require.NoError(t, err)
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
if tt.authFile != nil {
err := afero.WriteFile(fs, filepath.Join(homedir, ".Xauthority"), tt.authFile, 0o600)
require.NoError(t, err)
}
for _, entry := range tt.entries {
err := addXauthEntry(context.Background(), fs, entry.address, entry.display, entry.authProtocol, entry.authCookie)
require.NoError(t, err)
}
gotAuthFile, err := afero.ReadFile(fs, filepath.Join(homedir, ".Xauthority"))
require.NoError(t, err)
if diff := cmp.Diff(tt.wantAuthFile, gotAuthFile); diff != "" {
assert.Failf(t, "addXauthEntry() mismatch", "(-want +got):\n%s", diff)
}
})
}
}
+3 -7
View File
@@ -14,13 +14,11 @@ import (
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
gossh "golang.org/x/crypto/ssh"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/testutil"
)
@@ -34,14 +32,12 @@ func TestServer_X11(t *testing.T) {
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
fs := afero.NewOsFs()
dir := t.TempDir()
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), fs, 0, dir)
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), fs, &agentssh.Config{
X11SocketDir: dir,
})
require.NoError(t, err)
defer s.Close()
// The assumption is that these are set before serving SSH connections.
s.AgentToken = func() string { return "" }
s.Manifest = atomic.NewPointer(&agentsdk.Manifest{})
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
+210 -142
View File
@@ -3,164 +3,133 @@ package agenttest
import (
"context"
"io"
"net"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
"google.golang.org/protobuf/types/known/durationpb"
"storj.io/drpc"
"storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver"
"tailscale.com/tailcfg"
"cdr.dev/slog"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
drpcsdk "github.com/coder/coder/v2/codersdk/drpc"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/tailnet/proto"
"github.com/coder/coder/v2/testutil"
)
const statsInterval = 500 * time.Millisecond
func NewClient(t testing.TB,
logger slog.Logger,
agentID uuid.UUID,
manifest agentsdk.Manifest,
statsChan chan *agentsdk.Stats,
statsChan chan *agentproto.Stats,
coordinator tailnet.Coordinator,
) *Client {
if manifest.AgentID == uuid.Nil {
manifest.AgentID = agentID
}
coordPtr := atomic.Pointer[tailnet.Coordinator]{}
coordPtr.Store(&coordinator)
mux := drpcmux.New()
derpMapUpdates := make(chan *tailcfg.DERPMap)
drpcService := &tailnet.DRPCService{
CoordPtr: &coordPtr,
Logger: logger.Named("tailnetsvc"),
DerpMapUpdateFrequency: time.Microsecond,
DerpMapFn: func() *tailcfg.DERPMap { return <-derpMapUpdates },
}
err := proto.DRPCRegisterTailnet(mux, drpcService)
require.NoError(t, err)
mp, err := agentsdk.ProtoFromManifest(manifest)
require.NoError(t, err)
fakeAAPI := NewFakeAgentAPI(t, logger, mp, statsChan)
err = agentproto.DRPCRegisterAgent(mux, fakeAAPI)
require.NoError(t, err)
server := drpcserver.NewWithOptions(mux, drpcserver.Options{
Log: func(err error) {
if xerrors.Is(err, io.EOF) {
return
}
logger.Debug(context.Background(), "drpc server error", slog.Error(err))
},
})
return &Client{
t: t,
logger: logger.Named("client"),
agentID: agentID,
manifest: manifest,
statsChan: statsChan,
coordinator: coordinator,
derpMapUpdates: make(chan agentsdk.DERPMapUpdate),
server: server,
fakeAgentAPI: fakeAAPI,
derpMapUpdates: derpMapUpdates,
}
}
type Client struct {
t testing.TB
logger slog.Logger
agentID uuid.UUID
manifest agentsdk.Manifest
metadata map[string]agentsdk.Metadata
statsChan chan *agentsdk.Stats
coordinator tailnet.Coordinator
LastWorkspaceAgent func()
PatchWorkspaceLogs func() error
GetServiceBannerFunc func() (codersdk.ServiceBannerConfig, error)
t testing.TB
logger slog.Logger
agentID uuid.UUID
coordinator tailnet.Coordinator
server *drpcserver.Server
fakeAgentAPI *FakeAgentAPI
LastWorkspaceAgent func()
mu sync.Mutex // Protects following.
lifecycleStates []codersdk.WorkspaceAgentLifecycle
startup agentsdk.PostStartupRequest
logs []agentsdk.Log
derpMapUpdates chan agentsdk.DERPMapUpdate
mu sync.Mutex // Protects following.
logs []agentsdk.Log
derpMapUpdates chan *tailcfg.DERPMap
derpMapOnce sync.Once
}
func (c *Client) Manifest(_ context.Context) (agentsdk.Manifest, error) {
return c.manifest, nil
func (*Client) RewriteDERPMap(*tailcfg.DERPMap) {}
func (c *Client) Close() {
c.derpMapOnce.Do(func() { close(c.derpMapUpdates) })
}
func (c *Client) Listen(_ context.Context) (net.Conn, error) {
clientConn, serverConn := net.Pipe()
closed := make(chan struct{})
func (c *Client) ConnectRPC(ctx context.Context) (drpc.Conn, error) {
conn, lis := drpcsdk.MemTransportPipe()
c.LastWorkspaceAgent = func() {
_ = serverConn.Close()
_ = clientConn.Close()
<-closed
_ = conn.Close()
_ = lis.Close()
}
c.t.Cleanup(c.LastWorkspaceAgent)
serveCtx, cancel := context.WithCancel(ctx)
c.t.Cleanup(cancel)
streamID := tailnet.StreamID{
Name: "agenttest",
ID: c.agentID,
Auth: tailnet.AgentCoordinateeAuth{ID: c.agentID},
}
serveCtx = tailnet.WithStreamID(serveCtx, streamID)
go func() {
_ = c.coordinator.ServeAgent(serverConn, c.agentID, "")
close(closed)
_ = c.server.Serve(serveCtx, lis)
}()
return clientConn, nil
}
func (c *Client) ReportStats(ctx context.Context, _ slog.Logger, statsChan <-chan *agentsdk.Stats, setInterval func(time.Duration)) (io.Closer, error) {
doneCh := make(chan struct{})
ctx, cancel := context.WithCancel(ctx)
go func() {
defer close(doneCh)
setInterval(500 * time.Millisecond)
for {
select {
case <-ctx.Done():
return
case stat := <-statsChan:
select {
case c.statsChan <- stat:
case <-ctx.Done():
return
default:
// We don't want to send old stats.
continue
}
}
}
}()
return closeFunc(func() error {
cancel()
<-doneCh
close(c.statsChan)
return nil
}), nil
return conn, nil
}
func (c *Client) GetLifecycleStates() []codersdk.WorkspaceAgentLifecycle {
c.mu.Lock()
defer c.mu.Unlock()
return c.lifecycleStates
return c.fakeAgentAPI.GetLifecycleStates()
}
func (c *Client) PostLifecycle(ctx context.Context, req agentsdk.PostLifecycleRequest) error {
c.mu.Lock()
defer c.mu.Unlock()
c.lifecycleStates = append(c.lifecycleStates, req.State)
c.logger.Debug(ctx, "post lifecycle", slog.F("req", req))
return nil
}
func (c *Client) PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error {
c.logger.Debug(ctx, "post app health", slog.F("req", req))
return nil
}
func (c *Client) GetStartup() agentsdk.PostStartupRequest {
c.mu.Lock()
defer c.mu.Unlock()
return c.startup
func (c *Client) GetStartup() <-chan *agentproto.Startup {
return c.fakeAgentAPI.startupCh
}
func (c *Client) GetMetadata() map[string]agentsdk.Metadata {
c.mu.Lock()
defer c.mu.Unlock()
return maps.Clone(c.metadata)
}
func (c *Client) PostMetadata(ctx context.Context, req agentsdk.PostMetadataRequest) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.metadata == nil {
c.metadata = make(map[string]agentsdk.Metadata)
}
for _, md := range req.Metadata {
c.metadata[md.Key] = md
c.logger.Debug(ctx, "post metadata", slog.F("key", md.Key), slog.F("md", md))
}
return nil
}
func (c *Client) PostStartup(ctx context.Context, startup agentsdk.PostStartupRequest) error {
c.mu.Lock()
defer c.mu.Unlock()
c.startup = startup
c.logger.Debug(ctx, "post startup", slog.F("req", startup))
return nil
return c.fakeAgentAPI.GetMetadata()
}
func (c *Client) GetStartupLogs() []agentsdk.Log {
@@ -169,35 +138,11 @@ func (c *Client) GetStartupLogs() []agentsdk.Log {
return c.logs
}
func (c *Client) PatchLogs(ctx context.Context, logs agentsdk.PatchLogs) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.PatchWorkspaceLogs != nil {
return c.PatchWorkspaceLogs()
}
c.logs = append(c.logs, logs.Logs...)
c.logger.Debug(ctx, "patch startup logs", slog.F("req", logs))
return nil
}
func (c *Client) SetServiceBannerFunc(f func() (codersdk.ServiceBannerConfig, error)) {
c.mu.Lock()
defer c.mu.Unlock()
c.GetServiceBannerFunc = f
c.fakeAgentAPI.SetServiceBannerFunc(f)
}
func (c *Client) GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerConfig, error) {
c.mu.Lock()
defer c.mu.Unlock()
c.logger.Debug(ctx, "get service banner")
if c.GetServiceBannerFunc != nil {
return c.GetServiceBannerFunc()
}
return codersdk.ServiceBannerConfig{}, nil
}
func (c *Client) PushDERPMapUpdate(update agentsdk.DERPMapUpdate) error {
func (c *Client) PushDERPMapUpdate(update *tailcfg.DERPMap) error {
timer := time.NewTimer(testutil.WaitShort)
defer timer.Stop()
select {
@@ -209,16 +154,139 @@ func (c *Client) PushDERPMapUpdate(update agentsdk.DERPMapUpdate) error {
return nil
}
func (c *Client) DERPMapUpdates(_ context.Context) (<-chan agentsdk.DERPMapUpdate, io.Closer, error) {
closed := make(chan struct{})
return c.derpMapUpdates, closeFunc(func() error {
close(closed)
return nil
}), nil
func (c *Client) SetLogsChannel(ch chan<- *agentproto.BatchCreateLogsRequest) {
c.fakeAgentAPI.SetLogsChannel(ch)
}
type closeFunc func() error
type FakeAgentAPI struct {
sync.Mutex
t testing.TB
logger slog.Logger
func (c closeFunc) Close() error {
return c()
manifest *agentproto.Manifest
startupCh chan *agentproto.Startup
statsCh chan *agentproto.Stats
appHealthCh chan *agentproto.BatchUpdateAppHealthRequest
logsCh chan<- *agentproto.BatchCreateLogsRequest
lifecycleStates []codersdk.WorkspaceAgentLifecycle
metadata map[string]agentsdk.Metadata
getServiceBannerFunc func() (codersdk.ServiceBannerConfig, error)
}
func (f *FakeAgentAPI) GetManifest(context.Context, *agentproto.GetManifestRequest) (*agentproto.Manifest, error) {
return f.manifest, nil
}
func (f *FakeAgentAPI) SetServiceBannerFunc(fn func() (codersdk.ServiceBannerConfig, error)) {
f.Lock()
defer f.Unlock()
f.getServiceBannerFunc = fn
f.logger.Info(context.Background(), "updated ServiceBannerFunc")
}
func (f *FakeAgentAPI) GetServiceBanner(context.Context, *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
f.Lock()
defer f.Unlock()
if f.getServiceBannerFunc == nil {
return &agentproto.ServiceBanner{}, nil
}
sb, err := f.getServiceBannerFunc()
if err != nil {
return nil, err
}
return agentsdk.ProtoFromServiceBanner(sb), nil
}
func (f *FakeAgentAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsRequest) (*agentproto.UpdateStatsResponse, error) {
f.logger.Debug(ctx, "update stats called", slog.F("req", req))
// empty request is sent to get the interval; but our tests don't want empty stats requests
if req.Stats != nil {
f.statsCh <- req.Stats
}
return &agentproto.UpdateStatsResponse{ReportInterval: durationpb.New(statsInterval)}, nil
}
func (f *FakeAgentAPI) GetLifecycleStates() []codersdk.WorkspaceAgentLifecycle {
f.Lock()
defer f.Unlock()
return slices.Clone(f.lifecycleStates)
}
func (f *FakeAgentAPI) UpdateLifecycle(_ context.Context, req *agentproto.UpdateLifecycleRequest) (*agentproto.Lifecycle, error) {
f.Lock()
defer f.Unlock()
s, err := agentsdk.LifecycleStateFromProto(req.GetLifecycle().GetState())
if assert.NoError(f.t, err) {
f.lifecycleStates = append(f.lifecycleStates, s)
}
return req.GetLifecycle(), nil
}
func (f *FakeAgentAPI) BatchUpdateAppHealths(ctx context.Context, req *agentproto.BatchUpdateAppHealthRequest) (*agentproto.BatchUpdateAppHealthResponse, error) {
f.logger.Debug(ctx, "batch update app health", slog.F("req", req))
f.appHealthCh <- req
return &agentproto.BatchUpdateAppHealthResponse{}, nil
}
func (f *FakeAgentAPI) AppHealthCh() <-chan *agentproto.BatchUpdateAppHealthRequest {
return f.appHealthCh
}
func (f *FakeAgentAPI) UpdateStartup(_ context.Context, req *agentproto.UpdateStartupRequest) (*agentproto.Startup, error) {
f.startupCh <- req.GetStartup()
return req.GetStartup(), nil
}
func (f *FakeAgentAPI) GetMetadata() map[string]agentsdk.Metadata {
f.Lock()
defer f.Unlock()
return maps.Clone(f.metadata)
}
func (f *FakeAgentAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.BatchUpdateMetadataRequest) (*agentproto.BatchUpdateMetadataResponse, error) {
f.Lock()
defer f.Unlock()
if f.metadata == nil {
f.metadata = make(map[string]agentsdk.Metadata)
}
for _, md := range req.Metadata {
smd := agentsdk.MetadataFromProto(md)
f.metadata[md.Key] = smd
f.logger.Debug(ctx, "post metadata", slog.F("key", md.Key), slog.F("md", md))
}
return &agentproto.BatchUpdateMetadataResponse{}, nil
}
func (f *FakeAgentAPI) SetLogsChannel(ch chan<- *agentproto.BatchCreateLogsRequest) {
f.Lock()
defer f.Unlock()
f.logsCh = ch
}
func (f *FakeAgentAPI) BatchCreateLogs(ctx context.Context, req *agentproto.BatchCreateLogsRequest) (*agentproto.BatchCreateLogsResponse, error) {
f.logger.Info(ctx, "batch create logs called", slog.F("req", req))
f.Lock()
ch := f.logsCh
f.Unlock()
if ch != nil {
select {
case <-ctx.Done():
return nil, ctx.Err()
case ch <- req:
// ok
}
}
return &agentproto.BatchCreateLogsResponse{}, nil
}
func NewFakeAgentAPI(t testing.TB, logger slog.Logger, manifest *agentproto.Manifest, statsCh chan *agentproto.Stats) *FakeAgentAPI {
return &FakeAgentAPI{
t: t,
logger: logger.Named("FakeAgentAPI"),
manifest: manifest,
statsCh: statsCh,
startupCh: make(chan *agentproto.Startup, 100),
appHealthCh: make(chan *agentproto.BatchUpdateAppHealthRequest, 100),
}
}
+18 -5
View File
@@ -26,17 +26,30 @@ func (a *agent) apiHandler() http.Handler {
cpy[k] = b
}
lp := &listeningPortsHandler{ignorePorts: cpy}
cacheDuration := 1 * time.Second
if a.portCacheDuration > 0 {
cacheDuration = a.portCacheDuration
}
lp := &listeningPortsHandler{
ignorePorts: cpy,
cacheDuration: cacheDuration,
}
r.Get("/api/v0/listening-ports", lp.handler)
return r
}
type listeningPortsHandler struct {
mut sync.Mutex
ports []codersdk.WorkspaceAgentListeningPort
mtime time.Time
ignorePorts map[int]string
ignorePorts map[int]string
cacheDuration time.Duration
//nolint: unused // used on some but not all platforms
mut sync.Mutex
//nolint: unused // used on some but not all platforms
ports []codersdk.WorkspaceAgentListeningPort
//nolint: unused // used on some but not all platforms
mtime time.Time
}
// handler returns a list of listening ports. This is tested by coderd's
+16 -1
View File
@@ -26,7 +26,12 @@ type WorkspaceAppHealthReporter func(ctx context.Context)
// NewWorkspaceAppHealthReporter creates a WorkspaceAppHealthReporter that reports app health to coderd.
func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.WorkspaceApp, postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth) WorkspaceAppHealthReporter {
logger = logger.Named("apphealth")
runHealthcheckLoop := func(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// no need to run this loop if no apps for this workspace.
if len(apps) == 0 {
return nil
@@ -87,6 +92,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
return nil
}()
if err != nil {
nowUnhealthy := false
mu.Lock()
if failures[app.ID] < int(app.Healthcheck.Threshold) {
// increment the failure count and keep status the same.
@@ -96,14 +102,21 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
// set to unhealthy if we hit the failure threshold.
// we stop incrementing at the threshold to prevent the failure value from increasing forever.
health[app.ID] = codersdk.WorkspaceAppHealthUnhealthy
nowUnhealthy = true
}
mu.Unlock()
logger.Debug(ctx, "error checking app health",
slog.F("id", app.ID.String()),
slog.F("slug", app.Slug),
slog.F("now_unhealthy", nowUnhealthy), slog.Error(err),
)
} else {
mu.Lock()
// we only need one successful health check to be considered healthy.
health[app.ID] = codersdk.WorkspaceAppHealthHealthy
failures[app.ID] = 0
mu.Unlock()
logger.Debug(ctx, "workspace app healthy", slog.F("id", app.ID.String()), slog.F("slug", app.Slug))
}
t.Reset(time.Duration(app.Healthcheck.Interval) * time.Second)
@@ -137,7 +150,9 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
Healths: lastHealth,
})
if err != nil {
logger.Error(ctx, "failed to report workspace app stat", slog.Error(err))
logger.Error(ctx, "failed to report workspace app health", slog.Error(err))
} else {
logger.Debug(ctx, "sent workspace app health", slog.F("health", lastHealth))
}
}
}
+56 -14
View File
@@ -4,16 +4,21 @@ import (
"context"
"net/http"
"net/http/httptest"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agenttest"
"github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
@@ -40,12 +45,23 @@ func TestAppHealth_Healthy(t *testing.T) {
},
Health: codersdk.WorkspaceAppHealthInitializing,
},
{
Slug: "app3",
Healthcheck: codersdk.Healthcheck{
Interval: 2,
Threshold: 1,
},
Health: codersdk.WorkspaceAppHealthInitializing,
},
}
handlers := []http.Handler{
nil,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(r.Context(), w, http.StatusOK, nil)
}),
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(r.Context(), w, http.StatusOK, nil)
}),
}
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
defer closeFn()
@@ -58,7 +74,7 @@ func TestAppHealth_Healthy(t *testing.T) {
return false
}
return apps[1].Health == codersdk.WorkspaceAppHealthHealthy
return apps[1].Health == codersdk.WorkspaceAppHealthHealthy && apps[2].Health == codersdk.WorkspaceAppHealthHealthy
}, testutil.WaitLong, testutil.IntervalSlow)
}
@@ -163,6 +179,12 @@ func TestAppHealth_NotSpamming(t *testing.T) {
func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.WorkspaceApp, handlers []http.Handler) (agent.WorkspaceAgentApps, func()) {
closers := []func(){}
for i, app := range apps {
if app.ID == uuid.Nil {
app.ID = uuid.New()
apps[i] = app
}
}
for i, handler := range handlers {
if handler == nil {
continue
@@ -181,23 +203,43 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa
var newApps []codersdk.WorkspaceApp
return append(newApps, apps...), nil
}
postWorkspaceAgentAppHealth := func(_ context.Context, req agentsdk.PostAppHealthsRequest) error {
mu.Lock()
for id, health := range req.Healths {
for i, app := range apps {
if app.ID != id {
continue
// We don't care about manifest or stats in this test since it's not using
// a full agent and these RPCs won't get called.
//
// We use a proper fake agent API so we can test the conversion code and the
// request code as well. Before we were bypassing these by using a custom
// post function.
fakeAAPI := agenttest.NewFakeAgentAPI(t, slogtest.Make(t, nil), nil, nil)
// Process events from the channel and update the health of the apps.
go func() {
appHealthCh := fakeAAPI.AppHealthCh()
for {
select {
case <-ctx.Done():
return
case req := <-appHealthCh:
mu.Lock()
for _, update := range req.Updates {
updateID, err := uuid.FromBytes(update.Id)
assert.NoError(t, err)
updateHealth := codersdk.WorkspaceAppHealth(strings.ToLower(proto.AppHealth_name[int32(update.Health)]))
for i, app := range apps {
if app.ID != updateID {
continue
}
app.Health = updateHealth
apps[i] = app
}
}
app.Health = health
apps[i] = app
mu.Unlock()
}
}
mu.Unlock()
}()
return nil
}
go agent.NewWorkspaceAppHealthReporter(slogtest.Make(t, nil).Leveled(slog.LevelDebug), apps, postWorkspaceAgentAppHealth)(ctx)
go agent.NewWorkspaceAppHealthReporter(slogtest.Make(t, nil).Leveled(slog.LevelDebug), apps, agentsdk.AppHealthPoster(fakeAAPI))(ctx)
return workspaceAgentApps, func() {
for _, closeFn := range closers {
+26 -15
View File
@@ -10,13 +10,15 @@ import (
"tailscale.com/util/clientmetric"
"cdr.dev/slog"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/agent/proto"
)
type agentMetrics struct {
connectionsTotal prometheus.Counter
reconnectingPTYErrors *prometheus.CounterVec
// startupScriptSeconds is the time in seconds that the start script(s)
// took to run. This is reported once per agent.
startupScriptSeconds *prometheus.GaugeVec
}
func newAgentMetrics(registerer prometheus.Registerer) *agentMetrics {
@@ -35,14 +37,23 @@ func newAgentMetrics(registerer prometheus.Registerer) *agentMetrics {
)
registerer.MustRegister(reconnectingPTYErrors)
startupScriptSeconds := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "coderd",
Subsystem: "agentstats",
Name: "startup_script_seconds",
Help: "Amount of time taken to run the startup script in seconds.",
}, []string{"success"})
registerer.MustRegister(startupScriptSeconds)
return &agentMetrics{
connectionsTotal: connectionsTotal,
reconnectingPTYErrors: reconnectingPTYErrors,
startupScriptSeconds: startupScriptSeconds,
}
}
func (a *agent) collectMetrics(ctx context.Context) []agentsdk.AgentMetric {
var collected []agentsdk.AgentMetric
func (a *agent) collectMetrics(ctx context.Context) []*proto.Stats_Metric {
var collected []*proto.Stats_Metric
// Tailscale internal metrics
metrics := clientmetric.Metrics()
@@ -51,7 +62,7 @@ func (a *agent) collectMetrics(ctx context.Context) []agentsdk.AgentMetric {
continue
}
collected = append(collected, agentsdk.AgentMetric{
collected = append(collected, &proto.Stats_Metric{
Name: m.Name(),
Type: asMetricType(m.Type()),
Value: float64(m.Value()),
@@ -69,16 +80,16 @@ func (a *agent) collectMetrics(ctx context.Context) []agentsdk.AgentMetric {
labels := toAgentMetricLabels(metric.Label)
if metric.Counter != nil {
collected = append(collected, agentsdk.AgentMetric{
collected = append(collected, &proto.Stats_Metric{
Name: metricFamily.GetName(),
Type: agentsdk.AgentMetricTypeCounter,
Type: proto.Stats_Metric_COUNTER,
Value: metric.Counter.GetValue(),
Labels: labels,
})
} else if metric.Gauge != nil {
collected = append(collected, agentsdk.AgentMetric{
collected = append(collected, &proto.Stats_Metric{
Name: metricFamily.GetName(),
Type: agentsdk.AgentMetricTypeGauge,
Type: proto.Stats_Metric_GAUGE,
Value: metric.Gauge.GetValue(),
Labels: labels,
})
@@ -90,14 +101,14 @@ func (a *agent) collectMetrics(ctx context.Context) []agentsdk.AgentMetric {
return collected
}
func toAgentMetricLabels(metricLabels []*prompb.LabelPair) []agentsdk.AgentMetricLabel {
func toAgentMetricLabels(metricLabels []*prompb.LabelPair) []*proto.Stats_Metric_Label {
if len(metricLabels) == 0 {
return nil
}
labels := make([]agentsdk.AgentMetricLabel, 0, len(metricLabels))
labels := make([]*proto.Stats_Metric_Label, 0, len(metricLabels))
for _, metricLabel := range metricLabels {
labels = append(labels, agentsdk.AgentMetricLabel{
labels = append(labels, &proto.Stats_Metric_Label{
Name: metricLabel.GetName(),
Value: metricLabel.GetValue(),
})
@@ -118,12 +129,12 @@ func isIgnoredMetric(metricName string) bool {
return false
}
func asMetricType(typ clientmetric.Type) agentsdk.AgentMetricType {
func asMetricType(typ clientmetric.Type) proto.Stats_Metric_Type {
switch typ {
case clientmetric.TypeGauge:
return agentsdk.AgentMetricTypeGauge
return proto.Stats_Metric_GAUGE
case clientmetric.TypeCounter:
return agentsdk.AgentMetricTypeCounter
return proto.Stats_Metric_COUNTER
default:
panic(fmt.Sprintf("unknown metric type: %d", typ))
}
+1 -1
View File
@@ -15,7 +15,7 @@ func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentL
lp.mut.Lock()
defer lp.mut.Unlock()
if time.Since(lp.mtime) < time.Second {
if time.Since(lp.mtime) < lp.cacheDuration {
// copy
ports := make([]codersdk.WorkspaceAgentListeningPort, len(lp.ports))
copy(ports, lp.ports)
+1 -1
View File
@@ -4,7 +4,7 @@ package agent
import "github.com/coder/coder/v2/codersdk"
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
func (*listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
// Can't scan for ports on non-linux or non-windows_amd64 systems at the
// moment. The UI will not show any "no ports found" message to the user, so
// the user won't suspect a thing.
+1220 -541
View File
File diff suppressed because it is too large Load Diff
+74 -22
View File
@@ -8,7 +8,7 @@ import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
message WorkspaceApp {
bytes uuid = 1;
bytes id = 1;
string url = 2;
bool external = 3;
string slug = 4;
@@ -26,12 +26,12 @@ message WorkspaceApp {
}
SharingLevel sharing_level = 10;
message HealthCheck {
message Healthcheck {
string url = 1;
int32 interval = 2;
google.protobuf.Duration interval = 2;
int32 threshold = 3;
}
HealthCheck healthcheck = 11;
Healthcheck healthcheck = 11;
enum Health {
HEALTH_UNSPECIFIED = 0;
@@ -43,11 +43,54 @@ message WorkspaceApp {
Health health = 12;
}
message WorkspaceAgentScript {
bytes log_source_id = 1;
string log_path = 2;
string script = 3;
string cron = 4;
bool run_on_start = 5;
bool run_on_stop = 6;
bool start_blocks_login = 7;
google.protobuf.Duration timeout = 8;
}
message WorkspaceAgentMetadata {
message Result {
google.protobuf.Timestamp collected_at = 1;
int64 age = 2;
string value = 3;
string error = 4;
}
Result result = 1;
message Description {
string display_name = 1;
string key = 2;
string script = 3;
google.protobuf.Duration interval = 4;
google.protobuf.Duration timeout = 5;
}
Description description = 2;
}
message Manifest {
uint32 git_auth_configs = 1;
string vs_code_port_proxy_uri = 2;
repeated WorkspaceApp apps = 3;
coder.tailnet.v2.DERPMap derp_map = 4;
bytes agent_id = 1;
string agent_name = 15;
string owner_username = 13;
bytes workspace_id = 14;
string workspace_name = 16;
uint32 git_auth_configs = 2;
map<string, string> environment_variables = 3;
string directory = 4;
string vs_code_port_proxy_uri = 5;
string motd_path = 6;
bool disable_direct_connections = 7;
bool derp_force_websockets = 8;
coder.tailnet.v2.DERPMap derp_map = 9;
repeated WorkspaceAgentScript scripts = 10;
repeated WorkspaceApp apps = 11;
repeated WorkspaceAgentMetadata.Description metadata = 12;
}
message GetManifestRequest {}
@@ -100,8 +143,14 @@ message Stats {
Type type = 2;
double value = 3;
map<string, string> labels = 4;
message Label {
string name = 1;
string value = 2;
}
repeated Label labels = 4;
}
repeated Metric metrics = 12;
}
message UpdateStatsRequest{
@@ -109,14 +158,14 @@ message UpdateStatsRequest{
}
message UpdateStatsResponse {
google.protobuf.Duration report_interval_nanoseconds = 1;
google.protobuf.Duration report_interval = 1;
}
message Lifecycle {
enum State {
STATE_UNSPECIFIED = 0;
CREATED = 1;
STARTED = 2;
STARTING = 2;
START_TIMEOUT = 3;
START_ERROR = 4;
READY = 5;
@@ -126,6 +175,7 @@ message Lifecycle {
OFF = 9;
}
State state = 1;
google.protobuf.Timestamp changed_at = 2;
}
message UpdateLifecycleRequest {
@@ -142,7 +192,7 @@ enum AppHealth {
message BatchUpdateAppHealthRequest {
message HealthUpdate {
bytes uuid = 1;
bytes id = 1;
AppHealth health = 2;
}
repeated HealthUpdate updates = 1;
@@ -153,7 +203,13 @@ message BatchUpdateAppHealthResponse {}
message Startup {
string version = 1;
string expanded_directory = 2;
repeated string subsystems = 3;
enum Subsystem {
SUBSYSTEM_UNSPECIFIED = 0;
ENVBOX = 1;
ENVBUILDER = 2;
EXECTRACE = 3;
}
repeated Subsystem subsystems = 3;
}
message UpdateStartupRequest{
@@ -162,10 +218,7 @@ message UpdateStartupRequest{
message Metadata {
string key = 1;
google.protobuf.Timestamp collected_at = 2;
int64 age = 3;
string value = 4;
string error = 5;
WorkspaceAgentMetadata.Result result = 2;
}
message BatchUpdateMetadataRequest {
@@ -190,11 +243,13 @@ message Log {
}
message BatchCreateLogsRequest {
bytes source_id = 1;
bytes log_source_id = 1;
repeated Log logs = 2;
}
message BatchCreateLogsResponse {}
message BatchCreateLogsResponse {
bool log_limit_exceeded = 1;
}
service Agent {
rpc GetManifest(GetManifestRequest) returns (Manifest);
@@ -205,7 +260,4 @@ service Agent {
rpc UpdateStartup(UpdateStartupRequest) returns (Startup);
rpc BatchUpdateMetadata(BatchUpdateMetadataRequest) returns (BatchUpdateMetadataResponse);
rpc BatchCreateLogs(BatchCreateLogsRequest) returns (BatchCreateLogsResponse);
rpc StreamDERPMaps(tailnet.v2.StreamDERPMapsRequest) returns (stream tailnet.v2.DERPMap);
rpc CoordinateTailnet(stream tailnet.v2.CoordinateRequest) returns (stream tailnet.v2.CoordinateResponse);
}
+1 -149
View File
@@ -7,7 +7,6 @@ package proto
import (
context "context"
errors "errors"
proto1 "github.com/coder/coder/v2/tailnet/proto"
protojson "google.golang.org/protobuf/encoding/protojson"
proto "google.golang.org/protobuf/proto"
drpc "storj.io/drpc"
@@ -47,8 +46,6 @@ type DRPCAgentClient interface {
UpdateStartup(ctx context.Context, in *UpdateStartupRequest) (*Startup, error)
BatchUpdateMetadata(ctx context.Context, in *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
StreamDERPMaps(ctx context.Context, in *proto1.StreamDERPMapsRequest) (DRPCAgent_StreamDERPMapsClient, error)
CoordinateTailnet(ctx context.Context) (DRPCAgent_CoordinateTailnetClient, error)
}
type drpcAgentClient struct {
@@ -133,85 +130,6 @@ func (c *drpcAgentClient) BatchCreateLogs(ctx context.Context, in *BatchCreateLo
return out, nil
}
func (c *drpcAgentClient) StreamDERPMaps(ctx context.Context, in *proto1.StreamDERPMapsRequest) (DRPCAgent_StreamDERPMapsClient, error) {
stream, err := c.cc.NewStream(ctx, "/coder.agent.v2.Agent/StreamDERPMaps", drpcEncoding_File_agent_proto_agent_proto{})
if err != nil {
return nil, err
}
x := &drpcAgent_StreamDERPMapsClient{stream}
if err := x.MsgSend(in, drpcEncoding_File_agent_proto_agent_proto{}); err != nil {
return nil, err
}
if err := x.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type DRPCAgent_StreamDERPMapsClient interface {
drpc.Stream
Recv() (*proto1.DERPMap, error)
}
type drpcAgent_StreamDERPMapsClient struct {
drpc.Stream
}
func (x *drpcAgent_StreamDERPMapsClient) GetStream() drpc.Stream {
return x.Stream
}
func (x *drpcAgent_StreamDERPMapsClient) Recv() (*proto1.DERPMap, error) {
m := new(proto1.DERPMap)
if err := x.MsgRecv(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil {
return nil, err
}
return m, nil
}
func (x *drpcAgent_StreamDERPMapsClient) RecvMsg(m *proto1.DERPMap) error {
return x.MsgRecv(m, drpcEncoding_File_agent_proto_agent_proto{})
}
func (c *drpcAgentClient) CoordinateTailnet(ctx context.Context) (DRPCAgent_CoordinateTailnetClient, error) {
stream, err := c.cc.NewStream(ctx, "/coder.agent.v2.Agent/CoordinateTailnet", drpcEncoding_File_agent_proto_agent_proto{})
if err != nil {
return nil, err
}
x := &drpcAgent_CoordinateTailnetClient{stream}
return x, nil
}
type DRPCAgent_CoordinateTailnetClient interface {
drpc.Stream
Send(*proto1.CoordinateRequest) error
Recv() (*proto1.CoordinateResponse, error)
}
type drpcAgent_CoordinateTailnetClient struct {
drpc.Stream
}
func (x *drpcAgent_CoordinateTailnetClient) GetStream() drpc.Stream {
return x.Stream
}
func (x *drpcAgent_CoordinateTailnetClient) Send(m *proto1.CoordinateRequest) error {
return x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{})
}
func (x *drpcAgent_CoordinateTailnetClient) Recv() (*proto1.CoordinateResponse, error) {
m := new(proto1.CoordinateResponse)
if err := x.MsgRecv(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil {
return nil, err
}
return m, nil
}
func (x *drpcAgent_CoordinateTailnetClient) RecvMsg(m *proto1.CoordinateResponse) error {
return x.MsgRecv(m, drpcEncoding_File_agent_proto_agent_proto{})
}
type DRPCAgentServer interface {
GetManifest(context.Context, *GetManifestRequest) (*Manifest, error)
GetServiceBanner(context.Context, *GetServiceBannerRequest) (*ServiceBanner, error)
@@ -221,8 +139,6 @@ type DRPCAgentServer interface {
UpdateStartup(context.Context, *UpdateStartupRequest) (*Startup, error)
BatchUpdateMetadata(context.Context, *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
BatchCreateLogs(context.Context, *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
StreamDERPMaps(*proto1.StreamDERPMapsRequest, DRPCAgent_StreamDERPMapsStream) error
CoordinateTailnet(DRPCAgent_CoordinateTailnetStream) error
}
type DRPCAgentUnimplementedServer struct{}
@@ -259,17 +175,9 @@ func (s *DRPCAgentUnimplementedServer) BatchCreateLogs(context.Context, *BatchCr
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCAgentUnimplementedServer) StreamDERPMaps(*proto1.StreamDERPMapsRequest, DRPCAgent_StreamDERPMapsStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCAgentUnimplementedServer) CoordinateTailnet(DRPCAgent_CoordinateTailnetStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
type DRPCAgentDescription struct{}
func (DRPCAgentDescription) NumMethods() int { return 10 }
func (DRPCAgentDescription) NumMethods() int { return 8 }
func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n {
@@ -345,23 +253,6 @@ func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
in1.(*BatchCreateLogsRequest),
)
}, DRPCAgentServer.BatchCreateLogs, true
case 8:
return "/coder.agent.v2.Agent/StreamDERPMaps", drpcEncoding_File_agent_proto_agent_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return nil, srv.(DRPCAgentServer).
StreamDERPMaps(
in1.(*proto1.StreamDERPMapsRequest),
&drpcAgent_StreamDERPMapsStream{in2.(drpc.Stream)},
)
}, DRPCAgentServer.StreamDERPMaps, true
case 9:
return "/coder.agent.v2.Agent/CoordinateTailnet", drpcEncoding_File_agent_proto_agent_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return nil, srv.(DRPCAgentServer).
CoordinateTailnet(
&drpcAgent_CoordinateTailnetStream{in1.(drpc.Stream)},
)
}, DRPCAgentServer.CoordinateTailnet, true
default:
return "", nil, nil, nil, false
}
@@ -498,42 +389,3 @@ func (x *drpcAgent_BatchCreateLogsStream) SendAndClose(m *BatchCreateLogsRespons
}
return x.CloseSend()
}
type DRPCAgent_StreamDERPMapsStream interface {
drpc.Stream
Send(*proto1.DERPMap) error
}
type drpcAgent_StreamDERPMapsStream struct {
drpc.Stream
}
func (x *drpcAgent_StreamDERPMapsStream) Send(m *proto1.DERPMap) error {
return x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{})
}
type DRPCAgent_CoordinateTailnetStream interface {
drpc.Stream
Send(*proto1.CoordinateResponse) error
Recv() (*proto1.CoordinateRequest, error)
}
type drpcAgent_CoordinateTailnetStream struct {
drpc.Stream
}
func (x *drpcAgent_CoordinateTailnetStream) Send(m *proto1.CoordinateResponse) error {
return x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{})
}
func (x *drpcAgent_CoordinateTailnetStream) Recv() (*proto1.CoordinateRequest, error) {
m := new(proto1.CoordinateRequest)
if err := x.MsgRecv(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil {
return nil, err
}
return m, nil
}
func (x *drpcAgent_CoordinateTailnetStream) RecvMsg(m *proto1.CoordinateRequest) error {
return x.MsgRecv(m, drpcEncoding_File_agent_proto_agent_proto{})
}
+26
View File
@@ -0,0 +1,26 @@
package proto
func LabelsEqual(a, b []*Stats_Metric_Label) bool {
am := make(map[string]string, len(a))
for _, lbl := range a {
v := lbl.GetValue()
if v == "" {
// Prometheus considers empty labels as equivalent to being absent
continue
}
am[lbl.GetName()] = lbl.GetValue()
}
lenB := 0
for _, lbl := range b {
v := lbl.GetValue()
if v == "" {
// Prometheus considers empty labels as equivalent to being absent
continue
}
lenB++
if am[lbl.GetName()] != v {
return false
}
}
return len(am) == lenB
}
+77
View File
@@ -0,0 +1,77 @@
package proto_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/agent/proto"
)
func TestLabelsEqual(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
a []*proto.Stats_Metric_Label
b []*proto.Stats_Metric_Label
eq bool
}{
{
name: "mainlineEq",
a: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
},
b: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
},
eq: true,
},
{
name: "emptyValue",
a: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
{Name: "singularity", Value: ""},
},
b: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
},
eq: true,
},
{
name: "extra",
a: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
{Name: "opacity", Value: "seyshells"},
},
b: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
},
eq: false,
},
{
name: "different",
a: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "sus"},
{Name: "color", Value: "aquamarine"},
},
b: []*proto.Stats_Metric_Label{
{Name: "credulity", Value: "legit"},
{Name: "color", Value: "aquamarine"},
},
eq: false,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tc.eq, proto.LabelsEqual(tc.a, tc.b))
require.Equal(t, tc.eq, proto.LabelsEqual(tc.b, tc.a))
})
}
}
+10
View File
@@ -0,0 +1,10 @@
package proto
import (
"github.com/coder/coder/v2/tailnet/proto"
)
// CurrentVersion is the current version of the agent API. It is tied to the
// tailnet API version to avoid confusion, since agents connect to the tailnet
// API over the same websocket.
var CurrentVersion = proto.CurrentVersion
+7
View File
@@ -81,6 +81,13 @@ func newScreen(ctx context.Context, cmd *pty.Cmd, options *Options, logger slog.
rpty.id = hex.EncodeToString(buf)
settings := []string{
// Disable the startup message that appears for five seconds.
"startup_message off",
// Some message are hard-coded, the best we can do is set msgwait to 0
// which seems to hide them. This can happen for example if screen shows
// the version message when starting up.
"msgminwait 0",
"msgwait 0",
// Tell screen not to handle motion for xterm* terminals which allows
// scrolling the terminal via the mouse wheel or scroll bar (by default
// screen uses it to cycle through the command history). There does not
+126
View File
@@ -0,0 +1,126 @@
package agent
import (
"context"
"sync"
"time"
"golang.org/x/xerrors"
"tailscale.com/types/netlogtype"
"cdr.dev/slog"
"github.com/coder/coder/v2/agent/proto"
)
const maxConns = 2048
type networkStatsSource interface {
SetConnStatsCallback(maxPeriod time.Duration, maxConns int, dump func(start, end time.Time, virtual, physical map[netlogtype.Connection]netlogtype.Counts))
}
type statsCollector interface {
Collect(ctx context.Context, networkStats map[netlogtype.Connection]netlogtype.Counts) *proto.Stats
}
type statsDest interface {
UpdateStats(ctx context.Context, req *proto.UpdateStatsRequest) (*proto.UpdateStatsResponse, error)
}
// statsReporter is a subcomponent of the agent that handles registering the stats callback on the
// networkStatsSource (tailnet.Conn in prod), handling the callback, calling back to the
// statsCollector (agent in prod) to collect additional stats, then sending the update to the
// statsDest (agent API in prod)
type statsReporter struct {
*sync.Cond
networkStats *map[netlogtype.Connection]netlogtype.Counts
unreported bool
lastInterval time.Duration
source networkStatsSource
collector statsCollector
logger slog.Logger
}
func newStatsReporter(logger slog.Logger, source networkStatsSource, collector statsCollector) *statsReporter {
return &statsReporter{
Cond: sync.NewCond(&sync.Mutex{}),
logger: logger,
source: source,
collector: collector,
}
}
func (s *statsReporter) callback(_, _ time.Time, virtual, _ map[netlogtype.Connection]netlogtype.Counts) {
s.L.Lock()
defer s.L.Unlock()
s.logger.Debug(context.Background(), "got stats callback")
s.networkStats = &virtual
s.unreported = true
s.Broadcast()
}
// reportLoop programs the source (tailnet.Conn) to send it stats via the
// callback, then reports them to the dest.
//
// It's intended to be called within the larger retry loop that establishes a
// connection to the agent API, then passes that connection to go routines like
// this that use it. There is no retry and we fail on the first error since
// this will be inside a larger retry loop.
func (s *statsReporter) reportLoop(ctx context.Context, dest statsDest) error {
// send an initial, blank report to get the interval
resp, err := dest.UpdateStats(ctx, &proto.UpdateStatsRequest{})
if err != nil {
return xerrors.Errorf("initial update: %w", err)
}
s.lastInterval = resp.ReportInterval.AsDuration()
s.source.SetConnStatsCallback(s.lastInterval, maxConns, s.callback)
// use a separate goroutine to monitor the context so that we notice immediately, rather than
// waiting for the next callback (which might never come if we are closing!)
ctxDone := false
go func() {
<-ctx.Done()
s.L.Lock()
defer s.L.Unlock()
ctxDone = true
s.Broadcast()
}()
defer s.logger.Debug(ctx, "reportLoop exiting")
s.L.Lock()
defer s.L.Unlock()
for {
for !s.unreported && !ctxDone {
s.Wait()
}
if ctxDone {
return nil
}
networkStats := *s.networkStats
s.unreported = false
if err = s.reportLocked(ctx, dest, networkStats); err != nil {
return xerrors.Errorf("report stats: %w", err)
}
}
}
func (s *statsReporter) reportLocked(
ctx context.Context, dest statsDest, networkStats map[netlogtype.Connection]netlogtype.Counts,
) error {
// here we want to do our collecting/reporting while it is unlocked, but then relock
// when we return to reportLoop.
s.L.Unlock()
defer s.L.Lock()
stats := s.collector.Collect(ctx, networkStats)
resp, err := dest.UpdateStats(ctx, &proto.UpdateStatsRequest{Stats: stats})
if err != nil {
return err
}
interval := resp.GetReportInterval().AsDuration()
if interval != s.lastInterval {
s.logger.Info(ctx, "new stats report interval", slog.F("interval", interval))
s.lastInterval = interval
s.source.SetConnStatsCallback(s.lastInterval, maxConns, s.callback)
}
return nil
}
+212
View File
@@ -0,0 +1,212 @@
package agent
import (
"context"
"net/netip"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/durationpb"
"tailscale.com/types/ipproto"
"tailscale.com/types/netlogtype"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/testutil"
)
func TestStatsReporter(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitShort)
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
fSource := newFakeNetworkStatsSource(ctx, t)
fCollector := newFakeCollector(t)
fDest := newFakeStatsDest()
uut := newStatsReporter(logger, fSource, fCollector)
loopErr := make(chan error, 1)
loopCtx, loopCancel := context.WithCancel(ctx)
go func() {
err := uut.reportLoop(loopCtx, fDest)
loopErr <- err
}()
// initial request to get duration
req := testutil.RequireRecvCtx(ctx, t, fDest.reqs)
require.NotNil(t, req)
require.Nil(t, req.Stats)
interval := time.Second * 34
testutil.RequireSendCtx(ctx, t, fDest.resps, &proto.UpdateStatsResponse{ReportInterval: durationpb.New(interval)})
// call to source to set the callback and interval
gotInterval := testutil.RequireRecvCtx(ctx, t, fSource.period)
require.Equal(t, interval, gotInterval)
// callback returning netstats
netStats := map[netlogtype.Connection]netlogtype.Counts{
{
Proto: ipproto.TCP,
Src: netip.MustParseAddrPort("192.168.1.33:4887"),
Dst: netip.MustParseAddrPort("192.168.2.99:9999"),
}: {
TxPackets: 22,
TxBytes: 23,
RxPackets: 24,
RxBytes: 25,
},
}
fSource.callback(time.Now(), time.Now(), netStats, nil)
// collector called to complete the stats
gotNetStats := testutil.RequireRecvCtx(ctx, t, fCollector.calls)
require.Equal(t, netStats, gotNetStats)
// while we are collecting the stats, send in two new netStats to simulate
// what happens if we don't keep up. Only the latest should be kept.
netStats0 := map[netlogtype.Connection]netlogtype.Counts{
{
Proto: ipproto.TCP,
Src: netip.MustParseAddrPort("192.168.1.33:4887"),
Dst: netip.MustParseAddrPort("192.168.2.99:9999"),
}: {
TxPackets: 10,
TxBytes: 10,
RxPackets: 10,
RxBytes: 10,
},
}
fSource.callback(time.Now(), time.Now(), netStats0, nil)
netStats1 := map[netlogtype.Connection]netlogtype.Counts{
{
Proto: ipproto.TCP,
Src: netip.MustParseAddrPort("192.168.1.33:4887"),
Dst: netip.MustParseAddrPort("192.168.2.99:9999"),
}: {
TxPackets: 11,
TxBytes: 11,
RxPackets: 11,
RxBytes: 11,
},
}
fSource.callback(time.Now(), time.Now(), netStats1, nil)
// complete first collection
stats := &proto.Stats{SessionCountJetbrains: 55}
testutil.RequireSendCtx(ctx, t, fCollector.stats, stats)
// destination called to report the first stats
update := testutil.RequireRecvCtx(ctx, t, fDest.reqs)
require.NotNil(t, update)
require.Equal(t, stats, update.Stats)
testutil.RequireSendCtx(ctx, t, fDest.resps, &proto.UpdateStatsResponse{ReportInterval: durationpb.New(interval)})
// second update -- only netStats1 is reported
gotNetStats = testutil.RequireRecvCtx(ctx, t, fCollector.calls)
require.Equal(t, netStats1, gotNetStats)
stats = &proto.Stats{SessionCountJetbrains: 66}
testutil.RequireSendCtx(ctx, t, fCollector.stats, stats)
update = testutil.RequireRecvCtx(ctx, t, fDest.reqs)
require.NotNil(t, update)
require.Equal(t, stats, update.Stats)
interval2 := 27 * time.Second
testutil.RequireSendCtx(ctx, t, fDest.resps, &proto.UpdateStatsResponse{ReportInterval: durationpb.New(interval2)})
// set the new interval
gotInterval = testutil.RequireRecvCtx(ctx, t, fSource.period)
require.Equal(t, interval2, gotInterval)
loopCancel()
err := testutil.RequireRecvCtx(ctx, t, loopErr)
require.NoError(t, err)
}
type fakeNetworkStatsSource struct {
sync.Mutex
ctx context.Context
t testing.TB
callback func(start, end time.Time, virtual, physical map[netlogtype.Connection]netlogtype.Counts)
period chan time.Duration
}
func (f *fakeNetworkStatsSource) SetConnStatsCallback(maxPeriod time.Duration, _ int, dump func(start time.Time, end time.Time, virtual map[netlogtype.Connection]netlogtype.Counts, physical map[netlogtype.Connection]netlogtype.Counts)) {
f.Lock()
defer f.Unlock()
f.callback = dump
select {
case <-f.ctx.Done():
f.t.Error("timeout")
case f.period <- maxPeriod:
// OK
}
}
func newFakeNetworkStatsSource(ctx context.Context, t testing.TB) *fakeNetworkStatsSource {
f := &fakeNetworkStatsSource{
ctx: ctx,
t: t,
period: make(chan time.Duration),
}
return f
}
type fakeCollector struct {
t testing.TB
calls chan map[netlogtype.Connection]netlogtype.Counts
stats chan *proto.Stats
}
func (f *fakeCollector) Collect(ctx context.Context, networkStats map[netlogtype.Connection]netlogtype.Counts) *proto.Stats {
select {
case <-ctx.Done():
f.t.Error("timeout on collect")
return nil
case f.calls <- networkStats:
// ok
}
select {
case <-ctx.Done():
f.t.Error("timeout on collect")
return nil
case s := <-f.stats:
return s
}
}
func newFakeCollector(t testing.TB) *fakeCollector {
return &fakeCollector{
t: t,
calls: make(chan map[netlogtype.Connection]netlogtype.Counts),
stats: make(chan *proto.Stats),
}
}
type fakeStatsDest struct {
reqs chan *proto.UpdateStatsRequest
resps chan *proto.UpdateStatsResponse
}
func (f *fakeStatsDest) UpdateStats(ctx context.Context, req *proto.UpdateStatsRequest) (*proto.UpdateStatsResponse, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case f.reqs <- req:
// OK
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case resp := <-f.resps:
return resp, nil
}
}
func newFakeStatsDest() *fakeStatsDest {
return &fakeStatsDest{
reqs: make(chan *proto.UpdateStatsRequest),
resps: make(chan *proto.UpdateStatsResponse),
}
}
+4
View File
@@ -13,6 +13,10 @@ import (
func Get(username string) (string, error) {
// This command will output "UserShell: /bin/zsh" if successful, we
// can ignore the error since we have fallback behavior.
if !filepath.IsLocal(username) {
return "", xerrors.Errorf("username is nonlocal path: %s", username)
}
//nolint: gosec // input checked above
out, _ := exec.Command("dscl", ".", "-read", filepath.Join("/Users", username), "UserShell").Output()
s, ok := strings.CutPrefix(string(out), "UserShell: ")
if ok {
+89
View File
@@ -0,0 +1,89 @@
package apiversion
import (
"fmt"
"strconv"
"strings"
"golang.org/x/xerrors"
)
// New returns an *APIVersion with the given major.minor and
// additional supported major versions.
func New(maj, min int) *APIVersion {
v := &APIVersion{
supportedMajor: maj,
supportedMinor: min,
additionalMajors: make([]int, 0),
}
return v
}
type APIVersion struct {
supportedMajor int
supportedMinor int
additionalMajors []int
}
func (v *APIVersion) WithBackwardCompat(majs ...int) *APIVersion {
v.additionalMajors = append(v.additionalMajors, majs[:]...)
return v
}
func (v *APIVersion) String() string {
return fmt.Sprintf("%d.%d", v.supportedMajor, v.supportedMinor)
}
// Validate validates the given version against the given constraints:
// A given major.minor version is valid iff:
// 1. The requested major version is contained within v.supportedMajors
// 2. If the requested major version is the 'current major', then
// the requested minor version must be less than or equal to the supported
// minor version.
//
// For example, given majors {1, 2} and minor 2, then:
// - 0.x is not supported,
// - 1.x is supported,
// - 2.0, 2.1, and 2.2 are supported,
// - 2.3+ is not supported.
func (v *APIVersion) Validate(version string) error {
major, minor, err := Parse(version)
if err != nil {
return err
}
if major > v.supportedMajor {
return xerrors.Errorf("server is at version %d.%d, behind requested major version %s",
v.supportedMajor, v.supportedMinor, version)
}
if major == v.supportedMajor {
if minor > v.supportedMinor {
return xerrors.Errorf("server is at version %d.%d, behind requested minor version %s",
v.supportedMajor, v.supportedMinor, version)
}
return nil
}
for _, mjr := range v.additionalMajors {
if major == mjr {
return nil
}
}
return xerrors.Errorf("version %s is no longer supported", version)
}
// Parse parses a valid major.minor version string into (major, minor).
// Both major and minor must be valid integers separated by a period '.'.
func Parse(version string) (major int, minor int, err error) {
parts := strings.Split(version, ".")
if len(parts) != 2 {
return 0, 0, xerrors.Errorf("invalid version string: %s", version)
}
major, err = strconv.Atoi(parts[0])
if err != nil {
return 0, 0, xerrors.Errorf("invalid major version: %s", version)
}
minor, err = strconv.Atoi(parts[1])
if err != nil {
return 0, 0, xerrors.Errorf("invalid minor version: %s", version)
}
return major, minor, nil
}
+90
View File
@@ -0,0 +1,90 @@
package apiversion_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/apiversion"
)
func TestAPIVersionValidate(t *testing.T) {
t.Parallel()
// Given
v := apiversion.New(2, 1).WithBackwardCompat(1)
for _, tc := range []struct {
name string
version string
expectedError string
}{
{
name: "OK",
version: "2.1",
},
{
name: "MinorOK",
version: "2.0",
},
{
name: "MajorOK",
version: "1.0",
},
{
name: "TooNewMinor",
version: "2.2",
expectedError: "behind requested minor version",
},
{
name: "TooNewMajor",
version: "3.1",
expectedError: "behind requested major version",
},
{
name: "Malformed0",
version: "cats",
expectedError: "invalid version string",
},
{
name: "Malformed1",
version: "cats.dogs",
expectedError: "invalid major version",
},
{
name: "Malformed2",
version: "1.dogs",
expectedError: "invalid minor version",
},
{
name: "Malformed3",
version: "1.0.1",
expectedError: "invalid version string",
},
{
name: "Malformed4",
version: "11",
expectedError: "invalid version string",
},
{
name: "TooOld",
version: "0.8",
expectedError: "no longer supported",
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// When
err := v.Validate(tc.version)
// Then
if tc.expectedError == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tc.expectedError)
}
})
}
}
+24 -12
View File
@@ -40,6 +40,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
var (
auth string
logDir string
scriptDataDir string
pprofAddress string
noReap bool
sshMaxTimeout time.Duration
@@ -148,13 +149,14 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
// DumpHandler does signal handling, so we call it after the
// reaper.
go DumpHandler(ctx)
go DumpHandler(ctx, "agent")
logWriter := &lumberjackWriteCloseFixer{w: &lumberjack.Logger{
Filename: filepath.Join(logDir, "coder-agent.log"),
MaxSize: 5, // MB
// Without this, rotated logs will never be deleted.
MaxBackups: 1,
// Per customer incident on November 17th, 2023, its helpful
// to have the log of the last few restarts to debug a failing agent.
MaxBackups: 10,
}}
defer logWriter.Close()
@@ -277,12 +279,18 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
subsystems = append(subsystems, subsystem)
}
procTicker := time.NewTicker(time.Second)
defer procTicker.Stop()
environmentVariables := map[string]string{
"GIT_ASKPASS": executablePath,
}
if v, ok := os.LookupEnv(agent.EnvProcPrioMgmt); ok {
environmentVariables[agent.EnvProcPrioMgmt] = v
}
agnt := agent.New(agent.Options{
Client: client,
Logger: logger,
LogDir: logDir,
ScriptDataDir: scriptDataDir,
TailnetListenPort: uint16(tailnetListenPort),
ExchangeToken: func(ctx context.Context) (string, error) {
if exchangeToken == nil {
@@ -295,13 +303,10 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
client.SetSessionToken(resp.SessionToken)
return resp.SessionToken, nil
},
EnvironmentVariables: map[string]string{
"GIT_ASKPASS": executablePath,
agent.EnvProcPrioMgmt: os.Getenv(agent.EnvProcPrioMgmt),
},
IgnorePorts: ignorePorts,
SSHMaxTimeout: sshMaxTimeout,
Subsystems: subsystems,
EnvironmentVariables: environmentVariables,
IgnorePorts: ignorePorts,
SSHMaxTimeout: sshMaxTimeout,
Subsystems: subsystems,
PrometheusRegistry: prometheusRegistry,
Syscaller: agentproc.NewSyscaller(),
@@ -336,6 +341,13 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
Env: "CODER_AGENT_LOG_DIR",
Value: clibase.StringOf(&logDir),
},
{
Flag: "script-data-dir",
Default: os.TempDir(),
Description: "Specify the location for storing script data.",
Env: "CODER_AGENT_SCRIPT_DATA_DIR",
Value: clibase.StringOf(&scriptDataDir),
},
{
Flag: "pprof-address",
Default: "127.0.0.1:6060",
+64 -47
View File
@@ -31,22 +31,24 @@ func TestWorkspaceAgent(t *testing.T) {
client, db := coderdtest.NewWithDatabase(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ws, authToken := dbfake.WorkspaceWithAgent(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
})
}).
WithAgent().
Do()
logDir := t.TempDir()
inv, _ := clitest.New(t,
"agent",
"--auth", "token",
"--agent-token", authToken,
"--agent-token", r.AgentToken,
"--agent-url", client.URL.String(),
"--log-dir", logDir,
)
clitest.Start(t, inv)
coderdtest.AwaitWorkspaceAgents(t, client, ws.ID)
coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
require.Eventually(t, func() bool {
info, err := os.Stat(filepath.Join(logDir, "coder-agent.log"))
@@ -65,19 +67,13 @@ func TestWorkspaceAgent(t *testing.T) {
AzureCertificates: certificates,
})
user := coderdtest.CreateFirstUser(t, client)
ws := dbfake.Workspace(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
})
dbfake.WorkspaceBuild(t, db, ws, database.WorkspaceBuild{}, &proto.Resource{
Name: "somename",
Type: "someinstance",
Agents: []*proto.Agent{{
Auth: &proto.Agent_InstanceId{
InstanceId: instanceID,
},
}},
})
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
agents[0].Auth = &proto.Agent_InstanceId{InstanceId: instanceID}
return agents
}).Do()
inv, _ := clitest.New(t, "agent", "--auth", "azure-instance-identity", "--agent-url", client.URL.String())
inv = inv.WithContext(
@@ -87,8 +83,9 @@ func TestWorkspaceAgent(t *testing.T) {
ctx := inv.Context()
clitest.Start(t, inv)
coderdtest.AwaitWorkspaceAgents(t, client, ws.ID)
workspace, err := client.Workspace(ctx, ws.ID)
coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
MatchResources(matchAgentWithVersion).Wait()
workspace, err := client.Workspace(ctx, r.Workspace.ID)
require.NoError(t, err)
resources := workspace.LatestBuild.Resources
if assert.NotEmpty(t, workspace.LatestBuild.Resources) && assert.NotEmpty(t, resources[0].Agents) {
@@ -108,19 +105,13 @@ func TestWorkspaceAgent(t *testing.T) {
AWSCertificates: certificates,
})
user := coderdtest.CreateFirstUser(t, client)
ws := dbfake.Workspace(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
})
dbfake.WorkspaceBuild(t, db, ws, database.WorkspaceBuild{}, &proto.Resource{
Name: "somename",
Type: "someinstance",
Agents: []*proto.Agent{{
Auth: &proto.Agent_InstanceId{
InstanceId: instanceID,
},
}},
})
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
agents[0].Auth = &proto.Agent_InstanceId{InstanceId: instanceID}
return agents
}).Do()
inv, _ := clitest.New(t, "agent", "--auth", "aws-instance-identity", "--agent-url", client.URL.String())
inv = inv.WithContext(
@@ -130,8 +121,10 @@ func TestWorkspaceAgent(t *testing.T) {
clitest.Start(t, inv)
ctx := inv.Context()
coderdtest.AwaitWorkspaceAgents(t, client, ws.ID)
workspace, err := client.Workspace(ctx, ws.ID)
coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
MatchResources(matchAgentWithVersion).
Wait()
workspace, err := client.Workspace(ctx, r.Workspace.ID)
require.NoError(t, err)
resources := workspace.LatestBuild.Resources
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
@@ -152,19 +145,14 @@ func TestWorkspaceAgent(t *testing.T) {
})
owner := coderdtest.CreateFirstUser(t, client)
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
ws := dbfake.Workspace(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: owner.OrganizationID,
OwnerID: memberUser.ID,
})
dbfake.WorkspaceBuild(t, db, ws, database.WorkspaceBuild{}, &proto.Resource{
Name: "somename",
Type: "someinstance",
Agents: []*proto.Agent{{
Auth: &proto.Agent_InstanceId{
InstanceId: instanceID,
},
}},
})
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
agents[0].Auth = &proto.Agent_InstanceId{InstanceId: instanceID}
return agents
}).Do()
inv, cfg := clitest.New(t, "agent", "--auth", "google-instance-identity", "--agent-url", client.URL.String())
clitest.SetupConfig(t, member, cfg)
@@ -176,8 +164,10 @@ func TestWorkspaceAgent(t *testing.T) {
)
ctx := inv.Context()
coderdtest.AwaitWorkspaceAgents(t, client, ws.ID)
workspace, err := client.Workspace(ctx, ws.ID)
coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
MatchResources(matchAgentWithVersion).
Wait()
workspace, err := client.Workspace(ctx, r.Workspace.ID)
require.NoError(t, err)
resources := workspace.LatestBuild.Resources
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
@@ -209,16 +199,16 @@ func TestWorkspaceAgent(t *testing.T) {
client, db := coderdtest.NewWithDatabase(t, nil)
user := coderdtest.CreateFirstUser(t, client)
ws, authToken := dbfake.WorkspaceWithAgent(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
})
}).WithAgent().Do()
logDir := t.TempDir()
inv, _ := clitest.New(t,
"agent",
"--auth", "token",
"--agent-token", authToken,
"--agent-token", r.AgentToken,
"--agent-url", client.URL.String(),
"--log-dir", logDir,
)
@@ -227,7 +217,8 @@ func TestWorkspaceAgent(t *testing.T) {
clitest.Start(t, inv)
resources := coderdtest.AwaitWorkspaceAgents(t, client, ws.ID)
resources := coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
MatchResources(matchAgentWithSubsystems).Wait()
require.Len(t, resources, 1)
require.Len(t, resources[0].Agents, 1)
require.Len(t, resources[0].Agents[0].Subsystems, 2)
@@ -236,3 +227,29 @@ func TestWorkspaceAgent(t *testing.T) {
require.Equal(t, codersdk.AgentSubsystemExectrace, resources[0].Agents[0].Subsystems[1])
})
}
func matchAgentWithVersion(rs []codersdk.WorkspaceResource) bool {
if len(rs) < 1 {
return false
}
if len(rs[0].Agents) < 1 {
return false
}
if rs[0].Agents[0].Version == "" {
return false
}
return true
}
func matchAgentWithSubsystems(rs []codersdk.WorkspaceResource) bool {
if len(rs) < 1 {
return false
}
if len(rs[0].Agents) < 1 {
return false
}
if len(rs[0].Agents[0].Subsystems) < 1 {
return false
}
return true
}
+17 -3
View File
@@ -189,6 +189,7 @@ type Invocation struct {
Stderr io.Writer
Stdin io.Reader
Logger slog.Logger
Net Net
// testing
signalNotifyContext func(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)
@@ -203,6 +204,7 @@ func (inv *Invocation) WithOS() *Invocation {
i.Stdin = os.Stdin
i.Args = os.Args[1:]
i.Environ = ParseEnviron(os.Environ(), "")
i.Net = osNet{}
})
}
@@ -381,7 +383,8 @@ func (inv *Invocation) run(state *runState) error {
missing = append(missing, opt.Flag)
}
}
if len(missing) > 0 {
// Don't error for missing flags if `--help` was supplied.
if len(missing) > 0 && !errors.Is(state.flagParseErr, pflag.ErrHelp) {
return xerrors.Errorf("Missing values for the required flags: %s", strings.Join(missing, ", "))
}
@@ -559,6 +562,16 @@ func Chain(ms ...MiddlewareFunc) MiddlewareFunc {
return chain(reversed...)
}
func ShowUsageOnError(next HandlerFunc) HandlerFunc {
return func(i *Invocation) error {
err := next(i)
if err != nil {
return xerrors.Errorf("Usage: %s\nError: %w", i.Command.FullUsage(), err)
}
return nil
}
}
func RequireNArgs(want int) MiddlewareFunc {
return RequireRangeArgs(want, want)
}
@@ -571,7 +584,8 @@ func RequireRangeArgs(start, end int) MiddlewareFunc {
panic("start must be >= 0")
}
return func(next HandlerFunc) HandlerFunc {
return func(i *Invocation) error {
// ShowUsageOnError will add the command usage before the error message.
return ShowUsageOnError(func(i *Invocation) error {
got := len(i.Args)
switch {
case start == end && got != start:
@@ -611,7 +625,7 @@ func RequireRangeArgs(start, end int) MiddlewareFunc {
default:
return next(i)
}
}
})
}
}
+16
View File
@@ -79,6 +79,10 @@ func TestCommand(t *testing.T) {
Required: true,
},
},
HelpHandler: func(i *clibase.Invocation) error {
_, _ = i.Stdout.Write([]byte("help text.png"))
return nil
},
Handler: func(i *clibase.Invocation) error {
_, _ = i.Stdout.Write([]byte(fmt.Sprintf("%s-%t", reqStr, reqBool)))
return nil
@@ -255,6 +259,18 @@ func TestCommand(t *testing.T) {
require.ErrorContains(t, err, "Missing values")
})
t.Run("RequiredFlagsMissingWithHelp", func(t *testing.T) {
t.Parallel()
i := cmd().Invoke(
"required-flag",
"--help",
)
fio := fakeIO(i)
err := i.Run()
require.NoError(t, err)
require.Contains(t, fio.Stdout.String(), "help text.png")
})
t.Run("RequiredFlagsMissingBool", func(t *testing.T) {
t.Parallel()
i := cmd().Invoke(
+50
View File
@@ -0,0 +1,50 @@
package clibase
import (
"net"
"strconv"
"github.com/pion/udp"
"golang.org/x/xerrors"
)
// Net abstracts CLI commands interacting with the operating system networking.
//
// At present, it covers opening local listening sockets, since doing this
// in testing is a challenge without flakes, since it's hard to pick a port we
// know a priori will be free.
type Net interface {
// Listen has the same semantics as `net.Listen` but also supports `udp`
Listen(network, address string) (net.Listener, error)
}
// osNet is an implementation that call the real OS for networking.
type osNet struct{}
func (osNet) Listen(network, address string) (net.Listener, error) {
switch network {
case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
return net.Listen(network, address)
case "udp":
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, xerrors.Errorf("split %q: %w", address, err)
}
var portInt int
portInt, err = strconv.Atoi(port)
if err != nil {
return nil, xerrors.Errorf("parse port %v from %q as int: %w", port, address, err)
}
// Use pion here so that we get a stream-style net.Conn listener, instead
// of a packet-oriented connection that can read and write to multiple
// addresses.
return udp.Listen(network, &net.UDPAddr{
IP: net.ParseIP(host),
Port: portInt,
})
default:
return nil, xerrors.Errorf("unknown listen network %q", network)
}
}
+27 -1
View File
@@ -59,6 +59,28 @@ func (i *Validator[T]) Type() string {
return i.Value.Type()
}
func (i *Validator[T]) MarshalYAML() (interface{}, error) {
m, ok := any(i.Value).(yaml.Marshaler)
if !ok {
return i.Value, nil
}
return m.MarshalYAML()
}
func (i *Validator[T]) UnmarshalYAML(n *yaml.Node) error {
return n.Decode(i.Value)
}
func (i *Validator[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Value)
}
func (i *Validator[T]) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, i.Value)
}
func (i *Validator[T]) Underlying() pflag.Value { return i.Value }
// values.go contains a standard set of value types that can be used as
// Option Values.
@@ -378,6 +400,7 @@ func (s *Struct[T]) String() string {
return string(byt)
}
// nolint:revive
func (s *Struct[T]) MarshalYAML() (interface{}, error) {
var n yaml.Node
err := n.Encode(s.Value)
@@ -387,6 +410,7 @@ func (s *Struct[T]) MarshalYAML() (interface{}, error) {
return n, nil
}
// nolint:revive
func (s *Struct[T]) UnmarshalYAML(n *yaml.Node) error {
// HACK: for compatibility with flags, we use nil slices instead of empty
// slices. In most cases, nil slices and empty slices are treated
@@ -403,10 +427,12 @@ func (s *Struct[T]) Type() string {
return fmt.Sprintf("struct[%T]", s.Value)
}
// nolint:revive
func (s *Struct[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(s.Value)
}
// nolint:revive
func (s *Struct[T]) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &s.Value)
}
@@ -484,7 +510,7 @@ func (e *Enum) Set(v string) error {
}
func (e *Enum) Type() string {
return fmt.Sprintf("enum[%v]", strings.Join(e.Choices, "|"))
return fmt.Sprintf("enum[%v]", strings.Join(e.Choices, "\\|"))
}
func (e *Enum) String() string {
+5 -1
View File
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/mitchellh/go-wordwrap"
"github.com/spf13/pflag"
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
)
@@ -74,13 +75,16 @@ func (optSet *OptionSet) MarshalYAML() (any, error) {
Value: opt.YAML,
HeadComment: comment,
}
_, isValidator := opt.Value.(interface{ Underlying() pflag.Value })
var valueNode yaml.Node
if opt.Value == nil {
valueNode = yaml.Node{
Kind: yaml.ScalarNode,
Value: "null",
}
} else if m, ok := opt.Value.(yaml.Marshaler); ok {
} else if m, ok := opt.Value.(yaml.Marshaler); ok && !isValidator {
// Validators do a wrap, and should be handled by the else statement.
v, err := m.MarshalYAML()
if err != nil {
return nil, xerrors.Errorf(
+211
View File
@@ -0,0 +1,211 @@
package clilog
import (
"context"
"fmt"
"io"
"os"
"regexp"
"strings"
"golang.org/x/xerrors"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"cdr.dev/slog/sloggers/slogjson"
"cdr.dev/slog/sloggers/slogstackdriver"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/codersdk"
)
type (
Option func(*Builder)
Builder struct {
Filter []string
Human string
JSON string
Stackdriver string
Trace bool
Verbose bool
}
)
func New(opts ...Option) *Builder {
b := &Builder{}
for _, opt := range opts {
opt(b)
}
return b
}
func WithFilter(filters ...string) Option {
return func(b *Builder) {
b.Filter = filters
}
}
func WithHuman(loc string) Option {
return func(b *Builder) {
b.Human = loc
}
}
func WithJSON(loc string) Option {
return func(b *Builder) {
b.JSON = loc
}
}
func WithStackdriver(loc string) Option {
return func(b *Builder) {
b.Stackdriver = loc
}
}
func WithTrace() Option {
return func(b *Builder) {
b.Trace = true
}
}
func WithVerbose() Option {
return func(b *Builder) {
b.Verbose = true
}
}
func FromDeploymentValues(vals *codersdk.DeploymentValues) Option {
return func(b *Builder) {
b.Filter = vals.Logging.Filter.Value()
b.Human = vals.Logging.Human.Value()
b.JSON = vals.Logging.JSON.Value()
b.Stackdriver = vals.Logging.Stackdriver.Value()
b.Trace = vals.Trace.Enable.Value()
b.Verbose = vals.Verbose.Value()
}
}
func (b *Builder) Build(inv *clibase.Invocation) (log slog.Logger, closeLog func(), err error) {
var (
sinks = []slog.Sink{}
closers = []func() error{}
)
defer func() {
if err != nil {
for _, closer := range closers {
_ = closer()
}
}
}()
noopClose := func() {}
addSinkIfProvided := func(sinkFn func(io.Writer) slog.Sink, loc string) error {
switch loc {
case "":
case "/dev/stdout":
sinks = append(sinks, sinkFn(inv.Stdout))
case "/dev/stderr":
sinks = append(sinks, sinkFn(inv.Stderr))
default:
fi, err := os.OpenFile(loc, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
if err != nil {
return xerrors.Errorf("open log file %q: %w", loc, err)
}
closers = append(closers, fi.Close)
sinks = append(sinks, sinkFn(fi))
}
return nil
}
err = addSinkIfProvided(sloghuman.Sink, b.Human)
if err != nil {
return slog.Logger{}, noopClose, xerrors.Errorf("add human sink: %w", err)
}
err = addSinkIfProvided(slogjson.Sink, b.JSON)
if err != nil {
return slog.Logger{}, noopClose, xerrors.Errorf("add json sink: %w", err)
}
err = addSinkIfProvided(slogstackdriver.Sink, b.Stackdriver)
if err != nil {
return slog.Logger{}, noopClose, xerrors.Errorf("add stackdriver sink: %w", err)
}
if b.Trace {
sinks = append(sinks, tracing.SlogSink{})
}
// User should log to null device if they don't want logs.
if len(sinks) == 0 {
return slog.Logger{}, noopClose, xerrors.New("no loggers provided, use /dev/null to disable logging")
}
filter := &debugFilterSink{next: sinks}
err = filter.compile(b.Filter)
if err != nil {
return slog.Logger{}, noopClose, xerrors.Errorf("compile filters: %w", err)
}
level := slog.LevelInfo
// Debug logging is always enabled if a filter is present.
if b.Verbose || filter.re != nil {
level = slog.LevelDebug
}
return inv.Logger.AppendSinks(filter).Leveled(level), func() {
for _, closer := range closers {
_ = closer()
}
}, nil
}
var _ slog.Sink = &debugFilterSink{}
type debugFilterSink struct {
next []slog.Sink
re *regexp.Regexp
}
func (f *debugFilterSink) compile(res []string) error {
if len(res) == 0 {
return nil
}
var reb strings.Builder
for i, re := range res {
_, _ = fmt.Fprintf(&reb, "(%s)", re)
if i != len(res)-1 {
_, _ = reb.WriteRune('|')
}
}
re, err := regexp.Compile(reb.String())
if err != nil {
return xerrors.Errorf("compile regex: %w", err)
}
f.re = re
return nil
}
func (f *debugFilterSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
if ent.Level == slog.LevelDebug {
logName := strings.Join(ent.LoggerNames, ".")
if f.re != nil && !f.re.MatchString(logName) && !f.re.MatchString(ent.Message) {
return
}
}
for _, sink := range f.next {
sink.LogEntry(ctx, ent)
}
}
func (f *debugFilterSink) Sync() {
for _, sink := range f.next {
sink.Sync()
}
}
+243
View File
@@ -0,0 +1,243 @@
package clilog_test
import (
"encoding/json"
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/clilog"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBuilder(t *testing.T) {
t.Parallel()
t.Run("NoConfiguration", func(t *testing.T) {
t.Parallel()
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t),
}
err := cmd.Invoke().Run()
require.ErrorContains(t, err, "no loggers provided, use /dev/null to disable logging")
})
t.Run("Verbose", func(t *testing.T) {
t.Parallel()
tempFile := filepath.Join(t.TempDir(), "test.log")
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t,
clilog.WithHuman(tempFile),
clilog.WithVerbose(),
),
}
err := cmd.Invoke().Run()
require.NoError(t, err)
assertLogs(t, tempFile, debugLog, infoLog, warnLog, filterLog)
})
t.Run("WithFilter", func(t *testing.T) {
t.Parallel()
tempFile := filepath.Join(t.TempDir(), "test.log")
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t,
clilog.WithHuman(tempFile),
// clilog.WithVerbose(), // implicit
clilog.WithFilter("important debug message"),
),
}
err := cmd.Invoke().Run()
require.NoError(t, err)
assertLogs(t, tempFile, infoLog, warnLog, filterLog)
})
t.Run("WithHuman", func(t *testing.T) {
t.Parallel()
tempFile := filepath.Join(t.TempDir(), "test.log")
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t, clilog.WithHuman(tempFile)),
}
err := cmd.Invoke().Run()
require.NoError(t, err)
assertLogs(t, tempFile, infoLog, warnLog)
})
t.Run("WithJSON", func(t *testing.T) {
t.Parallel()
tempFile := filepath.Join(t.TempDir(), "test.log")
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t, clilog.WithJSON(tempFile), clilog.WithVerbose()),
}
err := cmd.Invoke().Run()
require.NoError(t, err)
assertLogsJSON(t, tempFile, debug, debugLog, info, infoLog, warn, warnLog, debug, filterLog)
})
t.Run("FromDeploymentValues", func(t *testing.T) {
t.Parallel()
t.Run("Defaults", func(t *testing.T) {
stdoutPath := filepath.Join(t.TempDir(), "stdout")
stderrPath := filepath.Join(t.TempDir(), "stderr")
stdout, err := os.OpenFile(stdoutPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
require.NoError(t, err)
t.Cleanup(func() { _ = stdout.Close() })
stderr, err := os.OpenFile(stderrPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
require.NoError(t, err)
t.Cleanup(func() { _ = stderr.Close() })
// Use the default deployment values.
dv := coderdtest.DeploymentValues(t)
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t, clilog.FromDeploymentValues(dv)),
}
inv := cmd.Invoke()
inv.Stdout = stdout
inv.Stderr = stderr
err = inv.Run()
require.NoError(t, err)
assertLogs(t, stdoutPath, "")
assertLogs(t, stderrPath, infoLog, warnLog)
})
t.Run("Override", func(t *testing.T) {
tempFile := filepath.Join(t.TempDir(), "test.log")
tempJSON := filepath.Join(t.TempDir(), "test.json")
dv := &codersdk.DeploymentValues{
Logging: codersdk.LoggingConfig{
Filter: []string{"foo", "baz"},
Human: clibase.String(tempFile),
JSON: clibase.String(tempJSON),
},
Verbose: true,
Trace: codersdk.TraceConfig{
Enable: true,
},
}
cmd := &clibase.Cmd{
Use: "test",
Handler: testHandler(t, clilog.FromDeploymentValues(dv)),
}
err := cmd.Invoke().Run()
require.NoError(t, err)
assertLogs(t, tempFile, infoLog, warnLog)
assertLogsJSON(t, tempJSON, info, infoLog, warn, warnLog)
})
})
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
tempFile := filepath.Join(t.TempDir(), "doesnotexist", "test.log")
cmd := &clibase.Cmd{
Use: "test",
Handler: func(inv *clibase.Invocation) error {
logger, closeLog, err := clilog.New(
clilog.WithFilter("foo", "baz"),
clilog.WithHuman(tempFile),
clilog.WithVerbose(),
).Build(inv)
if err != nil {
return err
}
defer closeLog()
logger.Error(inv.Context(), "you will never see this")
return nil
},
}
err := cmd.Invoke().Run()
require.ErrorIs(t, err, fs.ErrNotExist)
})
}
var (
debug = "DEBUG"
info = "INFO"
warn = "WARN"
debugLog = "this is a debug message"
infoLog = "this is an info message"
warnLog = "this is a warning message"
filterLog = "this is an important debug message you want to see"
)
func testHandler(t testing.TB, opts ...clilog.Option) clibase.HandlerFunc {
t.Helper()
return func(inv *clibase.Invocation) error {
logger, closeLog, err := clilog.New(opts...).Build(inv)
if err != nil {
return err
}
defer closeLog()
logger.Debug(inv.Context(), debugLog)
logger.Info(inv.Context(), infoLog)
logger.Warn(inv.Context(), warnLog)
logger.Debug(inv.Context(), filterLog)
return nil
}
}
func assertLogs(t testing.TB, path string, expected ...string) {
t.Helper()
data, err := os.ReadFile(path)
require.NoError(t, err)
logs := strings.Split(strings.TrimSpace(string(data)), "\n")
if !assert.Len(t, logs, len(expected)) {
t.Logf(string(data))
t.FailNow()
}
for i, log := range logs {
require.Contains(t, log, expected[i])
}
}
func assertLogsJSON(t testing.TB, path string, levelExpected ...string) {
t.Helper()
data, err := os.ReadFile(path)
require.NoError(t, err)
if len(levelExpected)%2 != 0 {
t.Errorf("levelExpected must be a list of level-message pairs")
return
}
logs := strings.Split(strings.TrimSpace(string(data)), "\n")
if !assert.Len(t, logs, len(levelExpected)/2) {
t.Logf(string(data))
t.FailNow()
}
for i, log := range logs {
var entry struct {
Level string `json:"level"`
Message string `json:"msg"`
}
err := json.NewDecoder(strings.NewReader(log)).Decode(&entry)
require.NoError(t, err)
require.Equal(t, levelExpected[2*i], entry.Level)
require.Equal(t, levelExpected[2*i+1], entry.Message)
}
}
+2
View File
@@ -0,0 +1,2 @@
// Package clilog provides a fluent API for configuring structured logging.
package clilog
+11
View File
@@ -44,6 +44,13 @@ const (
cgroupV2MemoryStat = "/sys/fs/cgroup/memory.stat"
)
const (
// 9223372036854771712 is the highest positive signed 64-bit integer (263-1),
// rounded down to multiples of 4096 (2^12), the most common page size on x86 systems.
// This is used by docker to indicate no memory limit.
UnlimitedMemory int64 = 9223372036854771712
)
// ContainerCPU returns the CPU usage of the container cgroup.
// This is calculated as difference of two samples of the
// CPU usage of the container cgroup.
@@ -271,6 +278,10 @@ func (s *Statter) cGroupV1Memory(p Prefix) (*Result, error) {
// Nonetheless, if it is not, assume there is no limit set.
maxUsageBytes = -1
}
// Set to unlimited if we detect the unlimited docker value.
if maxUsageBytes == UnlimitedMemory {
maxUsageBytes = -1
}
// need a space after total_rss so we don't hit something else
usageBytes, err := readInt64(s.fs, cgroupV1MemoryUsageBytes)
+23
View File
@@ -197,6 +197,18 @@ func TestStatter(t *testing.T) {
assert.Nil(t, mem.Total)
assert.Equal(t, "B", mem.Unit)
})
t.Run("ContainerMemory/NoLimit", func(t *testing.T) {
t.Parallel()
fs := initFS(t, fsContainerCgroupV1DockerNoMemoryLimit)
s, err := New(WithFS(fs), withNoWait)
require.NoError(t, err)
mem, err := s.ContainerMemory(PrefixDefault)
require.NoError(t, err)
require.NotNil(t, mem)
assert.Equal(t, 268435456.0, mem.Used)
assert.Nil(t, mem.Total)
assert.Equal(t, "B", mem.Unit)
})
})
t.Run("CGroupV2", func(t *testing.T) {
@@ -384,6 +396,17 @@ proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`,
cgroupV1MemoryUsageBytes: "536870912",
cgroupV1MemoryStat: "total_inactive_file 268435456",
}
fsContainerCgroupV1DockerNoMemoryLimit = map[string]string{
procOneCgroup: "0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f",
procMounts: `overlay / overlay rw,relatime,lowerdir=/some/path:/some/path,upperdir=/some/path:/some/path,workdir=/some/path:/some/path 0 0
proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0`,
cgroupV1CPUAcctUsage: "0",
cgroupV1CFSQuotaUs: "-1",
cgroupV1CFSPeriodUs: "100000",
cgroupV1MemoryMaxUsageBytes: "9223372036854771712",
cgroupV1MemoryUsageBytes: "536870912",
cgroupV1MemoryStat: "total_inactive_file 268435456",
}
fsContainerCgroupV1AltPath = map[string]string{
procOneCgroup: "0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f",
procMounts: `overlay / overlay rw,relatime,lowerdir=/some/path:/some/path,upperdir=/some/path:/some/path,workdir=/some/path:/some/path 0 0
+10
View File
@@ -141,6 +141,10 @@ func extractTar(t *testing.T, data []byte, directory string) {
// Start runs the command in a goroutine and cleans it up when the test
// completed.
func Start(t *testing.T, inv *clibase.Invocation) {
StartWithAssert(t, inv, nil)
}
func StartWithAssert(t *testing.T, inv *clibase.Invocation, assertCallback func(t *testing.T, err error)) { //nolint:revive
t.Helper()
closeCh := make(chan struct{})
@@ -155,6 +159,12 @@ func Start(t *testing.T, inv *clibase.Invocation) {
go func() {
defer close(closeCh)
err := waiter.Wait()
if assertCallback != nil {
assertCallback(t, err)
return
}
switch {
case errors.Is(err, context.Canceled):
return
+5 -1
View File
@@ -167,7 +167,11 @@ func prepareTestData(t *testing.T) (*codersdk.Client, map[string]string) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
db, pubsub := dbtestutil.NewDB(t)
// This needs to be a fixed timezone because timezones increase the length
// of timestamp strings. The increased length can pad table formatting's
// and differ the table header spacings.
//nolint:gocritic
db, pubsub := dbtestutil.NewDB(t, dbtestutil.WithTimezone("UTC"))
rootClient := coderdtest.New(t, &coderdtest.Options{
Database: db,
Pubsub: pubsub,
+80 -5
View File
@@ -2,13 +2,17 @@ package cliui
import (
"context"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/tailnet"
)
var errAgentShuttingDown = xerrors.New("agent is shutting down")
@@ -200,12 +204,12 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
switch agent.LifecycleState {
case codersdk.WorkspaceAgentLifecycleReady:
sw.Complete(stage, agent.ReadyAt.Sub(*agent.StartedAt))
sw.Complete(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
case codersdk.WorkspaceAgentLifecycleStartTimeout:
sw.Fail(stage, 0)
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script timed out and your workspace may be incomplete.")
case codersdk.WorkspaceAgentLifecycleStartError:
sw.Fail(stage, agent.ReadyAt.Sub(*agent.StartedAt))
sw.Fail(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
// Use zero time (omitted) to separate these from the startup logs.
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script exited with an error and your workspace may be incomplete.")
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#startup-script-exited-with-an-error"))
@@ -221,7 +225,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
case agent.LifecycleState.ShuttingDown():
// We no longer know if the startup script failed or not,
// but we need to tell the user something.
sw.Complete(stage, agent.ReadyAt.Sub(*agent.StartedAt))
sw.Complete(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
return errAgentShuttingDown
}
}
@@ -238,13 +242,13 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
sw.Log(time.Now(), codersdk.LogLevelWarn, "Wait for it to reconnect or restart your workspace.")
sw.Log(time.Now(), codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#agent-connection-issues"))
disconnectedAt := *agent.DisconnectedAt
disconnectedAt := agent.DisconnectedAt
for agent.Status == codersdk.WorkspaceAgentDisconnected {
if agent, err = fetch(); err != nil {
return xerrors.Errorf("fetch: %w", err)
}
}
sw.Complete(stage, agent.LastConnectedAt.Sub(disconnectedAt))
sw.Complete(stage, safeDuration(sw, agent.LastConnectedAt, disconnectedAt))
}
}
}
@@ -257,8 +261,79 @@ func troubleshootingMessage(agent codersdk.WorkspaceAgent, url string) string {
return m
}
// safeDuration returns a-b. If a or b is nil, it returns 0.
// This is because we often dereference a time pointer, which can
// cause a panic. These dereferences are used to calculate durations,
// which are not critical, and therefor should not break things
// when it fails.
// A panic has been observed in a test.
func safeDuration(sw *stageWriter, a, b *time.Time) time.Duration {
if a == nil || b == nil {
if sw != nil {
// Ideally the message includes which fields are <nil>, but you can
// use the surrounding log lines to figure that out. And passing more
// params makes this unwieldy.
sw.Log(time.Now(), codersdk.LogLevelWarn, "Warning: Failed to calculate duration from a time being <nil>.")
}
return 0
}
return a.Sub(*b)
}
type closeFunc func() error
func (c closeFunc) Close() error {
return c()
}
func PeerDiagnostics(w io.Writer, d tailnet.PeerDiagnostics) {
if d.PreferredDERP > 0 {
rn, ok := d.DERPRegionNames[d.PreferredDERP]
if !ok {
rn = "unknown"
}
_, _ = fmt.Fprintf(w, "✔ preferred DERP region: %d (%s)\n", d.PreferredDERP, rn)
} else {
_, _ = fmt.Fprint(w, "✘ not connected to DERP\n")
}
if d.SentNode {
_, _ = fmt.Fprint(w, "✔ sent local data to Coder networking coodinator\n")
} else {
_, _ = fmt.Fprint(w, "✘ have not sent local data to Coder networking coordinator\n")
}
if d.ReceivedNode != nil {
dp := d.ReceivedNode.DERP
dn := ""
// should be 127.3.3.40:N where N is the DERP region
ap := strings.Split(dp, ":")
if len(ap) == 2 {
dp = ap[1]
di, err := strconv.Atoi(dp)
if err == nil {
var ok bool
dn, ok = d.DERPRegionNames[di]
if ok {
dn = fmt.Sprintf("(%s)", dn)
} else {
dn = "(unknown)"
}
}
}
_, _ = fmt.Fprintf(w,
"✔ received remote agent data from Coder networking coordinator\n preferred DERP region: %s %s\n endpoints: %s\n",
dp, dn, strings.Join(d.ReceivedNode.Endpoints, ", "))
} else {
_, _ = fmt.Fprint(w, "✘ have not received remote agent data from Coder networking coordinator\n")
}
if !d.LastWireguardHandshake.IsZero() {
ago := time.Since(d.LastWireguardHandshake)
symbol := "✔"
// wireguard is supposed to refresh handshake on 5 minute intervals
if ago > 5*time.Minute {
symbol = "⚠"
}
_, _ = fmt.Fprintf(w, "%s Wireguard handshake %s ago\n", symbol, ago.Round(time.Second))
} else {
_, _ = fmt.Fprint(w, "✘ Wireguard is not connected\n")
}
}
+191
View File
@@ -6,6 +6,7 @@ import (
"context"
"io"
"os"
"regexp"
"strings"
"sync/atomic"
"testing"
@@ -15,12 +16,14 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"tailscale.com/tailcfg"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/testutil"
)
@@ -476,3 +479,191 @@ func TestAgent(t *testing.T) {
require.NoError(t, cmd.Invoke().Run())
})
}
func TestPeerDiagnostics(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
diags tailnet.PeerDiagnostics
want []*regexp.Regexp // must be ordered, can omit lines
}{
{
name: "noPreferredDERP",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: make(map[int]string),
SentNode: true,
ReceivedNode: &tailcfg.Node{DERP: "127.3.3.40:999"},
LastWireguardHandshake: time.Now(),
},
want: []*regexp.Regexp{
regexp.MustCompile("^✘ not connected to DERP$"),
},
},
{
name: "preferredDERP",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 23,
DERPRegionNames: map[int]string{
23: "testo",
},
SentNode: true,
ReceivedNode: &tailcfg.Node{DERP: "127.3.3.40:999"},
LastWireguardHandshake: time.Now(),
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✔ preferred DERP region: 23 \(testo\)$`),
},
},
{
name: "sentNode",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: true,
ReceivedNode: &tailcfg.Node{DERP: "127.3.3.40:999"},
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✔ sent local data to Coder networking coodinator$`),
},
},
{
name: "didntSendNode",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: false,
ReceivedNode: &tailcfg.Node{DERP: "127.3.3.40:999"},
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✘ have not sent local data to Coder networking coordinator$`),
},
},
{
name: "receivedNodeDERPOKNoEndpoints",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{999: "Embedded"},
SentNode: true,
ReceivedNode: &tailcfg.Node{DERP: "127.3.3.40:999"},
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✔ received remote agent data from Coder networking coordinator$`),
regexp.MustCompile(`preferred DERP region: 999 \(Embedded\)$`),
regexp.MustCompile(`endpoints: $`),
},
},
{
name: "receivedNodeDERPUnknownNoEndpoints",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: true,
ReceivedNode: &tailcfg.Node{DERP: "127.3.3.40:999"},
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✔ received remote agent data from Coder networking coordinator$`),
regexp.MustCompile(`preferred DERP region: 999 \(unknown\)$`),
regexp.MustCompile(`endpoints: $`),
},
},
{
name: "receivedNodeEndpointsNoDERP",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{999: "Embedded"},
SentNode: true,
ReceivedNode: &tailcfg.Node{Endpoints: []string{"99.88.77.66:4555", "33.22.11.0:3444"}},
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✔ received remote agent data from Coder networking coordinator$`),
regexp.MustCompile(`preferred DERP region:\s*$`),
regexp.MustCompile(`endpoints: 99\.88\.77\.66:4555, 33\.22\.11\.0:3444$`),
},
},
{
name: "didntReceiveNode",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: false,
ReceivedNode: nil,
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✘ have not received remote agent data from Coder networking coordinator$`),
},
},
{
name: "noWireguardHandshake",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: false,
ReceivedNode: nil,
LastWireguardHandshake: time.Time{},
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✘ Wireguard is not connected$`),
},
},
{
name: "wireguardHandshakeRecent",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: false,
ReceivedNode: nil,
LastWireguardHandshake: time.Now().Add(-5 * time.Second),
},
want: []*regexp.Regexp{
regexp.MustCompile(`^✔ Wireguard handshake \d+s ago$`),
},
},
{
name: "wireguardHandshakeOld",
diags: tailnet.PeerDiagnostics{
PreferredDERP: 0,
DERPRegionNames: map[int]string{},
SentNode: false,
ReceivedNode: nil,
LastWireguardHandshake: time.Now().Add(-450 * time.Second), // 7m30s
},
want: []*regexp.Regexp{
regexp.MustCompile(`^⚠ Wireguard handshake 7m\d+s ago$`),
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r, w := io.Pipe()
go func() {
defer w.Close()
cliui.PeerDiagnostics(w, tc.diags)
}()
s := bufio.NewScanner(r)
i := 0
got := make([]string, 0)
for s.Scan() {
got = append(got, s.Text())
if i < len(tc.want) {
reg := tc.want[i]
if reg.Match(s.Bytes()) {
i++
}
}
}
if i < len(tc.want) {
t.Logf("failed to match regexp: %s\ngot:\n%s", tc.want[i].String(), strings.Join(got, "\n"))
t.FailNow()
}
})
}
}
+21
View File
@@ -0,0 +1,21 @@
package cliui
import (
"fmt"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/pretty"
)
func DeprecationWarning(message string) clibase.MiddlewareFunc {
return func(next clibase.HandlerFunc) clibase.HandlerFunc {
return func(i *clibase.Invocation) error {
_, _ = fmt.Fprintln(i.Stdout, "\n"+pretty.Sprint(DefaultStyles.Wrap,
pretty.Sprint(
DefaultStyles.Warn,
"DEPRECATION WARNING: This command will be removed in a future release."+"\n"+message+"\n"),
))
return next(i)
}
}
}
+1 -1
View File
@@ -106,7 +106,7 @@ func TableFormat(out any, defaultColumns []string) OutputFormat {
}
// Get the list of table column headers.
headers, defaultSort, err := typeToTableHeaders(v.Type().Elem())
headers, defaultSort, err := typeToTableHeaders(v.Type().Elem(), true)
if err != nil {
panic("parse table headers: " + err.Error())
}
+2 -2
View File
@@ -71,9 +71,9 @@ func Prompt(inv *clibase.Invocation, opts PromptOptions) (string, error) {
} else {
renderedNo = Bold(ConfirmNo)
}
pretty.Fprintf(inv.Stdout, DefaultStyles.Placeholder, "(%s/%s)", renderedYes, renderedNo)
_, _ = fmt.Fprintf(inv.Stdout, "(%s/%s) ", renderedYes, renderedNo)
} else if opts.Default != "" {
_, _ = fmt.Fprint(inv.Stdout, pretty.Sprint(DefaultStyles.Placeholder, "("+opts.Default+") "))
_, _ = fmt.Fprintf(inv.Stdout, "(%s) ", pretty.Sprint(DefaultStyles.Placeholder, opts.Default))
}
interrupt := make(chan os.Signal, 1)
+17 -4
View File
@@ -70,7 +70,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
}
// Get the list of table column headers.
headersRaw, defaultSort, err := typeToTableHeaders(v.Type().Elem())
headersRaw, defaultSort, err := typeToTableHeaders(v.Type().Elem(), true)
if err != nil {
return "", xerrors.Errorf("get table headers recursively for type %q: %w", v.Type().Elem().String(), err)
}
@@ -230,7 +230,11 @@ func isStructOrStructPointer(t reflect.Type) bool {
// typeToTableHeaders converts a type to a slice of column names. If the given
// type is invalid (not a struct or a pointer to a struct, has invalid table
// tags, etc.), an error is returned.
func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
//
// requireDefault is only needed for the root call. This is recursive, so nested
// structs do not need the default sort name.
// nolint:revive
func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string, error) {
if !isStructOrStructPointer(t) {
return nil, "", xerrors.Errorf("typeToTableHeaders called with a non-struct or a non-pointer-to-a-struct type")
}
@@ -246,6 +250,12 @@ func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
if err != nil {
return nil, "", xerrors.Errorf("parse struct tags for field %q in type %q: %w", field.Name, t.String(), err)
}
if name == "" && (recursive && skip) {
return nil, "", xerrors.Errorf("a name is required for the field %q. "+
"recursive_line will ensure this is never shown to the user, but is still needed", field.Name)
}
// If recurse and skip is set, the name is intentionally empty.
if name == "" {
continue
}
@@ -262,7 +272,7 @@ func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
return nil, "", xerrors.Errorf("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct", field.Name, t.String())
}
childNames, _, err := typeToTableHeaders(fieldType)
childNames, defaultSort, err := typeToTableHeaders(fieldType, false)
if err != nil {
return nil, "", xerrors.Errorf("get child field header names for field %q in type %q: %w", field.Name, fieldType.String(), err)
}
@@ -273,13 +283,16 @@ func typeToTableHeaders(t reflect.Type) ([]string, string, error) {
}
headers = append(headers, fullName)
}
if defaultSortName == "" {
defaultSortName = defaultSort
}
continue
}
headers = append(headers, name)
}
if defaultSortName == "" {
if defaultSortName == "" && requireDefault {
return nil, "", xerrors.Errorf("no field marked as default_sort in type %q", t.String())
}
+2 -2
View File
@@ -46,12 +46,12 @@ type tableTest2 struct {
type tableTest3 struct {
NotIncluded string // no table tag
Sub tableTest2 `table:"inner,recursive,default_sort"`
Sub tableTest2 `table:"inner,recursive"`
}
type tableTest4 struct {
Inline tableTest2 `table:"ignored,recursive_inline"`
SortField string `table:"sort_field,default_sort"`
SortField string `table:"sort_field"`
}
func Test_DisplayTable(t *testing.T) {
+40
View File
@@ -0,0 +1,40 @@
package cliutil
import (
"os"
"strings"
"sync"
)
var (
hostname string
hostnameOnce sync.Once
)
// Hostname returns the hostname of the machine, lowercased,
// with any trailing domain suffix stripped.
// It is cached after the first call.
// If the hostname cannot be determined, for any reason,
// localhost will be returned instead.
func Hostname() string {
hostnameOnce.Do(func() { hostname = getHostname() })
return hostname
}
func getHostname() string {
h, err := os.Hostname()
if err != nil {
// Something must be very wrong if this fails.
// We'll just return localhost and hope for the best.
return "localhost"
}
// On some platforms, the hostname can be an FQDN. We only want the hostname.
if idx := strings.Index(h, "."); idx != -1 {
h = h[:idx]
}
// For the sake of consistency, we also want to lowercase the hostname.
// Per RFC 4343, DNS lookups must be case-insensitive.
return strings.ToLower(h)
}
+12 -2
View File
@@ -4,6 +4,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/kirsle/configdir"
"golang.org/x/xerrors"
@@ -69,6 +70,14 @@ func (r Root) PostgresPort() File {
// File provides convenience methods for interacting with *os.File.
type File string
func (f File) Exists() bool {
if f == "" {
return false
}
_, err := os.Stat(string(f))
return err == nil
}
// Delete deletes the file.
func (f File) Delete() error {
if f == "" {
@@ -85,13 +94,14 @@ func (f File) Write(s string) error {
return write(string(f), 0o600, []byte(s))
}
// Read reads the file to a string.
// Read reads the file to a string. All leading and trailing whitespace
// is removed.
func (f File) Read() (string, error) {
if f == "" {
return "", xerrors.Errorf("empty file path")
}
byt, err := read(string(f))
return string(byt), err
return strings.TrimSpace(string(byt)), err
}
// open opens a file in the configuration directory,
+72 -12
View File
@@ -13,11 +13,13 @@ import (
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"github.com/cli/safeexec"
"github.com/pkg/diff"
"github.com/pkg/diff/write"
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
@@ -46,9 +48,12 @@ const (
// sshConfigOptions represents options that can be stored and read
// from the coder config in ~/.ssh/coder.
type sshConfigOptions struct {
waitEnum string
userHostPrefix string
sshOptions []string
waitEnum string
userHostPrefix string
sshOptions []string
disableAutostart bool
header []string
headerCommand string
}
// addOptions expects options in the form of "option=value" or "option value".
@@ -98,15 +103,25 @@ func (o *sshConfigOptions) addOption(option string) error {
}
func (o sshConfigOptions) equal(other sshConfigOptions) bool {
// Compare without side-effects or regard to order.
opt1 := slices.Clone(o.sshOptions)
sort.Strings(opt1)
opt2 := slices.Clone(other.sshOptions)
sort.Strings(opt2)
if !slices.Equal(opt1, opt2) {
if !slicesSortedEqual(o.sshOptions, other.sshOptions) {
return false
}
return o.waitEnum == other.waitEnum && o.userHostPrefix == other.userHostPrefix
if !slicesSortedEqual(o.header, other.header) {
return false
}
return o.waitEnum == other.waitEnum && o.userHostPrefix == other.userHostPrefix && o.disableAutostart == other.disableAutostart && o.headerCommand == other.headerCommand
}
// slicesSortedEqual compares two slices without side-effects or regard to order.
func slicesSortedEqual[S ~[]E, E constraints.Ordered](a, b S) bool {
if len(a) != len(b) {
return false
}
a = slices.Clone(a)
slices.Sort(a)
b = slices.Clone(b)
slices.Sort(b)
return slices.Equal(a, b)
}
func (o sshConfigOptions) asList() (list []string) {
@@ -116,9 +131,19 @@ func (o sshConfigOptions) asList() (list []string) {
if o.userHostPrefix != "" {
list = append(list, fmt.Sprintf("ssh-host-prefix: %s", o.userHostPrefix))
}
if o.disableAutostart {
list = append(list, fmt.Sprintf("disable-autostart: %v", o.disableAutostart))
}
for _, opt := range o.sshOptions {
list = append(list, fmt.Sprintf("ssh-option: %s", opt))
}
for _, h := range o.header {
list = append(list, fmt.Sprintf("header: %s", h))
}
if o.headerCommand != "" {
list = append(list, fmt.Sprintf("header-command: %s", o.headerCommand))
}
return list
}
@@ -225,6 +250,8 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
// specifies skip-proxy-command, then wait cannot be applied.
return xerrors.Errorf("cannot specify both --skip-proxy-command and --wait")
}
sshConfigOpts.header = r.header
sshConfigOpts.headerCommand = r.headerCommand
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs(inv.Context(), client)
@@ -388,13 +415,24 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
}
if !skipProxyCommand {
rootFlags := fmt.Sprintf("--global-config %s", escapedGlobalConfig)
for _, h := range sshConfigOpts.header {
rootFlags += fmt.Sprintf(" --header %q", h)
}
if sshConfigOpts.headerCommand != "" {
rootFlags += fmt.Sprintf(" --header-command %q", sshConfigOpts.headerCommand)
}
flags := ""
if sshConfigOpts.waitEnum != "auto" {
flags += " --wait=" + sshConfigOpts.waitEnum
}
if sshConfigOpts.disableAutostart {
flags += " --disable-autostart=true"
}
defaultOptions = append(defaultOptions, fmt.Sprintf(
"ProxyCommand %s --global-config %s ssh --stdio%s %s",
escapedCoderBinary, escapedGlobalConfig, flags, workspaceHostname,
"ProxyCommand %s %s ssh --stdio%s %s",
escapedCoderBinary, rootFlags, flags, workspaceHostname,
))
}
@@ -566,6 +604,13 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
Default: "auto",
Value: clibase.EnumOf(&sshConfigOpts.waitEnum, "yes", "no", "auto"),
},
{
Flag: "disable-autostart",
Description: "Disable starting the workspace automatically when connecting via SSH.",
Env: "CODER_CONFIGSSH_DISABLE_AUTOSTART",
Value: clibase.BoolOf(&sshConfigOpts.disableAutostart),
Default: "false",
},
{
Flag: "force-unix-filepaths",
Env: "CODER_CONFIGSSH_UNIX_FILEPATHS",
@@ -602,9 +647,18 @@ func sshConfigWriteSectionHeader(w io.Writer, addNewline bool, o sshConfigOption
if o.userHostPrefix != "" {
_, _ = fmt.Fprintf(&ow, "# :%s=%s\n", "ssh-host-prefix", o.userHostPrefix)
}
if o.disableAutostart {
_, _ = fmt.Fprintf(&ow, "# :%s=%v\n", "disable-autostart", o.disableAutostart)
}
for _, opt := range o.sshOptions {
_, _ = fmt.Fprintf(&ow, "# :%s=%s\n", "ssh-option", opt)
}
for _, h := range o.header {
_, _ = fmt.Fprintf(&ow, "# :%s=%s\n", "header", h)
}
if o.headerCommand != "" {
_, _ = fmt.Fprintf(&ow, "# :%s=%s\n", "header-command", o.headerCommand)
}
if ow.Len() > 0 {
_, _ = fmt.Fprint(w, sshConfigOptionsHeader)
_, _ = fmt.Fprint(w, ow.String())
@@ -634,6 +688,12 @@ func sshConfigParseLastOptions(r io.Reader) (o sshConfigOptions) {
o.userHostPrefix = parts[1]
case "ssh-option":
o.sshOptions = append(o.sshOptions, parts[1])
case "disable-autostart":
o.disableAutostart, _ = strconv.ParseBool(parts[1])
case "header":
o.header = append(o.header, parts[1])
case "header-command":
o.headerCommand = parts[1]
default:
// Unknown option, ignore.
}
+65 -11
View File
@@ -77,12 +77,12 @@ func TestConfigSSH(t *testing.T) {
})
owner := coderdtest.CreateFirstUser(t, client)
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
ws, authToken := dbfake.WorkspaceWithAgent(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: owner.OrganizationID,
OwnerID: memberUser.ID,
})
_ = agenttest.New(t, client.URL, authToken)
resources := coderdtest.AwaitWorkspaceAgents(t, client, ws.ID)
}).WithAgent().Do()
_ = agenttest.New(t, client.URL, r.AgentToken)
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
agentConn, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil)
require.NoError(t, err)
defer agentConn.Close()
@@ -153,7 +153,7 @@ func TestConfigSSH(t *testing.T) {
home := filepath.Dir(filepath.Dir(sshConfigFile))
// #nosec
sshCmd := exec.Command("ssh", "-F", sshConfigFile, hostname+ws.Name, "echo", "test")
sshCmd := exec.Command("ssh", "-F", sshConfigFile, hostname+r.Workspace.Name, "echo", "test")
pty = ptytest.New(t)
// Set HOME because coder config is included from ~/.ssh/coder.
sshCmd.Env = append(sshCmd.Env, fmt.Sprintf("HOME=%s", home))
@@ -462,6 +462,9 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
"# Last config-ssh options:",
"# :wait=yes",
"# :ssh-host-prefix=coder-test.",
"# :header=X-Test-Header=foo",
"# :header=X-Test-Header2=bar",
"# :header-command=printf h1=v1 h2=\"v2\" h3='v3'",
"#",
headerEnd,
"",
@@ -471,6 +474,9 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
"--yes",
"--wait=yes",
"--ssh-host-prefix", "coder-test.",
"--header", "X-Test-Header=foo",
"--header", "X-Test-Header2=bar",
"--header-command", "printf h1=v1 h2=\"v2\" h3='v3'",
},
},
{
@@ -563,6 +569,55 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
regexMatch: "ProxyCommand /foo/bar/coder",
},
},
{
name: "Header",
args: []string{
"--yes",
"--header", "X-Test-Header=foo",
"--header", "X-Test-Header2=bar",
},
wantErr: false,
hasAgent: true,
wantConfig: wantConfig{
regexMatch: `ProxyCommand .* --header "X-Test-Header=foo" --header "X-Test-Header2=bar" ssh`,
},
},
{
name: "Header command",
args: []string{
"--yes",
"--header-command", "printf h1=v1",
},
wantErr: false,
hasAgent: true,
wantConfig: wantConfig{
regexMatch: `ProxyCommand .* --header-command "printf h1=v1" ssh`,
},
},
{
name: "Header command with double quotes",
args: []string{
"--yes",
"--header-command", "printf h1=v1 h2=\"v2\"",
},
wantErr: false,
hasAgent: true,
wantConfig: wantConfig{
regexMatch: `ProxyCommand .* --header-command "printf h1=v1 h2=\\\"v2\\\"" ssh`,
},
},
{
name: "Header command with single quotes",
args: []string{
"--yes",
"--header-command", "printf h1=v1 h2='v2'",
},
wantErr: false,
hasAgent: true,
wantConfig: wantConfig{
regexMatch: `ProxyCommand .* --header-command "printf h1=v1 h2='v2'" ssh`,
},
},
}
for _, tt := range tests {
tt := tt
@@ -572,10 +627,10 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
client, db := coderdtest.NewWithDatabase(t, nil)
user := coderdtest.CreateFirstUser(t, client)
if tt.hasAgent {
_, _ = dbfake.WorkspaceWithAgent(t, db, database.Workspace{
_ = dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
})
}).WithAgent().Do()
}
// Prepare ssh config files.
@@ -692,11 +747,10 @@ func TestConfigSSH_Hostnames(t *testing.T) {
owner := coderdtest.CreateFirstUser(t, client)
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
ws := dbfake.Workspace(t, db, database.Workspace{
r := dbfake.WorkspaceBuild(t, db, database.Workspace{
OrganizationID: owner.OrganizationID,
OwnerID: memberUser.ID,
})
dbfake.WorkspaceBuild(t, db, ws, database.WorkspaceBuild{}, resources...)
}).Resource(resources...).Do()
sshConfigFile := sshConfigFileName(t)
inv, root := clitest.New(t, "config-ssh", "--ssh-config-file", sshConfigFile)
@@ -721,7 +775,7 @@ func TestConfigSSH_Hostnames(t *testing.T) {
var expectedHosts []string
for _, hostnamePattern := range tt.expected {
hostname := strings.ReplaceAll(hostnamePattern, "@", ws.Name)
hostname := strings.ReplaceAll(hostnamePattern, "@", r.Workspace.Name)
expectedHosts = append(expectedHosts, hostname)
}
+1 -1
View File
@@ -43,7 +43,7 @@ func (r *RootCmd) create() *clibase.Cmd {
),
Middleware: clibase.Chain(r.InitClient(client)),
Handler: func(inv *clibase.Invocation) error {
organization, err := CurrentOrganization(inv, client)
organization, err := CurrentOrganization(r, inv, client)
if err != nil {
return err
}
+52 -6
View File
@@ -556,6 +556,14 @@ func TestCreateValidateRichParameters(t *testing.T) {
{Name: numberParameterName, Type: "number", Mutable: true, ValidationMin: ptr.Ref(int32(3)), ValidationMax: ptr.Ref(int32(10))},
}
numberCustomErrorRichParameters := []*proto.RichParameter{
{
Name: numberParameterName, Type: "number", Mutable: true,
ValidationMin: ptr.Ref(int32(3)), ValidationMax: ptr.Ref(int32(10)),
ValidationError: "These are values: {min}, {max}, and {value}.",
},
}
stringRichParameters := []*proto.RichParameter{
{Name: stringParameterName, Type: "string", Mutable: true, ValidationRegex: "^[a-z]+$", ValidationError: "this is error"},
}
@@ -644,6 +652,44 @@ func TestCreateValidateRichParameters(t *testing.T) {
<-doneChan
})
t.Run("ValidateNumber_CustomError", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client)
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(numberCustomErrorRichParameters))
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
clitest.SetupConfig(t, member, root)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
matches := []string{
numberParameterName, "12",
"These are values: 3, 10, and 12.", "",
"Enter a value", "8",
"Confirm create?", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
if value != "" {
pty.WriteLine(value)
}
}
<-doneChan
})
t.Run("ValidateBool", func(t *testing.T) {
t.Parallel()
@@ -757,7 +803,7 @@ func TestCreateWithGitAuth(t *testing.T) {
{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
ExternalAuthProviders: []string{"github"},
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github"}},
},
},
},
@@ -767,11 +813,11 @@ func TestCreateWithGitAuth(t *testing.T) {
client := coderdtest.New(t, &coderdtest.Options{
ExternalAuthConfigs: []*externalauth.Config{{
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
DisplayName: "GitHub",
InstrumentedOAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
DisplayName: "GitHub",
}},
IncludeProvisionerDaemon: true,
})
+1 -5
View File
@@ -11,7 +11,6 @@ import (
"github.com/coder/coder/v2/cli/clitest"
"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/codersdk"
"github.com/coder/coder/v2/pty/ptytest"
@@ -95,10 +94,7 @@ func TestDelete(t *testing.T) {
// this way.
ctx := testutil.Context(t, testutil.WaitShort)
// nolint:gocritic // Unit test
err := api.Database.UpdateUserDeletedByID(dbauthz.AsSystemRestricted(ctx), database.UpdateUserDeletedByIDParams{
ID: deleteMeUser.ID,
Deleted: true,
})
err := api.Database.UpdateUserDeletedByID(dbauthz.AsSystemRestricted(ctx), deleteMeUser.ID)
require.NoError(t, err)
inv, root := clitest.New(t, "delete", fmt.Sprintf("%s/%s", deleteMeUser.ID, workspace.Name), "-y", "--orphan")
+31 -2
View File
@@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
@@ -43,6 +44,11 @@ func (RootCmd) errorExample() *clibase.Cmd {
//nolint:errorlint,forcetypeassert
apiError.(*codersdk.Error).Helper = "Have you tried turning it off and on again?"
//nolint:errorlint,forcetypeassert
cpy := *apiError.(*codersdk.Error)
apiErrorNoHelper := &cpy
apiErrorNoHelper.Helper = ""
// Some flags
var magicWord clibase.String
@@ -65,6 +71,17 @@ func (RootCmd) errorExample() *clibase.Cmd {
// A multi-error
{
Use: "multi-error",
Handler: func(inv *clibase.Invocation) error {
return xerrors.Errorf("wrapped: %w", errors.Join(
xerrors.Errorf("first error: %w", errorWithStackTrace()),
xerrors.Errorf("second error: %w", errorWithStackTrace()),
xerrors.Errorf("wrapped api error: %w", apiErrorNoHelper),
))
},
},
{
Use: "multi-multi-error",
Short: "This is a multi error inside a multi error",
Handler: func(inv *clibase.Invocation) error {
// Closing the stdin file descriptor will cause the next close
// to fail. This is joined to the returned Command error.
@@ -72,10 +89,12 @@ func (RootCmd) errorExample() *clibase.Cmd {
_ = f.Close()
}
return xerrors.Errorf("some error: %w", errorWithStackTrace())
return errors.Join(
xerrors.Errorf("first error: %w", errorWithStackTrace()),
xerrors.Errorf("second error: %w", errorWithStackTrace()),
)
},
},
{
Use: "validation",
Options: clibase.OptionSet{
@@ -95,6 +114,16 @@ func (RootCmd) errorExample() *clibase.Cmd {
return nil
},
},
{
Use: "arg-required <required>",
Middleware: clibase.Chain(
clibase.RequireNArgs(1),
),
Handler: func(i *clibase.Invocation) error {
_, _ = fmt.Fprint(i.Stdout, "Try running this without an argument\n")
return nil
},
},
},
}
+176 -34
View File
@@ -10,6 +10,7 @@ import (
"math/rand"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"sync"
@@ -20,6 +21,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
"cdr.dev/slog"
@@ -173,11 +175,12 @@ func (s *scaletestStrategyFlags) attach(opts *clibase.OptionSet) {
func (s *scaletestStrategyFlags) toStrategy() harness.ExecutionStrategy {
var strategy harness.ExecutionStrategy
if s.concurrency == 1 {
switch s.concurrency {
case 1:
strategy = harness.LinearExecutionStrategy{}
} else if s.concurrency == 0 {
case 0:
strategy = harness.ConcurrentExecutionStrategy{}
} else {
default:
strategy = harness.ParallelExecutionStrategy{
Limit: int(s.concurrency),
}
@@ -244,7 +247,9 @@ func (o *scaleTestOutput) write(res harness.Results, stdout io.Writer) error {
err := s.Sync()
// On Linux, EINVAL is returned when calling fsync on /dev/stdout. We
// can safely ignore this error.
if err != nil && !xerrors.Is(err, syscall.EINVAL) {
// On macOS, ENOTTY is returned when calling sync on /dev/stdout. We
// can safely ignore this error.
if err != nil && !xerrors.Is(err, syscall.EINVAL) && !xerrors.Is(err, syscall.ENOTTY) {
return xerrors.Errorf("flush output file: %w", err)
}
}
@@ -415,9 +420,9 @@ func (r *RootCmd) scaletestCleanup() *clibase.Cmd {
}
client.HTTPClient = &http.Client{
Transport: &headerTransport{
transport: http.DefaultTransport,
header: map[string][]string{
Transport: &codersdk.HeaderTransport{
Transport: http.DefaultTransport,
Header: map[string][]string{
codersdk.BypassRatelimitHeader: {"true"},
},
},
@@ -519,6 +524,7 @@ func (r *RootCmd) scaletestCleanup() *clibase.Cmd {
func (r *RootCmd) scaletestCreateWorkspaces() *clibase.Cmd {
var (
count int64
retry int64
template string
noCleanup bool
@@ -566,9 +572,9 @@ func (r *RootCmd) scaletestCreateWorkspaces() *clibase.Cmd {
}
client.HTTPClient = &http.Client{
Transport: &headerTransport{
transport: http.DefaultTransport,
header: map[string][]string{
Transport: &codersdk.HeaderTransport{
Transport: http.DefaultTransport,
Header: map[string][]string{
codersdk.BypassRatelimitHeader: {"true"},
},
},
@@ -639,6 +645,7 @@ func (r *RootCmd) scaletestCreateWorkspaces() *clibase.Cmd {
RichParameterValues: richParameters,
},
NoWaitForAgents: noWaitForAgents,
Retry: int(retry),
},
NoCleanup: noCleanup,
}
@@ -748,6 +755,13 @@ func (r *RootCmd) scaletestCreateWorkspaces() *clibase.Cmd {
Description: "Required: Number of workspaces to create.",
Value: clibase.Int64Of(&count),
},
{
Flag: "retry",
Env: "CODER_SCALETEST_RETRY",
Default: "0",
Description: "Number of tries to create and bring up the workspace.",
Value: clibase.Int64Of(&retry),
},
{
Flag: "template",
FlagShorthand: "t",
@@ -852,10 +866,12 @@ func (r *RootCmd) scaletestCreateWorkspaces() *clibase.Cmd {
func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
var (
tickInterval time.Duration
bytesPerTick int64
ssh bool
template string
tickInterval time.Duration
bytesPerTick int64
ssh bool
app string
template string
targetWorkspaces string
client = &codersdk.Client{}
tracingFlags = &scaletestTracingFlags{}
@@ -871,9 +887,13 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
Middleware: clibase.Chain(
r.InitClient(client),
),
Handler: func(inv *clibase.Invocation) error {
Handler: func(inv *clibase.Invocation) (err error) {
ctx := inv.Context()
notifyCtx, stop := signal.NotifyContext(ctx, InterruptSignals...) // Checked later.
defer stop()
ctx = notifyCtx
me, err := requireAdmin(ctx, client)
if err != nil {
return err
@@ -888,9 +908,9 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
// Bypass rate limiting
client.HTTPClient = &http.Client{
Transport: &headerTransport{
transport: http.DefaultTransport,
header: map[string][]string{
Transport: &codersdk.HeaderTransport{
Transport: http.DefaultTransport,
Header: map[string][]string{
codersdk.BypassRatelimitHeader: {"true"},
},
},
@@ -902,15 +922,31 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
return xerrors.Errorf("parse template: %w", err)
}
}
targetWorkspaceStart, targetWorkspaceEnd, err := parseTargetRange("workspaces", targetWorkspaces)
if err != nil {
return xerrors.Errorf("parse target workspaces: %w", err)
}
appHost, err := client.AppHost(ctx)
if err != nil {
return xerrors.Errorf("get app host: %w", err)
}
workspaces, err := getScaletestWorkspaces(inv.Context(), client, template)
if err != nil {
return err
}
if targetWorkspaceEnd == 0 {
targetWorkspaceEnd = len(workspaces)
}
if len(workspaces) == 0 {
return xerrors.Errorf("no scaletest workspaces exist")
}
if targetWorkspaceEnd > len(workspaces) {
return xerrors.Errorf("target workspace end %d is greater than the number of workspaces %d", targetWorkspaceEnd, len(workspaces))
}
tracerProvider, closeTracing, tracingEnabled, err := tracingFlags.provider(ctx)
if err != nil {
@@ -936,35 +972,44 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
th := harness.NewTestHarness(strategy.toStrategy(), cleanupStrategy.toStrategy())
for idx, ws := range workspaces {
if idx < targetWorkspaceStart || idx >= targetWorkspaceEnd {
continue
}
var (
agentID uuid.UUID
agentName string
name = "workspace-traffic"
id = strconv.Itoa(idx)
agent codersdk.WorkspaceAgent
name = "workspace-traffic"
id = strconv.Itoa(idx)
)
for _, res := range ws.LatestBuild.Resources {
if len(res.Agents) == 0 {
continue
}
agentID = res.Agents[0].ID
agentName = res.Agents[0].Name
agent = res.Agents[0]
}
if agentID == uuid.Nil {
if agent.ID == uuid.Nil {
_, _ = fmt.Fprintf(inv.Stderr, "WARN: skipping workspace %s: no agent\n", ws.Name)
continue
}
appConfig, err := createWorkspaceAppConfig(client, appHost.Host, app, ws, agent)
if err != nil {
return xerrors.Errorf("configure workspace app: %w", err)
}
// Setup our workspace agent connection.
config := workspacetraffic.Config{
AgentID: agentID,
AgentID: agent.ID,
BytesPerTick: bytesPerTick,
Duration: strategy.timeout,
TickInterval: tickInterval,
ReadMetrics: metrics.ReadMetrics(ws.OwnerName, ws.Name, agentName),
WriteMetrics: metrics.WriteMetrics(ws.OwnerName, ws.Name, agentName),
ReadMetrics: metrics.ReadMetrics(ws.OwnerName, ws.Name, agent.Name),
WriteMetrics: metrics.WriteMetrics(ws.OwnerName, ws.Name, agent.Name),
SSH: ssh,
Echo: ssh,
App: appConfig,
}
if err := config.Validate(); err != nil {
@@ -990,6 +1035,11 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
return xerrors.Errorf("run test harness (harness failure, not a test failure): %w", err)
}
// If the command was interrupted, skip stats.
if notifyCtx.Err() != nil {
return notifyCtx.Err()
}
res := th.Results()
for _, o := range outputs {
err = o.write(res, inv.Stdout)
@@ -1014,6 +1064,12 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
Description: "Name or ID of the template. Traffic generation will be limited to workspaces created from this template.",
Value: clibase.StringOf(&template),
},
{
Flag: "target-workspaces",
Env: "CODER_SCALETEST_TARGET_WORKSPACES",
Description: "Target a specific range of workspaces in the format [START]:[END] (exclusive). Example: 0:10 will target the 10 first alphabetically sorted workspaces (0-9).",
Value: clibase.StringOf(&targetWorkspaces),
},
{
Flag: "bytes-per-tick",
Env: "CODER_SCALETEST_WORKSPACE_TRAFFIC_BYTES_PER_TICK",
@@ -1032,9 +1088,16 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
Flag: "ssh",
Env: "CODER_SCALETEST_WORKSPACE_TRAFFIC_SSH",
Default: "",
Description: "Send traffic over SSH.",
Description: "Send traffic over SSH, cannot be used with --app.",
Value: clibase.BoolOf(&ssh),
},
{
Flag: "app",
Env: "CODER_SCALETEST_WORKSPACE_TRAFFIC_APP",
Default: "",
Description: "Send WebSocket traffic to a workspace app (proxied via coderd), cannot be used with --ssh.",
Value: clibase.StringOf(&app),
},
}
tracingFlags.attach(&cmd.Options)
@@ -1048,10 +1111,11 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *clibase.Cmd {
func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
var (
interval time.Duration
jitter time.Duration
headless bool
randSeed int64
interval time.Duration
jitter time.Duration
headless bool
randSeed int64
targetUsers string
client = &codersdk.Client{}
tracingFlags = &scaletestTracingFlags{}
@@ -1074,6 +1138,10 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
if !(jitter < interval) {
return xerrors.Errorf("--jitter must be less than --interval")
}
targetUserStart, targetUserEnd, err := parseTargetRange("users", targetUsers)
if err != nil {
return xerrors.Errorf("parse target users: %w", err)
}
ctx := inv.Context()
logger := inv.Logger.AppendSinks(sloghuman.Sink(inv.Stdout))
if r.verbose {
@@ -1110,8 +1178,15 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
if err != nil {
return xerrors.Errorf("get scaletest users")
}
if targetUserEnd == 0 {
targetUserEnd = len(users)
}
for idx, usr := range users {
if idx < targetUserStart || idx >= targetUserEnd {
continue
}
for _, usr := range users {
//nolint:gosec // not used for cryptographic purposes
rndGen := rand.New(rand.NewSource(randSeed))
name := fmt.Sprintf("dashboard-%s", usr.Username)
@@ -1139,6 +1214,11 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
// This could be useful for debugging, but it will blow up the disk.
if r.verbose {
config.Screenshot = dashboard.Screenshot
} else {
// Disable screenshots otherwise.
config.Screenshot = func(context.Context, string) (string, error) {
return "/dev/null", nil
}
}
//nolint:gocritic
logger.Info(ctx, "runner config", slog.F("interval", interval), slog.F("jitter", jitter), slog.F("headless", headless), slog.F("trace", tracingEnabled))
@@ -1182,6 +1262,12 @@ func (r *RootCmd) scaletestDashboard() *clibase.Cmd {
}
cmd.Options = []clibase.Option{
{
Flag: "target-users",
Env: "CODER_SCALETEST_DASHBOARD_TARGET_USERS",
Description: "Target a specific range of users in the format [START]:[END] (exclusive). Example: 0:10 will target the 10 first alphabetically sorted users (0-9).",
Value: clibase.StringOf(&targetUsers),
},
{
Flag: "interval",
Env: "CODER_SCALETEST_DASHBOARD_INTERVAL",
@@ -1397,3 +1483,59 @@ func parseTemplate(ctx context.Context, client *codersdk.Client, organizationIDs
return tpl, nil
}
func parseTargetRange(name, targets string) (start, end int, err error) {
if targets == "" {
return 0, 0, nil
}
parts := strings.Split(targets, ":")
if len(parts) != 2 {
return 0, 0, xerrors.Errorf("invalid target %s %q", name, targets)
}
start, err = strconv.Atoi(parts[0])
if err != nil {
return 0, 0, xerrors.Errorf("invalid target %s %q: %w", name, targets, err)
}
end, err = strconv.Atoi(parts[1])
if err != nil {
return 0, 0, xerrors.Errorf("invalid target %s %q: %w", name, targets, err)
}
if start == end {
return 0, 0, xerrors.Errorf("invalid target %s %q: start and end cannot be equal", name, targets)
}
if end < start {
return 0, 0, xerrors.Errorf("invalid target %s %q: end cannot be less than start", name, targets)
}
return start, end, nil
}
func createWorkspaceAppConfig(client *codersdk.Client, appHost, app string, workspace codersdk.Workspace, agent codersdk.WorkspaceAgent) (workspacetraffic.AppConfig, error) {
if app == "" {
return workspacetraffic.AppConfig{}, nil
}
i := slices.IndexFunc(agent.Apps, func(a codersdk.WorkspaceApp) bool { return a.Slug == app })
if i == -1 {
return workspacetraffic.AppConfig{}, xerrors.Errorf("app %q not found in workspace %q", app, workspace.Name)
}
c := workspacetraffic.AppConfig{
Name: agent.Apps[i].Slug,
}
if agent.Apps[i].Subdomain {
if appHost == "" {
return workspacetraffic.AppConfig{}, xerrors.Errorf("app %q is a subdomain app but no app host is configured", app)
}
c.URL = fmt.Sprintf("%s://%s", client.URL.Scheme, strings.Replace(appHost, "*", agent.Apps[i].SubdomainName, 1))
} else {
c.URL = fmt.Sprintf("%s/@%s/%s.%s/apps/%s", client.URL.String(), workspace.OwnerName, workspace.Name, agent.Name, agent.Apps[i].Slug)
}
return c, nil
}
+48
View File
@@ -116,6 +116,31 @@ func TestScaleTestWorkspaceTraffic_Template(t *testing.T) {
require.ErrorContains(t, err, "could not find template \"doesnotexist\" in any organization")
}
// This test just validates that the CLI command accepts its known arguments.
func TestScaleTestWorkspaceTraffic_TargetWorkspaces(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancelFunc()
log := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
client := coderdtest.New(t, &coderdtest.Options{
Logger: &log,
})
_ = coderdtest.CreateFirstUser(t, client)
inv, root := clitest.New(t, "exp", "scaletest", "workspace-traffic",
"--target-workspaces", "0:0",
)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
inv.Stdout = pty.Output()
inv.Stderr = pty.Output()
err := inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "invalid target workspaces \"0:0\": start and end cannot be equal")
}
// This test just validates that the CLI command accepts its known arguments.
func TestScaleTestCleanup_Template(t *testing.T) {
t.Parallel()
@@ -218,4 +243,27 @@ func TestScaleTestDashboard(t *testing.T) {
err := inv.WithContext(ctx).Run()
require.NoError(t, err, "")
})
t.Run("TargetUsers", func(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancelFunc()
log := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
client := coderdtest.New(t, &coderdtest.Options{
Logger: &log,
})
_ = coderdtest.CreateFirstUser(t, client)
inv, root := clitest.New(t, "exp", "scaletest", "dashboard",
"--target-users", "0:0",
)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
inv.Stdout = pty.Output()
inv.Stderr = pty.Output()
err := inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "invalid target users \"0:0\": start and end cannot be equal")
})
}
+3
View File
@@ -52,6 +52,9 @@ fi
Command: "coder external-auth access-token slack --extra \"authed_user.id\"",
},
),
Middleware: clibase.Chain(
clibase.RequireNArgs(1),
),
Options: clibase.OptionSet{{
Name: "Extra",
Flag: "extra",
+13
View File
@@ -46,6 +46,19 @@ func TestExternalAuth(t *testing.T) {
clitest.Start(t, inv)
pty.ExpectMatch("bananas")
})
t.Run("NoArgs", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{
AccessToken: "bananas",
})
}))
t.Cleanup(srv.Close)
url := srv.URL
inv, _ := clitest.New(t, "--agent-url", url, "external-auth", "access-token")
watier := clitest.StartWithWaiter(t, inv)
watier.RequireContains("wanted 1 args but got 0")
})
t.Run("SuccessWithExtra", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+64
View File
@@ -0,0 +1,64 @@
package cli
import (
"fmt"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/codersdk"
)
func (r *RootCmd) favorite() *clibase.Cmd {
client := new(codersdk.Client)
cmd := &clibase.Cmd{
Aliases: []string{"fav", "favou" + "rite"},
Annotations: workspaceCommand,
Use: "favorite <workspace>",
Short: "Add a workspace to your favorites",
Middleware: clibase.Chain(
clibase.RequireNArgs(1),
r.InitClient(client),
),
Handler: func(inv *clibase.Invocation) error {
ws, err := namedWorkspace(inv.Context(), client, inv.Args[0])
if err != nil {
return xerrors.Errorf("get workspace: %w", err)
}
if err := client.FavoriteWorkspace(inv.Context(), ws.ID); err != nil {
return xerrors.Errorf("favorite workspace: %w", err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Workspace %q added to favorites.\n", ws.Name)
return nil
},
}
return cmd
}
func (r *RootCmd) unfavorite() *clibase.Cmd {
client := new(codersdk.Client)
cmd := &clibase.Cmd{
Aliases: []string{"unfav", "unfavou" + "rite"},
Annotations: workspaceCommand,
Use: "unfavorite <workspace>",
Short: "Remove a workspace from your favorites",
Middleware: clibase.Chain(
clibase.RequireNArgs(1),
r.InitClient(client),
),
Handler: func(inv *clibase.Invocation) error {
ws, err := namedWorkspace(inv.Context(), client, inv.Args[0])
if err != nil {
return xerrors.Errorf("get workspace: %w", err)
}
if err := client.UnfavoriteWorkspace(inv.Context(), ws.ID); err != nil {
return xerrors.Errorf("unfavorite workspace: %w", err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Workspace %q removed from favorites.\n", ws.Name)
return nil
},
}
return cmd
}

Some files were not shown because too many files have changed in this diff Show More