Compare commits

...

725 Commits

Author SHA1 Message Date
Jon Ayers 3716afac46 fix: add benign error suppression for process priority management (#15020)
This PR backports some benign error suppression into 2.15
2024-10-08 12:02:05 -05:00
Muhammad Atif Ali 0f63510d0d feat: add cursor IDE icon to release/2.15 (#14962) 2024-10-05 21:54:01 +05:00
Stephen Kirby 003dc5cc03 chore: patch known bugs in stable (#14925)
- [x] https://github.com/coder/coder/pull/14601
- [x] https://github.com/coder/coder/pull/14602
- [x] https://github.com/coder/coder/pull/14633

---------

Co-authored-by: Justin Fowler <justinfowler1996@gmail.com>
Co-authored-by: Ethan <39577870+ethanndickson@users.noreply.github.com>
Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
2024-10-01 17:20:26 -05:00
Stephen Kirby 190cd1c713 chore: apply fixes for the 2.15 release (#14540)
* Minor fixups, added troubleshooting (#14519)

(cherry picked from commit 66c8060605)

* fix: allow posting licenses that will be valid in future (#14491)

(cherry picked from commit 5bd5801286)

* fix: stop reporting future licenses as errors (#14492)

(cherry picked from commit 4eac2acede)

---------

Co-authored-by: Danny Kopping <danny@coder.com>
Co-authored-by: Spike Curtis <spike@coder.com>
2024-09-03 11:31:04 -05:00
Steven Masley 0ef85147cd chore: match templates search error with workspace search error (#14479)
* chore: make templates search error the same as workspaces
2024-08-30 15:14:59 -05:00
Cian Johnston 0f8251be41 feat(coderd/database/dbpurge): retain most recent agent build logs (#14460)
Updates the `DeleteOldWorkspaceAgentLogs` to:
- Retain logs for the most recent build regardless of age,
- Delete logs for agents that never connected and were created before
   the cutoff for deleting logs while still retaining the logs most recent build.
2024-08-30 17:39:09 +01:00
Steven Masley 10c958bba1 chore: implement organization sync and create idpsync package (#14432)
* chore: implement filters for the organizations query
* chore: implement organization sync and create idpsync package

Organization sync can now be configured to assign users to an org based on oidc claims.
2024-08-30 11:19:36 -05:00
Danny Kopping 043f4f5327 docs: add documentation for notifications feature (#14478) 2024-08-30 17:30:06 +02:00
Mathias Fredriksson 13e5c51c30 chore(dogfood): optimize dockerfile for envbuilder cache probing (#14497) 2024-08-30 17:54:19 +03:00
Danny Kopping 9596f236c1 fix: use negative deadline to ensure timeout in TestWebhook/timeout (#14498) 2024-08-30 16:46:13 +02:00
Danny Kopping 0f414a00d3 fix: restore closing SMTP message on method exit (#14496) 2024-08-30 13:03:25 +02:00
Cian Johnston a74273f1fd chore(coderd/database/dbpurge): replace usage of time.* with quartz (#14480)
Related to #10576

This PR introduces quartz to coderd/database/dbpurge and updates the following unit tests to make use of Quartz's functionality:

- TestPurge
- TestDeleteOldWorkspaceAgentLogs

Additionally, updates DeleteOldWorkspaceAgentLogs to replace the hard-coded interval with a parameter passed into the query. This aids in testing and brings us a step towards allowing operators to configure the cutoff interval for workspace agent logs.
2024-08-30 11:55:47 +01:00
Danny Kopping c90be9b0c1 fix: correctly close SMTP message and await response (#14495) 2024-08-30 11:37:50 +02:00
Ethan 851df91991 fix: serve test derp map locally (#14490) 2024-08-30 18:43:51 +10:00
Ethan 628750232f fix: delete workspace agent stats after 180 days (#14489)
Fixes #13430.

The test for purging old workspace agent stats from the DB was consistently failing when ran with Postgres towards the end of the month, but not with the in-memory DB. 

This was because month intervals are calculated differently for `time.Time` and the `interval` type in Postgres:

```
ethan=# SELECT
    '2024-08-30'::DATE AS original_date,
    ('2024-08-30'::DATE - INTERVAL '6 months') AS sub_date;
 original_date |      sub_date
---------------+---------------------
 2024-08-30    | 2024-02-29 00:00:00
(1 row)
```

Using `func (t Time) AddDate(years int, months int, days int) Time`, where `months` is `-6`:
```
Original: 2024-08-30 00:00:00 +0000 UTC
6 Months Earlier: 2024-03-01 00:00:00 +0000 UTC
```

Since 6 months was chosen arbitrarily, we should be able to change it to 180 days, to remove any ambiguity between the in-memory DB, and the Postgres DB. The alternative solution would involve implementing Postgres' month interval algorithm in Go.

The UI only shows stats as old as 168 days (24 weeks), so a frontend change isn't required for the extra days of stats we lose in some cases.
2024-08-30 18:30:04 +10:00
Kayla Washburn-Love 4672849d05 chore: clean up usage of Object.keys (#14484) 2024-08-29 17:21:29 -06:00
Kayla Washburn-Love d2a22c538b chore: add more stories and use new groups query for user settings page (#14481) 2024-08-29 17:00:12 -06:00
Kayla Washburn-Love 6bc93520c4 fix: fix light mode styles for organization delete button (#14485) 2024-08-29 16:10:31 -06:00
Asher cd38e297b6 fix: make non-existent asset paths return a 404 (#14472)
Before, if a file was not found we would serve the app.

This would cause either the login page or the workspace
page to load (and consequently error because `assets` is
likely not a valid user).
2024-08-29 13:52:08 -08:00
Asher ef7fcf3930 fix: include dormant users in template acl query (#14461)
The issue is that if you add a user and then immediately go to give them
permissions, you can add them but they will not show up in the UI.  They
also do not show up in the audit log entry.
2024-08-29 13:48:28 -08:00
Kayla Washburn-Love 49afab12d5 feat: show organization name for groups on user profile (#14448) 2024-08-29 10:55:00 -06:00
Ethan 4b5c45d6df feat(cli): add aws check to ping p2p diagnostics (#14450) 2024-08-29 21:41:12 +10:00
Ethan e65eb0321c fix: support additional http headers on agent (#14464) 2024-08-29 14:15:15 +10:00
Steven Masley 6dbfe6f7ae chore: remove duplicates using the symmetric difference function (#14469)
The `SymmetricDifferenceFunc` used to include duplicates, which was incorrect.
2024-08-28 21:06:27 -05:00
Jaayden Halko 15d74a11a0 feat: improve custom roles create/edit page (#14456)
* fix: improve show/hide checkbox text

* feat: add parent checkbox for grouped resource permissions

* fix: align action list item to a grid

* chore: add additional tests

* fix: format
2024-08-28 18:13:33 -04:00
dependabot[bot] f3ea740b27 chore: bump next from 14.2.4 to 14.2.7 in /offlinedocs (#14473)
Bumps [next](https://github.com/vercel/next.js) from 14.2.4 to 14.2.7.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.4...v14.2.7)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-28 14:20:04 -06:00
Steven Masley b96ac677f1 chore: add organization search query to workspaces (#14474)
* chore: add organization search query to workspaces
2024-08-28 15:18:45 -05:00
Steven Masley 54fe082551 chore: implement filters for the organizations query (#14468)
Required for organization sync. Allows fetching a filtered set of orgs.
2024-08-28 13:24:28 -05:00
dependabot[bot] 7667d64686 chore: bump the mui group across 1 directory with 6 updates (#14400)
* chore: bump the mui group across 1 directory with 6 updates

Bumps the mui group with 6 updates in the /site directory:

| Package | From | To |
| --- | --- | --- |
| [@mui/icons-material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material) | `5.16.0` | `5.16.7` |
| [@mui/lab](https://github.com/mui/material-ui/tree/HEAD/packages/mui-lab) | `5.0.0-alpha.129` | `5.0.0-alpha.173` |
| [@mui/material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-material) | `5.16.0` | `5.16.7` |
| [@mui/system](https://github.com/mui/material-ui/tree/HEAD/packages/mui-system) | `5.16.0` | `5.16.7` |
| [@mui/utils](https://github.com/mui/material-ui/tree/HEAD/packages/mui-utils) | `5.16.0` | `5.16.6` |
| [@mui/x-tree-view](https://github.com/mui/mui-x/tree/HEAD/packages/x-tree-view) | `7.9.0` | `7.13.0` |



Updates `@mui/icons-material` from 5.16.0 to 5.16.7
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.7/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.7/packages/mui-icons-material)

Updates `@mui/lab` from 5.0.0-alpha.129 to 5.0.0-alpha.173
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/HEAD/packages/mui-lab)

Updates `@mui/material` from 5.16.0 to 5.16.7
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.7/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.7/packages/mui-material)

Updates `@mui/system` from 5.16.0 to 5.16.7
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.7/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.7/packages/mui-system)

Updates `@mui/utils` from 5.16.0 to 5.16.6
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.6/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.6/packages/mui-utils)

Updates `@mui/x-tree-view` from 7.9.0 to 7.13.0
- [Release notes](https://github.com/mui/mui-x/releases)
- [Changelog](https://github.com/mui/mui-x/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui/mui-x/commits/v7.13.0/packages/x-tree-view)

---
updated-dependencies:
- dependency-name: "@mui/icons-material"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mui
- dependency-name: "@mui/lab"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mui
- dependency-name: "@mui/material"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mui
- dependency-name: "@mui/system"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mui
- dependency-name: "@mui/utils"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: mui
- dependency-name: "@mui/x-tree-view"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mui
...

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

* Fix breaking changes

* Fix fmt

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: BrunoQuaresma <bruno_nonato_quaresma@hotmail.com>
2024-08-28 15:19:05 -03:00
Danny Kopping f24cb5cc96 fix: prevent test flakiness (#14467)
Signed-off-by: Danny Kopping <danny@coder.com>
2024-08-28 16:33:27 +02:00
Ethan c597c9260d fix: wait for provisioner daemon jobs to finish after sigterm (#14466) 2024-08-28 20:16:15 +10:00
Danielle Maywood 839918c5e7 chore(docs): document agent api debug endpoints (#14454)
* chore(docs): add agent api debug docs

* chore(docs): add sections to agent api readme

* chore(docs): link debug manifest to agentsdk.Manifest schema

* chore(docs): add high level overview of agent api debug docs

* chore(docs): link to agent api docs from reference

* chore(docs): fix invalid paths

* chore(docs): use env variable for coder agent debug address
2024-08-28 09:47:14 +01:00
Ethan 8c15192433 feat(cli): add p2p diagnostics to ping (#14426)
First PR to address #14244.

Adds common potential reasons as to why a direct connection to the workspace agent couldn't be established to `coder ping`:
- If the Coder deployment administrator has blocked direction connections (`CODER_BLOCK_DIRECT`).
- If the client has no STUN servers within it's DERP map.
- If the client or agent appears to be behind a hard NAT, as per Tailscale `netInfo.MappingVariesByDestIP`

Also adds a warning if the client or agent has a network interface below the 'safe' MTU for tailnet. This warning is always displayed at the end of a `coder ping`.
2024-08-28 15:39:01 +10:00
Kayla Washburn-Love b36d979a60 chore: add provisioner api version to /buildinfo (#14446) 2024-08-27 13:40:51 -06:00
dependabot[bot] f3c76ce244 chore: bump eslint-config-next from 14.0.1 to 14.2.6 in /offlinedocs (#14402)
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.0.1 to 14.2.6.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.2.6/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  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-08-27 13:39:25 -06:00
Ethan 63fe2305f8 fix: detect nested dotfiles scripts (#14455) 2024-08-28 02:14:23 +10:00
Marcin Tojek 47f2c7d683 feat: notify about manual failed builds (#14419) 2024-08-27 14:35:28 +00:00
Ethan 0afff43f9d chore: remove dogfood import block (#14449) 2024-08-27 06:28:25 +00:00
Asher 499769187b fix: increase group name limit to 36 from 32 (#14443) 2024-08-26 11:38:31 -08:00
Asher 88d7181a47 fix: filter "add group member" by organization (#14404)
This is accomplished by using the members endpoint instead of the users
endpoint, and to that end the UserAutocomplete component has been
reworked to support either endpoint as separate components with a shared
base.

* Add Storybook for groups page

This ensures it is using the right endpoint for the add member dropdown.

* Add ability to mock react-query errors
2024-08-26 11:13:34 -08:00
Asher 83f9ea17b4 chore: update organizations doc link (#14361) 2024-08-26 10:40:18 -08:00
Steven Masley 93eef7b542 chore: keep entitlements in the options only, simplify fields (#14434)
* chore: refactor entitlements to keep it in just the options

Duplicating the reference did not feel valuable, just confusing
2024-08-26 13:05:03 -05:00
Jaayden Halko fb6b954222 chore: update secondary and disabled text colors (#14355) 2024-08-26 12:22:32 -04:00
Garrett Delfosse ded612d3ec fix: use authenticated urls for pubsub (#14261) 2024-08-26 15:04:04 +00:00
Cian Johnston 6914862903 fix(cli): add check for DisableOwnerWorkspaceExec in scaletest (#14417)
- Adds `--use-host-login` to `coder exp scaletest workspace-traffic`
- Modifies getScaletestWorkspaces to conditionally filter workspaces if `CODER_DISABLE_OWNER_WORKSPACE_ACCESS` is set
- Adds a warning if `CODER_DISABLE_OWNER_WORKSPACE_ACCESS` is set and scaletest workspaces are filtered out due to ownership mismatch.
- Modifies `coderdtest.New` to detect cross-test bleed of `CODER_DISABLE_OWNER_WORKSPACE_ACCESS` and fast-fail.
2024-08-26 12:02:54 +01:00
Steven Masley c8eacc6df7 chore!: allow CreateUser to accept multiple organizations (#14383)
* chore: allow CreateUser to accept multiple organizations

In a multi-org deployment, it makes more sense to allow for multiple
org memberships to be assigned at create. The legacy param will still
be honored.

* Handle sdk deprecation better by maintaining cli functions
2024-08-23 21:23:51 +00:00
Steven Masley af125c3795 chore: refactor entitlements to be a safe object to use (#14406)
* chore: refactor entitlements to be passable as an argument

Previously, all usage of entitlements requires mutex usage on the
api struct directly. This prevents passing the entitlements to
a sub package. It also creates the possibility for misuse.
2024-08-23 16:21:58 -05:00
Steven Masley cb6a47227f chore: implement generalized symmetric difference for set comparison (#14407)
* chore: implement generalized symmetric difference for set comparison

Going to be used in Organization Sync + maybe group sync. Felt
better to reuse, rather than copy
2024-08-23 14:52:35 -05:00
Cian Johnston 4bd7fe8506 fix(examples/templates/gcp-devcontainer): fix location of env file (#14422) 2024-08-23 17:46:02 +01:00
Cian Johnston 53e5746636 feat(examples/templates/gcp-devcontainer): add envbuilder provider (#14405)
This PR modifies the gcp-devcontainer example template to include
support for devcontainer caching using the envbuilder provider.

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-08-23 17:36:24 +01:00
Danny Kopping a4d785dec5 chore: use idiomatic test setup in notification tests (#14416) 2024-08-23 16:29:27 +02:00
Cian Johnston d4adfa3902 fix(coderd/database/dbmem): include a technical summary row on over-pagination (#14415) 2024-08-23 13:16:55 +01:00
Cian Johnston 99e103e790 feat(coderd/coderdtest): allow mutating deployment values (#14414) 2024-08-23 12:15:22 +01:00
Ethan 4cc26be5ec fix: set network telemetry client version on server (#14376) 2024-08-23 06:17:28 +00:00
Kayla Washburn-Love 5710a98714 chore: update @types/node and fix vite warning (#14403) 2024-08-22 16:44:14 -06:00
dependabot[bot] b0084e2229 chore: bump github.com/prometheus/client_golang from 1.19.1 to 1.20.1 (#14379)
* chore: bump github.com/prometheus/client_golang from 1.19.1 to 1.20.1

Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.1 to 1.20.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.20.1/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.1...v1.20.1)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 01:40:07 +03:00
Spike Curtis d52bc91e48 chore: add dbauthz to unhanger tests (#14394) 2024-08-22 22:23:52 +04:00
dependabot[bot] 337ee3544b chore: bump eslint from 8.56.0 to 8.57.0 in /offlinedocs (#14294)
Bumps [eslint](https://github.com/eslint/eslint) from 8.56.0 to 8.57.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.56.0...v8.57.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  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-08-22 19:59:12 +03:00
dependabot[bot] aeb4040958 chore: bump the emotion group across 1 directory with 3 updates (#14396)
* chore: bump the emotion group across 1 directory with 3 updates

Bumps the emotion group with 3 updates in the /site directory: [@emotion/css](https://github.com/emotion-js/emotion), [@emotion/react](https://github.com/emotion-js/emotion) and [@emotion/styled](https://github.com/emotion-js/emotion).


Updates `@emotion/css` from 11.11.2 to 11.13.0
- [Release notes](https://github.com/emotion-js/emotion/releases)
- [Changelog](https://github.com/emotion-js/emotion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emotion-js/emotion/compare/@emotion/css@11.11.2...@emotion/css@11.13.0)

Updates `@emotion/react` from 11.11.4 to 11.13.3
- [Release notes](https://github.com/emotion-js/emotion/releases)
- [Changelog](https://github.com/emotion-js/emotion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emotion-js/emotion/compare/@emotion/react@11.11.4...@emotion/react@11.13.3)

Updates `@emotion/styled` from 11.11.5 to 11.13.0
- [Release notes](https://github.com/emotion-js/emotion/releases)
- [Changelog](https://github.com/emotion-js/emotion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emotion-js/emotion/compare/@emotion/styled@11.11.5...@emotion/styled@11.13.0)

---
updated-dependencies:
- dependency-name: "@emotion/css"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: emotion
- dependency-name: "@emotion/react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: emotion
- dependency-name: "@emotion/styled"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: emotion
...

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

* Fix lint

* Fix type

* Fix fmt

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: BrunoQuaresma <bruno_nonato_quaresma@hotmail.com>
2024-08-22 13:46:59 -03:00
Marcin Tojek c818b4ddd4 feat: add notification for suspended/activated account (#14367)
* migrations

* notify

* fix

* TestNotifyUserSuspended

* TestNotifyUserReactivate

* post merge

* fix escape

* TestNotificationTemplatesCanRender

* links and events

* notifyEnq

* findUserAdmins

* notifyUserStatusChanged

* go build

* your and admin

* tests

* refactor

* 247

* Danny's review
2024-08-22 13:52:25 +02:00
dependabot[bot] 046c1c4228 chore: bump the vite group across 1 directory with 3 updates (#14281)
Bumps the vite group with 3 updates in the /site directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite), [vite-plugin-checker](https://github.com/fi3ework/vite-plugin-checker) and [vite-plugin-turbosnap](https://github.com/IanVS/vite-plugin-turbosnap).


Updates `vite` from 5.3.3 to 5.4.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.1/packages/vite)

Updates `vite-plugin-checker` from 0.7.1 to 0.7.2
- [Release notes](https://github.com/fi3ework/vite-plugin-checker/releases)
- [Changelog](https://github.com/fi3ework/vite-plugin-checker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fi3ework/vite-plugin-checker/compare/vite-plugin-checker@0.7.1...vite-plugin-checker@0.7.2)

Updates `vite-plugin-turbosnap` from 1.0.2 to 1.0.3
- [Release notes](https://github.com/IanVS/vite-plugin-turbosnap/releases)
- [Commits](https://github.com/IanVS/vite-plugin-turbosnap/compare/v1.0.2...v1.0.3)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: vite
- dependency-name: vite-plugin-checker
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: vite
- dependency-name: vite-plugin-turbosnap
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: vite
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 14:18:35 +03:00
dependabot[bot] 3e5cfa9e45 chore: bump msw from 2.2.3 to 2.3.5 in /site (#14138)
Bumps [msw](https://github.com/mswjs/msw) from 2.2.3 to 2.3.5.
- [Release notes](https://github.com/mswjs/msw/releases)
- [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mswjs/msw/compare/v2.2.3...v2.3.5)

---
updated-dependencies:
- dependency-name: msw
  dependency-type: direct:development
  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-08-22 14:17:49 +03:00
dependabot[bot] fbec45b807 chore: bump pretty-bytes from 6.1.0 to 6.1.1 in /site (#14301)
Bumps [pretty-bytes](https://github.com/sindresorhus/pretty-bytes) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/sindresorhus/pretty-bytes/releases)
- [Commits](https://github.com/sindresorhus/pretty-bytes/compare/v6.1.0...v6.1.1)

---
updated-dependencies:
- dependency-name: pretty-bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 14:17:13 +03:00
Danny Kopping cc944209ae fix: include provisioner timing action in hash func (#14388) 2024-08-22 12:31:50 +02:00
Cian Johnston 82e6070c7a fix(cli): ensure that the support bundle command does not panic on zero values (#14392)
We try to write a cute little summary at the end of the bundle, but that could panic if some of the fields of the bundle were nil. Adds a test that essentially ensures nil values in a bundle, and ensures that it can be handled without losing our towels.

Co-authored-by: Danny Kopping <danny@coder.com>
2024-08-22 11:15:02 +01:00
Ethan 3514ca3476 chore: skip completion install prompts in non-interactive shells (#14391) 2024-08-22 15:40:53 +10:00
Dean Sheather e8c59a1d9d chore: avoid flake in resume token test (#14378) 2024-08-22 13:27:43 +10:00
Ethan d7800a43e9 docs: add coderd terraform provider (#14374) 2024-08-22 13:03:33 +10:00
Stephen Kirby 9f4f88f38c version-flags (#14386) 2024-08-21 13:24:35 -05:00
Steven Masley a359879af5 chore: scope workspace quotas to organizations (#14352)
* chore: scope workspace quotas to organizations

Quotas are now a function of (user_id, organization_id). They are
still sourced from groups. Deprecate the old api endpoint.
2024-08-21 09:25:20 -05:00
Danny Kopping fa733318e0 Add missing content (#14380) 2024-08-21 12:46:11 +00:00
Danny Kopping 6960d194ae feat: add provisioning timings to understand slow build times (#14274) 2024-08-21 14:18:58 +02:00
Danny Kopping 9c8c6a952d feat: add notification deduplication trigger (#14172) 2024-08-21 11:18:03 +02:00
Ethan d9f419308a chore(dogfood): use remote tf state (#14363) 2024-08-21 12:46:28 +10:00
Jon Ayers b6d35edebd chore: remove meticulous from CI (#14369) 2024-08-20 14:13:13 -04:00
Kayla Washburn-Love 03f05e25f6 chore(site): make info gray (#14356) 2024-08-20 11:59:23 -06:00
Ethan cca4519420 feat: enable setting max port share level during template creation (#14366) 2024-08-20 22:48:39 +10:00
Cian Johnston 2bef1752f1 chore(envbuilder-dogfood): update envbuilder-dogfood template to use provider (#14324)
Updates the envbuilder-dogfood template to use the envbuilder provider.
Relates to coder/team-coconut#38

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-08-20 11:50:39 +01:00
Cian Johnston 40baa5bc72 chore(examples): update devcontainer-{docker,kubernetes} to use computed env (#14328)
Updates devcontainer-docker and devcontainer-kubernetes example templates to use computed env from provider, if applicable.
2024-08-20 10:47:02 +01:00
Dean Sheather cf8be4eac5 feat: add resume support to coordinator connections (#14234) 2024-08-20 17:16:49 +10:00
Ethan 0b2ba96065 feat(cli): add shell completions (#14341) 2024-08-20 14:47:46 +10:00
Kyle Carberry 6f9b3c1592 chore: sign the windows installer (#14353) 2024-08-19 20:33:37 -04:00
Asher f8f3d8967e fix: label premium features in middleware error (#14360)
Previously, all features were called enterprise in the license check middleware.
2024-08-19 15:58:41 -08:00
Asher 4446d61fcd fix: show org summary page if not entitled (#14336)
You cannot edit the settings without being entitled, so show the summary
page instead.
2024-08-19 15:57:02 -08:00
Kayla Washburn-Love 1c3dc8392e chore: remove dangling eslint-ignore comments (#14334) 2024-08-19 16:08:53 -06:00
Asher fa59b30cfb chore: update editorconfig with new tabs default (#14335) 2024-08-19 11:40:58 -08:00
dependabot[bot] f007c90a30 ci: bump contributor-assistant/github-action (#14345)
Bumps the github-actions group with 1 update in the / directory: [contributor-assistant/github-action](https://github.com/contributor-assistant/github-action).


Updates `contributor-assistant/github-action` from 2.4.0 to 2.5.1
- [Release notes](https://github.com/contributor-assistant/github-action/releases)
- [Commits](https://github.com/contributor-assistant/github-action/compare/v2.4.0...v2.5.1)

---
updated-dependencies:
- dependency-name: contributor-assistant/github-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-08-19 21:53:20 +03:00
Bruno Quaresma 10327fb3a9 fix(coderd): humanize duration on notifications (#14333) 2024-08-19 15:49:47 -03:00
Steven Masley 755afa31cf chore: ui error handling should be specific to general (#14346)
* chore: ui error handling should be specific to general

Specific errors should be checked before defaulting to a general
error handling
2024-08-19 13:28:43 -05:00
Steven Masley 422e044859 chore: forbidden error on create workspace without permissions (#14347)
Multi-org enables the possibility of a user having template permissions,
but not workspace create permissions. The unauthorized error should be
returned instead of a 404. This does not leak any information the user
cannot already obtain.
2024-08-19 13:28:27 -05:00
Ben Potter c3ef7dc33b docs: add organizations guide (#14012)
* wip

* add workspace & members screenshot

* add audit logs

* org id and provisioner key

* Update docs/guides/using-organizations.md

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>

* edits to docs

* fixup

* fmt

* changes based on kirby feedback

* fix link

* fix manifest

* fmt

---------

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
2024-08-19 15:11:36 +00:00
Ethan d0f36dc6ba ci: add networking release validation (#14273) 2024-08-19 16:31:49 +10:00
Muhammad Atif Ali cba6e93176 chore: update docs links (#14221) 2024-08-17 11:51:13 +00:00
Jaayden Halko bec6a26d0e fix: re-add original create template context menu (#14326) 2024-08-16 17:16:13 -04:00
dependabot[bot] 8c4d726cf6 chore: bump the x group with 6 updates (#14235)
* chore: bump the x group with 6 updates

Bumps the x group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.25.0` | `0.26.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.27.0` | `0.28.0` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.23.0` | `0.24.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.22.0` | `0.23.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.16.0` | `0.17.0` |
| [golang.org/x/tools](https://github.com/golang/tools) | `0.23.0` | `0.24.0` |


Updates `golang.org/x/crypto` from 0.25.0 to 0.26.0
- [Commits](https://github.com/golang/crypto/compare/v0.25.0...v0.26.0)

Updates `golang.org/x/net` from 0.27.0 to 0.28.0
- [Commits](https://github.com/golang/net/compare/v0.27.0...v0.28.0)

Updates `golang.org/x/sys` from 0.23.0 to 0.24.0
- [Commits](https://github.com/golang/sys/compare/v0.23.0...v0.24.0)

Updates `golang.org/x/term` from 0.22.0 to 0.23.0
- [Commits](https://github.com/golang/term/compare/v0.22.0...v0.23.0)

Updates `golang.org/x/text` from 0.16.0 to 0.17.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.16.0...v0.17.0)

Updates `golang.org/x/tools` from 0.23.0 to 0.24.0
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
...

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-16 20:37:37 +00:00
dependabot[bot] fc3b2ff06c chore: bump google.golang.org/api from 0.190.0 to 0.192.0 (#14291)
* chore: bump google.golang.org/api from 0.190.0 to 0.192.0

Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.190.0 to 0.192.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.190.0...v0.192.0)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-16 23:22:39 +03:00
Ethan 0613797934 fix: use dogfood contents as template dir (#14332) 2024-08-17 02:52:26 +10:00
Ethan 363a016281 fix: move to dogfood before terraform applying 2024-08-17 02:43:16 +10:00
Ethan 979430d635 chore: manage dogfood template using coderd provider (#14321) 2024-08-17 02:33:32 +10:00
Kayla Washburn-Love 7142cbb9e6 chore: enable noConsoleLog lint (#14329) 2024-08-16 10:12:06 -06:00
dependabot[bot] 2c150d03f6 chore: bump tzdata from 1.0.30 to 1.0.40 in /site (#14303)
Bumps [tzdata](https://github.com/rogierschouten/tzdata-generate) from 1.0.30 to 1.0.40.
- [Release notes](https://github.com/rogierschouten/tzdata-generate/releases)
- [Commits](https://github.com/rogierschouten/tzdata-generate/compare/v1.0.30...v1.0.40)

---
updated-dependencies:
- dependency-name: tzdata
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-16 17:38:05 +03:00
dependabot[bot] 9b9496cf4d chore: bump typescript from 5.3.2 to 5.5.4 in /offlinedocs (#14163)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.3.2 to 5.5.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.3.2...v5.5.4)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  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-08-16 17:36:54 +03:00
dependabot[bot] a62e69d34a chore: bump undici from 6.19.2 to 6.19.7 in /site (#14300)
Bumps [undici](https://github.com/nodejs/undici) from 6.19.2 to 6.19.7.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.19.2...v6.19.7)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-16 10:59:40 -03:00
Cian Johnston 91a74f0ead chore(examples): update kubernetes devcontainer template with envbuilder provider (#14267)
* chore(examples): update kubernetes devcontainer template with envbuilder provider

* make insecure a template variable

* Update examples/templates/devcontainer-kubernetes/README.md

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>

---------

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-08-15 22:08:24 +01:00
Kayla Washburn-Love 4db8fa661e chore: ignore tabs change when using git blame (#14297) 2024-08-15 15:02:24 -06:00
Kayla Washburn-Love 95a7c0c4f0 chore: use tabs for prettier and biome (#14283) 2024-08-15 14:53:53 -06:00
dependabot[bot] db2d0596d4 chore: bump axios from 1.7.2 to 1.7.4 in /site (#14265)
Bumps [axios](https://github.com/axios/axios) from 1.7.2 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.2...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  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-08-15 16:02:54 -04:00
dependabot[bot] f2a96ac984 chore: bump the jest group across 1 directory with 2 updates (#14157)
Bumps the jest group with 2 updates in the /site directory: [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) and [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest).


Updates `jest` from 29.6.2 to 29.7.0
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v29.7.0/packages/jest)

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

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: jest
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: jest
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-15 19:41:25 +00:00
dependabot[bot] 82cb6ef7ec chore: bump typescript from 5.2.2 to 5.5.4 in /site (#14164)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.2.2 to 5.5.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.2.2...v5.5.4)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  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-08-15 19:34:02 +00:00
Kayla Washburn-Love d15f16fa2e chore: replace eslint with biome (#14263) 2024-08-15 13:26:29 -06:00
Steven Masley 7b09d98238 chore: add /groups endpoint to filter by organization and/or member (#14260)
* chore: merge get groups sql queries into 1

* Add endpoint for fetching groups with filters
* remove 2 ways to customizing a fake authorizer
2024-08-15 13:40:15 -05:00
Steven Masley 83ccdaa755 chore: fixup quotas to only include groups you are a member of (#14271)
* chore: fixup quotas to only include groups you are a member of

Before all everyone groups were included in the allowance.

* chore: add unit test to execercise the bug
* add unit test to add rows into the everyone group
2024-08-15 13:27:50 -05:00
Bruno Quaresma f619500833 chore(site): reduce flakiness on terminal stories (#14269) 2024-08-15 11:30:17 -03:00
Kayla Washburn-Love 8563b372e8 feat: filter templates by organization (#14254) 2024-08-14 15:01:45 -06:00
Jon Ayers 4fc047954e fix: avoid deleting peers on graceful close (#14165)
* fix: avoid deleting peers on graceful close

- Fixes an issue where a coordinator deletes all
  its peers on shutdown. This can cause disconnects
  whenever a coderd is redeployed.
2024-08-14 15:16:08 -04:00
Bruno Quaresma 6f1951e1c8 feat: add template delete notification (#14250) 2024-08-14 14:22:43 -03:00
Chris LaRose 86b9c97e8e chore: update envbox template image (#14256) 2024-08-14 23:39:43 +10:00
Cian Johnston e978d4d9ac chore(examples): update devcontainer-docker template with envbuilder provider (#14199)
Updates the devcontainer-docker template with optional caching via the envbuilder provider
2024-08-14 10:32:53 +01:00
Danny Kopping c90e6d7b47 chore: fix up migration number fixer (#14266) 2024-08-14 11:05:03 +02:00
Steven Masley 84fdfd2a18 chore: remove UpsertCustomRole in favor of Insert + Update (#14217)
* chore: remove UpsertCustomRole in favor of Insert + Update

---------

Co-authored-by: Jaayden Halko <jaayden.halko@gmail.com>
2024-08-13 12:53:47 -05:00
Bruno Quaresma 712a1b50d8 fix(site): correct user agent data on audit row (#14243) 2024-08-13 14:02:16 -03:00
Steven Masley ccc664de37 chore: rename 'Deployment' button to 'Administration' (#14240)
* chore: rename 'Deployment' button to 'Administration' 

Reword "Auditing" to a noun like the rest of the dropdowns
2024-08-13 11:21:02 -05:00
Bruno Quaresma f1feb40e17 docs: clone git repositories (#14090) 2024-08-13 12:55:40 -03:00
Muhammad Atif Ali 48f29a1995 docs: move api and cli docs routes to reference/ (#14241) 2024-08-13 18:39:46 +03:00
Hugo Dutka 6f9b1a39f4 fix: allow group members to read group information (#14200)
* - allow group members to read basic Group info
- allow group members to see they are part of the group, but not see that information about other members
- add a GetGroupMembersCountByGroupID SQL query, which allows group members to see members count without revealing other information about the members
- add the group_members_expanded db view
- rewrite group member queries to use the group_members_expanded view
- add the RBAC ResourceGroupMember and add it to relevant roles
- rewrite GetGroupMembersByGroupID permission checks
- make the GroupMember type contain all user fields
- fix type issues coming from replacing User with GroupMember in group member queries
- add the MemberTotalCount field to codersdk.Group
- display `group.total_member_count` instead of `group.members.length` on the account page
2024-08-13 09:20:24 -05:00
Steven Masley 60218c4c78 chore: fix dead link to privledged docker containers in docs (#14259) 2024-08-13 09:01:20 -05:00
Benjamin Peinhardt 76722a7db5 fix: make default support links respect --docs-url (#14176)
make default support links respect --docs-url
2024-08-12 17:01:22 -04:00
Kayla Washburn-Love 4c7132f08b chore: redirect to the correct template page routes (#14230) 2024-08-12 13:12:28 -06:00
Kayla Washburn-Love 59a80d70dc feat: show organization information on templates page (#14224) 2024-08-12 09:15:13 -06:00
dependabot[bot] 9715ae5932 chore: bump github.com/ory/dockertest/v3 from 3.10.0 to 3.11.0 (#14237)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 17:04:35 +03:00
Steven Masley 8af8c77e2a test: add unit test to verify group permission behavior (#14223)
* test: add unit test to verify group permission behavior
* Update coderd/database/dbauthz/groupsauth_test.go

---------

Co-authored-by: Cian Johnston <cian@coder.com>
2024-08-12 08:34:00 -05:00
dependabot[bot] 0338250d86 chore: bump github.com/charmbracelet/glamour from 0.7.0 to 0.8.0 (#14238)
* chore: bump github.com/charmbracelet/glamour from 0.7.0 to 0.8.0

Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Commits](https://github.com/charmbracelet/glamour/compare/v0.7.0...v0.8.0)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 08:46:09 -04:00
Ethan 73402fc2f7 fix: fix flaking Test_sshConfigExecEscape (#14233)
Fixes #13962.
2024-08-12 18:56:34 +10:00
Asher ba4186dacc feat: show summary if unable to edit org (#14214)
This can happen if you can edit the members, for example, but not the
organization settings.  In this case you will see a new summary page
instead of the edit form.
2024-08-09 13:31:03 -08:00
Jaayden Halko 0b9ed57c10 feat: add delete custom role context menu button and modal (#14228)
* feat: delete custom role

* fix: add doc comment
2024-08-09 16:59:44 -04:00
Bruno Quaresma c648c548d8 refactor(site): make switches smaller (#14226) 2024-08-09 19:19:04 +00:00
Bruno Quaresma 21942afef3 feat(site): implement notification ui (#14175) 2024-08-09 13:43:09 -03:00
Steven Masley aaa5174bef chore: move custom-roles feature to permium license (#14201)
Currently an unsafe experiment, so it can be moved safely
2024-08-09 10:21:39 -05:00
Steven Masley 591385f2ca chore: implement fuzzy name matching for templates (#14211)
* chore: add fuzzy name search for templates
* chore: implement fuzzy name matching for templates

Templates search query defaults to a fuzzy name match
2024-08-09 10:21:26 -05:00
Bruno Quaresma 27b8f201a4 refactor: refactor notification email template (#14208) 2024-08-09 11:25:19 -03:00
Asher abbcffe181 fix: use multi-org settings layout even if not licensed (#14215)
* fix: only check flag for organization settings

I added checks against the license but actually what we want is for
these views to become the default even when not licensed (once the
experimental flag is removed).

* Move deployment settings header to components

This will let us use it in the org settings pages, for a consistent
look.

* Add premium badge

* Use settings header on org pages

* Add license badges to create org page

I am not sure if there is maybe a better place for this, but maybe this
is good enough.

* Change create org form description text

It says "change", but there is nothing to change yet since this is a new
organization.

* Consistently capitalize org menu items and headings

Also, remove the "organizations" prefix since it seems redundant.
2024-08-08 23:29:37 -08:00
Asher 9a47ea1279 chore: move back to single audit log page (#14212)
* chore: remove per-org audit links

For now at least, we will have the one audit page at /audit which lets
you filter by organization.

This also removes the need to do per-org audit permission checks.

* Filter audit org dropdown by auditable orgs

Previously all orgs you can list would appear, but you might not be
able to audit all of them.
2024-08-08 22:41:37 -08:00
Asher 6019d0ba96 fix: only show editable orgs on deployment page (#14193)
Also make sure the redirect from /organizations goes to an org that the
user can edit, rather than always the default org.
2024-08-08 22:18:20 -08:00
Benjamin Peinhardt d6c4d47229 fix: add version information to default docs links (#14205)
add version information to default docs links

---------

Co-authored-by: Kayla Washburn-Love <mckayla@hey.com>
2024-08-08 20:20:31 -05:00
Jaayden Halko 2e05329111 feat: add custom roles (#14069)
* feat: initial commit custom roles

* feat: add page to create and edit custom roles

* feat: add assign org role permission

* feat: wip

* feat: cleanup

* fix: role name is disabled when editing the role

* fix: assign role context menu falls back to name when no display_name

* feat: add helper text to let users know that role name is immutable

* fix: format

* feat: - hide custom roles tab if experiment is not enabled

* fix: use custom TableLoader

* fix: fix custom roles text

* fix: use PatchRoleRequest

* fix: use addIcon to create roles

* feat: add cancel and save buttons to top of page

* fix: use nameValidator for name

* chore: cleanup

* feat: add show all permissions checkbox

* fix: update sidebar for roles

* fix: fix format

* fix: custom roles is not needed outside orgs

* fix: fix sidebar stories

* feat: add custom roles page stories

* fix: use organization permissions

* feat: add stories for CreateEditRolePageView

* fix: design improvements for the create edit role form

* feat: add show all resources checkbox to bottom of table

* feat: improve spacing
2024-08-08 21:05:20 -04:00
Eric Paulsen 238e9956f4 docs: add vs code extensions documentation (#14119)
* docs: add vs code extensions documentation

* clarify msft marketplace connection

* `make fmt`

* fix links

* rm image

---------

Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-08-08 10:52:46 -04:00
Danny Kopping d79a7adf99 docs: advise against shared CODER_CACHE_DIRECTORY dir usage in note (#14216) 2024-08-08 12:42:47 +00:00
Ari Croock f50e1d5a9a fix(examples): use more precise example kubernetes template labels (#14028)
* fix: apply more specific selector labels to k8s example deployment template

* fix: use immutable ids instead of names for persistent resources in k8s example template as per docs
2024-08-08 11:17:18 +01:00
Steven Masley 2c13797350 chore: implement deleting custom roles (#14101)
* chore: implement deleting custom roles

* add trigger to delete role from organization members on delete
* chore: add comments to explain populated field
2024-08-07 12:37:55 -05:00
Kayla Washburn-Love d0feb70811 fix: add template editor to /templates/:templateName route group (#14206) 2024-08-07 10:31:36 -06:00
Steven Masley b55a7a8b78 chore: delete user codersdk to support status code regression (#14173)
* chore: delete user codersdk to support status code regression
* Update codersdk/users.go

Co-authored-by: Kayla Washburn-Love <mckayla@hey.com>

---------

Co-authored-by: Kayla Washburn-Love <mckayla@hey.com>
2024-08-07 11:19:31 -05:00
Mathias Fredriksson 8c0565177e chore(agent): remove err=<nil> log for batch update metadata complete (#14179)
Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
2024-08-07 11:31:47 +00:00
Danny Kopping c6076d2d0d chore: improve notification template tests' resilience (#14196) 2024-08-07 11:33:26 +02:00
Ethan e09ad1ddc1 fix: lock adding to tailnet waitgroup to avoid race (and fix flake) (#14195) 2024-08-07 15:52:42 +10:00
Stephen Kirby 46becc7201 updated version flags, release calendar (#14191) 2024-08-06 16:21:37 -05:00
Steven Masley 373b36c3c9 chore: update links to sharkymark's v2 templates (#14192)
* chore: update links to sharkymark's v2 templates

Links were broken with a refactor in the source repo.
2024-08-06 14:40:50 -05:00
Kayla Washburn-Love 3b53f5ab47 fix: only show valid organizations in CreateTemplateForm (#14174) 2024-08-06 12:10:44 -06:00
Kayla Washburn-Love ff785588fe chore: don't require an organization to read starter templates (#14190) 2024-08-06 11:26:26 -06:00
Garrett Delfosse fab196043e fix: allow tag removal in provisioner upsert (#14187) 2024-08-06 11:38:55 -04:00
Mathias Fredriksson 49feb12a7f chore(scripts): remove branch checks from release script (#14184)
The initial assumption that branch manipulations should be done by this
script and not pushed to remote manually has proven to get in the way of
the regular release flow.

These are just safety-checks to prevent user error, safe to remove.

Fixes #13648
2024-08-06 14:54:26 +00:00
Colin Adler 89e6afbc5e chore: update github.com/docker/docker (#14183)
This resolves a critical CVE that Coder is not affected by.
2024-08-06 10:50:09 -04:00
Danny Kopping 58428aafce fix: allow all users to read system notification templates (#14181) 2024-08-06 15:37:49 +02:00
Spike Curtis 70a694ed4c fix: document files API needs ustar format (#14152)
Signed-off-by: Spike Curtis <spike@coder.com>
2024-08-06 11:00:49 +04:00
Asher 097f739492 feat: add organization-scoped permission checks to deployment settings (#14063)
* s/readAllUsers/viewAllUsers

Other frontend variables use the `view` syntax.  Arguably we should 
use `read` to match the backend, but `view` does seem more UI-like.

* Check license for organizations

All the checks now require both the experiment and license.

I also renamed the variable canViewOrganizations everywhere for
consistency.

* Allow any auditor to view the audit log

* Use fine-grained permissions on settings page

Since in addition to deployment settings this page now also includes
users, audit logs, groups, and orgs.

Since you might not be able to fetch deployment values, move all the
loaders to the individual pages instead of in the wrapping layout.

* Add stories for organization members page

Needed to break it out into a separate view to do this.

* Add stories for multi-org sidebar

* Remove multi-org check from management settings layout

We only use this layout when multi-org is enabled, so no need to run the
check a second time.

* Add more stories for deployment dropdown
2024-08-05 17:55:35 -08:00
Steven Masley 0ad5f6067d chore: prevent removing members from the default organization (#14094)
* chore: prevent removing members from the default organization

Until multi-organizations is released outside an experiment, the
experiment should be backwards compatible.
2024-08-05 13:48:10 -05:00
Steven Masley 173dc0e35f chore: refactor patch custom organization route to live in enterprise (#14099)
* chore: refactor patch custom organization route to live in enterprise
2024-08-05 13:42:11 -05:00
Steven Masley a77a9ab0a6 chore: skip audit log filter for owner/admin users (#14132)
* chore: audit log filter to be skipped if user is owner/admin

Optimize for speed in the case the user can read all audit_logs

* fixup! chore: audit log filter to be skipped if user is owner/admin
2024-08-05 13:42:01 -05:00
Jon Ayers 203f48af56 fix: extend locking in wsproxy to avoid race (and fix flake) (#14167) 2024-08-05 14:30:44 -04:00
Steven Masley b80d99550a chore: revert status code change for delete users endpoint (#14168)
Revert from https://github.com/coder/coder/pull/13870
2024-08-05 13:10:56 -05:00
Jon Ayers 4e0cb60eeb fix: ignore errors on provided logger (#14169) 2024-08-05 17:22:34 +00:00
Kayla Washburn-Love dfeafa8f5a feat: show a warning when an organization has no provisioners (#14136) 2024-08-05 10:44:39 -06:00
Kayla Washburn-Love efbd6257e4 chore: remove global organization id state (#14135) 2024-08-05 10:33:58 -06:00
Jon Ayers f9b660e573 fix: ignore coderd log errors (#14166)
- This is the source of a lot of our flakes recently.
2024-08-05 12:07:06 -04:00
dependabot[bot] fce14fb9ad chore: bump github.com/hashicorp/hc-install from 0.7.0 to 0.8.0 (#14145)
* chore: bump github.com/hashicorp/hc-install from 0.7.0 to 0.8.0

Bumps [github.com/hashicorp/hc-install](https://github.com/hashicorp/hc-install) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/hashicorp/hc-install/releases)
- [Commits](https://github.com/hashicorp/hc-install/compare/v0.7.0...v0.8.0)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 15:39:25 +00:00
dependabot[bot] 33beb9bd70 chore: bump gopkg.in/DataDog/dd-trace-go.v1 from 1.64.0 to 1.66.0 (#14041)
* chore: bump gopkg.in/DataDog/dd-trace-go.v1 from 1.64.0 to 1.66.0

Bumps gopkg.in/DataDog/dd-trace-go.v1 from 1.64.0 to 1.66.0.

---
updated-dependencies:
- dependency-name: gopkg.in/DataDog/dd-trace-go.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 15:26:55 +00:00
dependabot[bot] 96642382b3 chore: bump github.com/chromedp/chromedp from 0.9.2 to 0.10.0 (#14146)
* chore: bump github.com/chromedp/chromedp from 0.9.2 to 0.10.0

Bumps [github.com/chromedp/chromedp](https://github.com/chromedp/chromedp) from 0.9.2 to 0.10.0.
- [Release notes](https://github.com/chromedp/chromedp/releases)
- [Commits](https://github.com/chromedp/chromedp/compare/v0.9.2...v0.10.0)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 18:14:49 +03:00
dependabot[bot] 25c83cf0b1 chore: bump archiver from 6.0.0 to 6.0.2 in /offlinedocs (#14162)
Bumps [archiver](https://github.com/archiverjs/node-archiver) from 6.0.0 to 6.0.2.
- [Release notes](https://github.com/archiverjs/node-archiver/releases)
- [Changelog](https://github.com/archiverjs/node-archiver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/archiverjs/node-archiver/compare/6.0.0...6.0.2)

---
updated-dependencies:
- dependency-name: archiver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 18:13:03 +03:00
Bruno Quaresma e398309a8f chore: allow minor and patch updates for npm deps (#14155) 2024-08-05 11:52:39 -03:00
Danny Kopping e164b1e71c feat: add notification preferences database & audit support (#14100) 2024-08-05 16:18:45 +02:00
Cian Johnston 49a2880abc fix(testutil): ensure GetRandomName never returns strings greater tha… (#14153) 2024-08-05 15:03:07 +01:00
dependabot[bot] 8acc7f2070 ci: bump crate-ci/typos in the github-actions group (#14149)
Bumps the github-actions group with 1 update: [crate-ci/typos](https://github.com/crate-ci/typos).


Updates `crate-ci/typos` from 1.23.5 to 1.23.6
- [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.23.5...v1.23.6)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-08-05 15:27:55 +03:00
dependabot[bot] 42336eef4a chore: bump github.com/gohugoio/hugo from 0.129.0 to 0.131.0 (#14147)
* chore: bump github.com/gohugoio/hugo from 0.129.0 to 0.131.0

Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.129.0 to 0.131.0.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/hugoreleaser.toml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.129.0...v0.131.0)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 14:57:30 +03:00
Spike Curtis dda9c56098 fix: fix TestTailnet/Connect to wait for listener before dialing (#14148) 2024-08-05 15:45:46 +04:00
dependabot[bot] e0351124b2 chore: bump the x group with 4 updates (#14144)
* chore: bump the x group with 4 updates

Bumps the x group with 4 updates: [golang.org/x/mod](https://github.com/golang/mod), [golang.org/x/oauth2](https://github.com/golang/oauth2), [golang.org/x/sync](https://github.com/golang/sync) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/mod` from 0.19.0 to 0.20.0
- [Commits](https://github.com/golang/mod/compare/v0.19.0...v0.20.0)

Updates `golang.org/x/oauth2` from 0.21.0 to 0.22.0
- [Commits](https://github.com/golang/oauth2/compare/v0.21.0...v0.22.0)

Updates `golang.org/x/sync` from 0.7.0 to 0.8.0
- [Commits](https://github.com/golang/sync/compare/v0.7.0...v0.8.0)

Updates `golang.org/x/sys` from 0.22.0 to 0.23.0
- [Commits](https://github.com/golang/sys/compare/v0.22.0...v0.23.0)

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

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 14:26:08 +03:00
Bruno Quaresma ae40f8a82e chore(site): fix storybook font issue (#14137) 2024-08-02 20:08:32 -03:00
Bruno Quaresma a3c45861bf chore: upgrade nodejs tooling (#14134) 2024-08-02 19:56:12 -03:00
Bruno Quaresma 500a789e05 chore(site): remove proxy menu warnings about using fragment as child (#14121) 2024-08-02 18:52:47 -03:00
Jon Ayers f3ff172979 chore: remove dependency license review (#14131)
- It's bafflingly buggy and is a source of annoyance for virtually the
  whole team.
- Will revisit if we don't have alternatives to catching invalid licenses.
2024-08-02 14:14:14 -04:00
Stephen Kirby 98202b309e version bumps (#14128) 2024-08-02 12:34:50 -05:00
Kayla Washburn-Love 166467caf0 fix: don't require organization_id in body when updating a custom role (#14102) 2024-08-02 11:25:00 -06:00
Kyle Carberry e2cec454bc fix: check for io.EOF error in derpmap to resolve flake (#14125)
See: https://github.com/coder/coder/actions/runs/10218717887/job/28275465405?pr=14045
2024-08-02 17:08:47 +00:00
Kyle Carberry 6e36082b0f chore: add github.com user id association (#14045)
* chore: add github.com user id association

This will eventually be used to show an indicator in the UI
to star the repository if you've been using Coder for a while
and have not starred the repo.

If you have, we'll never show a thing!

* gen

* Fix model query

* Fix linting

* Ignore auditing github.com user id

* Add test

* Fix gh url var name

* Update migration

* Update coderd/database/dbauthz/dbauthz.go

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>

* Fix updating to when the token changes

* Fix migration

---------

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
2024-08-02 12:49:36 -04:00
Bruno Quaresma 4d4d27c509 chore(site): allow pnpm packages license (#14122) 2024-08-02 13:02:22 -03:00
Marcin Tojek 6428a766a9 feat: notify when a user account is deleted (#14113) 2024-08-02 14:56:54 +02:00
Marcin Tojek 4242fd9c1b fix: wrong notification group (#14112) 2024-08-02 10:00:27 +00:00
Muhammad Atif Ali 7619d1c49a chore: skip dogfood workflow for dependabot PRs (#14111) 2024-08-02 09:32:35 +00:00
Marcin Tojek 76ce460cc4 fix: typo in notification template (#14108) 2024-08-02 11:03:11 +02:00
dependabot[bot] 83963b9e61 chore: bump github.com/moby/moby from 26.1.0+incompatible to 27.1.1+incompatible (#14042)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-02 10:24:45 +03:00
dependabot[bot] 1d9162dc2f chore: bump google.golang.org/api from 0.188.0 to 0.190.0 (#14107)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-02 09:53:57 +03:00
Muhammad Atif Ali 894020db6a chore: skip dogfood workflow for dependabot PRs (#14106) 2024-08-02 09:39:57 +03:00
dependabot[bot] 78f1cdaebe chore: bump github.com/fergusstrange/embedded-postgres from 1.27.0 to 1.28.0 (#14043)
* chore: bump github.com/fergusstrange/embedded-postgres

Bumps [github.com/fergusstrange/embedded-postgres](https://github.com/fergusstrange/embedded-postgres) from 1.27.0 to 1.28.0.
- [Release notes](https://github.com/fergusstrange/embedded-postgres/releases)
- [Commits](https://github.com/fergusstrange/embedded-postgres/compare/v1.27.0...v1.28.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>

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-02 09:28:34 +03:00
Kayla Washburn-Love 7125b37545 feat(site): allow selecting an organization when creating a template (#14061) 2024-08-01 18:04:21 -06:00
Steven Masley a27ac30e11 chore: add sql filter to fetching audit logs (#14070)
* chore: add sql filter to fetching audit logs
* use sqlc.embed for audit logs
* fix sql query matcher
2024-08-01 12:07:19 -05:00
dependabot[bot] d23670ad53 chore: bump github.com/open-policy-agent/opa from 0.58.0 to 0.67.0 (#14040)
* chore: bump github.com/open-policy-agent/opa from 0.58.0 to 0.67.0

Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.58.0 to 0.67.0.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v0.58.0...v0.67.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
2024-08-01 19:41:44 +03:00
Alex Ivanov 6d3f7fb2a2 chore: update meticulous CI job (#14073) 2024-08-01 12:26:44 -04:00
Muhammad Atif Ali b0eaf4ca94 chore: commit update-flake as @dependabot (#14091)
Thıs is needed to bypass the dependency check job for dependabot PRs.

https://github.com/coder/coder/blob/1289937eaeac63f27f2856a4374a0fedc5cc0e58/.github/workflows/ci.yaml#L973

The username and email are fetched from a previous  dependabot commit. 
https://github.com/coder/coder/commit/1289937eaeac63f27f2856a4374a0fedc5cc0e58.patch
2024-08-01 19:24:51 +03:00
dependabot[bot] a88e1cc5f8 chore: bump github.com/go-chi/httprate from 0.9.0 to 0.12.0 (#14039)
* chore: bump github.com/go-chi/httprate from 0.9.0 to 0.12.0

Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.9.0 to 0.12.0.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.9.0...v0.12.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>

* [dependabot skip] Update Nix Flake SRI Hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
2024-08-01 19:22:23 +03:00
dependabot[bot] 1289937eae chore: bump alpine from 3.20.1 to 3.20.2 in /scripts (#14037)
Bumps alpine from 3.20.1 to 3.20.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-01 18:57:37 +03:00
Ethan 956d0cb042 fix: block creating oidc users when oidc has not been configured (#14064) 2024-08-01 13:30:10 +10:00
Muhammad Atif Ali 7a4737cf76 ci: handle retriggering ci and human authors in update-flake (#14052)
Co-authored-by: Dean Sheather <dean@deansheather.com>
2024-07-31 16:12:40 +00:00
Kyle Carberry 5d42f4aa7b fix: run update-flake with PAT to allow workflow runs (#14067)
See the comment in the code.
2024-07-31 11:43:43 -04:00
Danny Kopping c3390993dd chore: update generated files after pnpm upgrade (#14036) 2024-07-31 17:23:55 +02:00
Kayla Washburn-Love bf4b7abf14 chore(coderd): allow creating workspaces without specifying an organization (#14048) 2024-07-30 10:44:02 -06:00
Kayla Washburn-Love 56dfc64bb0 fix: don't highlight inactive org in management settings sidebar (#14059) 2024-07-30 09:33:22 -06:00
Marcin Tojek cf1fcab514 feat: notify about created user account (#14010) 2024-07-30 15:37:45 +02:00
Cian Johnston c6fb779c50 chore(scaletest): update dashboard (#14054) 2024-07-30 10:47:13 +01:00
dependabot[bot] c88ea26d7c ci: bump crate-ci/typos from 1.23.2 to 1.23.5 in the github-actions group (#14038)
Bumps the github-actions group with 1 update: [crate-ci/typos](https://github.com/crate-ci/typos).


Updates `crate-ci/typos` from 1.23.2 to 1.23.5
- [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.23.2...v1.23.5)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-07-30 11:07:30 +03:00
Kyle Carberry 893169c83b fix: duplicate tags map in mutation to resolve race (#14047)
* fix: duplicate tags map in mutation to resolve race

See: https://github.com/coder/coder/actions/runs/10149619748/job/28064952716?pr=14046

* Fix deployment values race
2024-07-30 07:37:13 +00:00
Steven Masley 3209c863b8 chore: authz 'any_org' to return if at least 1 org has perms (#14009)
* chore: authz 'any_org' to return if at least 1 org has perms

Allows checking if a user can do an action in any organization,
rather than a specific one. Allows asking general questions on the
UI to determine which elements to show.

* more strict, add comments to policy
* add unit tests and extend to /authcheck api
* make field optional
2024-07-29 19:58:48 -05:00
Kyle Carberry b7102b39af chore: add script to update flake automatically (#14046) 2024-07-29 14:29:22 -04:00
Bruno Quaresma 58b810fb0a fix: fix dormancy notifications (#14029) 2024-07-29 11:20:04 -03:00
dependabot[bot] 22143d3e80 chore: bump github.com/gohugoio/hugo from 0.128.2 to 0.129.0 (#13966)
Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.128.2 to 0.129.0.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/hugoreleaser.toml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.128.2...v0.129.0)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hugo
  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-07-27 13:49:05 +00:00
dependabot[bot] f88a48df26 chore: bump github.com/zclconf/go-cty from 1.14.4 to 1.15.0 (#13967)
Bumps [github.com/zclconf/go-cty](https://github.com/zclconf/go-cty) from 1.14.4 to 1.15.0.
- [Release notes](https://github.com/zclconf/go-cty/releases)
- [Changelog](https://github.com/zclconf/go-cty/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zclconf/go-cty/compare/v1.14.4...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/zclconf/go-cty
  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-07-27 16:43:54 +03:00
Asher 712662d014 chore: embed audit log in deployment settings page (#14023)
* Move audit page to /deployment/audit

The existing link remains but will redirect to the new URL.
If multi-org is not enabled, nothing changes.

* Redirect organization audit page to site-wide audit page

* Always wrap audit log filters when in deployment settings

Otherwise the input is teeny tiny and barely fits a few characters.
Normally we only wrap when the screen shrinks.  Again, no change
if multi-org is not enabled.

This also makes the filter menus take up available space when
wrapping (*does* apply to non-multi-org setups as well).

* Show audit log details in a tooltip

If multi-org is not enabled, details continue to be shown inline.
2024-07-26 14:12:24 -08:00
Charlie Voiselle eacdfb9f9c fix: change time format string from 15:40 to 15:04 (#14033)
* Change string format to constant value
2024-07-26 17:57:47 -04:00
Asher d8ddce8628 chore: use latest code-server in examples (#14030)
Instead, leave a comment describing how to pin the version.  This negates
the need to continually update the version in the examples.
2024-07-26 21:53:17 +00:00
Kayla Washburn-Love d68340b125 feat: manage groups from deployment settings for single-org deployments (#14016) 2024-07-26 13:10:13 -06:00
Cian Johnston 68fa34feae ci: remove ci make concurrency to fix docker image race (#14027)
This PR removes the `-j` argument to `make` when building and pushing Docker images on merge to main.
Seen here: https://github.com/coder/coder/actions/runs/10108431095/job/27954323032#step:9:119
We ran into this previously in #13769 for the release workflow, but neglected to apply the same change to the CI workflow.
2024-07-26 10:53:18 +01:00
Cian Johnston 37a859f071 chore(testutil): add testutil.GetRandomName that does not return duplicates (#14020)
Fixes #13910

Adds testutil.GetRandomName that replaces namesgenerator.GetRandomName but instead appends a monotonically increasing integer instead of a number between 1 and 10.
2024-07-26 09:44:34 +01:00
Steven Masley 96011e1b29 fix: handle legacy licenses missing feature_set field (#14025)
* fix: legacy licenses missing feature_set field
2024-07-25 22:43:08 -05:00
Raul Salamanca 5b35f65305 docs: add proxmox coder template in list of community templates (#14022) 2024-07-26 06:05:49 +03:00
Michael Brewer ce6ee9c6c6 feat(site): add jetbrains fleet icon (#14021) 2024-07-26 06:05:08 +03:00
Garrett Delfosse 6c2336b8e9 chore: shorten provisioner key (#14017) 2024-07-25 16:08:12 -05:00
Steven Masley 7ea1a4c686 chore: protect organization endpoints with license (#14001)
* chore: move multi-org endpoints into enterprise directory

All multi-organization features are gated behind "premium" licenses. Enterprise licenses can no longer
access organization CRUD.
2024-07-25 16:07:53 -05:00
Steven Masley 915f69080a chore: fix csrf error message on empty session header (#14018)
* chore: fix csrf error message on empty session header

A more detailed error message was added to catch mismatched
session tokens. This error was mistakenly applying to all CSRF
failures.
2024-07-25 15:58:23 -05:00
Garrett Delfosse 2279441517 feat: add --key flag to provisionerd start (#14002) 2024-07-25 15:26:26 -04:00
Steven Masley c082868f55 chore: indicate premium vs enterprise on license page (#14008)
* chore: indicate premium vs enterprise on license page

Premium licenses should say "premium" instead of "enterprise"
2024-07-25 14:04:32 -05:00
Michael Smith 9f3c1c7367 fix: resolve text overflow issues for workspace empty state (#14015) 2024-07-25 18:19:57 +00:00
Kira Pilot 4eb67ad98a Revert "feat: implement multi-org template gallery (#13784)" (#14013)
This reverts commit 554c4ab1eb.
2024-07-25 13:09:04 -04:00
Kayla Washburn-Love 615bb94ec4 feat(site): embed users page in management settings (#14006) 2024-07-25 10:50:07 -06:00
Garrett Delfosse 6161d173d3 feat: add tags to provisioner keys api (#13989) 2024-07-25 15:20:45 +00:00
Garrett Delfosse ca83017dc1 feat: accept provisioner keys for provisioner auth (#13972) 2024-07-25 10:22:55 -04:00
Marcin Tojek d488853393 fix: notifications: use username in workspace URLs (#14011) 2024-07-25 12:02:24 +02:00
Muhammad Atif Ali 88bc491778 chore: add stable version info to repository-dispatch event (#13997)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-07-25 11:03:01 +03:00
Asher e8b3db8c7a feat: add organizations filter to audit table (#13978)
* Ignore organization ID in member and role audit logs

Since the organization will never change in any resources, 
and the org is already on the top-level of the response. 

* Add organization details and filter to audit table

These only display if the multi-org experiment is enabled.

This also includes a modification to customize the width 
of the filters since with four things get a bit squishy.

* Add more audit mocks

To test different org names and no org.
2024-07-24 14:28:23 -08:00
Steven Masley 4f01372179 feat: implement disabling oidc issuer checks (#13991)
* use DANGEROUS prefix and drop a warning log
2024-07-24 16:45:47 -05:00
Muhammad Atif Ali 652827f0e8 docs: add preview image to release schedule (#13948) 2024-07-24 23:20:52 +03:00
Kayla Washburn-Love 38b573857b feat(site): edit organization member roles (#13977) 2024-07-24 11:36:45 -06:00
Steven Masley 15fda232b7 feat: implement premium vs enterprise licenses (#13907)
* feat: implement premium vs enterprise licenses

Implement different sets of licensed features.
2024-07-24 12:07:59 -05:00
Bruno Quaresma 0d9615b4fd feat(coderd): notify when workspace is marked as dormant (#13868) 2024-07-24 13:38:21 -03:00
Garrett Delfosse ccb5b4df80 chore: move provisioner keys commands into slim build (#13993) 2024-07-24 11:30:50 -04:00
Marcin Tojek b3a3671c6a fix: use static port number for prometheus test (#14000) 2024-07-24 12:54:36 +00:00
Marcin Tojek dac14fe581 test: skip TestProvisionerDaemon_PrometheusEnabled (#13996) 2024-07-24 09:28:53 +02:00
Ethan 7028ff79c3 feat(codersdk): export template variable parser (#13984) 2024-07-24 14:11:29 +10:00
Stephen Kirby 177c7d3c68 updated version for patches 2.12.5 and 2.13.2 (#13995) 2024-07-23 15:36:14 -05:00
Kayla Washburn-Love 0d453437de fix(site): select default organization on /organizations page (#13992) 2024-07-23 12:35:38 -06:00
Steven Masley a61c09e4dc fix: use correct group url in multi-org experiment (#13986)
* fix: use correct group url in multi-org experiment

When not using the experiment, default to the "default" org.
Assuming groups are all in the primary org

---------

Co-authored-by: McKayla Washburn <mckayla@hey.com>
2024-07-23 13:17:21 -05:00
Jon Ayers 3a614f1602 fix: random typos in offline docs documentation (#13979) 2024-07-23 11:48:58 -05:00
Steven Masley ecc356f5a9 chore: generate rbac resource types to typescript (#13975)
* chore: generate rbac resource types to typescript

The existing typesGenerated.ts cannot support this as the generator
only inspects the types, not the values. So traversing the value AST
would have to be added. The rbac gen is already used for the sdk,
this extends it to the typescript
2024-07-23 10:07:52 -05:00
Danny Kopping b817c863ef fix: webhook endpoint YAML attribute (#13983)
Signed-off-by: Danny Kopping <danny@coder.com>
2024-07-23 14:59:27 +00:00
Garrett Delfosse 0a07c7e554 feat: get org scoped provisioners (#13953) 2024-07-23 14:56:46 +00:00
Danny Kopping 695afb80e6 fix: address TestPendingUpdatesMetric flake 2024-07-23 14:39:57 +00:00
Marcin Tojek 966c888e9d fix: test: no parallel when starting Prometheus endpoint (#13981)
* fix: test: no parallel when starting Prometheus endpoint

* fix
2024-07-23 11:31:27 +01:00
Mathias Fredriksson 5a4dbcfc02 chore(scripts): fix cherry-pick check in check_commit_metadata.sh (#13980) 2024-07-23 06:49:29 +00:00
Asher a8e6e89f65 feat: add organization details to audit log response (#13961)
* Allow creating test audits with nil org

Not all audit entries have organization IDs, so this will allow us to
test those cases.

* Add organization details to audit log queries

* Add organization to audit log response

This replaces the old ID.  This is a breaking change but organizations
were not being used before.
2024-07-22 13:28:44 -08:00
Dean Sheather 38c7dcda94 fix: avoid vscodessh exit when server restarts (#13970)
Mitigates an issue where vscodessh would restart when the control plane
restarts, causing the entire SSH session to be reestablished.
2024-07-22 17:19:44 +00:00
Dean Sheather d2b035312e chore: fix parse typo for network telemetry (#13971) 2024-07-22 17:14:37 +00:00
Kayla Washburn-Love 0a71c34d46 feat: create and modify organization groups (#13887) 2024-07-22 09:47:14 -06:00
Steven Masley dd99457a04 chore: although unfortunate, it is possible for a user to be in no orgs (#13956) 2024-07-22 10:39:50 -05:00
Cian Johnston 005254d64a chore(examples): update sample devcontainer templates (#13796)
Updates docker and kubernetes devcontainer templates

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-07-22 14:10:02 +01:00
Stephen Kirby 3c2c5ab7fc chore(docs): add missing author to support bundle guide (#13918)
* fixed missing author

* make fmt

Signed-off-by: Cian Johnston <cian@coder.com>

---------

Signed-off-by: Cian Johnston <cian@coder.com>
Co-authored-by: Cian Johnston <cian@coder.com>
2024-07-22 09:09:31 +01:00
Nano 40b70dbdb0 docs: update caddy config example & guide (#13964) 2024-07-22 10:47:41 +03:00
Muhammad Atif Ali 88d2dbd994 docs: replace coder_git_auth with coder_external_auth (#13936) 2024-07-20 07:43:09 +03:00
Steven Masley 03c5d42233 chore: keep active users active in scim (#13955)
* chore: scim should keep active users active
* chore: add a unit test to excercise dormancy bug
* audit log should not be dropped when there is no change
* add ability to cancel audit log
2024-07-19 16:30:02 -05:00
Steven Masley 49d6d0f41b chore: add built in organization roles to match site (#13938)
* chore: add built in organization roles to match site

Added org user admin, org template admin, and org auditor
2024-07-19 15:44:18 -05:00
Muhammad Atif Ali 8beb0b131f chore: update flake.nix to handle aarch64 linux (#13930) 2024-07-19 22:15:50 +03:00
Stephen Kirby bac9b38e05 autoversion 2.12.4 and 2.13.1 (#13951) 2024-07-19 13:26:13 -05:00
Alex Ivanov dba23872eb chore: only add Meticulous recorder when running in dev mode (#13950) 2024-07-19 12:47:38 -05:00
Jaayden Halko 554c4ab1eb feat: implement multi-org template gallery (#13784)
* feat: initial changes for multi-org templates page

* feat: add TemplateCard component

* feat: add component stories

* chore: update template query naming

* fix: fix formatting

* feat: template card interaction and navigation

* fix: copy updates

* chore: update TemplateFilter type to include FilterQuery

* chore: update typesGenerated.ts

* feat: update template filter api logic

* fix: fix format

* fix: get activeOrg

* fix: add format annotation

* chore: use organization display name

* feat: client side org filtering

* fix: use org display name

* fix: add ExactName

* feat: show orgs filter only if more than 1 org

* chore: updates for PR review

* fix: fix format

* chore: add story for multi org

* fix: aggregate templates by organization id

* fix: fix format

* fix: check org count

* fix: update ExactName type
2024-07-19 10:33:08 -04:00
Marcin Tojek 40609c26e9 fix: test: do not block Prometheus port (#13945) 2024-07-19 16:29:10 +02:00
Danny Kopping c88e4162d8 fix: TestPendingUpdatesMetric flake (#13944)
Signed-off-by: Danny Kopping <danny@coder.com>
2024-07-19 12:54:15 +02:00
Danny Kopping 492ab1cc7e chore: add webhook tests for notification subsystem (#13942) 2024-07-19 12:03:29 +02:00
Danny Kopping 943ea7c52a feat: add SMTP auth & TLS support (#13902) 2024-07-19 09:22:15 +02:00
Jon Ayers 8d4bccc612 feat: add meticulous recorder (#13886) 2024-07-18 20:15:07 -05:00
Kayla Washburn-Love 4dcbd7179f fix: hardcode default organization id in DashboardProvider (#13940) 2024-07-18 16:55:38 -06:00
Steven Masley aa6e6e3d58 chore: implement fetch all organizations endpoint (#13941)
* chore: implement fetch all organizations endpoint
* update ui to use list all orgs
2024-07-18 17:28:36 -05:00
Garrett Delfosse 6f20a64f9d chore: add multi-org flag to develop.sh (#13923) 2024-07-18 14:43:07 -05:00
Garrett Delfosse f975701b34 feat: add provisioner key cli commands (#13875) 2024-07-18 14:44:20 -04:00
Marcin Tojek 91cbe679c0 chore: move notiffake to testutil (#13933) 2024-07-18 13:36:02 +00:00
Marcin Tojek fbd1d7f9a7 feat: notify on successful autoupdate (#13903) 2024-07-18 15:19:12 +02:00
Steven Masley 44924cd8d8 chore: add updated_at to codersdk users (#13928)
* chore: add updated_at to codersdk users
2024-07-17 17:59:42 -05:00
Kyle Carberry 3e1fae7d3d chore: add Star the Repo to support links (#13924) 2024-07-17 15:39:03 -04:00
Kayla Washburn-Love 80cbffe843 chore: remove organizationIds from AuthProvider (#13917) 2024-07-17 09:53:40 -06:00
Spike Curtis f21f2dce57 fix: fix heartbeat select to prevent leak (#13909)
fixes #13816
2024-07-16 23:38:07 +04:00
Spike Curtis 70c5c47efd fix: stop blocking fake Agent API channel writes after context expires (#13908) 2024-07-16 23:22:13 +04:00
Spike Curtis 1f24aceea2 fix: change audit descriptions to indicate unsuccessful attempts (#13897) 2024-07-16 22:47:32 +04:00
Kayla Washburn-Love a3f40d5ef8 feat: add members settings page for organizations (#13817) 2024-07-16 12:25:36 -06:00
Garrett Delfosse b697c6939a chore: add provisioner key crud apis (#13857) 2024-07-16 13:27:12 -04:00
Marcin Tojek a5e4bf38fe feat: notify owner about failed autobuild (#13891) 2024-07-16 10:48:17 +02:00
Cian Johnston 36454aa81b fix(dogfood/Dockerfile): create /etc/suoders.d/nopasswd instead of COPY (#13900) 2024-07-15 23:19:23 +01:00
Muhammad Atif Ali ab59460e2c chore: bump terraform to v1.9.2 (#13899) 2024-07-15 21:25:42 +00:00
Cian Johnston 17626b8dd1 Revert "fix(dogfood/Dockerfile): change ownership of /etc/sudoers.d to root (#13793)" (#13898)
This reverts commit da8911426b.
2024-07-15 21:08:48 +00:00
Colin Adler 7a34a70cb8 chore: upgrade terraform to 1.9.2 (#13895) 2024-07-15 13:27:08 -05:00
dependabot[bot] d6e2801478 chore: bump github.com/adrg/xdg from 0.4.0 to 0.5.0 (#13892)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 19:26:25 +03:00
dependabot[bot] 0a73ae1036 chore: bump google.golang.org/api from 0.187.0 to 0.188.0 (#13894)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 19:25:57 +03:00
dependabot[bot] 6058bcdad8 chore: bump cloud.google.com/go/compute/metadata from 0.4.0 to 0.5.0 (#13893)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 18:51:55 +03:00
dependabot[bot] bece042fa8 chore: bump @testing-library/jest-dom from 6.1.2 to 6.4.6 in /site (#13732)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.1.2 to 6.4.6.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.1.2...v6.4.6)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  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-07-15 09:41:57 -06:00
dependabot[bot] aaf295badf ci: bump the github-actions group with 2 updates (#13890)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 15:56:12 +03:00
dependabot[bot] b00f746cac chore: bump monaco-editor from 0.44.0 to 0.50.0 in /site (#13835)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-07-14 15:48:29 +03:00
Steven Masley 9cbe2b27e7 chore: create workspaces and templates for multiple orgs (#13866)
* chore: creating workspaces and templates to work with orgs
* handle wrong org selected
* create org member in coderdtest helper
2024-07-12 15:47:28 -05:00
Steven Masley e4aef272fa chore: add example prompt command for multiple prompt bug (#13885)
Prompt message is not erased after the prompt ends
2024-07-12 11:59:13 -05:00
Steven Masley c6b7588933 chore: add organization id to provisioner sdk type (#13883)
* chore: add organization id to provisioner sdk type
2024-07-12 10:56:34 -05:00
Danny Kopping 1691768fb9 chore: use store enqueuer with external provisioners (#13881) 2024-07-12 13:51:13 +02:00
Kayla Washburn-Love de2585b0b6 chore: use rw.WriteHeader to write responses without bodies (#13870) 2024-07-11 13:38:33 -06:00
Mathias Fredriksson fd10ea1dcc chore(scripts): add script to update list of experiments after release (#13872)
Fixes #13119
2024-07-11 21:45:50 +03:00
Steven Masley 687d9538de chore: provisioner acquirer to respect organization ID of jobs (#13874)
* test: add unit test to verify creation of templates in multiple orgs
* chore: provisioner acquirer to respect organization ID of jobs

Prior to this the wrong provisioner was awakened on any new job
posting.

* add comment and stricter check
2024-07-11 11:26:47 -05:00
Marcin Tojek bee913ac45 feat(cli): pause notifications (#13873) 2024-07-11 15:22:20 +02:00
Ethan f36b816391 chore: add coder version to network telemetry events (#13871) 2024-07-11 20:46:37 +10:00
Danny Kopping b2dab3308d feat: implement observability of notifications subsystem (#13799) 2024-07-11 10:57:49 +02:00
Colin Adler a6d66cc7ec chore: ensure correct version of golangci-lint is run in ci (#13869) 2024-07-10 14:50:38 -05:00
Eric Paulsen 90a6025e18 fix-sa-docs (#13724) 2024-07-10 18:40:45 +00:00
Muhammad Atif Ali 0787de88a9 chore: update documentation links to the new format (#13797) 2024-07-10 21:31:37 +03:00
Eric Paulsen 2a297b073a docs: fix vs code web module reference (#13785) 2024-07-10 21:29:40 +03:00
Colin Adler 2238593f57 chore: update pnpm to v9 (#13843)
* chore: update pnpm to v9

* pin golangci-lint and shfmt
2024-07-10 13:13:19 -05:00
Steven Masley a588ec5b21 chore: assign user to multiple orgs in coderdtest user create (#13867)
* chore: coderdtest assign user to multiple orgs on create
2024-07-10 12:38:48 -05:00
Steven Masley 7bb3e0db4a chore: return organization's display name and icon in templates (#13858)
* chore: templates return organization display name and icon
* templates api response includes organization display name and icon
2024-07-10 10:06:49 -05:00
Marcin Tojek bf392ffea4 feat: add killswitch for notifications (#13794) 2024-07-10 16:15:06 +02:00
Danny Kopping 542fff7df0 chore: improve notifications tests (#13863) 2024-07-10 15:25:23 +02:00
Mathias Fredriksson c8484b4fc8 fix(cli): follow logs only when agent is starting (#13864) 2024-07-10 15:39:43 +03:00
Marcin Tojek 70046ea08d fix: missing nolint comment (#13862) 2024-07-10 09:55:30 +00:00
Ethan e8db21c89e chore: add additional network telemetry stats & events (#13800) 2024-07-10 14:14:35 +10:00
dependabot[bot] 38035da846 chore: bump github.com/google/nftables (#13859) 2024-07-09 19:02:16 -04:00
Jon Ayers 464e7979c4 docs: remove mention of built-in remote desktop on the roadmap (#13459) 2024-07-09 16:29:44 -05:00
dependabot[bot] e00a80e029 chore: bump github.com/gofrs/flock from 0.8.1 to 0.12.0 (#13782)
Bumps [github.com/gofrs/flock](https://github.com/gofrs/flock) from 0.8.1 to 0.12.0.
- [Release notes](https://github.com/gofrs/flock/releases)
- [Commits](https://github.com/gofrs/flock/compare/v0.8.1...v0.12.0)

---
updated-dependencies:
- dependency-name: github.com/gofrs/flock
  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-07-09 21:16:05 +00:00
dependabot[bot] d4f0a22ac6 chore: bump axios from 1.6.0 to 1.7.2 in /site (#13697)
Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.0...v1.7.2)

---
updated-dependencies:
- dependency-name: axios
  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-07-09 21:14:16 +00:00
dependabot[bot] f6cd002542 chore: bump yup from 1.3.2 to 1.4.0 in /site (#13715)
Bumps [yup](https://github.com/jquense/yup) from 1.3.2 to 1.4.0.
- [Release notes](https://github.com/jquense/yup/releases)
- [Changelog](https://github.com/jquense/yup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jquense/yup/compare/v1.3.2...v1.4.0)

---
updated-dependencies:
- dependency-name: yup
  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-07-09 21:12:56 +00:00
dependabot[bot] d209c5ff99 chore: bump github.com/bramvdbogaerde/go-scp from 1.4.0 to 1.5.0 (#13806)
Bumps [github.com/bramvdbogaerde/go-scp](https://github.com/bramvdbogaerde/go-scp) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/bramvdbogaerde/go-scp/releases)
- [Commits](https://github.com/bramvdbogaerde/go-scp/compare/v1.4.0...v1.5.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-07-09 21:10:55 +00:00
dependabot[bot] 7574a2d3ab chore: bump github.com/gohugoio/hugo from 0.126.1 to 0.128.2 (#13811)
Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.126.1 to 0.128.2.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/hugoreleaser.toml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.126.1...v0.128.2)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hugo
  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-07-09 16:04:51 -05:00
dependabot[bot] de1da93d04 chore: bump github.com/bgentry/speakeasy (#13729)
Bumps [github.com/bgentry/speakeasy](https://github.com/bgentry/speakeasy) from 0.1.1-0.20220910012023-760eaf8b6816 to 0.2.0.
- [Release notes](https://github.com/bgentry/speakeasy/releases)
- [Commits](https://github.com/bgentry/speakeasy/commits/v0.2.0)

---
updated-dependencies:
- dependency-name: github.com/bgentry/speakeasy
  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-07-09 16:59:06 -04:00
dependabot[bot] 03a8cc7d4e chore: bump cloud.google.com/go/compute/metadata from 0.3.0 to 0.4.0 (#13808)
Bumps [cloud.google.com/go/compute/metadata](https://github.com/googleapis/google-cloud-go) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/compute/metadata
  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-07-09 14:35:30 -05:00
Colin Adler d50ffa78f6 fix: exit reset password request before passwords are compared (#13856) 2024-07-09 14:28:39 -05:00
dependabot[bot] 3894ae17a7 chore: bump the mui group across 1 directory with 5 updates (#13829)
Bumps the mui group with 5 updates in the /site directory:

| Package | From | To |
| --- | --- | --- |
| [@mui/icons-material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material) | `5.15.20` | `5.16.0` |
| [@mui/material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-material) | `5.15.21` | `5.16.0` |
| [@mui/system](https://github.com/mui/material-ui/tree/HEAD/packages/mui-system) | `5.15.20` | `5.16.0` |
| [@mui/utils](https://github.com/mui/material-ui/tree/HEAD/packages/mui-utils) | `5.15.20` | `5.16.0` |
| [@mui/x-tree-view](https://github.com/mui/mui-x/tree/HEAD/packages/x-tree-view) | `7.8.0` | `7.9.0` |



Updates `@mui/icons-material` from 5.15.20 to 5.16.0
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.0/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.0/packages/mui-icons-material)

Updates `@mui/material` from 5.15.21 to 5.16.0
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.0/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.0/packages/mui-material)

Updates `@mui/system` from 5.15.20 to 5.16.0
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.0/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.0/packages/mui-system)

Updates `@mui/utils` from 5.15.20 to 5.16.0
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v5.16.0/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v5.16.0/packages/mui-utils)

Updates `@mui/x-tree-view` from 7.8.0 to 7.9.0
- [Release notes](https://github.com/mui/mui-x/releases)
- [Changelog](https://github.com/mui/mui-x/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui/mui-x/commits/v7.9.0/packages/x-tree-view)

---
updated-dependencies:
- dependency-name: "@mui/icons-material"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mui
- dependency-name: "@mui/material"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mui
- dependency-name: "@mui/system"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mui
- dependency-name: "@mui/utils"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mui
- dependency-name: "@mui/x-tree-view"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: mui
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 11:12:42 -08:00
Mathias Fredriksson 35a808f089 fix(coderd/agentapi): set ReadyAt for start timeout (#13846) 2024-07-09 18:55:16 +00:00
Jyotirmoy Bandyopadhayaya b07e3069dd feat: added whomai cmd to coder cli (#13814)
* feat: added whomai cmd to coder cli
* refactor: update Coder CLI's whoami command to use client URL instead of deployment config
* feat(cli): add unit tests for the whoami command
* chore(docs): add coder command to fetch authenticated user info
* chore(doc): update help desc
2024-07-09 13:23:11 -05:00
Bruno Quaresma 01b30eaa32 fix(site): enable dormant workspace to be deleted (#13850) 2024-07-09 14:55:46 -03:00
Colin Adler af001773db fix!: remove TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA cipher by default (#13837)
This cipher is included by default in Go as a fallback, but is marked as
an insecure cipher. This removes the 3des cipher by default.

Before:
```
$ nmap --script ssl-enum-ciphers -p 443 xxxxxxx
Starting Nmap 7.94 ( https://nmap.org ) at 2024-07-08 14:16 CDT
Nmap scan report for xxxxx (xxx.xxx.xxx.xxx)
Host is up (0.038s latency).
rDNS record for xxx.xxx.xxx.xxx: xxx.xxx.xxx.xxx.bc.googleusercontent.com

PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (secp256r1) - C
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|   TLSv1.3:
|     ciphers:
|       TLS_AKE_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
|       TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
|       TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
|     cipher preference: server
|_  least strength: C
```

After:
```
$ nmap --script ssl-enum-ciphers -p 443 xxxxxxx
Starting Nmap 7.94 ( https://nmap.org ) at 2024-07-08 15:04 CDT
Nmap scan report for xxxxx (xxx.xxx.xxx.xxx)
Host is up (0.039s latency).
rDNS record for xxx.xxx.xxx.xxx: xxx.xxx.xxx.xxx.bc.googleusercontent.com

PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|     compressors:
|       NULL
|     cipher preference: client
|   TLSv1.3:
|     ciphers:
|       TLS_AKE_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
|       TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
|       TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
|     cipher preference: server
|_  least strength: A
```

* fixup! fix!(cli): remove `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA` cipher by default

* fixup! fix!(cli): remove `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA` cipher by default
2024-07-09 17:18:27 +00:00
Bruno Quaresma 879c61ce23 feat(site): display tooltip in bars for app usage chart (#13854) 2024-07-09 14:02:45 -03:00
Bruno Quaresma 6bf7e5af91 feat(site): support match option for auto create workspace flow (#13836) 2024-07-09 16:14:08 +00:00
Steven Masley 8c33b028d2 chore: include all templates in cli template list (#13841)
* chore: cli template list includes all templates

Shows all accessible templates from all organizations
2024-07-09 11:04:16 -05:00
Steven Masley f9272046d5 chore: remove references to restarting/stopping in update workspace language (#13852)
* chore: remove references to restarting/stopping in update workspace language
* reword updating workspaces to remove the word "restart"
* fix batch wording
2024-07-09 11:02:13 -05:00
Eric Paulsen 266913a357 fix: remove templates plan docs (#13824)
* fix: remove templates plan docs

* make gen

* make update-golden-files
2024-07-09 14:27:34 +00:00
Muhammad Atif Ali c62512a8bb chore: use base64 encoded kubeconfig for pr deployments (#13851) 2024-07-09 14:24:43 +00:00
Muhammad Atif Ali a123badccc chore: use base64 encoded kubeconfig for pr deployments (#13849) 2024-07-09 13:44:59 +00:00
Marcin Tojek 54898033b1 fix: dbpurge: disable parallel tests (#13848) 2024-07-09 15:10:57 +02:00
dependabot[bot] c1440ac4f0 chore: bump github.com/coreos/go-oidc/v3 from 3.10.0 to 3.11.0 (#13827)
Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.10.0 to 3.11.0.
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.10.0...v3.11.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-oidc/v3
  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-07-09 13:09:04 +00:00
dependabot[bot] 65e1d0af4b ci: bump crate-ci/typos from 1.22.9 to 1.23.1 in the github-actions group (#13803)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-07-09 15:45:09 +03:00
Mathias Fredriksson ac6db5edf9 feat(cli): show information about --wait=no for ssh (#13847)
Fixes #11923
2024-07-09 14:32:52 +03:00
Mathias Fredriksson 54055dc4cc fix(cli): prevent asynchronous print of version mismatch in config-ssh (#13845) 2024-07-09 14:32:08 +03:00
dependabot[bot] 407d263cd2 chore: bump google.golang.org/api from 0.182.0 to 0.187.0 (#13828)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 07:59:58 +00:00
dependabot[bot] 0c423f07a7 chore: bump semver and @types/semver in /site (#13834)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 10:49:43 +03:00
Mathias Fredriksson 978364bd3e fix(cli): do not overwrite repeated SSH options in config-ssh (#13819)
Fixes #11593
2024-07-09 09:44:56 +03:00
dependabot[bot] 5cdfc08422 chore: bump google.golang.org/grpc from 1.64.0 to 1.65.0 (#13826)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.65.0)

---
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-07-09 06:25:53 +00:00
dependabot[bot] 1f05a4a05e chore: bump github.com/go-chi/chi/v5 from 5.0.10 to 5.1.0 (#13730)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.10 to 5.1.0.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.10...v5.1.0)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  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-07-09 01:19:18 -05:00
dependabot[bot] 996863936a chore: bump the x group with 7 updates (#13825)
Bumps the x group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.24.0` | `0.25.0` |
| [golang.org/x/mod](https://github.com/golang/mod) | `0.18.0` | `0.19.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.26.0` | `0.27.0` |
| [golang.org/x/oauth2](https://github.com/golang/oauth2) | `0.20.0` | `0.21.0` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.21.0` | `0.22.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.21.0` | `0.22.0` |
| [golang.org/x/tools](https://github.com/golang/tools) | `0.22.0` | `0.23.0` |


Updates `golang.org/x/crypto` from 0.24.0 to 0.25.0
- [Commits](https://github.com/golang/crypto/compare/v0.24.0...v0.25.0)

Updates `golang.org/x/mod` from 0.18.0 to 0.19.0
- [Commits](https://github.com/golang/mod/compare/v0.18.0...v0.19.0)

Updates `golang.org/x/net` from 0.26.0 to 0.27.0
- [Commits](https://github.com/golang/net/compare/v0.26.0...v0.27.0)

Updates `golang.org/x/oauth2` from 0.20.0 to 0.21.0
- [Commits](https://github.com/golang/oauth2/compare/v0.20.0...v0.21.0)

Updates `golang.org/x/sys` from 0.21.0 to 0.22.0
- [Commits](https://github.com/golang/sys/compare/v0.21.0...v0.22.0)

Updates `golang.org/x/term` from 0.21.0 to 0.22.0
- [Commits](https://github.com/golang/term/compare/v0.21.0...v0.22.0)

Updates `golang.org/x/tools` from 0.22.0 to 0.23.0
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: x
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 01:13:37 -05:00
dependabot[bot] 3085c4cf5b chore: bump @types/lodash from 4.14.196 to 4.17.6 in /site (#13704)
Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.196 to 4.17.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  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-07-09 09:11:00 +03:00
dependabot[bot] 83a177e0c7 chore: bump ssh2 and @types/ssh2 in /site (#13710)
Bumps [ssh2](https://github.com/mscdex/ssh2) and [@types/ssh2](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ssh2). These dependencies needed to be updated together.

Updates `ssh2` from 1.14.0 to 1.15.0
- [Commits](https://github.com/mscdex/ssh2/compare/v1.14.0...v1.15.0)

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

---
updated-dependencies:
- dependency-name: ssh2
  dependency-type: direct:development
  update-type: version-update:semver-minor
- dependency-name: "@types/ssh2"
  dependency-type: direct:development
  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-07-09 09:10:28 +03:00
Colin Adler b40d543cb5 chore(offlinedocs): update braces to v3.0.3 (#13842) 2024-07-09 01:01:49 -05:00
dependabot[bot] eafa8f5cb2 chore: bump the vite group across 1 directory with 3 updates (#13833)
Bumps the vite group with 3 updates in the /site directory: [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react), [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [vite-plugin-checker](https://github.com/fi3ework/vite-plugin-checker).


Updates `@vitejs/plugin-react` from 4.1.0 to 4.3.1
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.1/packages/plugin-react)

Updates `vite` from 4.5.3 to 5.3.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.3.3/packages/vite)

Updates `vite-plugin-checker` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/fi3ework/vite-plugin-checker/releases)
- [Changelog](https://github.com/fi3ework/vite-plugin-checker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fi3ework/vite-plugin-checker/compare/vite-plugin-checker@0.6.0...vite-plugin-checker@0.7.1)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: vite
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: vite
- dependency-name: vite-plugin-checker
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: vite
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 18:25:19 -04:00
Muhammad Atif Ali 05fdb9c1f7 chore: group dependencies (#13798) 2024-07-08 23:11:44 +03:00
Stephen Kirby 79b5d20cd2 typo (#13823) 2024-07-08 15:08:56 -05:00
Colin Adler f9ca9c7a22 chore: upgrade Go to 1.22.5 (#13820)
* chore: upgrade Go to 1.22.5

* fixup! chore: upgrade Go to 1.22.5
2024-07-08 19:42:55 +00:00
Steven Masley 44cb400c8e chore: include host and port in oidc test logs (#13818)
* chore: include host and port in oidc test logs

Log fake IDP's log for debugging port conflicts between tests
2024-07-08 10:24:41 -05:00
Steven Masley d9bdef915d chore: fix typo in oidctest package (#13815) 2024-07-08 14:35:06 +00:00
Danny Kopping bdd2caf95d feat: implement thin vertical slice of system-generated notifications (#13537) 2024-07-08 15:38:50 +02:00
Marcin Tojek 10aa32ca08 chore: refactor AgentHasNotConnectedSinceWeek_LogsExpired (#13802) 2024-07-08 13:52:56 +02:00
Muhammad Atif Ali fecc5b3027 docs: update release schedule (#13795) 2024-07-06 12:17:11 +03:00
Cian Johnston da8911426b fix(dogfood/Dockerfile): change ownership of /etc/sudoers.d to root (#13793) 2024-07-05 12:42:14 +01:00
Marcin Tojek 7c41f957de feat: autostop workspaces owned by suspended users (#13790) 2024-07-04 13:35:41 +00:00
Cian Johnston c2d44d16a3 feat(codersdk/agentsdk): export LogDest interface (#13792)
Signed-off-by: Cian Johnston <cian@coder.com>
2024-07-04 14:04:43 +01:00
Ferran Basora dd80958efb chore: update troubleshooting documentation about dir setting (#13681) 2024-07-03 22:24:49 +03:00
Steven Masley ccf34901bc chore: add templates search query to a filter (#13772)
* chore: add templates search query to a filter
2024-07-03 13:42:23 -05:00
Cian Johnston 8778aa0f71 chore(deps): update go-playground/validator and remove replace directive (#13779)
We had a replace directive in place due to a PR we were waiting to have
merged in go-playground/validator. This was since merged in v10.22.0.

Signed-off-by: Cian Johnston <cian@coder.com>
2024-07-03 18:14:27 +01:00
Cian Johnston ea675897fd fix(dogfood/Dockerfile): revert add explicit --chown to COPY directive (#13569) (#13781)
* Revert "fix(dogfood/Dockerfile): add explicit --chown to COPY directive (#13569)"

This reverts commit c587af7c0e.

* add a bogus comment to ensure hashes change
2024-07-03 17:42:18 +01:00
Michael Smith 940afa1ab1 fix: let workspace pages download partial logs for unhealthy workspaces (#13761)
* fix: get basic fix in for  preventing download logs from blowing up UI

* fix: make sure blob units can't go out of bounds

* fix: make sure timeout is cleared on component unmount

* fix: reduce risk of shared cache state breaking useAgentLogs

* fix: allow partial downloading of logs

* fix: make sure useMemo cache is used properly

* wip: commit current progress on updated logs functionality

* docs: rewrite comment for clarity

* refactor: clean up current code

* fix: update styles for unavailable logs

* fix: resolve linter violations

* fix: update type signature of getErrorDetail

* fix: revert log/enabled logic for useAgentLogs

* fix: remove memoization from DownloadLogsDialog

* fix: update name of timeout state

* refactor: make log web sockets logic more clear

* docs: reword comment for clarity

* fix: commit current style update progress

* fix: finish style updates
2024-07-03 10:17:54 -04:00
Marcin Tojek 07d41716ad fix(provisioner): handle multiple agents, apps, scripts and envs (#13741) 2024-07-03 14:55:28 +02:00
Spike Curtis f6639b788f feat: add ConnectRPC variants for older Agent API versions (#13778) 2024-07-03 16:11:05 +04:00
Spike Curtis e5268e4551 chore: spin clock library out to coder/quartz repo (#13777)
Code that was in `/clock` has been moved to github.com/coder/quartz.  This PR refactors our use of the clock library to point to the external Quartz repo.
2024-07-03 15:02:54 +04:00
Ethan a110d18275 chore: add DRPC tailnet & cli network telemetry (#13687) 2024-07-03 15:23:46 +10:00
Kayla Washburn-Love bfbf634bec chore: update mui (#13747) 2024-07-02 15:19:01 -06:00
Eric Paulsen 80a2a5d6a8 docs: clarify envbox version pinning (#13773) 2024-07-02 20:36:08 +00:00
Stephen Kirby e40cc9314c docs: bump mainline version to v2.13.0 (#13766) 2024-07-02 15:24:26 -05:00
Mathias Fredriksson a114288ef2 ci: remove release make concurrency to fix docker image race (#13769) 2024-07-02 17:49:18 +00:00
Cian Johnston 5ea5db29e9 ci: use postgres version 13 to test migrations (#13767) 2024-07-02 17:03:21 +00:00
Bruno Quaresma 9ee53e5b4e chore(site): refactor filter component to be more extendable (#13688) 2024-07-02 13:15:13 -03:00
Michael Smith 21a923a7a0 chore: add SVG desktop icon (#13765)
* chore: add SVG desktop icon

* fix: add desktop icon to to icons.json
2024-07-02 16:00:16 +00:00
Kayla Washburn-Love b1e7498e77 chore: update xterm (#13752) 2024-07-02 09:29:34 -06:00
Danny Kopping 98c09bf5d2 fix: add policy.go as dependency of Makefile rbac target (#13757) 2024-07-02 14:29:00 +00:00
Steven Masley bde9fd58ea chore: add organization name to workspaces (#13755)
* chore: add organization name to workspaces
2024-07-02 09:25:05 -05:00
Steven Masley 128674918b chore: include organization name when fetching templates (#13751)
* chore: include organization name when fetching templates
* chore: rename template_with_user to template_with_names
2024-07-02 09:08:30 -05:00
Muhammad Atif Ali b87c12ba92 chore(dogfood): fix duplicate security repository entry (#13758) 2024-07-02 14:09:45 +03:00
Steven Masley abc0ff9689 chore: remove database import from cli (#13756)
Cli was using a utility function from a database package.
2024-07-01 21:58:23 +00:00
Kayla Washburn-Love b1ec4630f2 chore: update react dependencies (#13749) 2024-07-01 15:55:27 -06:00
Steven Masley 5bf46f360a chore: remove org context switcher in the cli (#13674)
* chore: remove org context switcher in the cli
2024-07-01 16:04:45 -05:00
Kayla Washburn-Love 4a0fd7466c chore: update emoji-mart data (#13746) 2024-07-01 14:40:15 -06:00
Jaayden Halko f26f123391 feat: route groups by name instead of id (#13692)
* feat: route groups by name instead of id

* fix: update group navigation when name changes

* fix: update isLoading and error checking

* fix: fix format

* fix: update isLoading and error

* fix: cleanup
2024-07-01 15:48:14 -04:00
Kayla Washburn-Love 41e1383640 chore: update caniuse (#13745) 2024-07-01 13:11:04 -06:00
Steven Masley 10c2817f4d chore: swagger docs omit brower based credentials, rely on swagger auth (#13742)
* chore: swagger docs omit brower based credentials, rely on swagger auth

Swagger has an "Authorize" button which should be the only
authentication being used in the api requests
2024-07-01 13:44:35 -05:00
Kayla Washburn-Love cd069faf01 chore: update storybook (#13744) 2024-07-01 12:12:41 -06:00
Kayla Washburn-Love d977654f05 feat: unify organization and deployment management settings (#13602) 2024-07-01 11:15:00 -06:00
Jaayden Halko 9b1d8f79ae docs: update workspace filters docs (#13725) 2024-07-01 12:44:32 -04:00
Eric Paulsen d8d86b16dc docs: move architecture to top level (#13722) 2024-07-01 11:58:56 -04:00
Dean Sheather 6c94dd4f23 chore: add DRPC server implementation for network telemetry (#13675) 2024-07-02 01:50:52 +10:00
dependabot[bot] 2fde054e10 chore: bump rollup-plugin-visualizer from 5.9.0 to 5.12.0 in /site (#13706)
Bumps [rollup-plugin-visualizer](https://github.com/btd/rollup-plugin-visualizer) from 5.9.0 to 5.12.0.
- [Changelog](https://github.com/btd/rollup-plugin-visualizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/btd/rollup-plugin-visualizer/compare/v5.9.0...v5.12.0)

---
updated-dependencies:
- dependency-name: rollup-plugin-visualizer
  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-06-28 13:45:42 -08:00
dependabot[bot] 8735e234b4 chore: bump storybook/test from 8.0.5 to 8.1.11 in /site (#13716)
* chore: bump @storybook/test from 8.0.5 to 8.1.11 in /site

Bumps [@storybook/test](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/test) from 8.0.5 to 8.1.11.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.11/code/lib/test)

---
updated-dependencies:
- dependency-name: "@storybook/test"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

* Update remaining storybook dependencies

Not sure if you need to update all of them, but without this you get "no
matchinge export" from at least one of these.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Asher <ash@coder.com>
2024-06-28 20:45:25 +00:00
dependabot[bot] 472ea7ebbf chore: bump @storybook/addon-essentials from 8.0.5 to 8.1.11 in /site (#13712)
Bumps [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/essentials) from 8.0.5 to 8.1.11.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.11/code/addons/essentials)

---
updated-dependencies:
- dependency-name: "@storybook/addon-essentials"
  dependency-type: direct:development
  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-06-28 12:15:16 -08:00
Steven Masley 6daf330d3a chore: allow organization name or uuid for audit log searching (#13721)
* chore: allow organization name or uuid for audit log searching
2024-06-28 10:01:23 -05:00
Steven Masley 3cc86cf62d chore: implement sane default pagination limit for audit logs (#13676)
* chore: implement sane default pagination limit for audit logs
2024-06-28 07:38:04 -05:00
Cian Johnston 1a877716ca chore: add envbuilder-dogfood template (#13720)
Adds a template that can be used to dogfood on envbuilder!
2024-06-28 12:56:22 +01:00
Danny Kopping 0a221e8d5b feat: create database tables and queries for notifications (#13536) 2024-06-28 09:21:25 +00:00
dependabot[bot] 4213560b7a chore: bump @mui/icons-material from 5.14.0 to 5.15.20 in /site (#13703)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-28 03:49:56 +03:00
dependabot[bot] 2a21b0d144 ci: bump toshimaru/auto-author-assign in the github-actions group (#13696)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-28 03:48:01 +03:00
dependabot[bot] 86ee75b672 chore: bump undici from 6.11.1 to 6.19.2 in /site (#13699)
Bumps [undici](https://github.com/nodejs/undici) from 6.11.1 to 6.19.2.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.11.1...v6.19.2)

---
updated-dependencies:
- dependency-name: undici
  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-06-27 12:43:24 -06:00
dependabot[bot] 0b8b227dcf chore: bump emoji-mart from 5.4.0 to 5.6.0 in /site (#13709)
Bumps [emoji-mart](https://github.com/missive/emoji-mart/tree/HEAD/packages/emoji-mart) from 5.4.0 to 5.6.0.
- [Release notes](https://github.com/missive/emoji-mart/releases)
- [Commits](https://github.com/missive/emoji-mart/commits/v5.6.0/packages/emoji-mart)

---
updated-dependencies:
- dependency-name: emoji-mart
  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-06-27 12:41:09 -06:00
Muhammad Atif Ali bda94bfc77 chore: optimize dependabot configuration (#13670) 2024-06-27 21:14:17 +03:00
Kayla Washburn-Love 8b615f4522 fix: disable agent app buttons while a blocking startup script is running (#13667) 2024-06-27 11:25:03 -06:00
Jaayden Halko 093ec3d05b fix: improve checkbox text in template schedule settings dialog (#13669)
* fix: improve checkbox text in template schedule settings dialog

* fix: format

* fix: remove (s) plural language

* fix: fix format
2024-06-27 13:14:06 -04:00
Steven Masley 5a0afd8b7e chore: revert to default survey templates (#13690)
Revert to the default template provided by the package. Includes all fields like the "Message" field. Our template omits headers and keybind helpers.
2024-06-27 05:32:12 -10:00
Steven Masley 22f2c6da4f chore: add command to render all existing cli prompts (#13689)
Adds a command to render all the existing cli prompts we use. This is to validate a change to how our cli ui prompts look.
2024-06-27 05:20:15 -10:00
Spike Curtis ce7f13c6c3 fix: fix TestPGCoordinatorSingle_MissedHeartbeats flake (#13686) 2024-06-27 19:17:24 +04:00
Muhammad Atif Ali 089f06886b chore: add .pnpm-store to .gitignore (#13685) 2024-06-27 15:07:04 +03:00
Spike Curtis c94b5188bd fix: modify workspacesdk to ask for tailnet API 2.0 (#13684)
#13617 bumped the Agent/Tailnet API minor version because it adds telemetry features.  However, we don't actually use the protocol features yet, so it's a bit obnoxious for our CLI client to ask for the newest API version.

This is particularly true of the CLI client, since that's distributed separately, so if an end user installs the latest CLI client and their organization hasn't fully upgraded, then it will fail to connect.

Since we have a release coming up and the telemetry stuff won't make it, I think we should roll back to version 2.0 until we actually implement the telemetry stuff. That way the newest release (2.13) will work with Coder servers all the way back to 2.9.
2024-06-27 15:38:21 +04:00
Spike Curtis 5b59f2880f fix: fix workspacesdk to return error on API mismatch (#13683) 2024-06-27 15:02:43 +04:00
Marcin Tojek c4f1676055 feat: expose workspace build ID to terraform-plugin-coder (#13680) 2024-06-27 10:07:30 +02:00
Steven Masley 30c4b4db5c chore: implement fetch all authorized templates api (#13678) 2024-06-26 11:50:32 -06:00
Steven Masley 08e728bcb2 chore: implement organization scoped audit log requests (#13663)
* chore: add organization_id filter to audit logs
* chore: implement organization scoped audit log requests
2024-06-26 12:38:46 -05:00
Cian Johnston 20e59e0797 ci: test with multiple postgres versions (#13665)
- Tests now run on postgres 16 by default when run locally (can be specified with POSTGRES_VERSION)
- Adds test-go-pg-16 to test against postgres version 16
- Updates Dogfood dockerfile / nix flake to postgres version 16
- Updates docker-compose.yaml postgres tag to 16
2024-06-26 16:22:24 +01:00
Danny Kopping d5d8b918d7 feat: lint github actions workflows (#13552) 2024-06-26 10:28:16 +02:00
Cian Johnston 8a3592582b feat: add "Full Name" field to user creation (#13659)
Adds the ability to specify "Full Name" (a.k.a. Name) when
creating users either via CLI or UI.
2024-06-26 09:00:42 +01:00
austinrhode 87ad560aff feat: add groups and group members to telemetry snapshot (#13655)
* feat: Added in groups and groups members to telemetry snapshot
* feat: adding in test to dbauthz for getting group members and groups
2024-06-25 11:01:40 -07:00
Muhammad Atif Ali 58325dfd14 chore: minor improvements and link updates in README.md (#13656)
* chore: minor improvements and link updates in README.md

* fixup!
2024-06-25 13:29:08 -04:00
Garrett Delfosse fed668b432 chore: switch ssh session stats based on experiment (#13637) 2024-06-25 10:58:45 -04:00
Steven Masley d7eadee4d7 chore: insert audit log entries for organization CRUD (#13660)
* chore: insert audit log entries for organization CRUD
2024-06-25 09:03:15 -05:00
Spike Curtis 9c1a6a29f2 feat: add docstrings to mock timer and ticker methods and structs (#13658) 2024-06-25 16:59:35 +04:00
Spike Curtis 46e1c36c42 feat: add Next() method to mock Clock (#13657) 2024-06-25 16:48:26 +04:00
Spike Curtis 0d2f14606b chore: add usage information to clock library README (#13594)
Adds a Usage section to the README of the clock testing library.
2024-06-25 16:38:32 +04:00
Muhammad Atif Ali 136900268e ci: migrate to depot.dev runners (#13467) 2024-06-25 09:36:33 +03:00
Cian Johnston 313d4e02d2 chore(scaletest/dashboard): stub out initChromeDPCtx in unit tests (#13650) 2024-06-24 21:33:24 +01:00
Steven Masley 65b9f9bfd6 chore: audit organization member add/delete/edit (#13620)
* chore: audit organization member add/removals
2024-06-24 14:19:32 -05:00
Stephen Kirby 94639730f8 docs: bump mainline version to v2.12.3 (#13652)
* docs: bump mainline version to v2.12.3

* updated stable version
2024-06-24 14:07:42 -05:00
Steven Masley 34c67e8428 test: add unit test helper to create templates in second organizations (#13628)
* chore: add coderdtest helpers
2024-06-24 12:55:57 -05:00
Steven Masley e4333c0433 chore: 'coder login' reset cli organization context (#13646)
Cli organization context is reset on `coder login` if the organization
selected is an invalid organization.
2024-06-24 12:55:39 -05:00
dependabot[bot] 8ccdf05bbc chore: bump github.com/aws/aws-sdk-go-v2 from 1.27.0 to 1.30.0 (#13643)
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.27.0 to 1.30.0.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.27.0...v1.30.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-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-06-24 12:22:17 -05:00
dependabot[bot] 218f429336 chore: bump github.com/hashicorp/hcl/v2 from 2.20.0 to 2.21.0 (#13642)
Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.20.0 to 2.21.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.20.0...v2.21.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-06-24 12:21:45 -05:00
dependabot[bot] 6f4a9b6b51 chore: bump github.com/valyala/fasthttp from 1.54.0 to 1.55.0 (#13640)
Bumps [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp) from 1.54.0 to 1.55.0.
- [Release notes](https://github.com/valyala/fasthttp/releases)
- [Commits](https://github.com/valyala/fasthttp/compare/1.54.0...v1.55.0)

---
updated-dependencies:
- dependency-name: github.com/valyala/fasthttp
  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-06-24 12:21:18 -05:00
Colin Adler 3dec6ff32f chore: add protobuf types for tailnet telemetry (#13617) 2024-06-24 12:13:03 -05:00
Stephen Kirby b9d83c75de fixed changelog script release channel flag (#13649) 2024-06-24 11:50:24 -05:00
dependabot[bot] 7e20b56352 chore: bump alpine from 3.20.0 to 3.20.1 in /scripts (#13644)
Bumps alpine from 3.20.0 to 3.20.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 11:48:07 -05:00
Stephen Kirby 3d6c9799e3 fixed script ref (#13647) 2024-06-24 11:27:13 -05:00
Cian Johnston b4a5c7ffa9 chore: upgrade Go version to 1.22.4 (#13623)
Updates Go version to 1.22.4

Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
2024-06-24 15:50:52 +01:00
dependabot[bot] 7cb8bfb133 ci: bump the github-actions group with 2 updates (#13645)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 15:52:05 +03:00
Stephen Kirby 2cfadad023 manually updated autoversion (#13633) 2024-06-22 12:16:07 -05:00
Stephen Kirby 9abaa94599 docs: bump mainline version to v2.12.2 (#13632) 2024-06-21 15:11:58 -05:00
Kyle Carberry 54e8f30002 chore: remove failing_sections from healthcheck (#13426)
Closes #10854.
2024-06-21 14:49:02 -04:00
Steven Masley 5177f366f5 fix: organization 404 write 1 http status (#13629) 2024-06-21 13:01:46 -05:00
Steven Masley 0e933f0537 chore: refactor user -> rbac.subject into a function (#13624)
* chore: refactor user subject logic to be in 1 place
* test: implement test to assert deleted custom roles are omitted
* add unit test for deleted role
2024-06-21 11:30:02 -05:00
Kyle Carberry 3ef12ac284 fix: remove connected button (#13625)
It didn't make a lot of sense in current form. It will when we improve autostop.
2024-06-21 11:41:59 -04:00
Steven Masley 75e7213ac2 feat: add cli command to remove organization member (#13619) 2024-06-21 10:35:59 -05:00
Bruno Quaresma cbdaa63b68 chore(site): refactor popover to make it easier to extend (#13611) 2024-06-21 11:15:37 -03:00
Ethan 714f2ef83c fix: fix shallow clones not retrieving a valid semver (#13609) 2024-06-22 00:02:12 +10:00
Bruno Quaresma 73a25c3bc5 chore(site): add InputGroup component (#13597) 2024-06-21 10:54:55 -03:00
Steven Masley 819bfd3170 fix: remove assigning org-member role, this is implied from membership (#13578) 2024-06-21 08:01:39 -05:00
dependabot[bot] 66a604d779 chore: bump golang.org/x/tools from 0.21.0 to 0.22.0 (#13513)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-21 08:54:06 +03:00
Steven Masley 2ef2f97388 chore: improve error message on adding existing org_member (#13621) 2024-06-20 18:05:11 -05:00
Colin Adler 889daf200e feat(enterprise): add auditing to SCIM (#13614) 2024-06-20 17:22:27 -05:00
Steven Masley c4656d77cc chore: add help to error to reset organization context (#13616) 2024-06-20 16:44:47 -05:00
Kyle Carberry 495eea452f fix: track login page correctly (#13618) 2024-06-20 21:33:47 +00:00
Asher 43e45f4ab7 fix: fill out zero-value user properties in /audit (#13604) 2024-06-20 12:40:08 -08:00
Kyle Carberry 57b38e5bb8 fix: allow coder.com in CSP if telemetry is enabled (#13615)
* fix: allow coder.com in CSP if telemetry is enabled

* Fix control couple lint
2024-06-20 16:05:22 -04:00
Kyle Carberry 0793a4b35b feat: add cross-origin reporting for telemetry in the dashboard (#13612)
* feat: add cross-origin reporting for telemetry in the dashboard

* Respect the telemetry flag

* Fix embedded metadata

* Fix compilation error

* Fix linting
2024-06-20 15:19:45 -04:00
Steven Masley a1db6d809e chore: implement delete organization member (#13589)
Side effects of removing an organization member will orphan their
user resources. These side effects are not addressed here
2024-06-20 10:06:37 -05:00
Kelly Peilin Chan a1ec8ad6e9 Update docker-in-workspaces.md (#13606) 2024-06-20 11:05:21 -04:00
Steven Masley 8e06ad46d0 chore: add organization member api + cli (#13577) 2024-06-20 09:19:24 -05:00
Dean Sheather 4699adee5e chore: update dogfood sydney server (#13610) 2024-06-20 14:12:25 +00:00
Spike Curtis 8923ce5216 fix: fix flake in TestAppHealth_Healthy (#13607) 2024-06-20 12:02:31 +04:00
Spike Curtis 02ffff11dd feat: add NewTicker to clock testing library (#13593) 2024-06-20 10:16:04 +04:00
Kyle Carberry 7049d7a881 fix: display trial errors in the dashboard (#13601)
* fix: display trial errors in the dashboard

The error was essentially being ignored before!

* Remove day mention in product of trial

* fmt
2024-06-19 12:02:51 -04:00
dependabot[bot] 84cdcac8ad chore: bump ws from 8.14.2 to 8.17.1 in /site (#13595)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 10:51:25 +03:00
Kayla Washburn-Love e987ad1d89 fix: don't allow "new" or "create" as url-friendly names (#13596) 2024-06-18 15:36:13 -06:00
Kyle Carberry 3a1fa04590 fix: write server config to telemetry (#13590)
* fix: add external auth configs to telemetry

* Refactor telemetry to send the entire config

* gen

* Fix linting
2024-06-18 16:20:21 -04:00
Spike Curtis d0b2f6196c fix: allow mock clock Timers to accept negative duration (#13592)
The standard library `NewTimer`, `AfterFunc` and `Reset` allow negative durations, so our mock clock library should as well.
2024-06-18 15:40:56 +04:00
Spike Curtis 1de023a121 chore: add README to clock testing (#13583)
Adds README with some draft content explaining why the library exists.  Will be most relevant when we spin out into a standalone library.
2024-06-18 10:16:49 +04:00
Steven Masley 1d3642d0be chore: fix link in v2.0.0 changelog to scale tests (#13591) 2024-06-17 14:24:07 -05:00
Kayla Washburn-Love 8c1bd32c33 feat(site): add basic organization management ui (#13288) 2024-06-17 11:02:39 -06:00
Kayla Washburn-Love 07cd9acb2c fix: fix workspace actions options (#13572) 2024-06-17 10:24:30 -06:00
dependabot[bot] eed9794516 ci: bump crate-ci/typos in the github-actions group (#13584)
Bumps the github-actions group with 1 update: [crate-ci/typos](https://github.com/crate-ci/typos).


Updates `crate-ci/typos` from 1.22.3 to 1.22.7
- [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.22.3...v1.22.7)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-06-17 09:03:26 -04:00
Ben Potter 808e1c0d89 docs: add screenshots page (#13582)
* docs: add screenshots page

* fmt
2024-06-15 15:34:18 -05:00
Garrett Delfosse 44d69139d5 chore: accept payload on workspace usage route (#13544) 2024-06-14 10:08:45 -04:00
Eric Paulsen 87820a29d7 docs: reorganize scaling docs (#13574)
* refactor scaling docs

* manifest

* make fmt

* fix 404s

* fix 404s pt 2

* fix manifest
2024-06-14 09:30:04 -04:00
Spike Curtis c01d6fdf46 chore: refactor apphealth and tests to use clock testing library (#13576)
Refactors the apphealth subsystem and unit tests to use `clock.Clock`.

Also slightly simplifies the implementation, which wrapped a function that never returned an error in a `retry.Retry`.  The retry is entirely superfluous in that case, so removed.

UTs used to take a few seconds to run, and now run in milliseconds or better.  No sleeps, `Eventually`, or polling.

Dropped the "no spamming" test since we can directly assert the number of handler calls on the mainline test case.
2024-06-14 15:06:33 +04:00
Cian Johnston fe240add86 fix(coderd): userOIDC: ignore leading @ of EmailDomain (#13568) 2024-06-14 09:29:07 +01:00
Steven Masley d04959cea8 chore: implement custom role assignment for organization admins (#13570)
* chore: static role assignment mapping

Until a dynamic approach is created in the database, only org-admins
can assign custom organization roles.
2024-06-13 15:59:06 -05:00
Steven Masley 3d30c8dc68 chore: protect reserved builtin rolenames (#13571)
Conflicting built-in and database role names makes it hard to
disambiguate
2024-06-13 15:12:37 -05:00
Steven Masley 7d51515f9d chore: implement assign organization roles from the cli (#13558)
Basic functionality to assign roles to an organization member via cli.
2024-06-13 14:49:32 -05:00
Eric Paulsen 87a172fb14 docs: add validated architecture (#13561)
* docs: add validated architecture

* make: fmt

* formatting

* fix 404s

* fix 404s pt 2

* fix 404s pt 3
2024-06-13 13:00:26 -04:00
Cian Johnston c587af7c0e fix(dogfood/Dockerfile): add explicit --chown to COPY directive (#13569) 2024-06-13 15:16:34 +01:00
Cian Johnston 5d3f3c08cd chore(dogfood): add devcontainer for use with envbuilder (#13567) 2024-06-13 14:31:49 +01:00
Spike Curtis 0268c7a659 chore: refactor autobuild/notify to use clock test (#13566)
Refactor autobuild/notify and tests to use the clock testing library.

I also rewrote some of the comments because I didn't understand them when I was looking at the package.
2024-06-13 16:01:17 +04:00
Spike Curtis 4b0b9b08d5 feat: add interfaces report to support bundle (#13563) 2024-06-13 13:09:54 +04:00
Spike Curtis 88eb6ce378 fix: fix flake in TestDERPEndToEnd (#13564) 2024-06-13 11:38:51 +04:00
Spike Curtis fc09077b7b feat!: add interface report to coder netcheck (#13562)
re: #13327

Adds local interfaces to `coder netcheck` and checks their MTUs for potential problems.

This is mostly relevant for end-user systems where VPNs are common.  We _could_ also add it to coderd healthcheck, but until I see coderd connecting to workspaces over a VPN in the wild, I don't think its worth the UX effort.

Netcheck results get the following:

```
  "interfaces": {
    "error": null,
    "severity": "ok",
    "warnings": null,
    "dismissed": false,
    "interfaces": [
      {
        "name": "lo0",
        "mtu": 16384,
        "addresses": [
          "127.0.0.1/8",
          "::1/128",
          "fe80::1/64"
        ]
      },
      {
        "name": "en8",
        "mtu": 1500,
        "addresses": [
          "192.168.50.217/24",
          "fe80::c13:1a92:3fa5:dd7e/64"
        ]
      }
    ]
  }
```

_Technically_ not back compatible if anyone is parsing `coder netcheck` output as JSON, since the original output is now under `"derp"` in the output.
2024-06-13 10:19:36 +04:00
Steven Masley d0fc81a51c chore: implement cli list organization members (#13555)
example cli command: 
`coder organization members`
2024-06-12 10:07:12 -10:00
Steven Masley bbe23edc7d chore: implement api layer for listing organization members (#13546) 2024-06-12 09:52:18 -10:00
Steven Masley de9e6889bb chore: merge organization member db queries (#13542)
Merge members queries into 1 that also joins in the user table for username.
Required to list organization members on UI/cli
2024-06-12 09:23:48 -10:00
Kyle Carberry 1ca5dc0328 chore: always use the latest released version tag when building (#13556)
* chore: always use the latest released version tag when building

* Update version.sh

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

---------

Co-authored-by: Dean Sheather <dean@deansheather.com>
2024-06-12 14:52:35 -04:00
Kayla Washburn-Love 28228f1bcb feat: allow editing org icon (#13547) 2024-06-12 12:28:13 -06:00
Ethan 58bf0ec1c6 chore: add additional tailnet topology integration tests (#13549) 2024-06-12 16:02:34 +00:00
Spike Curtis ba7d1835e5 fix: fix flake in TestWorkspaceAgent_Metadata_CatchMemoryLeak (#13553)
Fixes flake seen here: https://github.com/coder/coder/actions/runs/9461246505/job/26061605278

#13486 subtly changes the test so that `post` uses the new v2 Agent API, and when canceling context, there is a race condition where the yamux session underpinning the API can get torn down before the RPC processes the canceled context, yielding a different error response than the test was previously expecting.

I've refactored the test to just stop posting when the test finishes, rather than depend on a context cancel to end the posting goroutine.
2024-06-12 18:33:22 +04:00
Bruno Quaresma 0c627a4cb9 refactor(site): refactor filter search field (#13545) 2024-06-12 10:22:20 -03:00
Ethan a11f8b003b chore: write speedtest connection updates to stderr (#13550) 2024-06-12 07:10:28 +00:00
Kira Pilot dd99897bb2 chore: updating Ashby link to be position agnostic (#13543) 2024-06-11 12:59:33 -04:00
Steven Masley 5ccf5084e8 chore: create type for unique role names (#13506)
* chore: create type for unique role names

Using `string` was confusing when something should be combined with
org context, and when not to. Naming this new name, "RoleIdentifier"
2024-06-11 08:55:28 -05:00
Kyle Carberry c9cca9d56e fix: transform underscores to hyphens for github login (#13384)
Fixes #13339.
2024-06-11 13:34:05 +00:00
Marcin Tojek 7958c52918 docs: faq: restrict file transfers from workspaces (#13534) 2024-06-11 09:29:29 +00:00
Spike Curtis 1f9bdc36bf fix: ignore yamux.ErrSessionShutdown on TestTailnetAPIConnector_Disconnects (#13532) 2024-06-11 11:16:49 +04:00
Ethan dd243686e4 chore!: remove deprecated agent v1 routes (#13486) 2024-06-11 12:22:59 +10:00
dependabot[bot] e7bea17e70 chore: bump braces from 3.0.2 to 3.0.3 in /site (#13526)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 03:54:42 +03:00
dependabot[bot] 363dbad3a3 ci: bump the github-actions group with 2 updates (#13521)
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.21.0 to 1.22.3
- [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.21.0...v1.22.3)

Updates `aquasecurity/trivy-action` from 0.21.0 to 0.22.0
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/fd25fed6972e341ff0007ddb61f77e88103953c2...595be6a0f6560a0a8fc419ddf630567fc623531d)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-06-11 01:06:40 +03:00
Garrett Delfosse 5b9a65e5c1 chore: move Batcher and Tracker to workspacestats (#13418) 2024-06-10 15:35:23 -04:00
Colin Adler c7e7312cb0 fix(site): don't show start button while starting (#13495) 2024-06-10 13:28:21 -05:00
Marcin Tojek e96652ebbc feat: block file transfers for security (#13501) 2024-06-10 12:12:23 +00:00
Spike Curtis 8326a3a675 chore: change mock clock to allow Advance() within timer/tick functions (#13500) 2024-06-10 15:27:24 +04:00
Kyle Carberry 7c081dcd6f fix: replace invalid utf-8 sequences in agent logs (#13436)
* fix: replace invalid utf-8 sequences in agent logs

Fixes #13433.

* fix: replace invalid UTF-8 with , add regression

Signed-off-by: Spike Curtis <spike@coder.com>

---------

Signed-off-by: Spike Curtis <spike@coder.com>
Co-authored-by: Spike Curtis <spike@coder.com>
2024-06-10 15:27:11 +04:00
Steven Masley 0d65143301 chore: implement audit log for custom role edits (#13494)
* chore: implement audit log for custom role edits
2024-06-07 14:11:57 -05:00
Bruno Quaresma 056a697eff feat(site): add download logs option (#13466) 2024-06-07 10:03:05 -03:00
Cian Johnston 48ecee1025 chore(cli): address cli netcheck test flake (#13492)
* netcheck: removes check for healthy node report in test
* coderd/healthcheck/derphealth: do not override parent context deadline
2024-06-07 10:01:54 +01:00
Steven Masley 7c3b8b6224 chore: duplicate migration file fix, 000216 (#13498) 2024-06-06 16:13:00 -05:00
Steven Masley e2b330fcba chore: change sql parameter for custom roles to be a (name,org_id) tuple (#13480)
* chore: sql parameter to custom roles to be a (name,org) tuple

CustomRole lookup takes (name,org_id) tuples as the search criteria.
2024-06-06 15:36:37 -05:00
Bruno Quaresma 1adc19b41f fix(site): allow user to update their name (#13493) 2024-06-06 15:32:51 -03:00
Bruno Quaresma 4dfa901990 refactor(site): hide select helper when only one proxy exists (#13496) 2024-06-06 15:17:43 -03:00
Bruno Quaresma a8a81a61cd fix(site): fix tooltip in start button group (#13497) 2024-06-06 14:51:52 -03:00
Kayla Washburn-Love 44a70a5bc2 feat: edit org display names and descriptions (#13474) 2024-06-06 10:59:59 -06:00
Cian Johnston 1131772e79 feat(coderd): set full name from IDP name claim (#13468)
* Updates OIDC and GitHub OAuth login to fetch set name from relevant claim fields
* Adds CODER_OIDC_NAME_FIELD as configurable source of user name claim
* Adds httpapi function to normalize a username such that it will pass validation
* Adds firstName / lastName fields to dev OIDC setup
2024-06-06 13:37:08 +01:00
Colin Adler e743588843 docs: bump k8s install version (#13487) 2024-06-06 03:31:32 +00:00
Colin Adler 37676c46d5 chore(scripts): remove remaining gh_auth calls from release scripts (#13485) 2024-06-05 22:24:26 -05:00
Jon Ayers 7995d7c3d6 fix: only render tooltip when require_active_version enabled (#13484) 2024-06-06 02:52:49 +00:00
Colin Adler f1b42a15fa fix(site): show workspace start button when require active version is enabled (#13482) 2024-06-05 16:50:52 -05:00
Steven Masley 8f62311f00 chore: remove organization_id suffix from org_member roles in database (#13473)
Organization member's table is already scoped to an organization.
Rolename should avoid having the org_id appended.

Wipes all existing organization role assignments, which should not be used anyway.
2024-06-05 11:25:02 -05:00
Spike Curtis fade8ba759 fix: fix MeasureLatencyRecvTimeout to accept send=0 (#13477)
Fixes the flake seen here: https://github.com/coder/coder/runs/25832852690

Linux is not a real time operating system, and so there is no guarantee that subsequent `time.Now()` `time.Since()` calls will return a non-zero time.  This assert is mainly there to ensure we don't return `-1`.
2024-06-05 18:27:56 +04:00
Spike Curtis 775fc3f5e9 chore: add Now, Since, AfterFunc to clock; use clock for configmaps test (#13476)
* chore: add Now, Since, AfterFunc to clock; use clock for configmaps test

* chore: update flake.nix vendor hash

Signed-off-by: Spike Curtis <spike@coder.com>

---------

Signed-off-by: Spike Curtis <spike@coder.com>
2024-06-05 16:00:02 +04:00
Spike Curtis ffcfbb6c55 chore: add example test case for clock package (#13465) 2024-06-05 15:49:31 +04:00
Spike Curtis 9c3fd5dd26 chore: add explicit Wait() to clock.Advance() (#13464) 2024-06-05 15:37:16 +04:00
Spike Curtis 42324b386a chore: add clock pkg for testing time (#13461)
Adds a package for testing time/timer/ticker functions.  Implementation is limited to `NewTimer` and `NewContextTicker`, but will eventually be expanded to all `time` functions from the standard library as well as `context.WithTimeout()`, `context.WithDeadline()`.

Replaces `benbjohnson/clock` for the pubsub watchdog, as a proof of concept.

Eventually, as we expand functionality, we will replace most time-related functions with this library for testing.
2024-06-05 13:55:45 +04:00
Ethan a4bba520a2 feat(cli): add json output to coder speedtest (#13475) 2024-06-05 08:31:44 +00:00
Mathias Fredriksson 9a757f8e74 chore(scripts): fix release promote stable to set latest tag (#13471) 2024-06-04 23:01:26 +00:00
dependabot[bot] 83ac386533 chore: bump ejs from 3.1.9 to 3.1.10 in /site (#13447)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 23:52:59 +03:00
Kayla Washburn-Love 0ea89a3d41 chore: add cleanup callbacks to some useEffect calls (#13444) 2024-06-04 12:18:03 -06:00
Stephen Kirby 213848e2e3 chore(docs): rename banners and show usage of multiple (#13435)
* renamed banners in docs

* fmt

* Update appearance.md

Co-authored-by: Kayla Washburn-Love <mckayla@hey.com>

* fmt

---------

Co-authored-by: Kayla Washburn-Love <mckayla@hey.com>
Co-authored-by: Ben <me@bpmct.net>
2024-06-04 17:49:13 +00:00
Ben Potter 8435b70bea chore: update docs for v2.12 mainline and v2.11 stable (#13469)
* chore: update docs for v2.12 mainline and v2.11 stable

* remove broken link
2024-06-04 12:22:13 -05:00
Mathias Fredriksson 3b7f9534fb chore(scripts): fix dry run for autoversion in release.sh (#13470) 2024-06-04 20:10:15 +03:00
Steven Masley e3206612e1 chore: implement typed database for custom permissions (breaks existing custom roles) (#13457)
* chore: typed database custom permissions
* add migration to fix any custom roles out there
2024-06-04 09:27:44 -05:00
Cian Johnston 168d2d6ba0 chore(coderd): add update user profile test for members (#13463) 2024-06-04 14:17:17 +01:00
Marcin Tojek cd32c42699 fix(cli): inherit provisioner tags from last template version (#13462) 2024-06-04 11:59:54 +00:00
Muhammad Atif Ali e527bc6242 chore(dogfood): replace deprecated coder_workspace.owner_oidc_access_token and add order to agent metadata (#13456) 2024-06-04 09:21:01 +01:00
Mathias Fredriksson a51076a4cd chore(scripts): fix unbound variable in tag_version.sh (#13428) 2024-06-03 21:29:24 +00:00
Kayla Washburn-Love 78b8264a90 feat(site): add deployment menu to navbar (#13401) 2024-06-03 15:05:49 -06:00
Muhammad Atif Ali c7233eccec chore(dogfood): bump module versions (#13455) 2024-06-04 00:03:34 +03:00
Colin Adler 40390ecc30 chore: fix TestServer/Prometheus/DBMetricsDisabled test flake (#13453)
See: https://github.com/coder/coder/actions/runs/9352137263/job/25739550487#step:5:368
2024-06-03 15:38:59 -05:00
Kayla Washburn-Love 2806752c7d chore: add light mode snapshot to chromatic for WorkspaceBuildPageView (#13449) 2024-06-03 13:50:59 -06:00
Colin Adler e4ac691468 chore: fix (*coderdtest.WorkspaceAgentWaiter).Wait() flake (#13451) 2024-06-03 14:46:56 -05:00
Colin Adler 43ef00401c chore: linting fixes (#13450) 2024-06-03 14:33:37 -05:00
Steven Masley 27f26910b6 chore: external auth validate response "Forbidden" should return invalid, not an error (#13446)
* chore: add unit test to delete workspace from suspended user
* chore: account for forbidden as well as unauthorized response codes
2024-06-03 13:16:51 -05:00
dependabot[bot] 0b019cad77 chore: bump google.golang.org/api from 0.181.0 to 0.182.0 (#13439)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.181.0 to 0.182.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.181.0...v0.182.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  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-06-03 12:30:12 -05:00
Colin Adler 9d00a26a90 fix: add missing route for codersdk.PostLogSource (#13421) 2024-06-03 12:29:50 -05:00
dependabot[bot] 8cdd468107 chore: bump github.com/coder/terraform-provider-coder from 0.22.0 to 0.23.0 (#13440)
Bumps [github.com/coder/terraform-provider-coder](https://github.com/coder/terraform-provider-coder) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/coder/terraform-provider-coder/releases)
- [Changelog](https://github.com/coder/terraform-provider-coder/blob/main/.goreleaser.yml)
- [Commits](https://github.com/coder/terraform-provider-coder/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: github.com/coder/terraform-provider-coder
  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-06-03 12:29:29 -05:00
Kayla Washburn-Love cb94dfb1f6 fix: fix build error background color (#13445) 2024-06-03 11:18:44 -06:00
Kayla Washburn-Love 79fd736387 chore(site): enable React's StrictMode (#13399) 2024-06-03 10:03:46 -06:00
Steven Masley 973cc2b875 chore: add edit organization role to cli (#13365)
Editing custom org roles from hidden org cli command.
2024-06-03 09:34:10 -05:00
Steven Masley 24ba81930b chore: return failed refresh errors on external auth as string (was boolean) (#13402)
* chore: return failed refresh errors on external auth

Failed refreshes should return errors. These errors are captured
as validate errors.
2024-06-03 09:33:49 -05:00
Marcin Tojek bf98b0dfe4 fix: correct swagger description for Insights API (#13442) 2024-06-03 15:48:31 +02:00
Colin Adler b723da9e91 chore: upgrade terraform to v1.8.5 (#13429) 2024-06-02 13:10:28 -04:00
Kayla Washburn-Love b248f125e1 chore: rename notification banners to announcement banners (#13419) 2024-05-31 10:59:28 -06:00
Garrett Delfosse de8149fbfd chore: move template meta last_used_at update to workspacestats (#13415) 2024-05-31 12:26:19 -04:00
Michael Smith 19530c6b44 fix: update DeleteWorkspaceOptions to pick properties correctly (#13423)
* fix: update typo

* fix: update typo in call site

* fix: update type for deleteWorkspace mock

* fix: update one more type mismatch
2024-05-31 10:23:59 -04:00
Mathias Fredriksson 4758952ebc chore(scripts): fix expression interpreted as exit code on some Bash versions (#13417) 2024-05-30 17:24:41 +00:00
Kira Pilot bee4ece1b9 fix: update install.sh to remove dead doc link (#13308)
* chore(docs): update install.sh to remove dead doc link

* Update install.sh

Co-authored-by: Kyle Carberry <kyle@coder.com>

* escaping script properly

---------

Co-authored-by: Kyle Carberry <kyle@coder.com>
2024-05-30 10:39:17 -04:00
Danny Kopping 7569cccc51 chore: remove git pinning (#13414)
Alpine 3.20 includes 2.45.1 by default: https://git.alpinelinux.org/aports/tree/main/git/APKBUILD?h=3.20-stable#n56

Follow-up from https://github.com/coder/coder/pull/13411#issuecomment-2139028721

Signed-off-by: Danny Kopping <danny@coder.com>
2024-05-30 15:58:32 +02:00
Danny Kopping 59ab5053b1 fix: return error if agent init script fails to download valid binary (#13280) 2024-05-30 13:33:00 +02:00
Cian Johnston e176867d77 chore: update deprecated usage of coder_workspace.owner* fields (#13390)
Per https://github.com/coder/terraform-provider-coder/releases/tag/v0.23.0

Performs a mechanical rename of existing usage deprecated fields in the latest version of the coder/coder provider.

Closes #13382
2024-05-30 11:31:51 +01:00
Cian Johnston 7cc96f5d40 chore(docs): add recommendations for dependency management (#13400) 2024-05-30 10:17:26 +01:00
Muhammad Atif Ali 7a7bef0dab ci: fix syntax issue in docker-base.yaml (#13412) 2024-05-30 08:49:30 +00:00
Danny Kopping a1671a633c Upgrade to git v2.45.1 to fix alpine 3.20 builds (#13411)
Possibly fixes https://github.com/coder/coder/issues/13407

Signed-off-by: Danny Kopping <danny@coder.com>
2024-05-30 08:36:24 +00:00
Muhammad Atif Ali 6730c24c58 ci: build base image on PRs (#13409) 2024-05-30 08:35:37 +00:00
Spike Curtis 5aea80381c fix: increses DERP send queue length to 512 for increased throughput (#13406) 2024-05-30 11:46:18 +04:00
Mathias Fredriksson 9eb797eb5a chore(scripts): add safety check for difference between dry run release notes (#13398) 2024-05-29 22:01:10 +03:00
Mathias Fredriksson 5fb231774c chore(scripts): add custom gh auth to release script (#13396) 2024-05-29 18:37:04 +00:00
Mathias Fredriksson 9ae825ebae chore(scripts): push version bump pr branch in release script (#13397) 2024-05-29 18:30:42 +00:00
Mathias Fredriksson 374f0a0fd1 chore(scripts): handle renamed cherry-pick commits in release script (#13395) 2024-05-29 21:30:11 +03:00
Michael Brewer bc8126fa45 fix(cli): skip optional coder_external_auth (#13368)
* fix(cli): skip over coder_external_auth that are optional

* chore: Delete package-lock.json
2024-05-29 17:37:54 +00:00
Garrett Delfosse 5789ea5397 chore: move stat reporting into workspacestats package (#13386) 2024-05-29 11:49:08 -04:00
Steven Masley afd9d3b35f feat: add api for patching custom org roles (#13357)
* chore: implement patching custom organization roles
2024-05-29 09:49:43 -05:00
Matt Vollmer b69f6358f0 Update manifest.json (#13391) 2024-05-29 12:37:06 +00:00
Cian Johnston cca3cb1c55 feat(provisioner): pass owner git ssh key (#13366) 2024-05-29 11:43:08 +01:00
Spike Curtis b7edf5bbc7 fix: block writes from gVisor to tailscale instead of dropping (#13389)
fixes: #13108

upgrades our tailscale fork to include https://github.com/coder/tailscale/pull/52
2024-05-29 14:30:24 +04:00
Spike Curtis 84b3121777 fix: stop logging workspace agent unless verbose (#13378) 2024-05-29 08:17:35 +04:00
Spike Curtis a551aa51ab fix: respect --disable-direct-connections on coder speedtest (#13377) 2024-05-29 08:07:48 +04:00
Stephen Kirby ec78f54941 added jetbrains fleet link to manifest.json (#13363) 2024-05-29 06:55:22 +03:00
dependabot[bot] ef4ed64a29 chore: bump gopkg.in/DataDog/dd-trace-go.v1 from 1.61.0 to 1.64.0 (#13316)
Bumps gopkg.in/DataDog/dd-trace-go.v1 from 1.61.0 to 1.64.0.

---
updated-dependencies:
- dependency-name: gopkg.in/DataDog/dd-trace-go.v1
  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-05-29 06:51:59 +03:00
Colin Adler 02c36868b2 chore: upgrade go.uber.org/goleak (#13388)
The latest published version is broken on go 1.20
2024-05-28 17:15:37 -05:00
dependabot[bot] 7ea510e091 chore: bump github.com/gohugoio/hugo from 0.125.3 to 0.126.1 (#13323)
Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.125.3 to 0.126.1.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/hugoreleaser.toml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.125.3...v0.126.1)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hugo
  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-05-28 15:49:34 -05:00
dependabot[bot] 18692058a9 chore: bump google.golang.org/grpc from 1.63.2 to 1.64.0 (#13319)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.63.2 to 1.64.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.63.2...v1.64.0)

---
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-05-28 15:48:58 -05:00
dependabot[bot] 5a8a254c93 chore: bump github.com/hashicorp/hc-install from 0.6.3 to 0.7.0 (#13372)
Bumps [github.com/hashicorp/hc-install](https://github.com/hashicorp/hc-install) from 0.6.3 to 0.7.0.
- [Release notes](https://github.com/hashicorp/hc-install/releases)
- [Commits](https://github.com/hashicorp/hc-install/compare/v0.6.3...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/hc-install
  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-05-28 20:17:23 +00:00
dependabot[bot] 00f6cfe3cf chore: bump github.com/hashicorp/go-version from 1.6.0 to 1.7.0 (#13374)
Bumps [github.com/hashicorp/go-version](https://github.com/hashicorp/go-version) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/hashicorp/go-version/releases)
- [Changelog](https://github.com/hashicorp/go-version/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-version/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-version
  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-05-28 20:04:49 +00:00
Dean Sheather 9299e9f6ba chore: hard NAT <-> easy NAT integration test (#13314) 2024-05-29 06:04:07 +10:00
dependabot[bot] e5d848f19d chore: bump github.com/valyala/fasthttp from 1.53.0 to 1.54.0 (#13373)
Bumps [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp) from 1.53.0 to 1.54.0.
- [Release notes](https://github.com/valyala/fasthttp/releases)
- [Commits](https://github.com/valyala/fasthttp/compare/v1.53.0...1.54.0)

---
updated-dependencies:
- dependency-name: github.com/valyala/fasthttp
  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-05-28 15:01:03 -05:00
dependabot[bot] 1edd46dd5f chore: bump github.com/hashicorp/terraform-json from 0.21.0 to 0.22.1 (#13322)
Bumps [github.com/hashicorp/terraform-json](https://github.com/hashicorp/terraform-json) from 0.21.0 to 0.22.1.
- [Release notes](https://github.com/hashicorp/terraform-json/releases)
- [Commits](https://github.com/hashicorp/terraform-json/compare/v0.21.0...v0.22.1)

---
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-05-28 19:54:06 +00:00
dependabot[bot] 762cb84f4a chore: bump github.com/aws/aws-sdk-go-v2 from 1.26.1 to 1.27.0 (#13324)
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.26.1 to 1.27.0.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.26.1...v1.27.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-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-05-28 19:48:23 +00:00
Steven Masley 6293c33746 chore: add refresh token and error to user's external auth page (#13380)
* chore: add story for failed refresh error
* chore: add refresh icon to tokens that can refresh
2024-05-28 14:07:22 -05:00
dependabot[bot] 5b78ec97b6 chore: bump alpine from 3.19.1 to 3.20.0 in /scripts (#13375)
Bumps alpine from 3.19.1 to 3.20.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>
2024-05-28 12:45:16 -05:00
Kyle Carberry 79d73f77f5 chore: skip Azure TestExpiresSoon (#13385)
Adds some context to the test skip so it can be removed or enabled in the future.
2024-05-28 16:45:41 +00:00
dependabot[bot] a1d3b82dd1 ci: bump aquasecurity/trivy-action from 0.20.0 to 0.21.0 in the github-actions group (#13376)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 19:57:36 +03:00
Muhammad Atif Ali 47f8f5d963 chore(docs): update github app permission to read org members (#13362) 2024-05-24 23:15:29 +03:00
dependabot[bot] 60224fa216 chore: bump github.com/fatih/color from 1.16.0 to 1.17.0 (#13321)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-05-24 16:45:11 +00:00
dependabot[bot] 87dd878779 chore: bump google.golang.org/api from 0.180.0 to 0.181.0 (#13317)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 19:33:35 +03:00
dependabot[bot] ff617cc545 chore: bump github.com/valyala/fasthttp from 1.52.0 to 1.53.0 (#13318)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 18:46:57 +03:00
Spike Curtis a0962ba089 fix: wait for PGCoordinator to clean up db state (#13351)
c.f. https://github.com/coder/coder/pull/13192#issuecomment-2097657692

We need to wait for PGCoordinator to finish its work before returning on `Close()`, so that we delete database state (best effort -- if this fails others will filter it out based on heartbeats).
2024-05-24 12:01:03 +04:00
Dean Sheather e5bb0a7a00 chore: add easy NAT integration tests part 2 (#13312) 2024-05-24 16:32:30 +10:00
Steven Masley 1b4ca00428 chore: include custom roles in list org roles (#13336)
* chore: include custom roles in list org roles
* move cli show roles to org scope
2024-05-23 07:54:59 -10:00
Kayla Washburn-Love d748c6d718 fix(site): correct the size and position of the timeline trail in safari (#13348) 2024-05-23 11:25:10 -06:00
Marcin Tojek 98fa823c79 docs: describe workspace tags (#13352) 2024-05-23 15:20:50 +02:00
Spike Curtis b43344b672 feat: use latest gVisor and go 1.22.3 (#13338) 2024-05-23 08:22:44 -04:00
Cian Johnston c67eba10d5 chore: update scale docs to include guidelines for wsproxies (#13350) 2024-05-23 10:00:23 +01:00
Marcin Tojek c2837a62e4 feat: evaluate provisioner tags (#13333) 2024-05-23 07:53:51 +00:00
Colin Adler fa9edc1f42 chore(scripts): remove gh_auth from release.sh (#13347)
It breaks the `gh` cli for creating workflows.
2024-05-22 14:28:21 -05:00
Colin Adler a40e954afc chore(docs): update k8s mainline version (#13346) 2024-05-22 14:01:11 -05:00
Kyle Carberry 3364abecdd chore: generate terraform testdata with matching terraform version (#13343)
Terraform changed the default output of the `terraform graph` command. You must put `-type=plan` to keep the prior behavior.


Co-authored-by: Colin Adler <colin1adler@gmail.com>
2024-05-22 12:45:47 -05:00
Ammar Bandukwala ed6ee9aaa8 chore(README): add hiring link (#13345) 2024-05-22 12:01:29 -05:00
Bruno Quaresma 390ff9ac05 refactor(site): hide unavailable usage information (#13341) 2024-05-22 13:26:59 +00:00
Justin Shoffstall 7ea4a89a20 chore: update kubernetes.md, bumping stable from v2.9.4 to v2.10.2 (#13275) 2024-05-22 12:24:28 +03:00
Bruno Quaresma 78deaba481 feat(site): show "update and start" button when update is forced (#13334) 2024-05-21 19:29:54 +00:00
Bruno Quaresma f27f5c0002 feat(site): show number of times coder_app is opened (#13335) 2024-05-21 16:04:41 -03:00
Kayla Washburn-Love 3f1e9c038a feat(coderd): add endpoints for editing and deleting organizations (#13287) 2024-05-21 12:46:31 -06:00
Steven Masley 0a86d6d176 chore: expose formatExamples enterprise commands (#13304)
Exporting it allows enterprise functions to also use it.
2024-05-21 13:26:34 -05:00
Steven Masley c61b64be61 feat: add hidden enterprise cmd command to list roles (#13303)
* feat: add hidden enterprise cmd command to list roles

This includes custom roles, and has a json ouput option for
more granular permissions
2024-05-21 13:14:00 -05:00
Asher 8e78b9495d feat: open most recent directory or workspace when launching VS Code (#13326) 2024-05-21 09:19:59 -08:00
Dean Sheather 273209432d chore: fix tailnet integration test flake (#13313) 2024-05-21 12:57:39 +10:00
Marcin Tojek b8b80fe6d2 feat: store coder_workspace_tags in the database (#13294) 2024-05-20 13:30:19 +00:00
Cian Johnston 45b45f1107 ci: re-enable test migrations in release workflow (#13307) 2024-05-20 10:35:06 +01:00
Kayla Washburn-Love a63d427efd chore: add unique org name constraint to db (#13311) 2024-05-17 12:40:38 -06:00
Bruno Quaresma 4af0f093ee fix(site): fix floating number on duration fields (#13209) 2024-05-17 15:26:00 -03:00
dependabot[bot] d8bb5a05db chore: bump github.com/fergusstrange/embedded-postgres from 1.26.0 to 1.27.0 (#13255)
Bumps [github.com/fergusstrange/embedded-postgres](https://github.com/fergusstrange/embedded-postgres) from 1.26.0 to 1.27.0.
- [Release notes](https://github.com/fergusstrange/embedded-postgres/releases)
- [Commits](https://github.com/fergusstrange/embedded-postgres/compare/v1.26.0...v1.27.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-05-17 12:03:32 -05:00
Colin Adler f176ff532f ci: re-pin actions/dependency-review-action back to a release (#13309) 2024-05-17 11:55:30 -05:00
Cian Johnston f23d4802b5 ci: fix test-migrations target when main branch is not present locally (#13306) 2024-05-17 10:24:56 +01:00
Mathias Fredriksson f66d0445da chore(scripts): fix stable release promote script (#13204) 2024-05-17 08:25:10 +00:00
Mathias Fredriksson 0998cedb5c chore(scripts): fix a few release script changelog issues (#13200) 2024-05-17 11:19:48 +03:00
Colin Adler 92c5dfa266 docs: bump k8s install version (#13302) 2024-05-16 20:24:03 +00:00
Colin Adler 80538c079d chore: update git -> 2.43.4 and terraform -> 1.7.5 (#13299)
This fixes an RCE in git and gets us one minor version closer to fixing
a critical Terraform vulnerability. In the next release we'll bump to
1.8.x.
2024-05-16 19:07:07 +00:00
Steven Masley ad8c314130 chore: implement api for creating custom roles (#13298)
api endpoint (gated by experiment) to create custom_roles
2024-05-16 13:47:47 -05:00
Colin Adler 85de0e966d chore: fix TestMeasureLatency/MeasureLatencyRecvTimeout flake (#13301) 2024-05-16 13:42:42 -05:00
Steven Masley cf91eff7cf chore: implement databased backend for custom roles (#13295)
Includes db schema and dbauthz layer for upserting custom roles. Unit test in `customroles_test.go` verify against escalating permissions through this feature.
2024-05-16 13:11:26 -05:00
Steven Masley 194be12133 chore: verify validity of built in rbac roles (#13296)
Verifies our built in roles are valid according to our policy.go. Working on custom roles requires the dynamic roles to adhere to these rules. Feels fair the built in ones do too.
2024-05-16 12:07:44 -05:00
Mathias Fredriksson a0fce363cd feat(coderd): add times_used to coder_apps in insights API (#13292)
For now, only applied to `coder_app`s, same logic can be implemented for
VS Code, SSH, etc.

Part of #13099
2024-05-16 16:53:01 +03:00
Michael Smith 63e06853eb fix: update tests for useClipboard to minimize risks of flakes (#13250)
* wip: commit progress on test revamps

* fix: update existing tests to new format

* chore: add test case for global snackbar

* refactor: consolidate files

* refactor: make http dependency more explicit

* chore: add extra test case for exposed error value

* docs: fix typos

* fix: make sure clipboard is reset between test runs

* docs: add more context to comments

* refactor: update mock console.error logic to use jest.spyOn

* docs: add more clarifying comments

* refactor: split off type alias for clarity
2024-05-15 16:59:15 -04:00
Stephen Kirby 114fb31fbb fixed sharable port + coder_app interaction (#13285) 2024-05-15 14:40:46 -05:00
Kayla Washburn-Love fc6f18aa96 feat(site): add an organization switcher to the user menu (#13269) 2024-05-15 13:14:34 -06:00
Steven Masley 1f5788feff chore: remove rbac psuedo resources, add custom verbs (#13276)
Removes our pseudo rbac resources like `WorkspaceApplicationConnect` in favor of additional verbs like `ssh`. This is to make more intuitive permissions for building custom roles.

The source of truth is now `policy.go`
2024-05-15 11:09:42 -05:00
Steven Masley cb6b5e8fbd chore: push rbac actions to policy package (#13274)
Just moved `rbac.Action` -> `policy.Action`. This is for the stacked PR to not have circular dependencies when doing autogen. Without this, the autogen can produce broken golang code, which prevents the autogen from compiling.

So just avoiding circular dependencies. Doing this in it's own PR to reduce LoC diffs in the primary PR, since this has 0 functional changes.
2024-05-15 09:46:35 -05:00
Bruno Quaresma f14927955d fix(site): fix group badge visual (#13263) 2024-05-14 13:52:16 -03:00
Kayla Washburn-Love a8a0be98b8 chore: expose all organization ids from AuthContext (#13268) 2024-05-14 10:48:15 -06:00
Garrett Delfosse 721ab2a1b4 chore: add workspace activity linter (#13273) 2024-05-14 12:31:31 -04:00
Kayla Washburn-Love 2b29559984 chore: add setting to enable multi-organization ui (#13266) 2024-05-13 14:41:45 -06:00
Steven Masley 9ced001570 chore: add multi-org experiment for UI view toggling (#13260)
* chore: Add multi-org experiment

UI will use to toggle different views
2024-05-13 13:46:01 -05:00
Garrett Delfosse ebee9288ae fix: properly convert max port share level for oss (#13261) 2024-05-13 14:37:51 -04:00
Bruno Quaresma a5a64948cd feat(site): open README links in new tab (#13264) 2024-05-13 15:11:01 -03:00
Bruno Quaresma 8412450ae3 chore(site): fix portforward issue with vite (#13262) 2024-05-13 17:13:41 +00:00
dependabot[bot] c41d0efff9 chore: bump github.com/prometheus/client_golang from 1.18.0 to 1.19.1 (#13232)
* chore: bump github.com/prometheus/client_golang from 1.18.0 to 1.19.1
2024-05-13 13:01:28 +00:00
Muhammad Atif Ali 7358c1b1ac chore(dogfood): bump module versions to latest (#13246)
We should use the latest versions as these are the ones most customers will use.

We can automate this with @dependabot once we resolve https://github.com/coder/registry.coder.com/issues/13
2024-05-13 09:51:47 +03:00
dependabot[bot] 4e7381341f chore: bump google.golang.org/api from 0.176.1 to 0.180.0 (#13235)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-12 23:34:59 +03:00
dependabot[bot] 228b99d9c2 chore: bump google.golang.org/protobuf from 1.33.0 to 1.34.1 (#13236)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-12 23:33:37 +03:00
Michael Smith f13b1c9af6 refactor: improve test isolation for Axios API logic (#13125)
* wip: commit progress on code split-up

* wip: commit more progress

* wip: finish initial version of class implementation

* chore: update all import paths to go through client instance

* fix: remove temp comments

* refactor: smoooooooosh the API

* refactor: update import setup for tests
2024-05-12 19:05:22 +00:00
dependabot[bot] 5ddbeddf85 chore: bump protobufjs from 7.2.4 to 7.2.5 in /site (#13245)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.2.4 to 7.2.5.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.2.4...protobufjs-v7.2.5)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-10 22:19:19 +00:00
dependabot[bot] 3d707cbe5a chore: bump tar from 6.2.0 to 6.2.1 in /site (#13244)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.2.0...v6.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-11 01:10:39 +03:00
Jon Ayers ee817b4d80 fix: fix nix flake sed command (#13243) 2024-05-11 01:10:19 +03:00
dependabot[bot] c557c25b3d chore: bump golang.org/x/tools from 0.20.0 to 0.21.0 (#13237)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  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-05-11 00:08:38 +03:00
Jon Ayers 82c1562f82 fix: skip license review for dependabot (#13239) 2024-05-10 18:14:03 +00:00
dependabot[bot] 8c9560ddb8 ci: bump the github-actions group with 2 updates (#13238)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-10 17:38:07 +00:00
Kayla Washburn-Love 7eb228e3ff feat: popover paywall in appearance settings (#13217) 2024-05-10 11:21:21 -06:00
Muhammad Atif Ali 6182ee90f0 chore: remove dependabot config for dogfood template (#13230) 2024-05-10 20:14:37 +03:00
Danny Kopping 989575c5b6 chore: prevent commit signing in tests (#13222) 2024-05-10 16:35:59 +02:00
Danny Kopping 4671ebb330 feat: measure pubsub latencies and expose metrics (#13126) 2024-05-10 12:31:49 +00:00
Kayla Washburn-Love e14f8fb64b fix(install.sh): install from github when using --stable on macOS (#13216) 2024-05-09 13:14:31 -06:00
Muhammad Atif Ali 679099373b docs(ides): document connection via JetBrains Fleet (#13179)
* docs: add docs to connect via JetBrains Fleet

* Create fleet.md

* Update fleet.md

* Create ssh-connect-to-coder.png

* Add files via upload

* `make fmt`

* Update fleet.md

* Update docs/ides/fleet.md

Co-authored-by: Kyle Carberry <kyle@coder.com>

* Update fleet.md

---------

Co-authored-by: Kyle Carberry <kyle@coder.com>
2024-05-09 21:29:53 +03:00
Kayla Washburn-Love d8e0be6ee6 feat: add support for multiple banners (#13081) 2024-05-08 15:40:43 -06:00
Steven Masley a4bd50c985 chore: enable terraform provisioners in e2e by default (#13134)
* skip docker test for now, it leaks containers
2024-05-08 13:34:22 -05:00
Spike Curtis 1832a755e1 docs: describe AWS hard NAT (#13205)
Documents what I've learned about getting direct connections on AWS.  Several customers have had issues.
2024-05-08 20:29:12 +04:00
Bruno Quaresma 35cb572888 refactor(site): refactor the workspace settings form (#13198) 2024-05-08 13:12:48 -03:00
Bruno Quaresma 24448e79fe fix: prevent extending if template disallows (#13182) 2024-05-08 12:58:14 -03:00
Stephen Kirby c73d5a2617 docs: bump mainline version to v2.11.0 (#13202)
* docs: bump mainline version to v2.11.0

* bump release schedule
2024-05-07 16:29:51 -05:00
Mathias Fredriksson 06dd656e08 ci: disable make test-migrations in release.yaml (#13201) 2024-05-07 17:15:12 +00:00
dependabot[bot] b7a921a2bf chore: bump express from 4.18.2 to 4.19.2 in /site (#13196)
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 11:08:33 -04:00
dependabot[bot] 30227dae97 chore: bump follow-redirects from 1.15.4 to 1.15.6 in /site (#13197)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 11:08:24 -04:00
dependabot[bot] 96f2cec541 chore: bump vite from 4.5.2 to 4.5.3 in /site (#13189)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.2 to 4.5.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.3/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 14:01:13 +03:00
dependabot[bot] 3905e2c541 chore: bump undici from 6.7.1 to 6.11.1 in /site (#13190)
Bumps [undici](https://github.com/nodejs/undici) from 6.7.1 to 6.11.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.7.1...v6.11.1)

---
updated-dependencies:
- dependency-name: undici
  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-05-07 14:00:48 +03:00
Colin Adler 421c0d1242 chore: add nginx topology to tailnet tests (#13188) 2024-05-07 18:17:38 +10:00
Dean Sheather 677be9aab2 chore: add tailnet integration test CI job (#13181) 2024-05-07 06:21:17 +00:00
Dean Sheather 72f2efe048 chore: implement easy NAT direct integration test (#13169) 2024-05-07 06:07:57 +00:00
Dean Sheather 5e8f97d8c3 chore: add DERP websocket integration tests (#13168)
- `DERPForceWebSockets`: Test that DERP over WebSocket (as well as DERPForceWebSockets works). This does not test the actual DERP failure detection code and automatic fallback.
- `DERPFallbackWebSockets`: Test that falling back to DERP over WebSocket works.

Also:
- Rearranges some test code and refactors `TestTopology.StartServer` to be `TestTopology.ServerOptions` and take a struct instead of a function

Closes #13045
2024-05-06 20:37:01 -07:00
Muhammad Atif Ali b56c9c438f ci: only send docs-check notifications on schedule (#13191) 2024-05-07 01:40:18 +03:00
Idleite 6f5c183c80 docs: show the proper Redirect URI for Gitea (#13162) 2024-05-06 22:28:04 +00:00
Kyle Carberry 3e3118794f chore: add build targets to nix flake (#13186)
* chore: add build targets to nix flake

Enables `nix build github:coder/coder#main`!

* Fix all packages

* Add back pnpm

* Update flake.nix

Co-authored-by: Asher <ash@coder.com>

* Remove yarn

* fmt

---------

Co-authored-by: Asher <ash@coder.com>
2024-05-06 18:21:20 -04:00
Muhammad Atif Ali 05facc971b ci: sync terraform version (#13187) 2024-05-06 20:06:21 +00:00
dependabot[bot] e7c87a806b ci: bump the github-actions group with 2 updates (#13177)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 22:43:17 +03:00
Spike Curtis dfd27f559e Revert "chore: fix build ci (#13164)" (#13180)
This reverts commit 886a97b425.
2024-05-06 13:13:24 +00:00
Spike Curtis deee9492e3 Revert "fix: install openrc service on alpine (#12294) (#12870)" (#13178)
This reverts commit b20c63c185.
2024-05-06 16:48:19 +04:00
Mathias Fredriksson 619ec927e9 test(coderd/database): fix DST issue in dbpurge test (#13170)
Fixes #13165
2024-05-06 14:14:38 +03:00
Spike Curtis e76b595052 fix: use a native websocket.NetConn for agent RPC client (#13142)
One cause of #13139 is a peculiar failure mode of `WebsocketNetConn` which causes it to return `context.Canceled` in some circumstances when the underlying websocket fails.  We have special processing for that error in the `agent.run()` routine, which is erroneously being triggered.

Since we don't actually need the returned context from `WebsocketNetConn`, we can simplify and just use the netConn from the `websocket` library directly.
2024-05-06 15:00:34 +04:00
Spike Curtis d51c6912a7 fix: make handleManifest always signal dependents (#13141)
Fixes #13139

Using a bare channel to signal dependent goroutines means that we can only signal success, not failure, which leads to deadlock if we fail in a way that doesn't cause the whole `apiConnRoutineManager` to tear down routines.

Instead, we use a new object called a `checkpoint` that signals success or failure, so that dependent routines get unblocked if the routine they depend on fails.
2024-05-06 14:47:41 +04:00
Spike Curtis 2efb46a10e chore: remove superfluous context.Canceled handling (#13140)
Removes a check for `context.Canceled` inside the `handleManifest` routine.  This checking is handled in the `apiConnRoutineManager`, so checking inside the handler is redundant.
2024-05-06 14:33:16 +04:00
Muhammad Atif Ali 7c3ec51997 docs(admin/external-auth.md): add JFrog Artifactory guide (#13166) 2024-05-06 11:34:21 +03:00
Muhammad Atif Ali 3e77f5b512 chore(docs): replace git-auth with external-auth (#13167) 2024-05-06 11:17:19 +03:00
Dean Sheather d956af0a3a chore: add EasyNATDERP tailnet integration test (#13138) 2024-05-06 15:36:54 +10:00
Colin Adler 886a97b425 chore: fix build ci (#13164) 2024-05-06 05:01:47 +00:00
Colin Adler 13dd526f11 fix: prevent stdlib logging from messing up ssh (#13161)
Fixes https://github.com/coder/coder/issues/13144
2024-05-03 22:12:06 +00:00
recanman b20c63c185 fix: install openrc service on alpine (#12294) (#12870)
* fix: install openrc service on alpine (#12294)

* fmt

---------

Co-authored-by: Kyle Carberry <kyle@coder.com>
2024-05-03 21:09:23 +00:00
Michael Brewer 060f023174 feat: mask coder login token to enhance security (#12948)
* feat(login): treat coder token as a secret

* Update login.go
2024-05-03 17:03:13 -04:00
Colin Adler 205c43da99 fix(enterprise): mark nodes from unhealthy coordinators as lost (#13123)
Instead of removing the mappings of unhealthy coordinators entirely,
mark them as lost instead. This prevents peers from disappearing from
other peers if a coordinator misses a heartbeat.
2024-05-03 14:07:29 -05:00
2223 changed files with 205524 additions and 136939 deletions
+10 -10
View File
@@ -1,13 +1,13 @@
{
"name": "Development environments on your infrastructure",
"image": "codercom/oss-dogfood:latest",
"name": "Development environments on your infrastructure",
"image": "codercom/oss-dogfood:latest",
"features": {
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": "false"
}
},
// SYS_PTRACE to enable go debugging
"runArgs": ["--cap-add=SYS_PTRACE"]
"features": {
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": "false"
}
},
// SYS_PTRACE to enable go debugging
"runArgs": ["--cap-add=SYS_PTRACE"]
}
+1 -1
View File
@@ -7,7 +7,7 @@ trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
[*.{md,json,yaml,yml,tf,tfvars,nix}]
[*.{yaml,yml,tf,tfvars,nix}]
indent_style = space
indent_size = 2
+2
View File
@@ -3,3 +3,5 @@
# chore: format code with semicolons when using prettier (#9555)
988c9af0153561397686c119da9d1336d2433fdd
# chore: use tabs for prettier and biome (#14283)
95a7c0c4f087744a22c2e88dd3c5d30024d5fb02
+2 -2
View File
@@ -1,7 +1,7 @@
# Generated files
coderd/apidoc/docs.go linguist-generated=true
docs/api/*.md linguist-generated=true
docs/cli/*.md linguist-generated=true
docs/reference/api/*.md linguist-generated=true
docs/reference/cli/*.md linguist-generated=true
coderd/apidoc/swagger.json linguist-generated=true
coderd/database/dump.sql linguist-generated=true
peerbroker/proto/*.go linguist-generated=true
+2 -2
View File
@@ -4,12 +4,12 @@ description: |
inputs:
version:
description: "The Go version to use."
default: "1.21.9"
default: "1.22.5"
runs:
using: "composite"
steps:
- name: Setup Go
uses: buildjet/setup-go@v5
uses: actions/setup-go@v5
with:
go-version: ${{ inputs.version }}
+3 -3
View File
@@ -13,11 +13,11 @@ runs:
- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 8
version: 9.6
- name: Setup Node
uses: buildjet/setup-node@v4.0.1
uses: actions/setup-node@v4.0.3
with:
node-version: 18.19.0
node-version: 20.16.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,5 +7,5 @@ runs:
- name: Install Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.7
terraform_version: 1.9.2
terraform_wrapper: false
+34 -51
View File
@@ -39,6 +39,10 @@ updates:
prefix: "chore"
labels: []
open-pull-requests-limit: 15
groups:
x:
patterns:
- "golang.org/x/*"
ignore:
# Ignore patch updates for all dependencies
- dependency-name: "*"
@@ -61,7 +65,9 @@ updates:
- dependency-name: "terraform"
- package-ecosystem: "npm"
directory: "/site/"
directories:
- "/site"
- "/offlinedocs"
schedule:
interval: "monthly"
time: "06:00"
@@ -71,58 +77,35 @@ updates:
commit-message:
prefix: "chore"
labels: []
groups:
xterm:
patterns:
- "@xterm*"
mui:
patterns:
- "@mui*"
react:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
emotion:
patterns:
- "@emotion*"
exclude-patterns:
- "jest-runner-eslint"
jest:
patterns:
- "jest"
- "@types/jest"
vite:
patterns:
- "vite*"
- "@vitejs/plugin-react"
ignore:
# Ignore patch updates for all dependencies
# Ignore major version updates to avoid breaking changes
- dependency-name: "*"
update-types:
- version-update:semver-patch
# Ignore major updates to Node.js types, because they need to
# correspond to the Node.js engine version
- dependency-name: "@types/node"
update-types:
- version-update:semver-major
open-pull-requests-limit: 15
groups:
site:
patterns:
- "*"
- package-ecosystem: "npm"
directory: "/offlinedocs/"
schedule:
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
reviewers:
- "coder/ts"
commit-message:
prefix: "chore"
labels: []
ignore:
# Ignore patch updates for all dependencies
- dependency-name: "*"
update-types:
- version-update:semver-patch
# Ignore major updates to Node.js types, because they need to
# correspond to the Node.js engine version
- dependency-name: "@types/node"
update-types:
- version-update:semver-major
groups:
offlinedocs:
patterns:
- "*"
# Update dogfood.
- package-ecosystem: "terraform"
directory: "/dogfood/"
schedule:
interval: "weekly"
time: "06:00"
timezone: "America/Chicago"
commit-message:
prefix: "chore"
labels: []
ignore:
# We likely want to update this ourselves.
- dependency-name: "coder/coder"
+11 -10
View File
@@ -86,6 +86,7 @@ provider "kubernetes" {
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_agent" "main" {
os = "linux"
@@ -175,21 +176,21 @@ resource "coder_app" "code-server" {
resource "kubernetes_persistent_volume_claim" "home" {
metadata {
name = "coder-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}-home"
name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}-home"
namespace = var.namespace
labels = {
"app.kubernetes.io/name" = "coder-pvc"
"app.kubernetes.io/instance" = "coder-pvc-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}"
"app.kubernetes.io/instance" = "coder-pvc-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
"app.kubernetes.io/part-of" = "coder"
//Coder-specific labels.
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace.me.owner_id
"com.coder.user.username" = data.coder_workspace.me.owner
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
annotations = {
"com.coder.user.email" = data.coder_workspace.me.owner_email
"com.coder.user.email" = data.coder_workspace_owner.me.email
}
}
wait_until_bound = false
@@ -210,20 +211,20 @@ resource "kubernetes_deployment" "main" {
]
wait_for_rollout = false
metadata {
name = "coder-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}"
name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
namespace = var.namespace
labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}"
"app.kubernetes.io/instance" = "coder-workspace-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace.me.owner_id
"com.coder.user.username" = data.coder_workspace.me.owner
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
annotations = {
"com.coder.user.email" = data.coder_workspace.me.owner_email
"com.coder.user.email" = data.coder_workspace_owner.me.email
}
}
+143 -67
View File
@@ -37,8 +37,10 @@ jobs:
k8s: ${{ steps.filter.outputs.k8s }}
ci: ${{ steps.filter.outputs.ci }}
db: ${{ steps.filter.outputs.db }}
gomod: ${{ steps.filter.outputs.gomod }}
offlinedocs-only: ${{ steps.filter.outputs.offlinedocs_count == steps.filter.outputs.all_count }}
offlinedocs: ${{ steps.filter.outputs.offlinedocs }}
tailnet-integration: ${{ steps.filter.outputs.tailnet-integration }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -90,6 +92,9 @@ jobs:
- "scaletest/**"
- "tailnet/**"
- "testutil/**"
gomod:
- "go.mod"
- "go.sum"
ts:
- "site/**"
- "Makefile"
@@ -103,15 +108,52 @@ jobs:
- ".github/workflows/ci.yaml"
offlinedocs:
- "offlinedocs/**"
tailnet-integration:
- "tailnet/**"
- "go.mod"
- "go.sum"
- id: debug
run: |
echo "${{ toJSON(steps.filter )}}"
update-flake:
needs: changes
if: needs.changes.outputs.gomod == 'true'
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
# See: https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#commits-made-by-this-action-do-not-trigger-new-workflow-runs
token: ${{ secrets.CDRCI_GITHUB_TOKEN }}
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Update Nix Flake SRI Hash
run: ./scripts/update-flake.sh
# auto update flake for dependabot
- uses: stefanzweifel/git-auto-commit-action@v5
if: github.actor == 'dependabot[bot]'
with:
# Allows dependabot to still rebase!
commit_message: "[dependabot skip] Update Nix Flake SRI Hash"
commit_user_name: "dependabot[bot]"
commit_user_email: "49699333+dependabot[bot]@users.noreply.github.com>"
commit_author: "dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>"
# require everyone else to update it themselves
- name: Ensure No Changes
if: github.actor != 'dependabot[bot]'
run: git diff --exit-code
lint:
needs: changes
if: needs.changes.outputs.offlinedocs-only == 'false' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -126,13 +168,13 @@ jobs:
- name: Get golangci-lint cache dir
run: |
linter_ver=$(egrep -o 'GOLANGCI_LINT_VERSION=\S+' dogfood/Dockerfile | cut -d '=' -f 2)
linter_ver=$(egrep -o 'GOLANGCI_LINT_VERSION=\S+' dogfood/contents/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@v4
uses: actions/cache@v4
with:
path: |
${{ env.LINT_CACHE_DIR }}
@@ -142,7 +184,7 @@ jobs:
# Check for any typos
- name: Check for typos
uses: crate-ci/typos@v1.20.10
uses: crate-ci/typos@v1.23.6
with:
config: .github/workflows/typos.toml
@@ -163,9 +205,15 @@ jobs:
run: |
make --output-sync=line -j lint
- name: Check workflow files
run: |
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.6.22
./actionlint -color -shellcheck= -ignore "set-output"
shell: bash
gen:
timeout-minutes: 8
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.docs-only == 'false' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
steps:
@@ -183,6 +231,9 @@ jobs:
- name: Setup sqlc
uses: ./.github/actions/setup-sqlc
- name: Setup Terraform
uses: ./.github/actions/setup-tf
- name: go install tools
run: |
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30
@@ -212,7 +263,7 @@ jobs:
fmt:
needs: changes
if: needs.changes.outputs.offlinedocs-only == 'false' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
timeout-minutes: 7
steps:
- name: Checkout
@@ -223,12 +274,9 @@ jobs:
- name: Setup Node
uses: ./.github/actions/setup-node
# Use default Go version
- name: Setup Go
uses: buildjet/setup-go@v5
with:
# This doesn't need caching. It's super fast anyways!
cache: false
go-version: 1.21.9
uses: ./.github/actions/setup-go
- name: Install shfmt
run: go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0
@@ -242,7 +290,7 @@ jobs:
run: ./scripts/check_unstaged.sh
test-go:
runs-on: ${{ matrix.os == 'ubuntu-latest' && github.repository_owner == 'coder' && 'buildjet-4vcpu-ubuntu-2204' || matrix.os == 'macos-latest' && github.repository_owner == 'coder' && 'macos-latest-xlarge' || matrix.os == 'windows-2022' && github.repository_owner == 'coder' && 'windows-latest-16-cores' || matrix.os }}
runs-on: ${{ matrix.os == 'ubuntu-latest' && github.repository_owner == 'coder' && 'depot-ubuntu-22.04-4' || matrix.os == 'macos-latest' && github.repository_owner == 'coder' && 'macos-latest-xlarge' || matrix.os == 'windows-2022' && github.repository_owner == 'coder' && 'windows-latest-16-cores' || matrix.os }}
needs: changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 20
@@ -298,7 +346,7 @@ jobs:
api-key: ${{ secrets.DATADOG_API_KEY }}
test-go-pg:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs:
- changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
@@ -320,8 +368,50 @@ jobs:
uses: ./.github/actions/setup-tf
- name: Test with PostgreSQL Database
env:
POSTGRES_VERSION: "13"
TS_DEBUG_DISCO: "true"
run: |
make test-postgres
- name: Upload test stats to Datadog
timeout-minutes: 1
continue-on-error: true
uses: ./.github/actions/upload-datadog
if: success() || failure()
with:
api-key: ${{ secrets.DATADOG_API_KEY }}
# NOTE: this could instead be defined as a matrix strategy, but we want to
# only block merging if tests on postgres 13 fail. Using a matrix strategy
# here makes the check in the above `required` job rather complicated.
test-go-pg-16:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
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
# goroutines. Setting this to the timeout +5m should work quite well
# even if some of the preceding steps are slow.
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Setup Terraform
uses: ./.github/actions/setup-tf
- name: Test with PostgreSQL Database
env:
POSTGRES_VERSION: "16"
TS_DEBUG_DISCO: "true"
run: |
export TS_DEBUG_DISCO=true
make test-postgres
- name: Upload test stats to Datadog
@@ -333,7 +423,7 @@ jobs:
api-key: ${{ secrets.DATADOG_API_KEY }}
test-go-race:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 25
@@ -361,8 +451,36 @@ jobs:
with:
api-key: ${{ secrets.DATADOG_API_KEY }}
# Tailnet integration tests only run when the `tailnet` directory or `go.sum`
# and `go.mod` are changed. These tests are to ensure we don't add regressions
# to tailnet, either due to our code or due to updating dependencies.
#
# These tests are skipped in the main go test jobs because they require root
# and mess with networking.
test-go-tailnet-integration:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
# Unnecessary to run on main for now
if: needs.changes.outputs.tailnet-integration == 'true' || needs.changes.outputs.ci == 'true'
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Go
uses: ./.github/actions/setup-go
# Used by some integration tests.
- name: Install Nginx
run: sudo apt-get update && sudo apt-get install -y nginx
- name: Run Tests
run: make test-tailnet-integration
test-js:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 20
@@ -379,7 +497,7 @@ jobs:
working-directory: site
test-e2e:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-16vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 20
@@ -523,7 +641,7 @@ jobs:
offlinedocs:
name: offlinedocs
needs: changes
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
if: needs.changes.outputs.offlinedocs == 'true' || needs.changes.outputs.ci == 'true' || needs.changes.outputs.docs == 'true'
steps:
@@ -591,7 +709,6 @@ jobs:
- test-e2e
- offlinedocs
- sqlc-vet
- dependency-license-review
# Allow this job to run even if the needed jobs fail, are skipped or
# cancelled.
if: always()
@@ -608,7 +725,6 @@ jobs:
echo "- test-js: ${{ needs.test-js.result }}"
echo "- test-e2e: ${{ needs.test-e2e.result }}"
echo "- offlinedocs: ${{ needs.offlinedocs.result }}"
echo "- dependency-license-review: ${{ needs.dependency-license-review.result }}"
echo
# We allow skipped jobs to pass, but not failed or cancelled jobs.
@@ -621,11 +737,10 @@ jobs:
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)
# to main branch.
needs: changes
if: github.ref == 'refs/heads/main' && 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' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
outputs:
@@ -685,13 +800,15 @@ jobs:
echo "tag=$tag" >> $GITHUB_OUTPUT
# build images for each architecture
make -j build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# note: omitting the -j argument to avoid race conditions when pushing
make 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
# note: omitting the -j argument to avoid race conditions when pushing
make push/build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# Define specific tags
tags=("$tag" "main" "latest")
@@ -831,7 +948,7 @@ jobs:
# 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' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.db == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
steps:
@@ -849,44 +966,3 @@ jobs:
- name: Setup and run sqlc vet
run: |
make sqlc-vet
# dependency-license-review checks that no license-incompatible dependencies have been introduced.
# This action is not intended to do a vulnerability check since that is handled by a separate action.
dependency-license-review:
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
- name: "Dependency Review"
id: review
# TODO: Replace this with the latest release once https://github.com/actions/dependency-review-action/pull/761 is merged.
uses: actions/dependency-review-action@49fbbe0acb033b7824f26d00b005d7d598d76301
with:
allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, CC0-1.0, ISC, MIT, MIT-0, MPL-2.0
allow-dependencies-licenses: "pkg:golang/github.com/pelletier/go-toml/v2"
license-check: true
vulnerability-check: false
- name: "Report"
# make sure this step runs even if the previous failed
if: always()
shell: bash
env:
VULNERABLE_CHANGES: ${{ steps.review.outputs.invalid-license-changes }}
run: |
fields=( "unlicensed" "unresolved" "forbidden" )
# This is unfortunate that we have to do this but the action does not support failing on
# an unknown license. The unknown dependency could easily have a GPL license which
# would be problematic for us.
# Track https://github.com/actions/dependency-review-action/issues/672 for when
# we can remove this brittle workaround.
for field in "${fields[@]}"; do
# Use jq to check if the array is not empty
if [[ $(echo "$VULNERABLE_CHANGES" | jq ".${field} | length") -ne 0 ]]; then
echo "Invalid or unknown licenses detected, contact @sreya to ensure your added dependency falls under one of our allowed licenses."
echo "$VULNERABLE_CHANGES" | jq
exit 1
fi
done
echo "No incompatible licenses detected"
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
steps:
- name: cla
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.3.2
uses: contributor-assistant/github-action@v2.5.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
+7 -1
View File
@@ -8,6 +8,11 @@ on:
- scripts/Dockerfile.base
- scripts/Dockerfile
pull_request:
paths:
- scripts/Dockerfile.base
- .github/workflows/docker-base.yaml
schedule:
# Run every week at 09:43 on Monday, Wednesday and Friday. We build this
# frequently to ensure that packages are up-to-date.
@@ -57,11 +62,12 @@ jobs:
platforms: linux/amd64,linux/arm64,linux/arm/v7
pull: true
no-cache: true
push: true
push: ${{ github.event_name != 'pull_request' }}
tags: |
ghcr.io/coder/coder-base:latest
- name: Verify that images are pushed properly
if: github.event_name != 'pull_request'
run: |
# retry 10 times with a 5 second delay as the images may not be
# available immediately
+24 -14
View File
@@ -17,8 +17,13 @@ on:
- "flake.nix"
workflow_dispatch:
permissions:
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
id-token: write
jobs:
build_image:
if: github.actor != 'dependabot[bot]' # Skip Dependabot PRs
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -55,7 +60,7 @@ jobs:
project: b4q6ltmpzh
token: ${{ secrets.DEPOT_TOKEN }}
buildx-fallback: true
context: "{{defaultContext}}:dogfood"
context: "{{defaultContext}}:dogfood/contents"
pull: true
save: true
push: ${{ github.ref == 'refs/heads/main' }}
@@ -68,7 +73,7 @@ jobs:
token: ${{ secrets.DEPOT_TOKEN }}
buildx-fallback: true
context: "."
file: "dogfood/Dockerfile.nix"
file: "dogfood/contents/Dockerfile.nix"
pull: true
save: true
push: ${{ github.ref == 'refs/heads/main' }}
@@ -84,11 +89,20 @@ jobs:
- name: Setup Terraform
uses: ./.github/actions/setup-tf
- 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: Terraform init and validate
run: |
cd dogfood
terraform init -upgrade
terraform validate
cd contents
terraform init -upgrade
terraform validate
- name: Get short commit SHA
if: github.ref == 'refs/heads/main'
@@ -100,22 +114,18 @@ jobs:
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"
cd dogfood
terraform apply -auto-approve
env:
# Consumed by Coder CLI
# Consumed by coderd provider
CODER_URL: https://dev.coder.com
CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }}
# Template source & details
CODER_TEMPLATE_NAME: ${{ secrets.CODER_TEMPLATE_NAME }}
CODER_TEMPLATE_VERSION: ${{ steps.vars.outputs.sha_short }}
CODER_TEMPLATE_DIR: ./dogfood
CODER_TEMPLATE_MESSAGE: ${{ steps.message.outputs.pr_title }}
TF_VAR_CODER_TEMPLATE_NAME: ${{ secrets.CODER_TEMPLATE_NAME }}
TF_VAR_CODER_TEMPLATE_VERSION: ${{ steps.vars.outputs.sha_short }}
TF_VAR_CODER_TEMPLATE_DIR: ./contents
TF_VAR_CODER_TEMPLATE_MESSAGE: ${{ steps.message.outputs.pr_title }}
TF_LOG: info
+24 -24
View File
@@ -1,26 +1,26 @@
{
"ignorePatterns": [
{
"pattern": "://localhost"
},
{
"pattern": "://.*.?example\\.com"
},
{
"pattern": "developer.github.com"
},
{
"pattern": "docs.github.com"
},
{
"pattern": "support.google.com"
},
{
"pattern": "tailscale.com"
},
{
"pattern": "wireguard.com"
}
],
"aliveStatusCodes": [200, 0]
"ignorePatterns": [
{
"pattern": "://localhost"
},
{
"pattern": "://.*.?example\\.com"
},
{
"pattern": "developer.github.com"
},
{
"pattern": "docs.github.com"
},
{
"pattern": "support.google.com"
},
{
"pattern": "tailscale.com"
},
{
"pattern": "wireguard.com"
}
],
"aliveStatusCodes": [200, 0]
}
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
# While GitHub's toaster runners are likelier to flake, we want consistency
# between this environment and the regular test environment for DataDog
# statistics and to only show real workflow threats.
runs-on: "buildjet-8vcpu-ubuntu-2204"
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
# This runner costs 0.016 USD per minute,
# so 0.016 * 240 = 3.84 USD per run.
timeout-minutes: 240
@@ -40,7 +40,7 @@ jobs:
go-timing:
# We run these tests with p=1 so we don't need a lot of compute.
runs-on: "buildjet-2vcpu-ubuntu-2204"
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Checkout
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Assign author
uses: toshimaru/auto-author-assign@v2.1.0
uses: toshimaru/auto-author-assign@v2.1.1
+3 -3
View File
@@ -101,7 +101,7 @@ jobs:
run: |
set -euo pipefail
mkdir -p ~/.kube
echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG }}" > ~/.kube/config
echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG_BASE64 }}" | base64 --decode > ~/.kube/config
chmod 644 ~/.kube/config
export KUBECONFIG=~/.kube/config
@@ -189,7 +189,7 @@ jobs:
needs: get_info
# Run build job only if there are changes in the files that we care about or if the workflow is manually triggered with --build flag
if: needs.get_info.outputs.BUILD == 'true'
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
# This concurrency only cancels build jobs if a new build is triggred. It will avoid cancelling the current deployemtn in case of docs chnages.
concurrency:
group: build-${{ github.workflow }}-${{ github.ref }}-${{ needs.get_info.outputs.BUILD }}
@@ -253,7 +253,7 @@ jobs:
run: |
set -euo pipefail
mkdir -p ~/.kube
echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG }}" > ~/.kube/config
echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG_BASE64 }}" | base64 --decode > ~/.kube/config
chmod 644 ~/.kube/config
export KUBECONFIG=~/.kube/config
+20
View File
@@ -0,0 +1,20 @@
name: release-validation
on:
push:
tags:
- "v*"
jobs:
network-performance:
runs-on: ubuntu-latest
steps:
- name: Run Schmoder CI
uses: benc-uk/workflow-dispatch@v1.2.4
with:
workflow: ci.yaml
repo: coder/schmoder
inputs: '{ "num_releases": "3", "commit": "${{ github.sha }}" }'
token: ${{ secrets.CDRCI_SCHMODER_ACTIONS_TOKEN }}
ref: main
+6 -6
View File
@@ -39,7 +39,7 @@ env:
jobs:
release:
name: Build and publish
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
env:
# Necessary for Docker manifest
DOCKER_CLI_EXPERIMENTAL: "enabled"
@@ -180,7 +180,7 @@ jobs:
- name: Test migrations from current ref to main
run: |
make test-migrations
POSTGRES_VERSION=13 make test-migrations
# Setup GCloud for signing Windows binaries.
- name: Authenticate to Google Cloud
@@ -297,7 +297,7 @@ jobs:
# build Docker images for each architecture
version="$(./scripts/version.sh)"
make -j build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
make build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# we can't build multi-arch if the images aren't pushed, so quit now
# if dry-running
@@ -308,7 +308,7 @@ jobs:
# 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.tag
make push/build/coder_"$version"_linux.tag
# if the current version is equal to the highest (according to semver)
# version in the repo, also create a multi-arch image as ":latest" and
@@ -396,14 +396,14 @@ jobs:
./build/*.rpm
retention-days: 7
- name: Start Packer builds
- name: Send repository-dispatch event
if: ${{ !inputs.dry_run }}
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.CDRCI_GITHUB_TOKEN }}
repository: coder/packages
event-type: coder-release
client-payload: '{"coder_version": "${{ steps.version.outputs.version }}"}'
client-payload: '{"coder_version": "${{ steps.version.outputs.version }}", "release_channel": "${{ inputs.release_channel }}"}'
publish-homebrew:
name: Publish to Homebrew tap
+3 -3
View File
@@ -23,7 +23,7 @@ concurrency:
jobs:
codeql:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -56,7 +56,7 @@ jobs:
"${{ secrets.SLACK_SECURITY_FAILURE_WEBHOOK_URL }}"
trivy:
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -114,7 +114,7 @@ jobs:
echo "image=$(cat "$image_job")" >> $GITHUB_OUTPUT
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@d710430a6722f083d3b36b8339ff66b32f22ee55
uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
with:
image-ref: ${{ steps.build.outputs.image }}
format: sarif
+9 -2
View File
@@ -14,8 +14,14 @@ darcula = "darcula"
Hashi = "Hashi"
trialer = "trialer"
encrypter = "encrypter"
hel = "hel" # as in helsinki
pn = "pn" # this is used as proto node
# as in helsinki
hel = "hel"
# this is used as proto node
pn = "pn"
# typos doesn't like the EDE in TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
EDE = "EDE"
# HELO is an SMTP command
HELO = "HELO"
[files]
extend-exclude = [
@@ -33,4 +39,5 @@ extend-exclude = [
"**/pnpm-lock.yaml",
"tailnet/testdata/**",
"site/src/pages/SetupPage/countries.tsx",
"provisioner/terraform/testdata/**",
]
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
file-path: "./README.md"
- name: Send Slack notification
if: failure() && github.event_name != 'workflow_dispatch'
if: failure() && github.event_name == 'schedule'
run: |
curl -X POST -H 'Content-type: application/json' -d '{"msg":"Broken links found in the documentation. Please check the logs at ${{ env.LOGS_URL }}"}' ${{ secrets.DOCS_LINK_SLACK_WEBHOOK }}
echo "Sent Slack notification"
+3
View File
@@ -68,3 +68,6 @@ result
# Filebrowser.db
**/filebrowser.db
# pnpm
.pnpm-store/
+5
View File
@@ -195,6 +195,11 @@ linters-settings:
- name: var-naming
- name: waitgroup-by-value
# irrelevant as of Go v1.22: https://go.dev/blog/loopvar-preview
govet:
disable:
- loopclosure
issues:
# Rules listed here: https://github.com/securego/gosec#available-rules
exclude-rules:
+9 -12
View File
@@ -71,24 +71,21 @@ result
# Filebrowser.db
**/filebrowser.db
# pnpm
.pnpm-store/
# .prettierignore.include:
# Helm templates contain variables that are invalid YAML and can't be formatted
# by Prettier.
helm/**/templates/*.yaml
# Terraform state files used in tests, these are automatically generated.
# Example: provisioner/terraform/testdata/instance-id/instance-id.tfstate.json
**/testdata/**/*.tf*.json
# 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
testdata/
# Ignore generated files
**/pnpm-lock.yaml
# Ignore generated JSON (e.g. examples/examples.gen.json).
**/*.gen.json
# Everything in site/ is formatted by Biome. For the rest of the repo though, we
# need broader language support.
site/
+6 -12
View File
@@ -2,19 +2,13 @@
# by Prettier.
helm/**/templates/*.yaml
# Terraform state files used in tests, these are automatically generated.
# Example: provisioner/terraform/testdata/instance-id/instance-id.tfstate.json
**/testdata/**/*.tf*.json
# 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
testdata/
# Ignore generated files
**/pnpm-lock.yaml
# Ignore generated JSON (e.g. examples/examples.gen.json).
**/*.gen.json
# Everything in site/ is formatted by Biome. For the rest of the repo though, we
# need broader language support.
site/
+3 -3
View File
@@ -4,13 +4,13 @@
printWidth: 80
proseWrap: always
trailingComma: all
useTabs: false
useTabs: true
tabWidth: 2
overrides:
- files:
- README.md
- docs/api/**/*.md
- docs/cli/**/*.md
- docs/reference/api/**/*.md
- docs/reference/cli/**/*.md
- docs/changelogs/*.md
- .github/**/*.{yaml,yml,toml}
- scripts/**/*.{yaml,yml,toml}
+13 -13
View File
@@ -1,15 +1,15 @@
{
"recommendations": [
"github.vscode-codeql",
"golang.go",
"hashicorp.terraform",
"esbenp.prettier-vscode",
"foxundermoon.shell-format",
"emeraldwalk.runonsave",
"zxh404.vscode-proto3",
"redhat.vscode-yaml",
"streetsidesoftware.code-spell-checker",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
]
"recommendations": [
"github.vscode-codeql",
"golang.go",
"hashicorp.terraform",
"esbenp.prettier-vscode",
"foxundermoon.shell-format",
"emeraldwalk.runonsave",
"zxh404.vscode-proto3",
"redhat.vscode-yaml",
"streetsidesoftware.code-spell-checker",
"EditorConfig.EditorConfig",
"biomejs.biome"
]
}
+236 -224
View File
@@ -1,226 +1,238 @@
{
"cSpell.words": [
"afero",
"agentsdk",
"apps",
"ASKPASS",
"authcheck",
"autostop",
"awsidentity",
"bodyclose",
"buildinfo",
"buildname",
"circbuf",
"cliflag",
"cliui",
"codecov",
"coderd",
"coderdenttest",
"coderdtest",
"codersdk",
"contravariance",
"cronstrue",
"databasefake",
"dbgen",
"dbmem",
"dbtype",
"DERP",
"derphttp",
"derpmap",
"devel",
"devtunnel",
"dflags",
"drpc",
"drpcconn",
"drpcmux",
"drpcserver",
"Dsts",
"embeddedpostgres",
"enablements",
"enterprisemeta",
"errgroup",
"eventsourcemock",
"externalauth",
"Failf",
"fatih",
"Formik",
"gitauth",
"gitsshkey",
"goarch",
"gographviz",
"goleak",
"gonet",
"gossh",
"gsyslog",
"GTTY",
"hashicorp",
"hclsyntax",
"httpapi",
"httpmw",
"idtoken",
"Iflag",
"incpatch",
"initialisms",
"ipnstate",
"isatty",
"Jobf",
"Keygen",
"kirsle",
"Kubernetes",
"ldflags",
"magicsock",
"manifoldco",
"mapstructure",
"mattn",
"mitchellh",
"moby",
"namesgenerator",
"namespacing",
"netaddr",
"netip",
"netmap",
"netns",
"netstack",
"nettype",
"nfpms",
"nhooyr",
"nmcfg",
"nolint",
"nosec",
"ntqry",
"OIDC",
"oneof",
"opty",
"paralleltest",
"parameterscopeid",
"pqtype",
"prometheusmetrics",
"promhttp",
"protobuf",
"provisionerd",
"provisionerdserver",
"provisionersdk",
"ptty",
"ptys",
"ptytest",
"quickstart",
"reconfig",
"replicasync",
"retrier",
"rpty",
"SCIM",
"sdkproto",
"sdktrace",
"Signup",
"slogtest",
"sourcemapped",
"spinbutton",
"Srcs",
"stdbuf",
"stretchr",
"STTY",
"stuntest",
"tailbroker",
"tailcfg",
"tailexchange",
"tailnet",
"tailnettest",
"Tailscale",
"tanstack",
"tbody",
"TCGETS",
"tcpip",
"TCSETS",
"templateversions",
"testdata",
"testid",
"testutil",
"tfexec",
"tfjson",
"tfplan",
"tfstate",
"thead",
"tios",
"tmpdir",
"tokenconfig",
"Topbar",
"tparallel",
"trialer",
"trimprefix",
"tsdial",
"tslogger",
"tstun",
"turnconn",
"typegen",
"typesafe",
"unconvert",
"Untar",
"Userspace",
"VMID",
"walkthrough",
"weblinks",
"webrtc",
"wgcfg",
"wgconfig",
"wgengine",
"wgmonitor",
"wgnet",
"workspaceagent",
"workspaceagents",
"workspaceapp",
"workspaceapps",
"workspacebuilds",
"workspacename",
"wsjson",
"xerrors",
"xlarge",
"xsmall",
"yamux"
],
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
"emeraldwalk.runonsave": {
"commands": [
{
"match": "database/queries/*.sql",
"cmd": "make gen"
},
{
"match": "provisionerd/proto/provisionerd.proto",
"cmd": "make provisionerd/proto/provisionerd.pb.go"
}
]
},
"eslint.workingDirectories": ["./site"],
"search.exclude": {
"**.pb.go": true,
"**/*.gen.json": true,
"**/testdata/*": true,
"**Generated.ts": true,
"coderd/apidoc/**": true,
"docs/api/*.md": true,
"docs/templates/*.md": true,
"LICENSE": true,
"scripts/metricsdocgen/metrics": true,
"site/out/**": true,
"site/storybook-static/**": true,
"**.map": true,
"pnpm-lock.yaml": true
},
// Ensure files always have a newline.
"files.insertFinalNewline": true,
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.coverageDecorator": {
"type": "gutter",
"coveredGutterStyle": "blockgreen",
"uncoveredGutterStyle": "blockred"
},
// The codersdk is used by coderd another other packages extensively.
// To reduce redundancy in tests, it's covered by other packages.
// Since package coverage pairing can't be defined, all packages cover
// all other packages.
"go.testFlags": ["-short", "-coverpkg=./..."],
// We often use a version of TypeScript that's ahead of the version shipped
// with VS Code.
"typescript.tsdk": "./site/node_modules/typescript/lib"
"cSpell.words": [
"afero",
"agentsdk",
"apps",
"ASKPASS",
"authcheck",
"autostop",
"awsidentity",
"bodyclose",
"buildinfo",
"buildname",
"circbuf",
"cliflag",
"cliui",
"codecov",
"coderd",
"coderdenttest",
"coderdtest",
"codersdk",
"contravariance",
"cronstrue",
"databasefake",
"dbgen",
"dbmem",
"dbtype",
"DERP",
"derphttp",
"derpmap",
"devel",
"devtunnel",
"dflags",
"drpc",
"drpcconn",
"drpcmux",
"drpcserver",
"Dsts",
"embeddedpostgres",
"enablements",
"enterprisemeta",
"errgroup",
"eventsourcemock",
"externalauth",
"Failf",
"fatih",
"Formik",
"gitauth",
"gitsshkey",
"goarch",
"gographviz",
"goleak",
"gonet",
"gossh",
"gsyslog",
"GTTY",
"hashicorp",
"hclsyntax",
"httpapi",
"httpmw",
"idtoken",
"Iflag",
"incpatch",
"initialisms",
"ipnstate",
"isatty",
"Jobf",
"Keygen",
"kirsle",
"Kubernetes",
"ldflags",
"magicsock",
"manifoldco",
"mapstructure",
"mattn",
"mitchellh",
"moby",
"namesgenerator",
"namespacing",
"netaddr",
"netip",
"netmap",
"netns",
"netstack",
"nettype",
"nfpms",
"nhooyr",
"nmcfg",
"nolint",
"nosec",
"ntqry",
"OIDC",
"oneof",
"opty",
"paralleltest",
"parameterscopeid",
"pqtype",
"prometheusmetrics",
"promhttp",
"protobuf",
"provisionerd",
"provisionerdserver",
"provisionersdk",
"ptty",
"ptys",
"ptytest",
"quickstart",
"reconfig",
"replicasync",
"retrier",
"rpty",
"SCIM",
"sdkproto",
"sdktrace",
"Signup",
"slogtest",
"sourcemapped",
"spinbutton",
"Srcs",
"stdbuf",
"stretchr",
"STTY",
"stuntest",
"tailbroker",
"tailcfg",
"tailexchange",
"tailnet",
"tailnettest",
"Tailscale",
"tanstack",
"tbody",
"TCGETS",
"tcpip",
"TCSETS",
"templateversions",
"testdata",
"testid",
"testutil",
"tfexec",
"tfjson",
"tfplan",
"tfstate",
"thead",
"tios",
"tmpdir",
"tokenconfig",
"Topbar",
"tparallel",
"trialer",
"trimprefix",
"tsdial",
"tslogger",
"tstun",
"turnconn",
"typegen",
"typesafe",
"unconvert",
"Untar",
"Userspace",
"VMID",
"walkthrough",
"weblinks",
"webrtc",
"wgcfg",
"wgconfig",
"wgengine",
"wgmonitor",
"wgnet",
"workspaceagent",
"workspaceagents",
"workspaceapp",
"workspaceapps",
"workspacebuilds",
"workspacename",
"wsjson",
"xerrors",
"xlarge",
"xsmall",
"yamux"
],
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
"emeraldwalk.runonsave": {
"commands": [
{
"match": "database/queries/*.sql",
"cmd": "make gen"
},
{
"match": "provisionerd/proto/provisionerd.proto",
"cmd": "make provisionerd/proto/provisionerd.pb.go"
}
]
},
"search.exclude": {
"**.pb.go": true,
"**/*.gen.json": true,
"**/testdata/*": true,
"coderd/apidoc/**": true,
"docs/reference/api/*.md": true,
"docs/reference/cli/*.md": true,
"docs/templates/*.md": true,
"LICENSE": true,
"scripts/metricsdocgen/metrics": true,
"site/out/**": true,
"site/storybook-static/**": true,
"**.map": true,
"pnpm-lock.yaml": true
},
// Ensure files always have a newline.
"files.insertFinalNewline": true,
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.coverageDecorator": {
"type": "gutter",
"coveredGutterStyle": "blockgreen",
"uncoveredGutterStyle": "blockred"
},
// The codersdk is used by coderd another other packages extensively.
// To reduce redundancy in tests, it's covered by other packages.
// Since package coverage pairing can't be defined, all packages cover
// all other packages.
"go.testFlags": ["-short", "-coverpkg=./..."],
// We often use a version of TypeScript that's ahead of the version shipped
// with VS Code.
"typescript.tsdk": "./site/node_modules/typescript/lib",
// Playwright tests in VSCode will open a browser to live "view" the test.
"playwright.reuseBrowser": true,
"[javascript][javascriptreact][json][jsonc][typescript][typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
// "editor.codeActionsOnSave": {
// "source.organizeImports.biome": "explicit"
// }
},
"[css][html][markdown][yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
+74 -85
View File
@@ -36,6 +36,7 @@ GOOS := $(shell go env GOOS)
GOARCH := $(shell go env GOARCH)
GOOS_BIN_EXT := $(if $(filter windows, $(GOOS)),.exe,)
VERSION := $(shell ./scripts/version.sh)
POSTGRES_VERSION ?= 16
# Use the highest ZSTD compression level in CI.
ifdef CI
@@ -56,6 +57,9 @@ GO_SRC_FILES := $(shell find . $(FIND_EXCLUSIONS) -type f -name '*.go' -not -nam
# All the shell files in the repo, excluding ignored files.
SHELL_SRC_FILES := $(shell find . $(FIND_EXCLUSIONS) -type f -name '*.sh')
# Ensure we don't use the user's git configs which might cause side-effects
GIT_FLAGS = GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
# All ${OS}_${ARCH} combos we build for. Windows binaries have the .exe suffix.
OS_ARCHES := \
linux_amd64 linux_arm64 linux_armv7 \
@@ -387,7 +391,7 @@ BOLD := $(shell tput bold 2>/dev/null)
GREEN := $(shell tput setaf 2 2>/dev/null)
RESET := $(shell tput sgr0 2>/dev/null)
fmt: fmt/eslint fmt/prettier fmt/terraform fmt/shfmt fmt/go
fmt: fmt/ts fmt/go fmt/terraform fmt/shfmt fmt/prettier
.PHONY: fmt
fmt/go:
@@ -397,15 +401,19 @@ fmt/go:
go run mvdan.cc/gofumpt@v0.4.0 -w -l .
.PHONY: fmt/go
fmt/eslint:
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/eslint$(RESET)"
fmt/ts:
echo "$(GREEN)==>$(RESET) $(BOLD)fmt/ts$(RESET)"
cd site
pnpm run lint:fix
.PHONY: fmt/eslint
# Avoid writing files in CI to reduce file write activity
ifdef CI
pnpm run check --linter-enabled=false
else
pnpm run check:fix
endif
.PHONY: fmt/ts
fmt/prettier:
fmt/prettier: .prettierignore
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
@@ -438,14 +446,13 @@ lint/site-icons:
lint/ts:
cd site
pnpm i && pnpm lint
pnpm lint
.PHONY: lint/ts
lint/go:
./scripts/check_enterprise_imports.sh
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
linter_ver=$(shell egrep -o 'GOLANGCI_LINT_VERSION=\S+' dogfood/contents/Dockerfile | cut -d '=' -f 2)
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v$$linter_ver run
.PHONY: lint/go
lint/examples:
@@ -483,15 +490,15 @@ gen: \
$(DB_GEN_FILES) \
site/src/api/typesGenerated.ts \
coderd/rbac/object_gen.go \
codersdk/rbacresources_gen.go \
site/src/api/rbacresourcesGenerated.ts \
docs/admin/prometheus.md \
docs/cli.md \
docs/reference/cli/README.md \
docs/admin/audit-logs.md \
coderd/apidoc/swagger.json \
.prettierignore.include \
.prettierignore \
site/.prettierrc.yaml \
site/.prettierignore \
site/.eslintignore \
provisioner/terraform/testdata/version \
site/e2e/provisionerGenerated.ts \
site/src/theme/icons.json \
examples/examples.gen.json \
@@ -512,15 +519,14 @@ gen/mark-fresh:
$(DB_GEN_FILES) \
site/src/api/typesGenerated.ts \
coderd/rbac/object_gen.go \
codersdk/rbacresources_gen.go \
site/src/api/rbacresourcesGenerated.ts \
docs/admin/prometheus.md \
docs/cli.md \
docs/reference/cli/README.md \
docs/admin/audit-logs.md \
coderd/apidoc/swagger.json \
.prettierignore.include \
.prettierignore \
site/.prettierrc.yaml \
site/.prettierignore \
site/.eslintignore \
site/e2e/provisionerGenerated.ts \
site/src/theme/icons.json \
examples/examples.gen.json \
@@ -554,6 +560,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/
coderd/database/pubsub/psmock/psmock.go: coderd/database/pubsub/pubsub.go
go generate ./coderd/database/pubsub/psmock
tailnet/tailnettest/coordinatormock.go tailnet/tailnettest/multiagentmock.go tailnet/tailnettest/coordinateemock.go: tailnet/coordinator.go tailnet/multiagent.go
go generate ./tailnet/tailnettest/
@@ -592,7 +601,6 @@ 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/ > $@
./scripts/pnpm_install.sh
pnpm exec prettier --write "$@"
site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go provisionersdk/proto/provisioner.pb.go
cd site
@@ -602,23 +610,30 @@ 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 "$@"
./scripts/pnpm_install.sh
pnpm exec prettier --write "$@"
pnpm -C site/ exec biome format --write src/theme/icons.json
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
coderd/rbac/object_gen.go: scripts/rbacgen/main.go coderd/rbac/object.go
go run scripts/rbacgen/main.go ./coderd/rbac > coderd/rbac/object_gen.go
coderd/rbac/object_gen.go: scripts/rbacgen/rbacobject.gotmpl scripts/rbacgen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
go run scripts/rbacgen/main.go rbac > coderd/rbac/object_gen.go
codersdk/rbacresources_gen.go: scripts/rbacgen/codersdk.gotmpl scripts/rbacgen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
go run scripts/rbacgen/main.go codersdk > codersdk/rbacresources_gen.go
site/src/api/rbacresourcesGenerated.ts: scripts/rbacgen/codersdk.gotmpl scripts/rbacgen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
go run scripts/rbacgen/main.go typescript > "$@"
docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/metrics
go run scripts/metricsdocgen/main.go
./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)
docs/reference/cli/README.md: scripts/clidocgen/main.go examples/examples.gen.json $(GO_SRC_FILES)
CI=true BASE_PATH="." go run ./scripts/clidocgen
./scripts/pnpm_install.sh
pnpm exec prettier --write ./docs/cli.md ./docs/cli/*.md ./docs/manifest.json
pnpm exec prettier --write ./docs/reference/cli/README.md ./docs/reference/cli/*.md ./docs/manifest.json
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
@@ -628,7 +643,7 @@ docs/admin/audit-logs.md: coderd/database/querier.go scripts/auditdocgen/main.go
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
./scripts/pnpm_install.sh
pnpm exec prettier --write ./docs/api ./docs/manifest.json ./coderd/apidoc/swagger.json
pnpm exec prettier --write ./docs/reference/api ./docs/manifest.json ./coderd/apidoc/swagger.json
update-golden-files: \
cli/testdata/.gen-golden \
@@ -674,27 +689,16 @@ provisioner/terraform/testdata/.gen-golden: $(wildcard provisioner/terraform/tes
go test ./provisioner/terraform -run="Test.*Golden$$" -update
touch "$@"
provisioner/terraform/testdata/version:
if [[ "$(shell cat provisioner/terraform/testdata/version.txt)" != "$(shell terraform version -json | jq -r '.terraform_version')" ]]; then
./provisioner/terraform/testdata/generate.sh
fi
.PHONY: provisioner/terraform/testdata/version
scripts/ci-report/testdata/.gen-golden: $(wildcard scripts/ci-report/testdata/*) $(wildcard scripts/ci-report/*.go)
go test ./scripts/ci-report -run=TestOutputMatchesGoldenFile -update
touch "$@"
# Generate a prettierrc for the site package that uses relative paths for
# overrides. This allows us to share the same prettier config between the
# site and the root of the repo.
site/.prettierrc.yaml: .prettierrc.yaml
. ./scripts/lib.sh
dependencies yq
echo "# Code generated by Makefile (../$<). DO NOT EDIT." > "$@"
echo "" >> "$@"
# Replace all listed override files with relative paths inside site/.
# - ./ -> ../
# - ./site -> ./
yq \
'.overrides[].files |= map(. | sub("^./"; "") | sub("^"; "../") | sub("../site/"; "./") | sub("../!"; "!../"))' \
"$<" >> "$@"
# Combine .gitignore with .prettierignore.include to generate .prettierignore.
.prettierignore: .gitignore .prettierignore.include
echo "# Code generated by Makefile ($^). DO NOT EDIT." > "$@"
@@ -704,42 +708,8 @@ site/.prettierrc.yaml: .prettierrc.yaml
cat "$$f" >> "$@"
done
# Generate ignore files based on gitignore into the site directory. We turn all
# rules into relative paths for the `site/` directory (where applicable),
# following the pattern format defined by git:
# https://git-scm.com/docs/gitignore#_pattern_format
#
# This is done for compatibility reasons, see:
# https://github.com/prettier/prettier/issues/8048
# https://github.com/prettier/prettier/issues/8506
# https://github.com/prettier/prettier/issues/8679
site/.eslintignore site/.prettierignore: .prettierignore Makefile
rm -f "$@"
touch "$@"
# Skip generated by header, inherit `.prettierignore` header as-is.
while read -r rule; do
# Remove leading ! if present to simplify rule, added back at the end.
tmp="$${rule#!}"
ignore="$${rule%"$$tmp"}"
rule="$$tmp"
case "$$rule" in
# Comments or empty lines (include).
\#*|'') ;;
# Generic rules (include).
\*\**) ;;
# Site prefixed rules (include).
site/*) rule="$${rule#site/}";;
./site/*) rule="$${rule#./site/}";;
# Rules that are non-generic and don't start with site (rewrite).
/*) rule=.."$$rule";;
*/?*) rule=../"$$rule";;
*) ;;
esac
echo "$${ignore}$${rule}" >> "$@"
done < "$<"
test:
gotestsum --format standard-quiet -- -v -short -count=1 ./...
$(GIT_FLAGS) 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
@@ -775,7 +745,7 @@ sqlc-vet: test-postgres-docker
test-postgres: test-postgres-docker
# The postgres test is prone to failure, so we limit parallelism for
# more consistent execution.
DB=ci DB_FROM=$(shell go run scripts/migrate-ci/main.go) gotestsum \
$(GIT_FLAGS) DB=ci DB_FROM=$(shell go run scripts/migrate-ci/main.go) gotestsum \
--junitfile="gotests.xml" \
--jsonfile="gotests.json" \
--packages="./..." -- \
@@ -787,14 +757,17 @@ test-postgres: test-postgres-docker
test-migrations: test-postgres-docker
echo "--- test migrations"
set -euo pipefail
COMMIT_FROM=$(shell git rev-parse --short HEAD)
COMMIT_TO=$(shell git rev-parse --short main)
COMMIT_FROM=$(shell git log -1 --format='%h' HEAD)
echo "COMMIT_FROM=$${COMMIT_FROM}"
COMMIT_TO=$(shell git log -1 --format='%h' origin/main)
echo "COMMIT_TO=$${COMMIT_TO}"
if [[ "$${COMMIT_FROM}" == "$${COMMIT_TO}" ]]; then echo "Nothing to do!"; exit 0; fi
echo "DROP DATABASE IF EXISTS migrate_test_$${COMMIT_FROM}; CREATE DATABASE migrate_test_$${COMMIT_FROM};" | psql 'postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable'
go run ./scripts/migrate-test/main.go --from="$$COMMIT_FROM" --to="$$COMMIT_TO" --postgres-url="postgresql://postgres:postgres@localhost:5432/migrate_test_$${COMMIT_FROM}?sslmode=disable"
# NOTE: we set --memory to the same size as a GitHub runner.
test-postgres-docker:
docker rm -f test-postgres-docker || true
docker rm -f test-postgres-docker-${POSTGRES_VERSION} || true
docker run \
--env POSTGRES_PASSWORD=postgres \
--env POSTGRES_USER=postgres \
@@ -802,11 +775,11 @@ test-postgres-docker:
--env PGDATA=/tmp \
--tmpfs /tmp \
--publish 5432:5432 \
--name test-postgres-docker \
--name test-postgres-docker-${POSTGRES_VERSION} \
--restart no \
--detach \
--memory 16GB \
gcr.io/coder-dev-1/postgres:13 \
gcr.io/coder-dev-1/postgres:${POSTGRES_VERSION} \
-c shared_buffers=1GB \
-c work_mem=1GB \
-c effective_cache_size=1GB \
@@ -824,12 +797,28 @@ test-postgres-docker:
# Make sure to keep this in sync with test-go-race from .github/workflows/ci.yaml.
test-race:
gotestsum --junitfile="gotests.xml" -- -race -count=1 ./...
$(GIT_FLAGS) gotestsum --junitfile="gotests.xml" -- -race -count=1 ./...
.PHONY: test-race
test-tailnet-integration:
env \
CODER_TAILNET_TESTS=true \
CODER_MAGICSOCK_DEBUG_LOGGING=true \
TS_DEBUG_NETCHECK=true \
GOTRACEBACK=single \
go test \
-exec "sudo -E" \
-timeout=5m \
-count=1 \
./tailnet/test/integration
# Note: we used to add this to the test target, but it's not necessary and we can
# achieve the desired result by specifying -count=1 in the go test invocation
# instead. Keeping it here for convenience.
test-clean:
go clean -testcache
.PHONY: test-clean
.PHONY: test-e2e
test-e2e:
cd ./site && DEBUG=pw:api pnpm playwright:test --forbid-only --workers 1
+19 -15
View File
@@ -20,17 +20,17 @@
<br>
<br>
[Quickstart](#quickstart) | [Docs](https://coder.com/docs) | [Why Coder](https://coder.com/why) | [Enterprise](https://coder.com/docs/v2/latest/enterprise)
[Quickstart](#quickstart) | [Docs](https://coder.com/docs) | [Why Coder](https://coder.com/why) | [Enterprise](https://coder.com/docs/enterprise)
[![discord](https://img.shields.io/discord/747933592273027093?label=discord)](https://discord.gg/coder)
[![release](https://img.shields.io/github/v/release/coder/coder)](https://github.com/coder/coder/releases/latest)
[![godoc](https://pkg.go.dev/badge/github.com/coder/coder.svg)](https://pkg.go.dev/github.com/coder/coder)
[![Go Report Card](https://goreportcard.com/badge/github.com/coder/coder)](https://goreportcard.com/report/github.com/coder/coder)
[![Go Report Card](https://goreportcard.com/badge/github.com/coder/coder/v2)](https://goreportcard.com/report/github.com/coder/coder/v2)
[![license](https://img.shields.io/github/license/coder/coder)](./LICENSE)
</div>
[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.
[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 automatically shut down when not used to save on costs. Coder gives engineering teams the flexibility to use the cloud for workloads most beneficial to them.
- Define cloud development environments in Terraform
- EC2 VMs, Kubernetes Pods, Docker Containers, etc.
@@ -53,7 +53,7 @@ curl -L https://coder.com/install.sh | sh
coder server
# Navigate to http://localhost:3000 to create your initial user,
# create a Docker template, and provision a workspace
# create a Docker template and provision a workspace
```
## Install
@@ -69,7 +69,7 @@ curl -L https://coder.com/install.sh | sh
You can run the install script with `--dry-run` to see the commands that will be used to install without executing them. Run the install script with `--help` for additional flags.
> See [install](https://coder.com/docs/v2/latest/install) for additional methods.
> See [install](https://coder.com/docs/install) for additional methods.
Once installed, you can start a production deployment with a single command:
@@ -81,27 +81,27 @@ coder server
coder server --postgres-url <url> --access-url <url>
```
Use `coder --help` to get a list of flags and environment variables. Use our [install guides](https://coder.com/docs/v2/latest/install) for a full walkthrough.
Use `coder --help` to get a list of flags and environment variables. Use our [install guides](https://coder.com/docs/install) for a complete walkthrough.
## Documentation
Browse our docs [here](https://coder.com/docs/v2) or visit a specific section below:
Browse our docs [here](https://coder.com/docs) or visit a specific section below:
- [**Templates**](https://coder.com/docs/v2/latest/templates): Templates are written in Terraform and describe the infrastructure for workspaces
- [**Workspaces**](https://coder.com/docs/v2/latest/workspaces): Workspaces contain the IDEs, dependencies, and configuration information needed for software development
- [**IDEs**](https://coder.com/docs/v2/latest/ides): Connect your existing editor to a workspace
- [**Administration**](https://coder.com/docs/v2/latest/admin): Learn how to operate Coder
- [**Enterprise**](https://coder.com/docs/v2/latest/enterprise): Learn about our paid features built for large teams
- [**Templates**](https://coder.com/docs/templates): Templates are written in Terraform and describe the infrastructure for workspaces
- [**Workspaces**](https://coder.com/docs/workspaces): Workspaces contain the IDEs, dependencies, and configuration information needed for software development
- [**IDEs**](https://coder.com/docs/ides): Connect your existing editor to a workspace
- [**Administration**](https://coder.com/docs/admin): Learn how to operate Coder
- [**Enterprise**](https://coder.com/docs/enterprise): Learn about our paid features built for large teams
## Support
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) to provide feedback on in-progress features and chat with the community using Coder!
## Integrations
We are always working on new integrations. Feel free to open an issue to request an integration. Contributions are welcome in any official or community repositories.
We are always working on new integrations. Please feel free to open an issue and ask for an integration. Contributions are welcome in any official or community repositories.
### Official
@@ -120,5 +120,9 @@ We are always working on new integrations. Feel free to open an issue to request
## Contributing
We are always happy to see new contributors to Coder. If you are new to the Coder codebase, we have
[a guide on how to get started](https://coder.com/docs/v2/latest/CONTRIBUTING). We'd love to see your
[a guide on how to get started](https://coder.com/docs/CONTRIBUTING). We'd love to see your
contributions!
## Hiring
Apply [here](https://jobs.ashbyhq.com/coder?utm_source=github&utm_medium=readme&utm_campaign=unknown) if you're interested in joining our team.
+114 -93
View File
@@ -91,6 +91,7 @@ type Options struct {
ModifiedProcesses chan []*agentproc.Process
// ProcessManagementTick is used for testing process priority management.
ProcessManagementTick <-chan time.Time
BlockFileTransfer bool
}
type Client interface {
@@ -155,35 +156,36 @@ func New(options Options) Agent {
hardCtx, hardCancel := context.WithCancel(context.Background())
gracefulCtx, gracefulCancel := context.WithCancel(hardCtx)
a := &agent{
tailnetListenPort: options.TailnetListenPort,
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
logger: options.Logger,
gracefulCtx: gracefulCtx,
gracefulCancel: gracefulCancel,
hardCtx: hardCtx,
hardCancel: hardCancel,
coordDisconnected: make(chan struct{}),
environmentVariables: options.EnvironmentVariables,
client: options.Client,
exchangeToken: options.ExchangeToken,
filesystem: options.Filesystem,
logDir: options.LogDir,
tempDir: options.TempDir,
scriptDataDir: options.ScriptDataDir,
lifecycleUpdate: make(chan struct{}, 1),
lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1),
lifecycleStates: []agentsdk.PostLifecycleRequest{{State: codersdk.WorkspaceAgentLifecycleCreated}},
ignorePorts: options.IgnorePorts,
portCacheDuration: options.PortCacheDuration,
reportMetadataInterval: options.ReportMetadataInterval,
serviceBannerRefreshInterval: options.ServiceBannerRefreshInterval,
sshMaxTimeout: options.SSHMaxTimeout,
subsystems: options.Subsystems,
addresses: options.Addresses,
syscaller: options.Syscaller,
modifiedProcs: options.ModifiedProcesses,
processManagementTick: options.ProcessManagementTick,
logSender: agentsdk.NewLogSender(options.Logger),
tailnetListenPort: options.TailnetListenPort,
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
logger: options.Logger,
gracefulCtx: gracefulCtx,
gracefulCancel: gracefulCancel,
hardCtx: hardCtx,
hardCancel: hardCancel,
coordDisconnected: make(chan struct{}),
environmentVariables: options.EnvironmentVariables,
client: options.Client,
exchangeToken: options.ExchangeToken,
filesystem: options.Filesystem,
logDir: options.LogDir,
tempDir: options.TempDir,
scriptDataDir: options.ScriptDataDir,
lifecycleUpdate: make(chan struct{}, 1),
lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1),
lifecycleStates: []agentsdk.PostLifecycleRequest{{State: codersdk.WorkspaceAgentLifecycleCreated}},
ignorePorts: options.IgnorePorts,
portCacheDuration: options.PortCacheDuration,
reportMetadataInterval: options.ReportMetadataInterval,
announcementBannersRefreshInterval: options.ServiceBannerRefreshInterval,
sshMaxTimeout: options.SSHMaxTimeout,
subsystems: options.Subsystems,
addresses: options.Addresses,
syscaller: options.Syscaller,
modifiedProcs: options.ModifiedProcesses,
processManagementTick: options.ProcessManagementTick,
logSender: agentsdk.NewLogSender(options.Logger),
blockFileTransfer: options.BlockFileTransfer,
prometheusRegistry: prometheusRegistry,
metrics: newAgentMetrics(prometheusRegistry),
@@ -193,7 +195,7 @@ func New(options Options) Agent {
// that gets closed on disconnection. This is used to wait for graceful disconnection from the
// coordinator during shut down.
close(a.coordDisconnected)
a.serviceBanner.Store(new(codersdk.ServiceBannerConfig))
a.announcementBanners.Store(new([]codersdk.BannerConfig))
a.sessionToken.Store(new(string))
a.init()
return a
@@ -231,14 +233,15 @@ type agent struct {
environmentVariables map[string]string
manifest atomic.Pointer[agentsdk.Manifest] // manifest is atomic because values can change after reconnection.
reportMetadataInterval time.Duration
scriptRunner *agentscripts.Runner
serviceBanner atomic.Pointer[codersdk.ServiceBannerConfig] // serviceBanner is atomic because it is periodically updated.
serviceBannerRefreshInterval time.Duration
sessionToken atomic.Pointer[string]
sshServer *agentssh.Server
sshMaxTimeout time.Duration
manifest atomic.Pointer[agentsdk.Manifest] // manifest is atomic because values can change after reconnection.
reportMetadataInterval time.Duration
scriptRunner *agentscripts.Runner
announcementBanners atomic.Pointer[[]codersdk.BannerConfig] // announcementBanners is atomic because it is periodically updated.
announcementBannersRefreshInterval time.Duration
sessionToken atomic.Pointer[string]
sshServer *agentssh.Server
sshMaxTimeout time.Duration
blockFileTransfer bool
lifecycleUpdate chan struct{}
lifecycleReported chan codersdk.WorkspaceAgentLifecycle
@@ -272,11 +275,12 @@ func (a *agent) TailnetConn() *tailnet.Conn {
func (a *agent) init() {
// pass the "hard" context because we explicitly close the SSH server as part of graceful shutdown.
sshSrv, err := agentssh.NewServer(a.hardCtx, a.logger.Named("ssh-server"), a.prometheusRegistry, a.filesystem, &agentssh.Config{
MaxTimeout: a.sshMaxTimeout,
MOTDFile: func() string { return a.manifest.Load().MOTDFile },
ServiceBanner: func() *codersdk.ServiceBannerConfig { return a.serviceBanner.Load() },
UpdateEnv: a.updateCommandEnv,
WorkingDirectory: func() string { return a.manifest.Load().Directory },
MaxTimeout: a.sshMaxTimeout,
MOTDFile: func() string { return a.manifest.Load().MOTDFile },
AnnouncementBanners: func() *[]codersdk.BannerConfig { return a.announcementBanners.Load() },
UpdateEnv: a.updateCommandEnv,
WorkingDirectory: func() string { return a.manifest.Load().Directory },
BlockFileTransfer: a.blockFileTransfer,
})
if err != nil {
panic(err)
@@ -584,10 +588,12 @@ func (a *agent) reportMetadata(ctx context.Context, conn drpc.Conn) error {
updatedMetadata[mr.key] = mr.result
continue
case err := <-reportError:
a.logger.Debug(ctx, "batch update metadata complete", slog.Error(err))
logMsg := "batch update metadata complete"
if err != nil {
a.logger.Debug(ctx, logMsg, slog.Error(err))
return xerrors.Errorf("failed to report metadata: %w", err)
}
a.logger.Debug(ctx, logMsg)
reportInFlight = false
case <-report:
if len(updatedMetadata) == 0 {
@@ -709,23 +715,26 @@ func (a *agent) setLifecycle(state codersdk.WorkspaceAgentLifecycle) {
// (and must be done before the session actually starts).
func (a *agent) fetchServiceBannerLoop(ctx context.Context, conn drpc.Conn) error {
aAPI := proto.NewDRPCAgentClient(conn)
ticker := time.NewTicker(a.serviceBannerRefreshInterval)
ticker := time.NewTicker(a.announcementBannersRefreshInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
bannersProto, err := aAPI.GetAnnouncementBanners(ctx, &proto.GetAnnouncementBannersRequest{})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
a.logger.Error(ctx, "failed to update service banner", slog.Error(err))
a.logger.Error(ctx, "failed to update notification banners", slog.Error(err))
return err
}
serviceBanner := agentsdk.ServiceBannerFromProto(sbp)
a.serviceBanner.Store(&serviceBanner)
banners := make([]codersdk.BannerConfig, 0, len(bannersProto.AnnouncementBanners))
for _, bannerProto := range bannersProto.AnnouncementBanners {
banners = append(banners, agentsdk.BannerConfigFromProto(bannerProto))
}
a.announcementBanners.Store(&banners)
}
}
}
@@ -757,15 +766,18 @@ func (a *agent) run() (retErr error) {
// redial the coder server and retry.
connMan := newAPIConnRoutineManager(a.gracefulCtx, a.hardCtx, a.logger, conn)
connMan.start("init service banner", gracefulShutdownBehaviorStop,
connMan.start("init notification banners", gracefulShutdownBehaviorStop,
func(ctx context.Context, conn drpc.Conn) error {
aAPI := proto.NewDRPCAgentClient(conn)
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
bannersProto, err := aAPI.GetAnnouncementBanners(ctx, &proto.GetAnnouncementBannersRequest{})
if err != nil {
return xerrors.Errorf("fetch service banner: %w", err)
}
serviceBanner := agentsdk.ServiceBannerFromProto(sbp)
a.serviceBanner.Store(&serviceBanner)
banners := make([]codersdk.BannerConfig, 0, len(bannersProto.AnnouncementBanners))
for _, bannerProto := range bannersProto.AnnouncementBanners {
banners = append(banners, agentsdk.BannerConfigFromProto(bannerProto))
}
a.announcementBanners.Store(&banners)
return nil
},
)
@@ -807,23 +819,21 @@ func (a *agent) run() (retErr error) {
// coordination <--------------------------+
// derp map subscriber <----------------+
// stats report loop <---------------+
networkOK := make(chan struct{})
manifestOK := make(chan struct{})
networkOK := newCheckpoint(a.logger)
manifestOK := newCheckpoint(a.logger)
connMan.start("handle manifest", gracefulShutdownBehaviorStop, a.handleManifest(manifestOK))
connMan.start("app health reporter", gracefulShutdownBehaviorStop,
func(ctx context.Context, conn drpc.Conn) error {
select {
case <-ctx.Done():
return nil
case <-manifestOK:
manifest := a.manifest.Load()
NewWorkspaceAppHealthReporter(
a.logger, manifest.Apps, agentsdk.AppHealthPoster(proto.NewDRPCAgentClient(conn)),
)(ctx)
return nil
if err := manifestOK.wait(ctx); err != nil {
return xerrors.Errorf("no manifest: %w", err)
}
manifest := a.manifest.Load()
NewWorkspaceAppHealthReporter(
a.logger, manifest.Apps, agentsdk.AppHealthPoster(proto.NewDRPCAgentClient(conn)),
)(ctx)
return nil
})
connMan.start("create or update network", gracefulShutdownBehaviorStop,
@@ -831,10 +841,8 @@ func (a *agent) run() (retErr error) {
connMan.start("coordination", gracefulShutdownBehaviorStop,
func(ctx context.Context, conn drpc.Conn) error {
select {
case <-ctx.Done():
return nil
case <-networkOK:
if err := networkOK.wait(ctx); err != nil {
return xerrors.Errorf("no network: %w", err)
}
return a.runCoordinator(ctx, conn, a.network)
},
@@ -842,10 +850,8 @@ func (a *agent) run() (retErr error) {
connMan.start("derp map subscriber", gracefulShutdownBehaviorStop,
func(ctx context.Context, conn drpc.Conn) error {
select {
case <-ctx.Done():
return nil
case <-networkOK:
if err := networkOK.wait(ctx); err != nil {
return xerrors.Errorf("no network: %w", err)
}
return a.runDERPMapSubscriber(ctx, conn, a.network)
})
@@ -853,10 +859,8 @@ func (a *agent) run() (retErr error) {
connMan.start("fetch service banner loop", gracefulShutdownBehaviorStop, a.fetchServiceBannerLoop)
connMan.start("stats report loop", gracefulShutdownBehaviorStop, func(ctx context.Context, conn drpc.Conn) error {
select {
case <-ctx.Done():
return nil
case <-networkOK:
if err := networkOK.wait(ctx); err != nil {
return xerrors.Errorf("no network: %w", err)
}
return a.statsReporter.reportLoop(ctx, proto.NewDRPCAgentClient(conn))
})
@@ -865,8 +869,17 @@ func (a *agent) run() (retErr error) {
}
// handleManifest returns a function that fetches and processes the manifest
func (a *agent) handleManifest(manifestOK chan<- struct{}) func(ctx context.Context, conn drpc.Conn) error {
func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context, conn drpc.Conn) error {
return func(ctx context.Context, conn drpc.Conn) error {
var (
sentResult = false
err error
)
defer func() {
if !sentResult {
manifestOK.complete(err)
}
}()
aAPI := proto.NewDRPCAgentClient(conn)
mp, err := aAPI.GetManifest(ctx, &proto.GetManifestRequest{})
if err != nil {
@@ -903,14 +916,12 @@ func (a *agent) handleManifest(manifestOK chan<- struct{}) func(ctx context.Cont
Subsystems: subsys,
}})
if err != nil {
if xerrors.Is(err, context.Canceled) {
return nil
}
return xerrors.Errorf("update workspace agent startup: %w", err)
}
oldManifest := a.manifest.Swap(&manifest)
close(manifestOK)
manifestOK.complete(nil)
sentResult = true
// The startup script should only execute on the first run!
if oldManifest == nil {
@@ -971,14 +982,15 @@ func (a *agent) handleManifest(manifestOK chan<- struct{}) func(ctx context.Cont
// createOrUpdateNetwork waits for the manifest to be set using manifestOK, then creates or updates
// the tailnet using the information in the manifest
func (a *agent) createOrUpdateNetwork(manifestOK <-chan struct{}, networkOK chan<- struct{}) func(context.Context, drpc.Conn) error {
return func(ctx context.Context, _ drpc.Conn) error {
select {
case <-ctx.Done():
return nil
case <-manifestOK:
func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(context.Context, drpc.Conn) error {
return func(ctx context.Context, _ drpc.Conn) (retErr error) {
if err := manifestOK.wait(ctx); err != nil {
return xerrors.Errorf("no manifest: %w", err)
}
var err error
defer func() {
networkOK.complete(retErr)
}()
manifest := a.manifest.Load()
a.closeMutex.Lock()
network := a.network
@@ -1014,7 +1026,6 @@ func (a *agent) createOrUpdateNetwork(manifestOK <-chan struct{}, networkOK chan
network.SetDERPForceWebSockets(manifest.DERPForceWebSockets)
network.SetBlockEndpoints(manifest.DisableDirectConnections)
}
close(networkOK)
return nil
}
}
@@ -1658,13 +1669,12 @@ func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebounc
}
score, niceErr := proc.Niceness(a.syscaller)
if niceErr != nil && !xerrors.Is(niceErr, os.ErrPermission) {
if !isBenignProcessErr(niceErr) {
debouncer.Warn(ctx, "unable to get proc niceness",
slog.F("cmd", proc.Cmd()),
slog.F("pid", proc.PID),
slog.Error(niceErr),
)
continue
}
// We only want processes that don't have a nice value set
@@ -1678,7 +1688,7 @@ func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebounc
if niceErr == nil {
err := proc.SetNiceness(a.syscaller, niceness)
if err != nil && !xerrors.Is(err, os.ErrPermission) {
if !isBenignProcessErr(err) {
debouncer.Warn(ctx, "unable to set proc niceness",
slog.F("cmd", proc.Cmd()),
slog.F("pid", proc.PID),
@@ -1692,7 +1702,7 @@ func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebounc
if oomScore != unsetOOMScore && oomScore != proc.OOMScoreAdj && !isCustomOOMScore(agentScore, proc) {
oomScoreStr := strconv.Itoa(oomScore)
err := afero.WriteFile(a.filesystem, fmt.Sprintf("/proc/%d/oom_score_adj", proc.PID), []byte(oomScoreStr), 0o644)
if err != nil && !xerrors.Is(err, os.ErrPermission) {
if !isBenignProcessErr(err) {
debouncer.Warn(ctx, "unable to set oom_score_adj",
slog.F("cmd", proc.Cmd()),
slog.F("pid", proc.PID),
@@ -1776,7 +1786,7 @@ func (a *agent) HandleHTTPDebugLogs(w http.ResponseWriter, r *http.Request) {
}
defer f.Close()
// Limit to 10MB.
// Limit to 10MiB.
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, io.LimitReader(f, 10*1024*1024))
if err != nil && !errors.Is(err, io.EOF) {
@@ -2128,3 +2138,14 @@ func (l *logDebouncer) log(ctx context.Context, level slog.Level, msg string, fi
}
l.messages[msg] = time.Now()
}
func isBenignProcessErr(err error) bool {
return err != nil &&
(xerrors.Is(err, os.ErrNotExist) ||
xerrors.Is(err, os.ErrPermission) ||
isNoSuchProcessErr(err))
}
func isNoSuchProcessErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "no such process")
}
+98 -5
View File
@@ -614,12 +614,12 @@ func TestAgent_Session_TTY_MOTD_Update(t *testing.T) {
// Set new banner func and wait for the agent to call it to update the
// banner.
ready := make(chan struct{}, 2)
client.SetServiceBannerFunc(func() (codersdk.ServiceBannerConfig, error) {
client.SetAnnouncementBannersFunc(func() ([]codersdk.BannerConfig, error) {
select {
case ready <- struct{}{}:
default:
}
return test.banner, nil
return []codersdk.BannerConfig{test.banner}, nil
})
<-ready
<-ready // Wait for two updates to ensure the value has propagated.
@@ -970,6 +970,99 @@ func TestAgent_SCP(t *testing.T) {
require.NoError(t, err)
}
func TestAgent_FileTransferBlocked(t *testing.T) {
t.Parallel()
assertFileTransferBlocked := func(t *testing.T, errorMessage string) {
// NOTE: Checking content of the error message is flaky. Most likely there is a race condition, which results
// in stopping the client in different phases, and returning different errors:
// - client read the full error message: File transfer has been disabled.
// - client's stream was terminated before reading the error message: EOF
// - client just read the error code (Windows): Process exited with status 65
isErr := strings.Contains(errorMessage, agentssh.BlockedFileTransferErrorMessage) ||
strings.Contains(errorMessage, "EOF") ||
strings.Contains(errorMessage, "Process exited with status 65")
require.True(t, isErr, fmt.Sprintf("Message: "+errorMessage))
}
t.Run("SFTP", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0, func(_ *agenttest.Client, o *agent.Options) {
o.BlockFileTransfer = true
})
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
_, err = sftp.NewClient(sshClient)
require.Error(t, err)
assertFileTransferBlocked(t, err.Error())
})
t.Run("SCP with go-scp package", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0, func(_ *agenttest.Client, o *agent.Options) {
o.BlockFileTransfer = true
})
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
scpClient, err := scp.NewClientBySSH(sshClient)
require.NoError(t, err)
defer scpClient.Close()
tempFile := filepath.Join(t.TempDir(), "scp")
err = scpClient.CopyFile(context.Background(), strings.NewReader("hello world"), tempFile, "0755")
require.Error(t, err)
assertFileTransferBlocked(t, err.Error())
})
t.Run("Forbidden commands", func(t *testing.T) {
t.Parallel()
for _, c := range agentssh.BlockedFileTransferCommands {
t.Run(c, func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0, func(_ *agenttest.Client, o *agent.Options) {
o.BlockFileTransfer = true
})
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
session, err := sshClient.NewSession()
require.NoError(t, err)
defer session.Close()
stdout, err := session.StdoutPipe()
require.NoError(t, err)
//nolint:govet // we don't need `c := c` in Go 1.22
err = session.Start(c)
require.NoError(t, err)
defer session.Close()
msg, err := io.ReadAll(stdout)
require.NoError(t, err)
assertFileTransferBlocked(t, string(msg))
})
}
})
}
func TestAgent_EnvironmentVariables(t *testing.T) {
t.Parallel()
key := "EXAMPLE"
@@ -2193,15 +2286,15 @@ func setupAgentSSHClient(ctx context.Context, t *testing.T) *ssh.Client {
func setupSSHSession(
t *testing.T,
manifest agentsdk.Manifest,
serviceBanner codersdk.ServiceBannerConfig,
banner codersdk.BannerConfig,
prepareFS func(fs afero.Fs),
opts ...func(*agenttest.Client, *agent.Options),
) *ssh.Session {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
opts = append(opts, func(c *agenttest.Client, o *agent.Options) {
c.SetServiceBannerFunc(func() (codersdk.ServiceBannerConfig, error) {
return serviceBanner, nil
c.SetAnnouncementBannersFunc(func() ([]codersdk.BannerConfig, error) {
return []codersdk.BannerConfig{banner}, nil
})
})
//nolint:dogsled
+11 -3
View File
@@ -45,8 +45,7 @@ func List(fs afero.Fs, syscaller Syscaller) ([]*Process, error) {
cmdline, err := afero.ReadFile(fs, filepath.Join(defaultProcDir, entry, "cmdline"))
if err != nil {
var errNo syscall.Errno
if xerrors.As(err, &errNo) && errNo == syscall.EPERM {
if isBenignError(err) {
continue
}
return nil, xerrors.Errorf("read cmdline: %w", err)
@@ -54,7 +53,7 @@ func List(fs afero.Fs, syscaller Syscaller) ([]*Process, error) {
oomScore, err := afero.ReadFile(fs, filepath.Join(defaultProcDir, entry, "oom_score_adj"))
if err != nil {
if xerrors.Is(err, os.ErrPermission) {
if isBenignError(err) {
continue
}
@@ -124,3 +123,12 @@ func (p *Process) Cmd() string {
func (p *Process) cmdLine() []string {
return strings.Split(p.CmdLine, "\x00")
}
func isBenignError(err error) bool {
var errno syscall.Errno
if !xerrors.As(err, &errno) {
return false
}
return errno == syscall.ESRCH || errno == syscall.EPERM || xerrors.Is(err, os.ErrNotExist)
}
+1 -1
View File
@@ -349,7 +349,7 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
"This usually means a child process was started with references to stdout or stderr. As a result, this " +
"process may now have been terminated. Consider redirecting the output or using a separate " +
"\"coder_script\" for the process, see " +
"https://coder.com/docs/v2/latest/templates/troubleshooting#startup-script-issues for more information.",
"https://coder.com/docs/templates/troubleshooting#startup-script-issues for more information.",
)
// Inform the user by propagating the message via log writers.
_, _ = fmt.Fprintf(cmd.Stderr, "WARNING: %s. %s\n", message, details)
+67 -11
View File
@@ -52,8 +52,16 @@ const (
// MagicProcessCmdlineJetBrains is a string in a process's command line that
// uniquely identifies it as JetBrains software.
MagicProcessCmdlineJetBrains = "idea.vendor.name=JetBrains"
// BlockedFileTransferErrorCode indicates that SSH server restricted the raw command from performing
// the file transfer.
BlockedFileTransferErrorCode = 65 // Error code: host not allowed to connect
BlockedFileTransferErrorMessage = "File transfer has been disabled."
)
// BlockedFileTransferCommands contains a list of restricted file transfer commands.
var BlockedFileTransferCommands = []string{"nc", "rsync", "scp", "sftp"}
// Config sets configuration parameters for the agent SSH server.
type Config struct {
// MaxTimeout sets the absolute connection timeout, none if empty. If set to
@@ -63,7 +71,7 @@ type Config struct {
// 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
AnnouncementBanners func() *[]codersdk.BannerConfig
// 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)
@@ -74,6 +82,8 @@ type Config struct {
// X11SocketDir is the directory where X11 sockets are created. Default is
// /tmp/.X11-unix.
X11SocketDir string
// BlockFileTransfer restricts use of file transfer applications.
BlockFileTransfer bool
}
type Server struct {
@@ -123,8 +133,8 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
if config.MOTDFile == nil {
config.MOTDFile = func() string { return "" }
}
if config.ServiceBanner == nil {
config.ServiceBanner = func() *codersdk.ServiceBannerConfig { return &codersdk.ServiceBannerConfig{} }
if config.AnnouncementBanners == nil {
config.AnnouncementBanners = func() *[]codersdk.BannerConfig { return &[]codersdk.BannerConfig{} }
}
if config.WorkingDirectory == nil {
config.WorkingDirectory = func() string {
@@ -272,6 +282,18 @@ func (s *Server) sessionHandler(session ssh.Session) {
extraEnv = append(extraEnv, fmt.Sprintf("DISPLAY=:%d.0", x11.ScreenNumber))
}
if s.fileTransferBlocked(session) {
s.logger.Warn(ctx, "file transfer blocked", slog.F("session_subsystem", session.Subsystem()), slog.F("raw_command", session.RawCommand()))
if session.Subsystem() == "" { // sftp does not expect error, otherwise it fails with "package too long"
// Response format: <status_code><message body>\n
errorMessage := fmt.Sprintf("\x02%s\n", BlockedFileTransferErrorMessage)
_, _ = session.Write([]byte(errorMessage))
}
_ = session.Exit(BlockedFileTransferErrorCode)
return
}
switch ss := session.Subsystem(); ss {
case "":
case "sftp":
@@ -322,6 +344,37 @@ func (s *Server) sessionHandler(session ssh.Session) {
_ = session.Exit(0)
}
// fileTransferBlocked method checks if the file transfer commands should be blocked.
//
// Warning: consider this mechanism as "Do not trespass" sign, as a violator can still ssh to the host,
// smuggle the `scp` binary, or just manually send files outside with `curl` or `ftp`.
// If a user needs a more sophisticated and battle-proof solution, consider full endpoint security.
func (s *Server) fileTransferBlocked(session ssh.Session) bool {
if !s.config.BlockFileTransfer {
return false // file transfers are permitted
}
// File transfers are restricted.
if session.Subsystem() == "sftp" {
return true
}
cmd := session.Command()
if len(cmd) == 0 {
return false // no command?
}
c := cmd[0]
c = filepath.Base(c) // in case the binary is absolute path, /usr/sbin/scp
for _, cmd := range BlockedFileTransferCommands {
if cmd == c {
return true
}
}
return false
}
func (s *Server) sessionStart(logger slog.Logger, session ssh.Session, extraEnv []string) (retErr error) {
ctx := session.Context()
env := append(session.Environ(), extraEnv...)
@@ -441,12 +494,15 @@ func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTy
session.DisablePTYEmulation()
if isLoginShell(session.RawCommand()) {
serviceBanner := s.config.ServiceBanner()
if serviceBanner != nil {
err := showServiceBanner(session, serviceBanner)
if err != nil {
logger.Error(ctx, "agent failed to show service banner", slog.Error(err))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "service_banner").Add(1)
banners := s.config.AnnouncementBanners()
if banners != nil {
for _, banner := range *banners {
err := showAnnouncementBanner(session, banner)
if err != nil {
logger.Error(ctx, "agent failed to show announcement banner", slog.Error(err))
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "announcement_banner").Add(1)
break
}
}
}
}
@@ -891,9 +947,9 @@ func isQuietLogin(fs afero.Fs, rawCommand string) bool {
return err == nil
}
// showServiceBanner will write the service banner if enabled and not blank
// showAnnouncementBanner will write the service banner if enabled and not blank
// along with a blank line for spacing.
func showServiceBanner(session io.Writer, banner *codersdk.ServiceBannerConfig) error {
func showAnnouncementBanner(session io.Writer, banner codersdk.BannerConfig) error {
if banner.Enabled && banner.Message != "" {
// The banner supports Markdown so we might want to parse it but Markdown is
// still fairly readable in its raw form.
+40 -19
View File
@@ -138,8 +138,8 @@ func (c *Client) GetStartupLogs() []agentsdk.Log {
return c.logs
}
func (c *Client) SetServiceBannerFunc(f func() (codersdk.ServiceBannerConfig, error)) {
c.fakeAgentAPI.SetServiceBannerFunc(f)
func (c *Client) SetAnnouncementBannersFunc(f func() ([]codersdk.BannerConfig, error)) {
c.fakeAgentAPI.SetAnnouncementBannersFunc(f)
}
func (c *Client) PushDERPMapUpdate(update *tailcfg.DERPMap) error {
@@ -171,38 +171,51 @@ type FakeAgentAPI struct {
lifecycleStates []codersdk.WorkspaceAgentLifecycle
metadata map[string]agentsdk.Metadata
getServiceBannerFunc func() (codersdk.ServiceBannerConfig, error)
getAnnouncementBannersFunc func() ([]codersdk.BannerConfig, 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 (*FakeAgentAPI) GetServiceBanner(context.Context, *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
return &agentproto.ServiceBanner{}, nil
}
func (f *FakeAgentAPI) GetServiceBanner(context.Context, *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
func (f *FakeAgentAPI) SetAnnouncementBannersFunc(fn func() ([]codersdk.BannerConfig, error)) {
f.Lock()
defer f.Unlock()
if f.getServiceBannerFunc == nil {
return &agentproto.ServiceBanner{}, nil
f.getAnnouncementBannersFunc = fn
f.logger.Info(context.Background(), "updated notification banners")
}
func (f *FakeAgentAPI) GetAnnouncementBanners(context.Context, *agentproto.GetAnnouncementBannersRequest) (*agentproto.GetAnnouncementBannersResponse, error) {
f.Lock()
defer f.Unlock()
if f.getAnnouncementBannersFunc == nil {
return &agentproto.GetAnnouncementBannersResponse{AnnouncementBanners: []*agentproto.BannerConfig{}}, nil
}
sb, err := f.getServiceBannerFunc()
banners, err := f.getAnnouncementBannersFunc()
if err != nil {
return nil, err
}
return agentsdk.ProtoFromServiceBanner(sb), nil
bannersProto := make([]*agentproto.BannerConfig, 0, len(banners))
for _, banner := range banners {
bannersProto = append(bannersProto, agentsdk.ProtoFromBannerConfig(banner))
}
return &agentproto.GetAnnouncementBannersResponse{AnnouncementBanners: bannersProto}, 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
select {
case <-ctx.Done():
return nil, ctx.Err()
case f.statsCh <- req.Stats:
// OK!
}
}
return &agentproto.UpdateStatsResponse{ReportInterval: durationpb.New(statsInterval)}, nil
}
@@ -225,17 +238,25 @@ func (f *FakeAgentAPI) UpdateLifecycle(_ context.Context, req *agentproto.Update
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
select {
case <-ctx.Done():
return nil, ctx.Err()
case 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) UpdateStartup(ctx context.Context, req *agentproto.UpdateStartupRequest) (*agentproto.Startup, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case f.startupCh <- req.GetStartup():
return req.GetStartup(), nil
}
}
func (f *FakeAgentAPI) GetMetadata() map[string]agentsdk.Metadata {
+1
View File
@@ -37,6 +37,7 @@ func (a *agent) apiHandler() http.Handler {
}
promHandler := PrometheusMetricsHandler(a.prometheusRegistry, a.logger)
r.Get("/api/v0/listening-ports", lp.handler)
r.Get("/api/v0/netcheck", a.HandleNetcheck)
r.Get("/debug/logs", a.HandleHTTPDebugLogs)
r.Get("/debug/magicsock", a.HandleHTTPDebugMagicsock)
r.Get("/debug/magicsock/debug-logging/{state}", a.HandleHTTPMagicsockDebugLoggingState)
+53 -59
View File
@@ -12,12 +12,9 @@ import (
"cdr.dev/slog"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/retry"
"github.com/coder/quartz"
)
// WorkspaceAgentApps fetches the workspace apps.
type WorkspaceAgentApps func(context.Context) ([]codersdk.WorkspaceApp, error)
// PostWorkspaceAgentAppHealth updates the workspace app health.
type PostWorkspaceAgentAppHealth func(context.Context, agentsdk.PostAppHealthsRequest) error
@@ -26,15 +23,26 @@ 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 {
return NewAppHealthReporterWithClock(logger, apps, postWorkspaceAgentAppHealth, quartz.NewReal())
}
// NewAppHealthReporterWithClock is only called directly by test code. Product code should call
// NewAppHealthReporter.
func NewAppHealthReporterWithClock(
logger slog.Logger,
apps []codersdk.WorkspaceApp,
postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth,
clk quartz.Clock,
) WorkspaceAppHealthReporter {
logger = logger.Named("apphealth")
runHealthcheckLoop := func(ctx context.Context) error {
return func(ctx context.Context) {
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
return
}
hasHealthchecksEnabled := false
@@ -49,7 +57,7 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
// no need to run this loop if no health checks are configured.
if !hasHealthchecksEnabled {
return nil
return
}
// run a ticker for each app health check.
@@ -61,25 +69,29 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
}
app := nextApp
go func() {
t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second)
defer t.Stop()
_ = clk.TickerFunc(ctx, time.Duration(app.Healthcheck.Interval)*time.Second, func() error {
// We time out at the healthcheck interval to prevent getting too backed up, but
// set it 1ms early so that it's not simultaneous with the next tick in testing,
// which makes the test easier to understand.
//
// It would be idiomatic to use the http.Client.Timeout or a context.WithTimeout,
// but we are passing this off to the native http library, which is not aware
// of the clock library we are using. That means in testing, with a mock clock
// it will compare mocked times with real times, and we will get strange results.
// So, we just implement the timeout as a context we cancel with an AfterFunc
reqCtx, reqCancel := context.WithCancel(ctx)
timeout := clk.AfterFunc(
time.Duration(app.Healthcheck.Interval)*time.Second-time.Millisecond,
reqCancel,
"timeout", app.Slug)
defer timeout.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
// we set the http timeout to the healthcheck interval to prevent getting too backed up.
client := &http.Client{
Timeout: time.Duration(app.Healthcheck.Interval) * time.Second,
}
err := func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil)
req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, app.Healthcheck.URL, nil)
if err != nil {
return err
}
res, err := client.Do(req)
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
@@ -118,54 +130,36 @@ func NewWorkspaceAppHealthReporter(logger slog.Logger, apps []codersdk.Workspace
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)
}
return nil
}, "healthcheck", app.Slug)
}()
}
mu.Lock()
lastHealth := copyHealth(health)
mu.Unlock()
reportTicker := time.NewTicker(time.Second)
defer reportTicker.Stop()
// every second we check if the health values of the apps have changed
// and if there is a change we will report the new values.
for {
select {
case <-ctx.Done():
reportTicker := clk.TickerFunc(ctx, time.Second, func() error {
mu.RLock()
changed := healthChanged(lastHealth, health)
mu.RUnlock()
if !changed {
return nil
case <-reportTicker.C:
mu.RLock()
changed := healthChanged(lastHealth, health)
mu.RUnlock()
if !changed {
continue
}
mu.Lock()
lastHealth = copyHealth(health)
mu.Unlock()
err := postWorkspaceAgentAppHealth(ctx, agentsdk.PostAppHealthsRequest{
Healths: lastHealth,
})
if err != nil {
logger.Error(ctx, "failed to report workspace app health", slog.Error(err))
} else {
logger.Debug(ctx, "sent workspace app health", slog.F("health", lastHealth))
}
}
}
}
return func(ctx context.Context) {
for r := retry.New(time.Second, 30*time.Second); r.Wait(ctx); {
err := runHealthcheckLoop(ctx)
if err == nil || xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) {
return
mu.Lock()
lastHealth = copyHealth(health)
mu.Unlock()
err := postWorkspaceAgentAppHealth(ctx, agentsdk.PostAppHealthsRequest{
Healths: lastHealth,
})
if err != nil {
logger.Error(ctx, "failed to report workspace app health", slog.Error(err))
} else {
logger.Debug(ctx, "sent workspace app health", slog.F("health", lastHealth))
}
logger.Error(ctx, "failed running workspace app reporter", slog.Error(err))
}
return nil
}, "report")
_ = reportTicker.Wait() // only possible error is context done
}
}
+154 -113
View File
@@ -4,14 +4,12 @@ import (
"context"
"net/http"
"net/http/httptest"
"slices"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
@@ -23,19 +21,22 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
)
func TestAppHealth_Healthy(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
ID: uuid.UUID{1},
Slug: "app1",
Healthcheck: codersdk.Healthcheck{},
Health: codersdk.WorkspaceAppHealthDisabled,
},
{
ID: uuid.UUID{2},
Slug: "app2",
Healthcheck: codersdk.Healthcheck{
// URL: We don't set the URL for this test because the setup will
@@ -46,6 +47,7 @@ func TestAppHealth_Healthy(t *testing.T) {
Health: codersdk.WorkspaceAppHealthInitializing,
},
{
ID: uuid.UUID{3},
Slug: "app3",
Healthcheck: codersdk.Healthcheck{
Interval: 2,
@@ -54,36 +56,71 @@ func TestAppHealth_Healthy(t *testing.T) {
Health: codersdk.WorkspaceAppHealthInitializing,
},
}
checks2 := 0
checks3 := 0
handlers := []http.Handler{
nil,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
checks2++
httpapi.Write(r.Context(), w, http.StatusOK, nil)
}),
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
checks3++
httpapi.Write(r.Context(), w, http.StatusOK, nil)
}),
}
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
defer closeFn()
apps, err := getApps(ctx)
require.NoError(t, err)
require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, apps[0].Health)
require.Eventually(t, func() bool {
apps, err := getApps(ctx)
if err != nil {
return false
}
mClock := quartz.NewMock(t)
healthcheckTrap := mClock.Trap().TickerFunc("healthcheck")
defer healthcheckTrap.Close()
reportTrap := mClock.Trap().TickerFunc("report")
defer reportTrap.Close()
return apps[1].Health == codersdk.WorkspaceAppHealthHealthy && apps[2].Health == codersdk.WorkspaceAppHealthHealthy
}, testutil.WaitLong, testutil.IntervalSlow)
fakeAPI, closeFn := setupAppReporter(ctx, t, slices.Clone(apps), handlers, mClock)
defer closeFn()
healthchecksStarted := make([]string, 2)
for i := 0; i < 2; i++ {
c := healthcheckTrap.MustWait(ctx)
c.Release()
healthchecksStarted[i] = c.Tags[1]
}
slices.Sort(healthchecksStarted)
require.Equal(t, []string{"app2", "app3"}, healthchecksStarted)
// advance the clock 1ms before the report ticker starts, so that it's not
// simultaneous with the checks.
mClock.Advance(time.Millisecond).MustWait(ctx)
reportTrap.MustWait(ctx).Release()
mClock.Advance(999 * time.Millisecond).MustWait(ctx) // app2 is now healthy
mClock.Advance(time.Millisecond).MustWait(ctx) // report gets triggered
update := testutil.RequireRecvCtx(ctx, t, fakeAPI.AppHealthCh())
require.Len(t, update.GetUpdates(), 2)
applyUpdate(t, apps, update)
require.Equal(t, codersdk.WorkspaceAppHealthHealthy, apps[1].Health)
require.Equal(t, codersdk.WorkspaceAppHealthInitializing, apps[2].Health)
mClock.Advance(999 * time.Millisecond).MustWait(ctx) // app3 is now healthy
mClock.Advance(time.Millisecond).MustWait(ctx) // report gets triggered
update = testutil.RequireRecvCtx(ctx, t, fakeAPI.AppHealthCh())
require.Len(t, update.GetUpdates(), 2)
applyUpdate(t, apps, update)
require.Equal(t, codersdk.WorkspaceAppHealthHealthy, apps[1].Health)
require.Equal(t, codersdk.WorkspaceAppHealthHealthy, apps[2].Health)
// ensure we aren't spamming
require.Equal(t, 2, checks2)
require.Equal(t, 1, checks3)
}
func TestAppHealth_500(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
ID: uuid.UUID{2},
Slug: "app2",
Healthcheck: codersdk.Healthcheck{
// URL: We don't set the URL for this test because the setup will
@@ -99,59 +136,40 @@ func TestAppHealth_500(t *testing.T) {
httpapi.Write(r.Context(), w, http.StatusInternalServerError, nil)
}),
}
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
defer closeFn()
require.Eventually(t, func() bool {
apps, err := getApps(ctx)
if err != nil {
return false
}
return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy
}, testutil.WaitLong, testutil.IntervalSlow)
mClock := quartz.NewMock(t)
healthcheckTrap := mClock.Trap().TickerFunc("healthcheck")
defer healthcheckTrap.Close()
reportTrap := mClock.Trap().TickerFunc("report")
defer reportTrap.Close()
fakeAPI, closeFn := setupAppReporter(ctx, t, slices.Clone(apps), handlers, mClock)
defer closeFn()
healthcheckTrap.MustWait(ctx).Release()
// advance the clock 1ms before the report ticker starts, so that it's not
// simultaneous with the checks.
mClock.Advance(time.Millisecond).MustWait(ctx)
reportTrap.MustWait(ctx).Release()
mClock.Advance(999 * time.Millisecond).MustWait(ctx) // check gets triggered
mClock.Advance(time.Millisecond).MustWait(ctx) // report gets triggered, but unsent since we are at the threshold
mClock.Advance(999 * time.Millisecond).MustWait(ctx) // 2nd check, crosses threshold
mClock.Advance(time.Millisecond).MustWait(ctx) // 2nd report, sends update
update := testutil.RequireRecvCtx(ctx, t, fakeAPI.AppHealthCh())
require.Len(t, update.GetUpdates(), 1)
applyUpdate(t, apps, update)
require.Equal(t, codersdk.WorkspaceAppHealthUnhealthy, apps[0].Health)
}
func TestAppHealth_Timeout(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
Slug: "app2",
Healthcheck: codersdk.Healthcheck{
// URL: We don't set the URL for this test because the setup will
// create a httptest server for us and set it for us.
Interval: 1,
Threshold: 1,
},
Health: codersdk.WorkspaceAppHealthInitializing,
},
}
handlers := []http.Handler{
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// sleep longer than the interval to cause the health check to time out
time.Sleep(2 * time.Second)
httpapi.Write(r.Context(), w, http.StatusOK, nil)
}),
}
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
defer closeFn()
require.Eventually(t, func() bool {
apps, err := getApps(ctx)
if err != nil {
return false
}
return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy
}, testutil.WaitLong, testutil.IntervalSlow)
}
func TestAppHealth_NotSpamming(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
ID: uuid.UUID{2},
Slug: "app2",
Healthcheck: codersdk.Healthcheck{
// URL: We don't set the URL for this test because the setup will
@@ -163,27 +181,65 @@ func TestAppHealth_NotSpamming(t *testing.T) {
},
}
counter := new(int32)
handlers := []http.Handler{
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(counter, 1)
http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
// allow the request to time out
<-r.Context().Done()
}),
}
_, closeFn := setupAppReporter(ctx, t, apps, handlers)
mClock := quartz.NewMock(t)
start := mClock.Now()
// for this test, it's easier to think in the number of milliseconds elapsed
// since start.
ms := func(n int) time.Time {
return start.Add(time.Duration(n) * time.Millisecond)
}
healthcheckTrap := mClock.Trap().TickerFunc("healthcheck")
defer healthcheckTrap.Close()
reportTrap := mClock.Trap().TickerFunc("report")
defer reportTrap.Close()
timeoutTrap := mClock.Trap().AfterFunc("timeout")
defer timeoutTrap.Close()
fakeAPI, closeFn := setupAppReporter(ctx, t, apps, handlers, mClock)
defer closeFn()
// Ensure we haven't made more than 2 (expected 1 + 1 for buffer) requests in the last second.
// if there is a bug where we are spamming the healthcheck route this will catch it.
time.Sleep(time.Second)
require.LessOrEqual(t, atomic.LoadInt32(counter), int32(2))
healthcheckTrap.MustWait(ctx).Release()
// advance the clock 1ms before the report ticker starts, so that it's not
// simultaneous with the checks.
mClock.Set(ms(1)).MustWait(ctx)
reportTrap.MustWait(ctx).Release()
w := mClock.Set(ms(1000)) // 1st check starts
timeoutTrap.MustWait(ctx).Release()
mClock.Set(ms(1001)).MustWait(ctx) // report tick, no change
mClock.Set(ms(1999)) // timeout pops
w.MustWait(ctx) // 1st check finished
w = mClock.Set(ms(2000)) // 2nd check starts
timeoutTrap.MustWait(ctx).Release()
mClock.Set(ms(2001)).MustWait(ctx) // report tick, no change
mClock.Set(ms(2999)) // timeout pops
w.MustWait(ctx) // 2nd check finished
// app is now unhealthy after 2 timeouts
mClock.Set(ms(3000)) // 3rd check starts
timeoutTrap.MustWait(ctx).Release()
mClock.Set(ms(3001)).MustWait(ctx) // report tick, sends changes
update := testutil.RequireRecvCtx(ctx, t, fakeAPI.AppHealthCh())
require.Len(t, update.GetUpdates(), 1)
applyUpdate(t, apps, update)
require.Equal(t, codersdk.WorkspaceAppHealthUnhealthy, apps[0].Health)
}
func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.WorkspaceApp, handlers []http.Handler) (agent.WorkspaceAgentApps, func()) {
func setupAppReporter(
ctx context.Context, t *testing.T,
apps []codersdk.WorkspaceApp,
handlers []http.Handler,
clk quartz.Clock,
) (*agenttest.FakeAgentAPI, func()) {
closers := []func(){}
for i, app := range apps {
if app.ID == uuid.Nil {
app.ID = uuid.New()
apps[i] = app
}
for _, app := range apps {
require.NotEqual(t, uuid.Nil, app.ID, "all apps must have ID set")
}
for i, handler := range handlers {
if handler == nil {
@@ -196,14 +252,6 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa
closers = append(closers, ts.Close)
}
var mu sync.Mutex
workspaceAgentApps := func(context.Context) ([]codersdk.WorkspaceApp, error) {
mu.Lock()
defer mu.Unlock()
var newApps []codersdk.WorkspaceApp
return append(newApps, apps...), nil
}
// 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.
//
@@ -212,38 +260,31 @@ func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.Workspa
// 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)]))
go agent.NewAppHealthReporterWithClock(
slogtest.Make(t, nil).Leveled(slog.LevelDebug),
apps, agentsdk.AppHealthPoster(fakeAAPI), clk,
)(ctx)
for i, app := range apps {
if app.ID != updateID {
continue
}
app.Health = updateHealth
apps[i] = app
}
}
mu.Unlock()
}
}
}()
go agent.NewWorkspaceAppHealthReporter(slogtest.Make(t, nil).Leveled(slog.LevelDebug), apps, agentsdk.AppHealthPoster(fakeAAPI))(ctx)
return workspaceAgentApps, func() {
return fakeAAPI, func() {
for _, closeFn := range closers {
closeFn()
}
}
}
func applyUpdate(t *testing.T, apps []codersdk.WorkspaceApp, req *proto.BatchUpdateAppHealthRequest) {
t.Helper()
for _, update := range req.Updates {
updateID, err := uuid.FromBytes(update.Id)
require.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
}
}
}
+51
View File
@@ -0,0 +1,51 @@
package agent
import (
"context"
"runtime"
"sync"
"cdr.dev/slog"
)
// checkpoint allows a goroutine to communicate when it is OK to proceed beyond some async condition
// to other dependent goroutines.
type checkpoint struct {
logger slog.Logger
mu sync.Mutex
called bool
done chan struct{}
err error
}
// complete the checkpoint. Pass nil to indicate the checkpoint was ok. It is an error to call this
// more than once.
func (c *checkpoint) complete(err error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.called {
b := make([]byte, 2048)
n := runtime.Stack(b, false)
c.logger.Critical(context.Background(), "checkpoint complete called more than once", slog.F("stacktrace", b[:n]))
return
}
c.called = true
c.err = err
close(c.done)
}
func (c *checkpoint) wait(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-c.done:
return c.err
}
}
func newCheckpoint(logger slog.Logger) *checkpoint {
return &checkpoint{
logger: logger,
done: make(chan struct{}),
}
}
+49
View File
@@ -0,0 +1,49 @@
package agent
import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/testutil"
)
func TestCheckpoint_CompleteWait(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, nil)
ctx := testutil.Context(t, testutil.WaitShort)
uut := newCheckpoint(logger)
err := xerrors.New("test")
uut.complete(err)
got := uut.wait(ctx)
require.Equal(t, err, got)
}
func TestCheckpoint_CompleteTwice(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
ctx := testutil.Context(t, testutil.WaitShort)
uut := newCheckpoint(logger)
err := xerrors.New("test")
uut.complete(err)
uut.complete(nil) // drops CRITICAL log
got := uut.wait(ctx)
require.Equal(t, err, got)
}
func TestCheckpoint_WaitComplete(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, nil)
ctx := testutil.Context(t, testutil.WaitShort)
uut := newCheckpoint(logger)
err := xerrors.New("test")
errCh := make(chan error, 1)
go func() {
errCh <- uut.wait(ctx)
}()
uut.complete(err)
got := testutil.RequireRecvCtx(ctx, t, errCh)
require.Equal(t, err, got)
}
+31
View File
@@ -0,0 +1,31 @@
package agent
import (
"net/http"
"github.com/coder/coder/v2/coderd/healthcheck/health"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/healthsdk"
)
func (a *agent) HandleNetcheck(rw http.ResponseWriter, r *http.Request) {
ni := a.TailnetConn().GetNetInfo()
ifReport, err := healthsdk.RunInterfacesReport()
if err != nil {
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to run interfaces report",
Detail: err.Error(),
})
return
}
httpapi.Write(r.Context(), rw, http.StatusOK, healthsdk.AgentNetcheckReport{
BaseReport: healthsdk.BaseReport{
Severity: health.SeverityOK,
},
NetInfo: ni,
Interfaces: ifReport,
})
}
+342 -129
View File
@@ -1859,6 +1859,154 @@ func (x *BatchCreateLogsResponse) GetLogLimitExceeded() bool {
return false
}
type GetAnnouncementBannersRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetAnnouncementBannersRequest) Reset() {
*x = GetAnnouncementBannersRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetAnnouncementBannersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAnnouncementBannersRequest) ProtoMessage() {}
func (x *GetAnnouncementBannersRequest) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetAnnouncementBannersRequest.ProtoReflect.Descriptor instead.
func (*GetAnnouncementBannersRequest) Descriptor() ([]byte, []int) {
return file_agent_proto_agent_proto_rawDescGZIP(), []int{22}
}
type GetAnnouncementBannersResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AnnouncementBanners []*BannerConfig `protobuf:"bytes,1,rep,name=announcement_banners,json=announcementBanners,proto3" json:"announcement_banners,omitempty"`
}
func (x *GetAnnouncementBannersResponse) Reset() {
*x = GetAnnouncementBannersResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetAnnouncementBannersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAnnouncementBannersResponse) ProtoMessage() {}
func (x *GetAnnouncementBannersResponse) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetAnnouncementBannersResponse.ProtoReflect.Descriptor instead.
func (*GetAnnouncementBannersResponse) Descriptor() ([]byte, []int) {
return file_agent_proto_agent_proto_rawDescGZIP(), []int{23}
}
func (x *GetAnnouncementBannersResponse) GetAnnouncementBanners() []*BannerConfig {
if x != nil {
return x.AnnouncementBanners
}
return nil
}
type BannerConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
BackgroundColor string `protobuf:"bytes,3,opt,name=background_color,json=backgroundColor,proto3" json:"background_color,omitempty"`
}
func (x *BannerConfig) Reset() {
*x = BannerConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BannerConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BannerConfig) ProtoMessage() {}
func (x *BannerConfig) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BannerConfig.ProtoReflect.Descriptor instead.
func (*BannerConfig) Descriptor() ([]byte, []int) {
return file_agent_proto_agent_proto_rawDescGZIP(), []int{24}
}
func (x *BannerConfig) GetEnabled() bool {
if x != nil {
return x.Enabled
}
return false
}
func (x *BannerConfig) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *BannerConfig) GetBackgroundColor() string {
if x != nil {
return x.BackgroundColor
}
return ""
}
type WorkspaceApp_Healthcheck struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1872,7 +2020,7 @@ type WorkspaceApp_Healthcheck struct {
func (x *WorkspaceApp_Healthcheck) Reset() {
*x = WorkspaceApp_Healthcheck{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[22]
mi := &file_agent_proto_agent_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1885,7 +2033,7 @@ func (x *WorkspaceApp_Healthcheck) String() string {
func (*WorkspaceApp_Healthcheck) ProtoMessage() {}
func (x *WorkspaceApp_Healthcheck) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[22]
mi := &file_agent_proto_agent_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1936,7 +2084,7 @@ type WorkspaceAgentMetadata_Result struct {
func (x *WorkspaceAgentMetadata_Result) Reset() {
*x = WorkspaceAgentMetadata_Result{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[23]
mi := &file_agent_proto_agent_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1949,7 +2097,7 @@ func (x *WorkspaceAgentMetadata_Result) String() string {
func (*WorkspaceAgentMetadata_Result) ProtoMessage() {}
func (x *WorkspaceAgentMetadata_Result) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[23]
mi := &file_agent_proto_agent_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2008,7 +2156,7 @@ type WorkspaceAgentMetadata_Description struct {
func (x *WorkspaceAgentMetadata_Description) Reset() {
*x = WorkspaceAgentMetadata_Description{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[24]
mi := &file_agent_proto_agent_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2021,7 +2169,7 @@ func (x *WorkspaceAgentMetadata_Description) String() string {
func (*WorkspaceAgentMetadata_Description) ProtoMessage() {}
func (x *WorkspaceAgentMetadata_Description) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[24]
mi := &file_agent_proto_agent_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2086,7 +2234,7 @@ type Stats_Metric struct {
func (x *Stats_Metric) Reset() {
*x = Stats_Metric{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[27]
mi := &file_agent_proto_agent_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2099,7 +2247,7 @@ func (x *Stats_Metric) String() string {
func (*Stats_Metric) ProtoMessage() {}
func (x *Stats_Metric) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[27]
mi := &file_agent_proto_agent_proto_msgTypes[30]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2155,7 +2303,7 @@ type Stats_Metric_Label struct {
func (x *Stats_Metric_Label) Reset() {
*x = Stats_Metric_Label{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[28]
mi := &file_agent_proto_agent_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2168,7 +2316,7 @@ func (x *Stats_Metric_Label) String() string {
func (*Stats_Metric_Label) ProtoMessage() {}
func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[28]
mi := &file_agent_proto_agent_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2210,7 +2358,7 @@ type BatchUpdateAppHealthRequest_HealthUpdate struct {
func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() {
*x = BatchUpdateAppHealthRequest_HealthUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_agent_proto_agent_proto_msgTypes[29]
mi := &file_agent_proto_agent_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2223,7 +2371,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string {
func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {}
func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message {
mi := &file_agent_proto_agent_proto_msgTypes[29]
mi := &file_agent_proto_agent_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2594,64 +2742,87 @@ var file_agent_proto_agent_proto_rawDesc = []byte{
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69,
0x6d, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x08, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65,
0x65, 0x64, 0x65, 0x64, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74,
0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f,
0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a,
0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49,
0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a,
0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e,
0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xf6, 0x05, 0x0a, 0x05, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61,
0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65,
0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f,
0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69,
0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e,
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c,
0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61,
0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c,
0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e,
0x65, 0x64, 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75,
0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f,
0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x6e, 0x6e, 0x6f, 0x75,
0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x52, 0x13, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e,
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65,
0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c,
0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10,
0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02,
0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a,
0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xef, 0x06, 0x0a,
0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e,
0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65,
0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66,
0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e,
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76,
0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12,
0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a,
0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48,
0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76,
0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70,
0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e,
0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12,
0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e,
0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62,
0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67,
0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f,
0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65,
0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f,
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74,
0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65,
0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65,
0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61,
0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f,
0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12,
0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e,
0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42,
0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27,
0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64,
0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -2667,7 +2838,7 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte {
}
var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 33)
var file_agent_proto_agent_proto_goTypes = []interface{}{
(AppHealth)(0), // 0: coder.agent.v2.AppHealth
(WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel
@@ -2698,73 +2869,79 @@ var file_agent_proto_agent_proto_goTypes = []interface{}{
(*Log)(nil), // 26: coder.agent.v2.Log
(*BatchCreateLogsRequest)(nil), // 27: coder.agent.v2.BatchCreateLogsRequest
(*BatchCreateLogsResponse)(nil), // 28: coder.agent.v2.BatchCreateLogsResponse
(*WorkspaceApp_Healthcheck)(nil), // 29: coder.agent.v2.WorkspaceApp.Healthcheck
(*WorkspaceAgentMetadata_Result)(nil), // 30: coder.agent.v2.WorkspaceAgentMetadata.Result
(*WorkspaceAgentMetadata_Description)(nil), // 31: coder.agent.v2.WorkspaceAgentMetadata.Description
nil, // 32: coder.agent.v2.Manifest.EnvironmentVariablesEntry
nil, // 33: coder.agent.v2.Stats.ConnectionsByProtoEntry
(*Stats_Metric)(nil), // 34: coder.agent.v2.Stats.Metric
(*Stats_Metric_Label)(nil), // 35: coder.agent.v2.Stats.Metric.Label
(*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 36: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
(*durationpb.Duration)(nil), // 37: google.protobuf.Duration
(*proto.DERPMap)(nil), // 38: coder.tailnet.v2.DERPMap
(*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp
(*GetAnnouncementBannersRequest)(nil), // 29: coder.agent.v2.GetAnnouncementBannersRequest
(*GetAnnouncementBannersResponse)(nil), // 30: coder.agent.v2.GetAnnouncementBannersResponse
(*BannerConfig)(nil), // 31: coder.agent.v2.BannerConfig
(*WorkspaceApp_Healthcheck)(nil), // 32: coder.agent.v2.WorkspaceApp.Healthcheck
(*WorkspaceAgentMetadata_Result)(nil), // 33: coder.agent.v2.WorkspaceAgentMetadata.Result
(*WorkspaceAgentMetadata_Description)(nil), // 34: coder.agent.v2.WorkspaceAgentMetadata.Description
nil, // 35: coder.agent.v2.Manifest.EnvironmentVariablesEntry
nil, // 36: coder.agent.v2.Stats.ConnectionsByProtoEntry
(*Stats_Metric)(nil), // 37: coder.agent.v2.Stats.Metric
(*Stats_Metric_Label)(nil), // 38: coder.agent.v2.Stats.Metric.Label
(*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 39: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
(*durationpb.Duration)(nil), // 40: google.protobuf.Duration
(*proto.DERPMap)(nil), // 41: coder.tailnet.v2.DERPMap
(*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp
}
var file_agent_proto_agent_proto_depIdxs = []int32{
1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel
29, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck
32, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck
2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health
37, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration
30, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
31, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
32, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry
38, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap
40, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration
33, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
34, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
35, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry
41, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap
8, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript
7, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp
31, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
33, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry
34, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric
34, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
36, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry
37, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric
14, // 13: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats
37, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration
40, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration
4, // 15: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State
39, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp
42, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp
17, // 17: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle
36, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
39, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
5, // 19: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem
21, // 20: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup
30, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
33, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
23, // 22: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata
39, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp
42, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp
6, // 24: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level
26, // 25: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log
37, // 26: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration
39, // 27: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp
37, // 28: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration
37, // 29: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration
3, // 30: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type
35, // 31: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label
0, // 32: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth
11, // 33: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest
13, // 34: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest
15, // 35: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest
18, // 36: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest
19, // 37: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest
22, // 38: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest
24, // 39: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest
27, // 40: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest
10, // 41: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest
12, // 42: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner
16, // 43: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse
17, // 44: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle
20, // 45: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse
21, // 46: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup
25, // 47: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse
28, // 48: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse
41, // [41:49] is the sub-list for method output_type
33, // [33:41] is the sub-list for method input_type
33, // [33:33] is the sub-list for extension type_name
33, // [33:33] is the sub-list for extension extendee
0, // [0:33] is the sub-list for field type_name
31, // 26: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig
40, // 27: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration
42, // 28: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp
40, // 29: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration
40, // 30: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration
3, // 31: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type
38, // 32: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label
0, // 33: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth
11, // 34: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest
13, // 35: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest
15, // 36: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest
18, // 37: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest
19, // 38: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest
22, // 39: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest
24, // 40: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest
27, // 41: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest
29, // 42: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest
10, // 43: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest
12, // 44: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner
16, // 45: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse
17, // 46: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle
20, // 47: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse
21, // 48: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup
25, // 49: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse
28, // 50: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse
30, // 51: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse
43, // [43:52] is the sub-list for method output_type
34, // [34:43] is the sub-list for method input_type
34, // [34:34] is the sub-list for extension type_name
34, // [34:34] is the sub-list for extension extendee
0, // [0:34] is the sub-list for field type_name
}
func init() { file_agent_proto_agent_proto_init() }
@@ -3038,7 +3215,7 @@ func file_agent_proto_agent_proto_init() {
}
}
file_agent_proto_agent_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WorkspaceApp_Healthcheck); i {
switch v := v.(*GetAnnouncementBannersRequest); i {
case 0:
return &v.state
case 1:
@@ -3050,7 +3227,7 @@ func file_agent_proto_agent_proto_init() {
}
}
file_agent_proto_agent_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WorkspaceAgentMetadata_Result); i {
switch v := v.(*GetAnnouncementBannersResponse); i {
case 0:
return &v.state
case 1:
@@ -3062,7 +3239,31 @@ func file_agent_proto_agent_proto_init() {
}
}
file_agent_proto_agent_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WorkspaceAgentMetadata_Description); i {
switch v := v.(*BannerConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_proto_agent_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WorkspaceApp_Healthcheck); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_proto_agent_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WorkspaceAgentMetadata_Result); i {
case 0:
return &v.state
case 1:
@@ -3074,6 +3275,18 @@ func file_agent_proto_agent_proto_init() {
}
}
file_agent_proto_agent_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WorkspaceAgentMetadata_Description); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_agent_proto_agent_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Stats_Metric); i {
case 0:
return &v.state
@@ -3085,7 +3298,7 @@ func file_agent_proto_agent_proto_init() {
return nil
}
}
file_agent_proto_agent_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
file_agent_proto_agent_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Stats_Metric_Label); i {
case 0:
return &v.state
@@ -3097,7 +3310,7 @@ func file_agent_proto_agent_proto_init() {
return nil
}
}
file_agent_proto_agent_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
file_agent_proto_agent_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i {
case 0:
return &v.state
@@ -3116,7 +3329,7 @@ func file_agent_proto_agent_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_agent_proto_agent_proto_rawDesc,
NumEnums: 7,
NumMessages: 30,
NumMessages: 33,
NumExtensions: 0,
NumServices: 1,
},
+13
View File
@@ -251,6 +251,18 @@ message BatchCreateLogsResponse {
bool log_limit_exceeded = 1;
}
message GetAnnouncementBannersRequest {}
message GetAnnouncementBannersResponse {
repeated BannerConfig announcement_banners = 1;
}
message BannerConfig {
bool enabled = 1;
string message = 2;
string background_color = 3;
}
service Agent {
rpc GetManifest(GetManifestRequest) returns (Manifest);
rpc GetServiceBanner(GetServiceBannerRequest) returns (ServiceBanner);
@@ -260,4 +272,5 @@ service Agent {
rpc UpdateStartup(UpdateStartupRequest) returns (Startup);
rpc BatchUpdateMetadata(BatchUpdateMetadataRequest) returns (BatchUpdateMetadataResponse);
rpc BatchCreateLogs(BatchCreateLogsRequest) returns (BatchCreateLogsResponse);
rpc GetAnnouncementBanners(GetAnnouncementBannersRequest) returns (GetAnnouncementBannersResponse);
}
+41 -1
View File
@@ -46,6 +46,7 @@ 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)
GetAnnouncementBanners(ctx context.Context, in *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error)
}
type drpcAgentClient struct {
@@ -130,6 +131,15 @@ func (c *drpcAgentClient) BatchCreateLogs(ctx context.Context, in *BatchCreateLo
return out, nil
}
func (c *drpcAgentClient) GetAnnouncementBanners(ctx context.Context, in *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error) {
out := new(GetAnnouncementBannersResponse)
err := c.cc.Invoke(ctx, "/coder.agent.v2.Agent/GetAnnouncementBanners", drpcEncoding_File_agent_proto_agent_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
type DRPCAgentServer interface {
GetManifest(context.Context, *GetManifestRequest) (*Manifest, error)
GetServiceBanner(context.Context, *GetServiceBannerRequest) (*ServiceBanner, error)
@@ -139,6 +149,7 @@ type DRPCAgentServer interface {
UpdateStartup(context.Context, *UpdateStartupRequest) (*Startup, error)
BatchUpdateMetadata(context.Context, *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
BatchCreateLogs(context.Context, *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
GetAnnouncementBanners(context.Context, *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error)
}
type DRPCAgentUnimplementedServer struct{}
@@ -175,9 +186,13 @@ func (s *DRPCAgentUnimplementedServer) BatchCreateLogs(context.Context, *BatchCr
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCAgentUnimplementedServer) GetAnnouncementBanners(context.Context, *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
type DRPCAgentDescription struct{}
func (DRPCAgentDescription) NumMethods() int { return 8 }
func (DRPCAgentDescription) NumMethods() int { return 9 }
func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n {
@@ -253,6 +268,15 @@ func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
in1.(*BatchCreateLogsRequest),
)
}, DRPCAgentServer.BatchCreateLogs, true
case 8:
return "/coder.agent.v2.Agent/GetAnnouncementBanners", drpcEncoding_File_agent_proto_agent_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCAgentServer).
GetAnnouncementBanners(
ctx,
in1.(*GetAnnouncementBannersRequest),
)
}, DRPCAgentServer.GetAnnouncementBanners, true
default:
return "", nil, nil, nil, false
}
@@ -389,3 +413,19 @@ func (x *drpcAgent_BatchCreateLogsStream) SendAndClose(m *BatchCreateLogsRespons
}
return x.CloseSend()
}
type DRPCAgent_GetAnnouncementBannersStream interface {
drpc.Stream
SendAndClose(*GetAnnouncementBannersResponse) error
}
type drpcAgent_GetAnnouncementBannersStream struct {
drpc.Stream
}
func (x *drpcAgent_GetAnnouncementBannersStream) SendAndClose(m *GetAnnouncementBannersResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil {
return err
}
return x.CloseSend()
}
+38
View File
@@ -0,0 +1,38 @@
package proto
import (
"context"
"storj.io/drpc"
)
// DRPCAgentClient20 is the Agent API at v2.0. Notably, it is missing GetAnnouncementBanners, but
// is useful when you want to be maximally compatible with Coderd Release Versions from 2.9+
type DRPCAgentClient20 interface {
DRPCConn() drpc.Conn
GetManifest(ctx context.Context, in *GetManifestRequest) (*Manifest, error)
GetServiceBanner(ctx context.Context, in *GetServiceBannerRequest) (*ServiceBanner, error)
UpdateStats(ctx context.Context, in *UpdateStatsRequest) (*UpdateStatsResponse, error)
UpdateLifecycle(ctx context.Context, in *UpdateLifecycleRequest) (*Lifecycle, error)
BatchUpdateAppHealths(ctx context.Context, in *BatchUpdateAppHealthRequest) (*BatchUpdateAppHealthResponse, error)
UpdateStartup(ctx context.Context, in *UpdateStartupRequest) (*Startup, error)
BatchUpdateMetadata(ctx context.Context, in *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
}
// DRPCAgentClient21 is the Agent API at v2.1. It is useful if you want to be maximally compatible
// with Coderd Release Versions from 2.12+
type DRPCAgentClient21 interface {
DRPCConn() drpc.Conn
GetManifest(ctx context.Context, in *GetManifestRequest) (*Manifest, error)
GetServiceBanner(ctx context.Context, in *GetServiceBannerRequest) (*ServiceBanner, error)
UpdateStats(ctx context.Context, in *UpdateStatsRequest) (*UpdateStatsResponse, error)
UpdateLifecycle(ctx context.Context, in *UpdateLifecycleRequest) (*Lifecycle, error)
BatchUpdateAppHealths(ctx context.Context, in *BatchUpdateAppHealthRequest) (*BatchUpdateAppHealthResponse, error)
UpdateStartup(ctx context.Context, in *UpdateStartupRequest) (*Startup, error)
BatchUpdateMetadata(ctx context.Context, in *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
GetAnnouncementBanners(ctx context.Context, in *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error)
}
+1 -1
View File
@@ -26,7 +26,7 @@ type APIVersion struct {
}
func (v *APIVersion) WithBackwardCompat(majs ...int) *APIVersion {
v.additionalMajors = append(v.additionalMajors, majs[:]...)
v.additionalMajors = append(v.additionalMajors, majs...)
return v
}
+33
View File
@@ -27,6 +27,7 @@ import (
"cdr.dev/slog/sloggers/slogstackdriver"
"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agentproc"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/agent/reaper"
"github.com/coder/coder/v2/buildinfo"
"github.com/coder/coder/v2/codersdk"
@@ -48,6 +49,9 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
slogHumanPath string
slogJSONPath string
slogStackdriverPath string
blockFileTransfer bool
agentHeaderCommand string
agentHeader []string
)
cmd := &serpent.Command{
Use: "agent",
@@ -174,6 +178,14 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
// with large payloads can take a bit. e.g. startup scripts
// may take a while to insert.
client.SDK.HTTPClient.Timeout = 30 * time.Second
// Attach header transport so we process --agent-header and
// --agent-header-command flags
headerTransport, err := headerTransport(ctx, r.agentURL, agentHeader, agentHeaderCommand)
if err != nil {
return xerrors.Errorf("configure header transport: %w", err)
}
headerTransport.Transport = client.SDK.HTTPClient.Transport
client.SDK.HTTPClient.Transport = headerTransport
// Enable pprof handler
// This prevents the pprof import from being accidentally deleted.
@@ -314,6 +326,8 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
// Intentionally set this to nil. It's mainly used
// for testing.
ModifiedProcesses: nil,
BlockFileTransfer: blockFileTransfer,
})
promHandler := agent.PrometheusMetricsHandler(prometheusRegistry, logger)
@@ -357,6 +371,18 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
Value: serpent.StringOf(&pprofAddress),
Description: "The address to serve pprof.",
},
{
Flag: "agent-header-command",
Env: "CODER_AGENT_HEADER_COMMAND",
Value: serpent.StringOf(&agentHeaderCommand),
Description: "An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line.",
},
{
Flag: "agent-header",
Env: "CODER_AGENT_HEADER",
Value: serpent.StringArrayOf(&agentHeader),
Description: "Additional HTTP headers added to all requests. Provide as " + `key=value` + ". Can be specified multiple times.",
},
{
Flag: "no-reap",
@@ -417,6 +443,13 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
Default: "",
Value: serpent.StringOf(&slogStackdriverPath),
},
{
Flag: "block-file-transfer",
Default: "false",
Env: "CODER_AGENT_BLOCK_FILE_TRANSFER",
Description: fmt.Sprintf("Block file transfer using known applications: %s.", strings.Join(agentssh.BlockedFileTransferCommands, ",")),
Value: serpent.BoolOf(&blockFileTransfer),
},
}
return cmd
+40
View File
@@ -3,10 +3,13 @@ package cli_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"sync/atomic"
"testing"
"github.com/google/uuid"
@@ -229,6 +232,43 @@ func TestWorkspaceAgent(t *testing.T) {
require.Equal(t, codersdk.AgentSubsystemEnvbox, resources[0].Agents[0].Subsystems[0])
require.Equal(t, codersdk.AgentSubsystemExectrace, resources[0].Agents[0].Subsystems[1])
})
t.Run("Header", func(t *testing.T) {
t.Parallel()
var url string
var called int64
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "wow", r.Header.Get("X-Testing"))
assert.Equal(t, "Ethan was Here!", r.Header.Get("Cool-Header"))
assert.Equal(t, "very-wow-"+url, r.Header.Get("X-Process-Testing"))
assert.Equal(t, "more-wow", r.Header.Get("X-Process-Testing2"))
atomic.AddInt64(&called, 1)
w.WriteHeader(http.StatusGone)
}))
defer srv.Close()
url = srv.URL
coderURLEnv := "$CODER_URL"
if runtime.GOOS == "windows" {
coderURLEnv = "%CODER_URL%"
}
logDir := t.TempDir()
inv, _ := clitest.New(t,
"agent",
"--auth", "token",
"--agent-token", "fake-token",
"--agent-url", srv.URL,
"--log-dir", logDir,
"--agent-header", "X-Testing=wow",
"--agent-header", "Cool-Header=Ethan was Here!",
"--agent-header-command", "printf X-Process-Testing=very-wow-"+coderURLEnv+"'\\r\\n'X-Process-Testing2=more-wow",
)
clitest.Start(t, inv)
require.Eventually(t, func() bool {
return atomic.LoadInt64(&called) > 0
}, testutil.WaitShort, testutil.IntervalFast)
})
}
func matchAgentWithVersion(rs []codersdk.WorkspaceResource) bool {
+1 -1
View File
@@ -24,7 +24,7 @@ func TestAutoUpdate(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
require.Equal(t, codersdk.AutomaticUpdatesNever, workspace.AutomaticUpdates)
+7 -6
View File
@@ -11,6 +11,7 @@ import (
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/config"
@@ -183,11 +184,11 @@ func prepareTestData(t *testing.T) (*codersdk.Client, map[string]string) {
IncludeProvisionerDaemon: true,
})
firstUser := coderdtest.CreateFirstUser(t, rootClient)
secondUser, err := rootClient.CreateUser(ctx, codersdk.CreateUserRequest{
Email: "testuser2@coder.com",
Username: "testuser2",
Password: coderdtest.FirstUserParams.Password,
OrganizationID: firstUser.OrganizationID,
secondUser, err := rootClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
Email: "testuser2@coder.com",
Username: "testuser2",
Password: coderdtest.FirstUserParams.Password,
OrganizationIDs: []uuid.UUID{firstUser.OrganizationID},
})
require.NoError(t, err)
version := coderdtest.CreateTemplateVersion(t, rootClient, firstUser.OrganizationID, nil)
@@ -195,7 +196,7 @@ func prepareTestData(t *testing.T) (*codersdk.Client, map[string]string) {
template := coderdtest.CreateTemplate(t, rootClient, firstUser.OrganizationID, version.ID, func(req *codersdk.CreateTemplateRequest) {
req.Name = "test-template"
})
workspace := coderdtest.CreateWorkspace(t, rootClient, firstUser.OrganizationID, template.ID, func(req *codersdk.CreateWorkspaceRequest) {
workspace := coderdtest.CreateWorkspace(t, rootClient, template.ID, func(req *codersdk.CreateWorkspaceRequest) {
req.Name = "test-workspace"
})
workspaceBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, rootClient, workspace.LatestBuild.ID)
+117 -6
View File
@@ -10,8 +10,11 @@ import (
"github.com/google/uuid"
"golang.org/x/xerrors"
"tailscale.com/tailcfg"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/healthsdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/tailnet"
)
@@ -116,7 +119,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
if agent.Status == codersdk.WorkspaceAgentTimeout {
now := time.Now()
sw.Log(now, codersdk.LogLevelInfo, "The workspace agent is having trouble connecting, wait for it to connect or restart your workspace.")
sw.Log(now, codersdk.LogLevelInfo, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#agent-connection-issues"))
sw.Log(now, codersdk.LogLevelInfo, troubleshootingMessage(agent, "https://coder.com/docs/templates#agent-connection-issues"))
for agent.Status == codersdk.WorkspaceAgentTimeout {
if agent, err = fetch(); err != nil {
return xerrors.Errorf("fetch: %w", err)
@@ -132,11 +135,14 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
}
stage := "Running workspace agent startup scripts"
follow := opts.Wait
follow := opts.Wait && agent.LifecycleState.Starting()
if !follow {
stage += " (non-blocking)"
}
sw.Start(stage)
if follow {
sw.Log(time.Time{}, codersdk.LogLevelInfo, "==> ︎ To connect immediately, reconnect with --wait=no or CODER_SSH_WAIT=no, see --help for more information.")
}
err = func() error { // Use func because of defer in for loop.
logStream, logsCloser, err := opts.FetchLogs(ctx, agent.ID, 0, follow)
@@ -206,19 +212,25 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
case codersdk.WorkspaceAgentLifecycleReady:
sw.Complete(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
case codersdk.WorkspaceAgentLifecycleStartTimeout:
sw.Fail(stage, 0)
// Backwards compatibility: Avoid printing warning if
// coderd is old and doesn't set ReadyAt for timeouts.
if agent.ReadyAt == nil {
sw.Fail(stage, 0)
} else {
sw.Fail(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
}
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script timed out and your workspace may be incomplete.")
case codersdk.WorkspaceAgentLifecycleStartError:
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/troubleshooting#startup-script-exited-with-an-error"))
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/templates/troubleshooting#startup-script-exited-with-an-error"))
default:
switch {
case agent.LifecycleState.Starting():
// Use zero time (omitted) to separate these from the startup logs.
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Notice: The startup scripts are still running and your workspace may be incomplete.")
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates/troubleshooting#your-workspace-may-be-incomplete"))
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/templates/troubleshooting#your-workspace-may-be-incomplete"))
// Note: We don't complete or fail the stage here, it's
// intentionally left open to indicate this stage didn't
// complete.
@@ -240,7 +252,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
stage := "The workspace agent lost connection"
sw.Start(stage)
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/troubleshooting#agent-connection-issues"))
sw.Log(time.Now(), codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/templates/troubleshooting#agent-connection-issues"))
disconnectedAt := agent.DisconnectedAt
for agent.Status == codersdk.WorkspaceAgentDisconnected {
@@ -337,3 +349,102 @@ func PeerDiagnostics(w io.Writer, d tailnet.PeerDiagnostics) {
_, _ = fmt.Fprint(w, "✘ Wireguard is not connected\n")
}
}
type ConnDiags struct {
ConnInfo workspacesdk.AgentConnectionInfo
PingP2P bool
DisableDirect bool
LocalNetInfo *tailcfg.NetInfo
LocalInterfaces *healthsdk.InterfacesReport
AgentNetcheck *healthsdk.AgentNetcheckReport
ClientIPIsAWS bool
AgentIPIsAWS bool
Verbose bool
// TODO: More diagnostics
}
func (d ConnDiags) Write(w io.Writer) {
_, _ = fmt.Fprintln(w, "")
general, client, agent := d.splitDiagnostics()
for _, msg := range general {
_, _ = fmt.Fprintln(w, msg)
}
if len(client) > 0 {
_, _ = fmt.Fprint(w, "Possible client-side issues with direct connection:\n\n")
for _, msg := range client {
_, _ = fmt.Fprintf(w, " - %s\n\n", msg)
}
}
if len(agent) > 0 {
_, _ = fmt.Fprint(w, "Possible agent-side issues with direct connections:\n\n")
for _, msg := range agent {
_, _ = fmt.Fprintf(w, " - %s\n\n", msg)
}
}
}
func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
if d.PingP2P {
general = append(general, "✔ You are connected directly (p2p)")
} else {
general = append(general, "❗ You are connected via a DERP relay, not directly (p2p)")
}
if d.AgentNetcheck != nil {
for _, msg := range d.AgentNetcheck.Interfaces.Warnings {
agent = append(agent, msg.Message)
}
}
if d.LocalInterfaces != nil {
for _, msg := range d.LocalInterfaces.Warnings {
client = append(client, msg.Message)
}
}
if d.PingP2P && !d.Verbose {
return general, client, agent
}
if d.DisableDirect {
general = append(general, "❗ Direct connections are disabled locally, by `--disable-direct` or `CODER_DISABLE_DIRECT`")
if !d.Verbose {
return general, client, agent
}
}
if d.ConnInfo.DisableDirectConnections {
general = append(general, "❗ Your Coder administrator has blocked direct connections")
if !d.Verbose {
return general, client, agent
}
}
if !d.ConnInfo.DERPMap.HasSTUN() {
general = append(general, "The DERP map is not configured to use STUN")
} else if d.LocalNetInfo != nil && !d.LocalNetInfo.UDP {
client = append(client, "Client could not connect to STUN over UDP")
}
if d.LocalNetInfo != nil && d.LocalNetInfo.MappingVariesByDestIP.EqualBool(true) {
client = append(client, "Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers")
}
if d.AgentNetcheck != nil && d.AgentNetcheck.NetInfo != nil {
if d.AgentNetcheck.NetInfo.MappingVariesByDestIP.EqualBool(true) {
agent = append(agent, "Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers")
}
if !d.AgentNetcheck.NetInfo.UDP {
agent = append(agent, "Agent could not connect to STUN over UDP")
}
}
if d.ClientIPIsAWS {
client = append(client, "Client IP address is within an AWS range (AWS uses hard NAT)")
}
if d.AgentIPIsAWS {
agent = append(agent, "Agent IP address is within an AWS range (AWS uses hard NAT)")
}
return general, client, agent
}
+230 -2
View File
@@ -20,8 +20,11 @@ import (
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/healthcheck/health"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/healthsdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/testutil"
"github.com/coder/serpent"
@@ -95,6 +98,8 @@ func TestAgent(t *testing.T) {
iter: []func(context.Context, *testing.T, *codersdk.WorkspaceAgent, <-chan string, chan []codersdk.WorkspaceAgentLog) error{
func(_ context.Context, _ *testing.T, agent *codersdk.WorkspaceAgent, _ <-chan string, _ chan []codersdk.WorkspaceAgentLog) error {
agent.Status = codersdk.WorkspaceAgentConnecting
agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStarting
agent.StartedAt = ptr.Ref(time.Now())
return nil
},
func(_ context.Context, t *testing.T, agent *codersdk.WorkspaceAgent, output <-chan string, _ chan []codersdk.WorkspaceAgentLog) error {
@@ -104,6 +109,7 @@ func TestAgent(t *testing.T) {
agent.Status = codersdk.WorkspaceAgentConnected
agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStartTimeout
agent.FirstConnectedAt = ptr.Ref(time.Now())
agent.ReadyAt = ptr.Ref(time.Now())
return nil
},
},
@@ -226,6 +232,7 @@ func TestAgent(t *testing.T) {
},
want: []string{
"⧗ Running workspace agent startup scripts",
"︎ To connect immediately, reconnect with --wait=no or CODER_SSH_WAIT=no, see --help for more information.",
"testing: Hello world",
"Bye now",
"✔ Running workspace agent startup scripts",
@@ -254,9 +261,9 @@ func TestAgent(t *testing.T) {
},
},
want: []string{
"⧗ Running workspace agent startup scripts",
"⧗ Running workspace agent startup scripts (non-blocking)",
"Hello world",
"✘ Running workspace agent startup scripts",
"✘ Running workspace agent startup scripts (non-blocking)",
"Warning: A startup script exited with an error and your workspace may be incomplete.",
"For more information and troubleshooting, see",
},
@@ -306,6 +313,7 @@ func TestAgent(t *testing.T) {
},
want: []string{
"⧗ Running workspace agent startup scripts",
"︎ To connect immediately, reconnect with --wait=no or CODER_SSH_WAIT=no, see --help for more information.",
"Hello world",
"✔ Running workspace agent startup scripts",
},
@@ -667,3 +675,223 @@ func TestPeerDiagnostics(t *testing.T) {
})
}
}
func TestConnDiagnostics(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
diags cliui.ConnDiags
want []string
}{
{
name: "Direct",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: true,
LocalNetInfo: &tailcfg.NetInfo{},
},
want: []string{
`✔ You are connected directly (p2p)`,
},
},
{
name: "DirectBlocked",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
DisableDirectConnections: true,
},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`❗ Your Coder administrator has blocked direct connections`,
},
},
{
name: "NoStun",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
LocalNetInfo: &tailcfg.NetInfo{},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`The DERP map is not configured to use STUN`,
},
},
{
name: "ClientHasStunNoUDP",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
999: {
Nodes: []*tailcfg.DERPNode{
{
STUNPort: 1337,
},
},
},
},
},
},
LocalNetInfo: &tailcfg.NetInfo{
UDP: false,
},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`Client could not connect to STUN over UDP`,
},
},
{
name: "AgentHasStunNoUDP",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
999: {
Nodes: []*tailcfg.DERPNode{
{
STUNPort: 1337,
},
},
},
},
},
},
AgentNetcheck: &healthsdk.AgentNetcheckReport{
NetInfo: &tailcfg.NetInfo{
UDP: false,
},
},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`Agent could not connect to STUN over UDP`,
},
},
{
name: "ClientHardNat",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
LocalNetInfo: &tailcfg.NetInfo{
MappingVariesByDestIP: "true",
},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers`,
},
},
{
name: "AgentHardNat",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: false,
LocalNetInfo: &tailcfg.NetInfo{},
AgentNetcheck: &healthsdk.AgentNetcheckReport{
NetInfo: &tailcfg.NetInfo{MappingVariesByDestIP: "true"},
},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers`,
},
},
{
name: "AgentInterfaceWarnings",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: true,
AgentNetcheck: &healthsdk.AgentNetcheckReport{
Interfaces: healthsdk.InterfacesReport{
BaseReport: healthsdk.BaseReport{
Warnings: []health.Message{
health.Messagef(health.CodeInterfaceSmallMTU, "Network interface eth0 has MTU 1280, (less than 1378), which may degrade the quality of direct connections"),
},
},
},
},
},
want: []string{
`✔ You are connected directly (p2p)`,
`Network interface eth0 has MTU 1280, (less than 1378), which may degrade the quality of direct connections`,
},
},
{
name: "LocalInterfaceWarnings",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: true,
LocalInterfaces: &healthsdk.InterfacesReport{
BaseReport: healthsdk.BaseReport{
Warnings: []health.Message{
health.Messagef(health.CodeInterfaceSmallMTU, "Network interface eth1 has MTU 1310, (less than 1378), which may degrade the quality of direct connections"),
},
},
},
},
want: []string{
`✔ You are connected directly (p2p)`,
`Network interface eth1 has MTU 1310, (less than 1378), which may degrade the quality of direct connections`,
},
},
{
name: "ClientAWSIP",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
ClientIPIsAWS: true,
AgentIPIsAWS: false,
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`Client IP address is within an AWS range (AWS uses hard NAT)`,
},
},
{
name: "AgentAWSIP",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
ClientIPIsAWS: false,
AgentIPIsAWS: true,
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`Agent IP address is within an AWS range (AWS uses hard NAT)`,
},
},
}
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()
tc.diags.Write(w)
}()
bytes, err := io.ReadAll(r)
require.NoError(t, err)
output := string(bytes)
for _, want := range tc.want {
require.Contains(t, output, want)
}
})
}
}
+3
View File
@@ -37,6 +37,9 @@ func ExternalAuth(ctx context.Context, writer io.Writer, opts ExternalAuthOption
if auth.Authenticated {
return nil
}
if auth.Optional {
continue
}
_, _ = fmt.Fprintf(writer, "You must authenticate with %s to create a workspace with this template. Visit:\n\n\t%s\n\n", auth.DisplayName, auth.AuthenticateURL)
+10 -5
View File
@@ -7,6 +7,7 @@ import (
"reflect"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"golang.org/x/xerrors"
"github.com/coder/serpent"
@@ -64,8 +65,8 @@ func (f *OutputFormatter) AttachOptions(opts *serpent.OptionSet) {
Flag: "output",
FlagShorthand: "o",
Default: f.formats[0].ID(),
Value: serpent.StringOf(&f.formatID),
Description: "Output format. Available formats: " + strings.Join(formatNames, ", ") + ".",
Value: serpent.EnumOf(&f.formatID, formatNames...),
Description: "Output format.",
},
)
}
@@ -135,15 +136,19 @@ func (f *tableFormat) AttachOptions(opts *serpent.OptionSet) {
Flag: "column",
FlagShorthand: "c",
Default: strings.Join(f.defaultColumns, ","),
Value: serpent.StringArrayOf(&f.columns),
Description: "Columns to display in table output. Available columns: " + strings.Join(f.allColumns, ", ") + ".",
Value: serpent.EnumArrayOf(&f.columns, f.allColumns...),
Description: "Columns to display in table output.",
},
)
}
// Format implements OutputFormat.
func (f *tableFormat) Format(_ context.Context, data any) (string, error) {
return DisplayTable(data, f.sort, f.columns)
headers := make(table.Row, len(f.allColumns))
for i, header := range f.allColumns {
headers[i] = header
}
return renderTable(data, f.sort, headers, f.columns)
}
type jsonFormat struct{}
+8 -9
View File
@@ -106,11 +106,11 @@ func Test_OutputFormatter(t *testing.T) {
fs := cmd.Options.FlagSet()
selected, err := fs.GetString("output")
require.NoError(t, err)
require.Equal(t, "json", selected)
selected := cmd.Options.ByFlag("output")
require.NotNil(t, selected)
require.Equal(t, "json", selected.Value.String())
usage := fs.FlagUsages()
require.Contains(t, usage, "Available formats: json, foo")
require.Contains(t, usage, "Output format.")
require.Contains(t, usage, "foo flag 1234")
ctx := context.Background()
@@ -129,11 +129,10 @@ func Test_OutputFormatter(t *testing.T) {
require.Equal(t, "foo", out)
require.EqualValues(t, 1, atomic.LoadInt64(&called))
require.NoError(t, fs.Set("output", "bar"))
require.Error(t, fs.Set("output", "bar"))
out, err = f.Format(ctx, data)
require.Error(t, err)
require.ErrorContains(t, err, "bar")
require.Equal(t, "", out)
require.EqualValues(t, 1, atomic.LoadInt64(&called))
require.NoError(t, err)
require.Equal(t, "foo", out)
require.EqualValues(t, 2, atomic.LoadInt64(&called))
})
}
+4 -1
View File
@@ -43,7 +43,10 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
return "", err
}
values, err := MultiSelect(inv, options)
values, err := MultiSelect(inv, MultiSelectOptions{
Options: options,
Defaults: options,
})
if err == nil {
v, err := json.Marshal(&values)
if err != nil {
+13 -42
View File
@@ -14,48 +14,11 @@ import (
"github.com/coder/serpent"
)
func init() {
survey.SelectQuestionTemplate = `
{{- define "option"}}
{{- " " }}{{- if eq .SelectedIndex .CurrentIndex }}{{color "green" }}{{ .Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
{{- .CurrentOpt.Value}}
{{- color "reset"}}
{{end}}
{{- if not .ShowAnswer }}
{{- if .Config.Icons.Help.Text }}
{{- if .FilterMessage }}{{ "Search:" }}{{ .FilterMessage }}
{{- else }}
{{- color "black+h"}}{{- "Type to search" }}{{color "reset"}}
{{- end }}
{{- "\n" }}
{{- end }}
{{- "\n" }}
{{- range $ix, $option := .PageEntries}}
{{- template "option" $.IterateOption $ix $option}}
{{- end}}
{{- end }}`
survey.MultiSelectQuestionTemplate = `
{{- define "option"}}
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }}{{color "reset"}}{{else}} {{end}}
{{- if index .Checked .CurrentOpt.Index }}{{color .Config.Icons.MarkedOption.Format }} {{ .Config.Icons.MarkedOption.Text }} {{else}}{{color .Config.Icons.UnmarkedOption.Format }} {{ .Config.Icons.UnmarkedOption.Text }} {{end}}
{{- color "reset"}}
{{- " "}}{{- .CurrentOpt.Value}}
{{end}}
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
{{- if not .ShowAnswer }}
{{- "\n"}}
{{- range $ix, $option := .PageEntries}}
{{- template "option" $.IterateOption $ix $option}}
{{- end}}
{{- end}}`
}
type SelectOptions struct {
Options []string
// Default will be highlighted first if it's a valid option.
Default string
Message string
Size int
HideSearch bool
}
@@ -122,6 +85,7 @@ func Select(inv *serpent.Invocation, opts SelectOptions) (string, error) {
Options: opts.Options,
Default: defaultOption,
PageSize: opts.Size,
Message: opts.Message,
}, &value, survey.WithIcons(func(is *survey.IconSet) {
is.Help.Text = "Type to search"
if opts.HideSearch {
@@ -138,15 +102,22 @@ func Select(inv *serpent.Invocation, opts SelectOptions) (string, error) {
return value, err
}
func MultiSelect(inv *serpent.Invocation, items []string) ([]string, error) {
type MultiSelectOptions struct {
Message string
Options []string
Defaults []string
}
func MultiSelect(inv *serpent.Invocation, opts MultiSelectOptions) ([]string, error) {
// Similar hack is applied to Select()
if flag.Lookup("test.v") != nil {
return items, nil
return opts.Defaults, nil
}
prompt := &survey.MultiSelect{
Options: items,
Default: items,
Options: opts.Options,
Default: opts.Defaults,
Message: opts.Message,
}
var values []string
+4 -1
View File
@@ -107,7 +107,10 @@ func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
var values []string
cmd := &serpent.Command{
Handler: func(inv *serpent.Invocation) error {
selectedItems, err := cliui.MultiSelect(inv, items)
selectedItems, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Options: items,
Defaults: items,
})
if err == nil {
values = selectedItems
}
+74 -18
View File
@@ -22,6 +22,13 @@ func Table() table.Writer {
return tableWriter
}
// This type can be supplied as part of a slice to DisplayTable
// or to a `TableFormat` `Format` call to render a separator.
// Leading separators are not supported and trailing separators
// are ignored by the table formatter.
// e.g. `[]any{someRow, TableSeparator, someRow}`
type TableSeparator struct{}
// filterTableColumns returns configurations to hide columns
// that are not provided in the array. If the array is empty,
// no filtering will occur!
@@ -47,8 +54,12 @@ func filterTableColumns(header table.Row, columns []string) []table.ColumnConfig
return columnConfigs
}
// DisplayTable renders a table as a string. The input argument must be a slice
// of structs. At least one field in the struct must have a `table:""` tag
// DisplayTable renders a table as a string. The input argument can be:
// - a struct slice.
// - an interface slice, where the first element is a struct,
// and all other elements are of the same type, or a TableSeparator.
//
// At least one field in the struct must have a `table:""` tag
// containing the name of the column in the outputted table.
//
// If `sort` is not specified, the field with the `table:"$NAME,default_sort"`
@@ -66,11 +77,20 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
v := reflect.Indirect(reflect.ValueOf(out))
if v.Kind() != reflect.Slice {
return "", xerrors.Errorf("DisplayTable called with a non-slice type")
return "", xerrors.New("DisplayTable called with a non-slice type")
}
var tableType reflect.Type
if v.Type().Elem().Kind() == reflect.Interface {
if v.Len() == 0 {
return "", xerrors.New("DisplayTable called with empty interface slice")
}
tableType = reflect.Indirect(reflect.ValueOf(v.Index(0).Interface())).Type()
} else {
tableType = v.Type().Elem()
}
// Get the list of table column headers.
headersRaw, defaultSort, err := typeToTableHeaders(v.Type().Elem(), true)
headersRaw, defaultSort, err := typeToTableHeaders(tableType, true)
if err != nil {
return "", xerrors.Errorf("get table headers recursively for type %q: %w", v.Type().Elem().String(), err)
}
@@ -82,9 +102,8 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
}
headers := make(table.Row, len(headersRaw))
for i, header := range headersRaw {
headers[i] = header
headers[i] = strings.ReplaceAll(header, "_", " ")
}
// Verify that the given sort column and filter columns are valid.
if sort != "" || len(filterColumns) != 0 {
headersMap := make(map[string]string, len(headersRaw))
@@ -130,6 +149,11 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
return "", xerrors.Errorf("specified sort column %q not found in table headers, available columns are %q", sort, strings.Join(headersRaw, `", "`))
}
}
return renderTable(out, sort, headers, filterColumns)
}
func renderTable(out any, sort string, headers table.Row, filterColumns []string) (string, error) {
v := reflect.Indirect(reflect.ValueOf(out))
// Setup the table formatter.
tw := Table()
@@ -143,15 +167,22 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
// Write each struct to the table.
for i := 0; i < v.Len(); i++ {
cur := v.Index(i).Interface()
_, ok := cur.(TableSeparator)
if ok {
tw.AppendSeparator()
continue
}
// Format the row as a slice.
rowMap, err := valueToTableMap(v.Index(i))
// ValueToTableMap does what `reflect.Indirect` does
rowMap, err := valueToTableMap(reflect.ValueOf(cur))
if err != nil {
return "", xerrors.Errorf("get table row map %v: %w", i, err)
}
rowSlice := make([]any, len(headers))
for i, h := range headersRaw {
v, ok := rowMap[h]
for i, h := range headers {
v, ok := rowMap[h.(string)]
if !ok {
v = nil
}
@@ -174,6 +205,24 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
}
}
// Guard against nil dereferences
if v != nil {
rt := reflect.TypeOf(v)
switch rt.Kind() {
case reflect.Slice:
// By default, the behavior is '%v', which just returns a string like
// '[a b c]'. This will add commas in between each value.
strs := make([]string, 0)
vt := reflect.ValueOf(v)
for i := 0; i < vt.Len(); i++ {
strs = append(strs, fmt.Sprintf("%v", vt.Index(i).Interface()))
}
v = "[" + strings.Join(strs, ", ") + "]"
default:
// Leave it as it is
}
}
rowSlice[i] = v
}
@@ -188,25 +237,28 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
// returned. If the table tag is malformed, an error is returned.
//
// The returned name is transformed from "snake_case" to "normal text".
func parseTableStructTag(field reflect.StructField) (name string, defaultSort, recursive bool, skipParentName bool, err error) {
func parseTableStructTag(field reflect.StructField) (name string, defaultSort, noSortOpt, recursive, skipParentName bool, err error) {
tags, err := structtag.Parse(string(field.Tag))
if err != nil {
return "", false, false, false, xerrors.Errorf("parse struct field tag %q: %w", string(field.Tag), err)
return "", false, false, false, false, xerrors.Errorf("parse struct field tag %q: %w", string(field.Tag), err)
}
tag, err := tags.Get("table")
if err != nil || tag.Name == "-" {
// tags.Get only returns an error if the tag is not found.
return "", false, false, false, nil
return "", false, false, false, false, nil
}
defaultSortOpt := false
noSortOpt = false
recursiveOpt := false
skipParentNameOpt := false
for _, opt := range tag.Options {
switch opt {
case "default_sort":
defaultSortOpt = true
case "nosort":
noSortOpt = true
case "recursive":
recursiveOpt = true
case "recursive_inline":
@@ -216,11 +268,11 @@ func parseTableStructTag(field reflect.StructField) (name string, defaultSort, r
recursiveOpt = true
skipParentNameOpt = true
default:
return "", false, false, false, xerrors.Errorf("unknown option %q in struct field tag", opt)
return "", false, false, false, false, xerrors.Errorf("unknown option %q in struct field tag", opt)
}
}
return strings.ReplaceAll(tag.Name, "_", " "), defaultSortOpt, recursiveOpt, skipParentNameOpt, nil
return strings.ReplaceAll(tag.Name, "_", " "), defaultSortOpt, noSortOpt, recursiveOpt, skipParentNameOpt, nil
}
func isStructOrStructPointer(t reflect.Type) bool {
@@ -244,12 +296,16 @@ func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string,
headers := []string{}
defaultSortName := ""
noSortOpt := false
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
name, defaultSort, recursive, skip, err := parseTableStructTag(field)
name, defaultSort, noSort, recursive, skip, err := parseTableStructTag(field)
if err != nil {
return nil, "", xerrors.Errorf("parse struct tags for field %q in type %q: %w", field.Name, t.String(), err)
}
if requireDefault && noSort {
noSortOpt = true
}
if name == "" && (recursive && skip) {
return nil, "", xerrors.Errorf("a name is required for the field %q. "+
@@ -292,8 +348,8 @@ func typeToTableHeaders(t reflect.Type, requireDefault bool) ([]string, string,
headers = append(headers, name)
}
if defaultSortName == "" && requireDefault {
return nil, "", xerrors.Errorf("no field marked as default_sort in type %q", t.String())
if defaultSortName == "" && requireDefault && !noSortOpt {
return nil, "", xerrors.Errorf("no field marked as default_sort or nosort in type %q", t.String())
}
return headers, defaultSortName, nil
@@ -320,7 +376,7 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)
fieldVal := val.Field(i)
name, _, recursive, skip, err := parseTableStructTag(field)
name, _, _, recursive, skip, err := parseTableStructTag(field)
if err != nil {
return nil, xerrors.Errorf("parse struct tags for field %q in type %T: %w", field.Name, val, err)
}
+44 -16
View File
@@ -138,10 +138,10 @@ func Test_DisplayTable(t *testing.T) {
t.Parallel()
expected := `
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
`
// Test with non-pointer values.
@@ -165,10 +165,10 @@ foo 10 [a b c] foo1 11 foo2 12 foo3
t.Parallel()
expected := `
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
`
out, err := cliui.DisplayTable(in, "age", nil)
@@ -218,6 +218,42 @@ Alice 25
compareTables(t, expected, out)
})
// This test ensures we can display dynamically typed slices
t.Run("Interfaces", func(t *testing.T) {
t.Parallel()
in := []any{tableTest1{}}
out, err := cliui.DisplayTable(in, "", nil)
t.Log("rendered table:\n" + out)
require.NoError(t, err)
other := []tableTest1{{}}
expected, err := cliui.DisplayTable(other, "", nil)
require.NoError(t, err)
compareTables(t, expected, out)
})
t.Run("WithSeparator", func(t *testing.T) {
t.Parallel()
expected := `
NAME AGE ROLES SUB 1 NAME SUB 1 AGE SUB 2 NAME SUB 2 AGE SUB 3 INNER NAME SUB 3 INNER AGE SUB 4 TIME TIME PTR
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } 2022-08-02T15:49:10Z <nil>
---------------------------------------------------------------------------------------------------------------------------------------------------------------
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } 2022-08-02T15:49:10Z <nil>
---------------------------------------------------------------------------------------------------------------------------------------------------------------
foo 10 [a, b, c] foo1 11 foo2 12 foo3 13 {foo4 14 } 2022-08-02T15:49:10Z 2022-08-02T15:49:10Z
`
var inlineIn []any
for _, v := range in {
inlineIn = append(inlineIn, v)
inlineIn = append(inlineIn, cliui.TableSeparator{})
}
out, err := cliui.DisplayTable(inlineIn, "", nil)
t.Log("rendered table:\n" + out)
require.NoError(t, err)
compareTables(t, expected, out)
})
// This test ensures that safeties against invalid use of `table` tags
// causes errors (even without data).
t.Run("Errors", func(t *testing.T) {
@@ -255,14 +291,6 @@ Alice 25
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
t.Run("WithData", func(t *testing.T) {
t.Parallel()
in := []any{tableTest1{}}
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
})
t.Run("NotStruct", func(t *testing.T) {
+114
View File
@@ -0,0 +1,114 @@
package cliutil
import (
"context"
"encoding/json"
"io"
"net/http"
"net/netip"
"time"
"golang.org/x/xerrors"
)
const AWSIPRangesURL = "https://ip-ranges.amazonaws.com/ip-ranges.json"
type awsIPv4Prefix struct {
Prefix string `json:"ip_prefix"`
Region string `json:"region"`
Service string `json:"service"`
NetworkBorderGroup string `json:"network_border_group"`
}
type awsIPv6Prefix struct {
Prefix string `json:"ipv6_prefix"`
Region string `json:"region"`
Service string `json:"service"`
NetworkBorderGroup string `json:"network_border_group"`
}
type AWSIPRanges struct {
V4 []netip.Prefix
V6 []netip.Prefix
}
type awsIPRangesResponse struct {
SyncToken string `json:"syncToken"`
CreateDate string `json:"createDate"`
IPV4Prefixes []awsIPv4Prefix `json:"prefixes"`
IPV6Prefixes []awsIPv6Prefix `json:"ipv6_prefixes"`
}
func FetchAWSIPRanges(ctx context.Context, url string) (*AWSIPRanges, error) {
client := &http.Client{}
reqCtx, reqCancel := context.WithTimeout(ctx, 5*time.Second)
defer reqCancel()
req, _ := http.NewRequestWithContext(reqCtx, http.MethodGet, url, nil)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return nil, xerrors.Errorf("unexpected status code %d: %s", resp.StatusCode, b)
}
var body awsIPRangesResponse
err = json.NewDecoder(resp.Body).Decode(&body)
if err != nil {
return nil, xerrors.Errorf("json decode: %w", err)
}
out := &AWSIPRanges{
V4: make([]netip.Prefix, 0, len(body.IPV4Prefixes)),
V6: make([]netip.Prefix, 0, len(body.IPV6Prefixes)),
}
for _, p := range body.IPV4Prefixes {
prefix, err := netip.ParsePrefix(p.Prefix)
if err != nil {
return nil, xerrors.Errorf("parse ip prefix: %w", err)
}
if prefix.Addr().Is6() {
return nil, xerrors.Errorf("ipv4 prefix contains ipv6 address: %s", p.Prefix)
}
out.V4 = append(out.V4, prefix)
}
for _, p := range body.IPV6Prefixes {
prefix, err := netip.ParsePrefix(p.Prefix)
if err != nil {
return nil, xerrors.Errorf("parse ip prefix: %w", err)
}
if prefix.Addr().Is4() {
return nil, xerrors.Errorf("ipv6 prefix contains ipv4 address: %s", p.Prefix)
}
out.V6 = append(out.V6, prefix)
}
return out, nil
}
// CheckIP checks if the given IP address is an AWS IP.
func (r *AWSIPRanges) CheckIP(ip netip.Addr) bool {
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsPrivate() {
return false
}
if ip.Is4() {
for _, p := range r.V4 {
if p.Contains(ip) {
return true
}
}
} else {
for _, p := range r.V6 {
if p.Contains(ip) {
return true
}
}
}
return false
}
+96
View File
@@ -0,0 +1,96 @@
package cliutil
import (
"context"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/testutil"
)
func TestIPV4Check(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(context.Background(), w, http.StatusOK, awsIPRangesResponse{
IPV4Prefixes: []awsIPv4Prefix{
{
Prefix: "3.24.0.0/14",
},
{
Prefix: "15.230.15.29/32",
},
{
Prefix: "47.128.82.100/31",
},
},
IPV6Prefixes: []awsIPv6Prefix{
{
Prefix: "2600:9000:5206::/48",
},
{
Prefix: "2406:da70:8800::/40",
},
{
Prefix: "2600:1f68:5000::/40",
},
},
})
}))
t.Cleanup(srv.Close)
ctx := testutil.Context(t, testutil.WaitShort)
ranges, err := FetchAWSIPRanges(ctx, srv.URL)
require.NoError(t, err)
t.Run("Private/IPV4", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("192.168.0.1")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
t.Run("AWS/IPV4", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("3.25.61.113")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.True(t, isAws)
})
t.Run("NonAWS/IPV4", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("159.196.123.40")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
t.Run("Private/IPV6", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("::1")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
t.Run("AWS/IPV6", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("2600:9000:5206:0001:0000:0000:0000:0001")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.True(t, isAws)
})
t.Run("NonAWS/IPV6", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("2403:5807:885f:0:a544:49d4:58f8:aedf")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
}
+97
View File
@@ -0,0 +1,97 @@
package cli
import (
"fmt"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/serpent"
"github.com/coder/serpent/completion"
)
func (*RootCmd) completion() *serpent.Command {
var shellName string
var printOutput bool
shellOptions := completion.ShellOptions(&shellName)
return &serpent.Command{
Use: "completion",
Short: "Install or update shell completion scripts for the detected or chosen shell.",
Options: []serpent.Option{
{
Flag: "shell",
FlagShorthand: "s",
Description: "The shell to install completion for.",
Value: shellOptions,
},
{
Flag: "print",
Description: "Print the completion script instead of installing it.",
FlagShorthand: "p",
Value: serpent.BoolOf(&printOutput),
},
},
Handler: func(inv *serpent.Invocation) error {
if shellName != "" {
shell, err := completion.ShellByName(shellName, inv.Command.Parent.Name())
if err != nil {
return err
}
if printOutput {
return shell.WriteCompletion(inv.Stdout)
}
return installCompletion(inv, shell)
}
shell, err := completion.DetectUserShell(inv.Command.Parent.Name())
if err == nil {
return installCompletion(inv, shell)
}
if !isTTYOut(inv) {
return xerrors.New("could not detect the current shell, please specify one with --shell or run interactively")
}
// Silently continue to the shell selection if detecting failed in interactive mode
choice, err := cliui.Select(inv, cliui.SelectOptions{
Message: "Select a shell to install completion for:",
Options: shellOptions.Choices,
})
if err != nil {
return err
}
shellChoice, err := completion.ShellByName(choice, inv.Command.Parent.Name())
if err != nil {
return err
}
if printOutput {
return shellChoice.WriteCompletion(inv.Stdout)
}
return installCompletion(inv, shellChoice)
},
}
}
func installCompletion(inv *serpent.Invocation, shell completion.Shell) error {
path, err := shell.InstallPath()
if err != nil {
cliui.Error(inv.Stderr, fmt.Sprintf("Failed to determine completion path %v", err))
return shell.WriteCompletion(inv.Stdout)
}
if !isTTYOut(inv) {
return shell.WriteCompletion(inv.Stdout)
}
choice, err := cliui.Select(inv, cliui.SelectOptions{
Options: []string{
"Confirm",
"Print to terminal",
},
Message: fmt.Sprintf("Install completion for %s at %s?", shell.Name(), path),
HideSearch: true,
})
if err != nil {
return err
}
if choice == "Print to terminal" {
return shell.WriteCompletion(inv.Stdout)
}
return completion.InstallShellCompletion(shell)
}
+42 -82
View File
@@ -17,6 +17,7 @@ import (
"strings"
"github.com/cli/safeexec"
"github.com/natefinch/atomic"
"github.com/pkg/diff"
"github.com/pkg/diff/write"
"golang.org/x/exp/constraints"
@@ -54,6 +55,7 @@ type sshConfigOptions struct {
disableAutostart bool
header []string
headerCommand string
removedKeys map[string]bool
}
// addOptions expects options in the form of "option=value" or "option value".
@@ -74,30 +76,20 @@ func (o *sshConfigOptions) addOption(option string) error {
if err != nil {
return err
}
for i, existing := range o.sshOptions {
// Override existing option if they share the same key.
// This is case-insensitive. Parsing each time might be a little slow,
// but it is ok.
existingKey, _, err := codersdk.ParseSSHConfigOption(existing)
if err != nil {
// Don't mess with original values if there is an error.
// This could have come from the user's manual edits.
continue
}
if strings.EqualFold(existingKey, key) {
if value == "" {
// Delete existing option.
o.sshOptions = append(o.sshOptions[:i], o.sshOptions[i+1:]...)
} else {
// Override existing option.
o.sshOptions[i] = option
}
return nil
}
lowerKey := strings.ToLower(key)
if o.removedKeys != nil && o.removedKeys[lowerKey] {
// Key marked as removed, skip.
return nil
}
// Only append the option if it is not empty.
// Only append the option if it is not empty
// (we interpret empty as removal).
if value != "" {
o.sshOptions = append(o.sshOptions, option)
} else {
if o.removedKeys == nil {
o.removedKeys = make(map[string]bool)
}
o.removedKeys[lowerKey] = true
}
return nil
}
@@ -230,12 +222,12 @@ func (r *RootCmd) configSSH() *serpent.Command {
Annotations: workspaceCommand,
Use: "config-ssh",
Short: "Add an SSH Host entry for your workspaces \"ssh coder.workspace\"",
Long: formatExamples(
example{
Long: FormatExamples(
Example{
Description: "You can use -o (or --ssh-option) so set SSH options to be used for all your workspaces",
Command: "coder config-ssh -o ForwardAgent=yes",
},
example{
Example{
Description: "You can use --dry-run (or -n) to see the changes that would be made",
Command: "coder config-ssh --dry-run",
},
@@ -245,6 +237,8 @@ func (r *RootCmd) configSSH() *serpent.Command {
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
if sshConfigOpts.waitEnum != "auto" && skipProxyCommand {
// The wait option is applied to the ProxyCommand. If the user
// specifies skip-proxy-command, then wait cannot be applied.
@@ -253,7 +247,14 @@ func (r *RootCmd) configSSH() *serpent.Command {
sshConfigOpts.header = r.header
sshConfigOpts.headerCommand = r.headerCommand
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs(inv.Context(), client)
// Talk to the API early to prevent the version mismatch
// warning from being printed in the middle of a prompt.
// This is needed because the asynchronous requests issued
// by sshPrepareWorkspaceConfigs may otherwise trigger the
// warning at any time.
_, _ = client.BuildInfo(ctx)
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs(ctx, client)
out := inv.Stdout
if dryRun {
@@ -375,7 +376,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
return xerrors.Errorf("fetch workspace configs failed: %w", err)
}
coderdConfig, err := client.SSHConfiguration(inv.Context())
coderdConfig, err := client.SSHConfiguration(ctx)
if err != nil {
// If the error is 404, this deployment does not support
// this endpoint yet. Do not error, just assume defaults.
@@ -440,13 +441,17 @@ func (r *RootCmd) configSSH() *serpent.Command {
configOptions := sshConfigOpts
configOptions.sshOptions = nil
// Add standard options.
err := configOptions.addOptions(defaultOptions...)
if err != nil {
return err
// User options first (SSH only uses the first
// option unless it can be given multiple times)
for _, opt := range sshConfigOpts.sshOptions {
err := configOptions.addOptions(opt)
if err != nil {
return xerrors.Errorf("add flag config option %q: %w", opt, err)
}
}
// Override with deployment options
// Deployment options second, allow them to
// override standard options.
for k, v := range coderdConfig.SSHConfigOptions {
opt := fmt.Sprintf("%s %s", k, v)
err := configOptions.addOptions(opt)
@@ -454,12 +459,11 @@ func (r *RootCmd) configSSH() *serpent.Command {
return xerrors.Errorf("add coderd config option %q: %w", opt, err)
}
}
// Override with flag options
for _, opt := range sshConfigOpts.sshOptions {
err := configOptions.addOptions(opt)
if err != nil {
return xerrors.Errorf("add flag config option %q: %w", opt, err)
}
// Finally, add the standard options.
err := configOptions.addOptions(defaultOptions...)
if err != nil {
return err
}
hostBlock := []string{
@@ -521,7 +525,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
}
if !bytes.Equal(configRaw, configModified) {
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configModified))
err = atomic.WriteFile(sshConfigFile, bytes.NewReader(configModified))
if err != nil {
return xerrors.Errorf("write ssh config failed: %w", err)
}
@@ -755,50 +759,6 @@ func sshConfigSplitOnCoderSection(data []byte) (before, section []byte, after []
return data, nil, nil, nil
}
// writeWithTempFileAndMove writes to a temporary file in the same
// directory as path and renames the temp file to the file provided in
// path. This ensure we avoid trashing the file we are writing due to
// unforeseen circumstance like filesystem full, command killed, etc.
func writeWithTempFileAndMove(path string, r io.Reader) (err error) {
dir := filepath.Dir(path)
name := filepath.Base(path)
// Ensure that e.g. the ~/.ssh directory exists.
if err = os.MkdirAll(dir, 0o700); err != nil {
return xerrors.Errorf("create directory: %w", err)
}
// Create a tempfile in the same directory for ensuring write
// operation does not fail.
f, err := os.CreateTemp(dir, fmt.Sprintf(".%s.", name))
if err != nil {
return xerrors.Errorf("create temp file failed: %w", err)
}
defer func() {
if err != nil {
_ = os.Remove(f.Name()) // Cleanup in case a step failed.
}
}()
_, err = io.Copy(f, r)
if err != nil {
_ = f.Close()
return xerrors.Errorf("write temp file failed: %w", err)
}
err = f.Close()
if err != nil {
return xerrors.Errorf("close temp file failed: %w", err)
}
err = os.Rename(f.Name(), path)
if err != nil {
return xerrors.Errorf("rename temp file failed: %w", err)
}
return nil
}
// sshConfigExecEscape quotes the string if it contains spaces, as per
// `man 5 ssh_config`. However, OpenSSH uses exec in the users shell to
// run the command, and as such the formatting/escape requirements
+6 -6
View File
@@ -138,6 +138,7 @@ func Test_sshConfigSplitOnCoderSection(t *testing.T) {
// This test tries to mimic the behavior of OpenSSH
// when executing e.g. a ProxyCommand.
// nolint:tparallel
func Test_sshConfigExecEscape(t *testing.T) {
t.Parallel()
@@ -154,11 +155,10 @@ func Test_sshConfigExecEscape(t *testing.T) {
{"tabs", "path with \ttabs", false},
{"newline fails", "path with \nnewline", true},
}
// nolint:paralleltest // Fixes a flake
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("Windows doesn't typically execute via /bin/sh or cmd.exe, so this test is not applicable.")
}
@@ -272,24 +272,25 @@ func Test_sshConfigOptions_addOption(t *testing.T) {
},
},
{
Name: "Replace",
Name: "AddTwo",
Start: []string{
"foo bar",
},
Add: []string{"Foo baz"},
Expect: []string{
"foo bar",
"Foo baz",
},
},
{
Name: "AddAndReplace",
Name: "AddAndRemove",
Start: []string{
"a b",
"foo bar",
"buzz bazz",
},
Add: []string{
"b c",
"a ", // Empty value, means remove all following entries that start with "a", i.e. next line.
"A hello",
"hello world",
},
@@ -297,7 +298,6 @@ func Test_sshConfigOptions_addOption(t *testing.T) {
"foo bar",
"buzz bazz",
"b c",
"A hello",
"hello world",
},
},
+14 -1
View File
@@ -65,7 +65,7 @@ func TestConfigSSH(t *testing.T) {
const hostname = "test-coder."
const expectedKey = "ConnectionAttempts"
const removeKey = "ConnectionTimeout"
const removeKey = "ConnectTimeout"
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
ConfigSSH: codersdk.SSHConfigResponse{
HostnamePrefix: hostname,
@@ -620,6 +620,19 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
regexMatch: `ProxyCommand .* --header-command "printf h1=v1 h2='v2'" ssh`,
},
},
{
name: "Multiple remote forwards",
args: []string{
"--yes",
"--ssh-option", "RemoteForward 2222 192.168.11.1:2222",
"--ssh-option", "RemoteForward 2223 192.168.11.1:2223",
},
wantErr: false,
hasAgent: true,
wantConfig: wantConfig{
regexMatch: "RemoteForward 2222 192.168.11.1:2222.*\n.*RemoteForward 2223 192.168.11.1:2223",
},
},
}
for _, tt := range tests {
tt := tt
+79 -11
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"strings"
"time"
"github.com/google/uuid"
@@ -29,25 +30,24 @@ func (r *RootCmd) create() *serpent.Command {
parameterFlags workspaceParameterFlags
autoUpdates string
copyParametersFrom string
// Organization context is only required if more than 1 template
// shares the same name across multiple organizations.
orgContext = NewOrganizationContext()
)
client := new(codersdk.Client)
cmd := &serpent.Command{
Annotations: workspaceCommand,
Use: "create [name]",
Short: "Create a workspace",
Long: formatExamples(
example{
Long: FormatExamples(
Example{
Description: "Create a workspace for another user (if you have permission)",
Command: "coder create <username>/<workspace_name>",
},
),
Middleware: serpent.Chain(r.InitClient(client)),
Handler: func(inv *serpent.Invocation) error {
organization, err := CurrentOrganization(r, inv, client)
if err != nil {
return err
}
var err error
workspaceOwner := codersdk.Me
if len(inv.Args) >= 1 {
workspaceOwner, workspaceName, err = splitNamedWorkspace(inv.Args[0])
@@ -98,7 +98,7 @@ func (r *RootCmd) create() *serpent.Command {
if templateName == "" {
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select a template below to preview the provisioned infrastructure:"))
templates, err := client.TemplatesByOrganization(inv.Context(), organization.ID)
templates, err := client.Templates(inv.Context(), codersdk.TemplateFilter{})
if err != nil {
return err
}
@@ -110,13 +110,28 @@ func (r *RootCmd) create() *serpent.Command {
templateNames := make([]string, 0, len(templates))
templateByName := make(map[string]codersdk.Template, len(templates))
// If more than 1 organization exists in the list of templates,
// then include the organization name in the select options.
uniqueOrganizations := make(map[uuid.UUID]bool)
for _, template := range templates {
uniqueOrganizations[template.OrganizationID] = true
}
for _, template := range templates {
templateName := template.Name
if len(uniqueOrganizations) > 1 {
templateName += cliui.Placeholder(
fmt.Sprintf(
" (%s)",
template.OrganizationName,
),
)
}
if template.ActiveUserCount > 0 {
templateName += cliui.Placeholder(
fmt.Sprintf(
" (used by %s)",
" used by %s",
formatActiveDevelopers(template.ActiveUserCount),
),
)
@@ -144,13 +159,65 @@ func (r *RootCmd) create() *serpent.Command {
}
templateVersionID = sourceWorkspace.LatestBuild.TemplateVersionID
} else {
template, err = client.TemplateByName(inv.Context(), organization.ID, templateName)
templates, err := client.Templates(inv.Context(), codersdk.TemplateFilter{
ExactName: templateName,
})
if err != nil {
return xerrors.Errorf("get template by name: %w", err)
}
if len(templates) == 0 {
return xerrors.Errorf("no template found with the name %q", templateName)
}
if len(templates) > 1 {
templateOrgs := []string{}
for _, tpl := range templates {
templateOrgs = append(templateOrgs, tpl.OrganizationName)
}
selectedOrg, err := orgContext.Selected(inv, client)
if err != nil {
return xerrors.Errorf("multiple templates found with the name %q, use `--org=<organization_name>` to specify which template by that name to use. Organizations available: %s", templateName, strings.Join(templateOrgs, ", "))
}
index := slices.IndexFunc(templates, func(i codersdk.Template) bool {
return i.OrganizationID == selectedOrg.ID
})
if index == -1 {
return xerrors.Errorf("no templates found with the name %q in the organization %q. Templates by that name exist in organizations: %s. Use --org=<organization_name> to select one.", templateName, selectedOrg.Name, strings.Join(templateOrgs, ", "))
}
// remake the list with the only template selected
templates = []codersdk.Template{templates[index]}
}
template = templates[0]
templateVersionID = template.ActiveVersionID
}
// If the user specified an organization via a flag or env var, the template **must**
// be in that organization. Otherwise, we should throw an error.
orgValue, orgValueSource := orgContext.ValueSource(inv)
if orgValue != "" && !(orgValueSource == serpent.ValueSourceDefault || orgValueSource == serpent.ValueSourceNone) {
selectedOrg, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
if template.OrganizationID != selectedOrg.ID {
orgNameFormat := "'--org=%q'"
if orgValueSource == serpent.ValueSourceEnv {
orgNameFormat = "CODER_ORGANIZATION=%q"
}
return xerrors.Errorf("template is in organization %q, but %s was specified. Use %s to use this template",
template.OrganizationName,
fmt.Sprintf(orgNameFormat, selectedOrg.Name),
fmt.Sprintf(orgNameFormat, template.OrganizationName),
)
}
}
var schedSpec *string
if startAt != "" {
sched, err := parseCLISchedule(startAt)
@@ -206,7 +273,7 @@ func (r *RootCmd) create() *serpent.Command {
ttlMillis = ptr.Ref(stopAfter.Milliseconds())
}
workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, workspaceOwner, codersdk.CreateWorkspaceRequest{
workspace, err := client.CreateWorkspace(inv.Context(), template.OrganizationID, workspaceOwner, codersdk.CreateWorkspaceRequest{
TemplateVersionID: templateVersionID,
Name: workspaceName,
AutostartSchedule: schedSpec,
@@ -269,6 +336,7 @@ func (r *RootCmd) create() *serpent.Command {
)
cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...)
cmd.Options = append(cmd.Options, parameterFlags.cliParameterDefaults()...)
orgContext.AttachOptions(cmd)
return cmd
}
+4 -5
View File
@@ -27,7 +27,7 @@ func TestDelete(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
inv, root := clitest.New(t, "delete", workspace.Name, "-y")
clitest.SetupConfig(t, member, root)
@@ -52,7 +52,7 @@ func TestDelete(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, client, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
inv, root := clitest.New(t, "delete", workspace.Name, "-y", "--orphan")
@@ -86,8 +86,7 @@ func TestDelete(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, deleteMeClient, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, deleteMeClient, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, deleteMeClient, workspace.LatestBuild.ID)
// The API checks if the user has any workspaces, so we cannot delete a user
@@ -128,7 +127,7 @@ func TestDelete(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, adminClient, orgID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, adminClient, version.ID)
template := coderdtest.CreateTemplate(t, adminClient, orgID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, orgID, template.ID)
workspace := coderdtest.CreateWorkspace(t, client, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
inv, root := clitest.New(t, "delete", user.Username+"/"+workspace.Name, "-y")
+7 -11
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
@@ -28,8 +27,8 @@ func (r *RootCmd) dotfiles() *serpent.Command {
Use: "dotfiles <git_repo_url>",
Middleware: serpent.RequireNArgs(1),
Short: "Personalize your workspace by applying a canonical dotfiles repository",
Long: formatExamples(
example{
Long: FormatExamples(
Example{
Description: "Check out and install a dotfiles repository without prompts",
Command: "coder dotfiles --yes git@github.com:example/dotfiles.git",
},
@@ -184,7 +183,7 @@ func (r *RootCmd) dotfiles() *serpent.Command {
}
}
script := findScript(installScriptSet, files)
script := findScript(installScriptSet, dotfilesDir)
if script != "" {
_, err = cliui.Prompt(inv, cliui.PromptOptions{
Text: fmt.Sprintf("Running install script %s.\n\n Continue?", script),
@@ -204,7 +203,7 @@ func (r *RootCmd) dotfiles() *serpent.Command {
}
if fi.Mode()&0o111 == 0 {
return xerrors.Errorf("script %q is not executable. See https://coder.com/docs/v2/latest/dotfiles for information on how to resolve the issue.", script)
return xerrors.Errorf("script %q is not executable. See https://coder.com/docs/dotfiles for information on how to resolve the issue.", script)
}
// it is safe to use a variable command here because it's from
@@ -361,15 +360,12 @@ func dirExists(name string) (bool, error) {
}
// findScript will find the first file that matches the script set.
func findScript(scriptSet []string, files []fs.DirEntry) string {
func findScript(scriptSet []string, directory string) string {
for _, i := range scriptSet {
for _, f := range files {
if f.Name() == i {
return f.Name()
}
if _, err := os.Stat(filepath.Join(directory, i)); err == nil {
return i
}
}
return ""
}
+35
View File
@@ -142,6 +142,41 @@ func TestDotfiles(t *testing.T) {
require.NoError(t, err)
require.Equal(t, string(b), "wow\n")
})
t.Run("NestedInstallScript", func(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("install scripts on windows require sh and aren't very practical")
}
_, root := clitest.New(t)
testRepo := testGitRepo(t, root)
scriptPath := filepath.Join("script", "setup")
err := os.MkdirAll(filepath.Join(testRepo, "script"), 0o750)
require.NoError(t, err)
// nolint:gosec
err = os.WriteFile(filepath.Join(testRepo, scriptPath), []byte("#!/bin/bash\necho wow > "+filepath.Join(string(root), ".bashrc")), 0o750)
require.NoError(t, err)
c := exec.Command("git", "add", scriptPath)
c.Dir = testRepo
err = c.Run()
require.NoError(t, err)
c = exec.Command("git", "commit", "-m", `"add script"`)
c.Dir = testRepo
err = c.Run()
require.NoError(t, err)
inv, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
err = inv.Run()
require.NoError(t, err)
b, err := os.ReadFile(filepath.Join(string(root), ".bashrc"))
require.NoError(t, err)
require.Equal(t, string(b), "wow\n")
})
t.Run("InstallScriptChangeBranch", func(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
+1
View File
@@ -13,6 +13,7 @@ func (r *RootCmd) expCmd() *serpent.Command {
Children: []*serpent.Command{
r.scaletestCmd(),
r.errorExample(),
r.promptExample(),
},
}
return cmd
+42 -8
View File
@@ -117,7 +117,7 @@ func (s *scaletestTracingFlags) provider(ctx context.Context) (trace.TracerProvi
}
var closeTracingOnce sync.Once
return tracerProvider, func(ctx context.Context) error {
return tracerProvider, func(_ context.Context) error {
var err error
closeTracingOnce.Do(func() {
// Allow time to upload traces even if ctx is canceled
@@ -430,7 +430,7 @@ func (r *RootCmd) scaletestCleanup() *serpent.Command {
}
cliui.Infof(inv.Stdout, "Fetching scaletest workspaces...")
workspaces, err := getScaletestWorkspaces(ctx, client, template)
workspaces, _, err := getScaletestWorkspaces(ctx, client, "", template)
if err != nil {
return err
}
@@ -863,6 +863,7 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
tickInterval time.Duration
bytesPerTick int64
ssh bool
useHostLogin bool
app string
template string
targetWorkspaces string
@@ -926,10 +927,18 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
return xerrors.Errorf("get app host: %w", err)
}
workspaces, err := getScaletestWorkspaces(inv.Context(), client, template)
var owner string
if useHostLogin {
owner = codersdk.Me
}
workspaces, numSkipped, err := getScaletestWorkspaces(inv.Context(), client, owner, template)
if err != nil {
return err
}
if numSkipped > 0 {
cliui.Warnf(inv.Stdout, "CODER_DISABLE_OWNER_WORKSPACE_ACCESS is set on the deployment.\n\t%d workspace(s) were skipped due to ownership mismatch.\n\tSet --use-host-login to only target workspaces you own.", numSkipped)
}
if targetWorkspaceEnd == 0 {
targetWorkspaceEnd = len(workspaces)
@@ -1092,6 +1101,13 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
Description: "Send WebSocket traffic to a workspace app (proxied via coderd), cannot be used with --ssh.",
Value: serpent.StringOf(&app),
},
{
Flag: "use-host-login",
Env: "CODER_SCALETEST_USE_HOST_LOGIN",
Default: "false",
Description: "Connect as the currently logged in user.",
Value: serpent.BoolOf(&useHostLogin),
},
}
tracingFlags.attach(&cmd.Options)
@@ -1378,22 +1394,35 @@ func isScaleTestWorkspace(workspace codersdk.Workspace) bool {
strings.HasPrefix(workspace.Name, "scaletest-")
}
func getScaletestWorkspaces(ctx context.Context, client *codersdk.Client, template string) ([]codersdk.Workspace, error) {
func getScaletestWorkspaces(ctx context.Context, client *codersdk.Client, owner, template string) ([]codersdk.Workspace, int, error) {
var (
pageNumber = 0
limit = 100
workspaces []codersdk.Workspace
skipped int
)
me, err := client.User(ctx, codersdk.Me)
if err != nil {
return nil, 0, xerrors.Errorf("check logged-in user")
}
dv, err := client.DeploymentConfig(ctx)
if err != nil {
return nil, 0, xerrors.Errorf("fetch deployment config: %w", err)
}
noOwnerAccess := dv.Values != nil && dv.Values.DisableOwnerWorkspaceExec.Value()
for {
page, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
Name: "scaletest-",
Template: template,
Owner: owner,
Offset: pageNumber * limit,
Limit: limit,
})
if err != nil {
return nil, xerrors.Errorf("fetch scaletest workspaces page %d: %w", pageNumber, err)
return nil, 0, xerrors.Errorf("fetch scaletest workspaces page %d: %w", pageNumber, err)
}
pageNumber++
@@ -1403,13 +1432,18 @@ func getScaletestWorkspaces(ctx context.Context, client *codersdk.Client, templa
pageWorkspaces := make([]codersdk.Workspace, 0, len(page.Workspaces))
for _, w := range page.Workspaces {
if isScaleTestWorkspace(w) {
pageWorkspaces = append(pageWorkspaces, w)
if !isScaleTestWorkspace(w) {
continue
}
if noOwnerAccess && w.OwnerID != me.ID {
skipped++
continue
}
pageWorkspaces = append(pageWorkspaces, w)
}
workspaces = append(workspaces, pageWorkspaces...)
}
return workspaces, nil
return workspaces, skipped, nil
}
func getScaletestUsers(ctx context.Context, client *codersdk.Client) ([]codersdk.User, error) {
+70
View File
@@ -0,0 +1,70 @@
package exptest_test
import (
"bytes"
"context"
"testing"
"github.com/stretchr/testify/require"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
// This test validates that the scaletest CLI filters out workspaces not owned
// when disable owner workspace access is set.
// This test is in its own package because it mutates a global variable that
// can influence other tests in the same package.
// nolint:paralleltest
func TestScaleTestWorkspaceTraffic_UseHostLogin(t *testing.T) {
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,
IncludeProvisionerDaemon: true,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.DisableOwnerWorkspaceExec = true
}),
})
owner := coderdtest.CreateFirstUser(t, client)
tv := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, tv.ID)
tpl := coderdtest.CreateTemplate(t, client, owner.OrganizationID, tv.ID)
// Create a workspace owned by a different user
memberClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
_ = coderdtest.CreateWorkspace(t, memberClient, tpl.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.Name = "scaletest-workspace"
})
// Test without --use-host-login first.g
inv, root := clitest.New(t, "exp", "scaletest", "workspace-traffic",
"--template", tpl.Name,
)
// nolint:gocritic // We are intentionally testing this as the owner.
clitest.SetupConfig(t, client, root)
var stdoutBuf bytes.Buffer
inv.Stdout = &stdoutBuf
err := inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "no scaletest workspaces exist")
require.Contains(t, stdoutBuf.String(), `1 workspace(s) were skipped`)
// Test once again with --use-host-login.
inv, root = clitest.New(t, "exp", "scaletest", "workspace-traffic",
"--template", tpl.Name,
"--use-host-login",
)
// nolint:gocritic // We are intentionally testing this as the owner.
clitest.SetupConfig(t, client, root)
stdoutBuf.Reset()
inv.Stdout = &stdoutBuf
err = inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "no scaletest workspaces exist")
require.NotContains(t, stdoutBuf.String(), `1 workspace(s) were skipped`)
}
+3 -3
View File
@@ -35,8 +35,8 @@ func (r *RootCmd) externalAuthAccessToken() *serpent.Command {
Short: "Print auth for an external provider",
Long: "Print an access-token for an external auth provider. " +
"The access-token will be validated and sent to stdout with exit code 0. " +
"If a valid access-token cannot be obtained, the URL to authenticate will be sent to stdout with exit code 1\n" + formatExamples(
example{
"If a valid access-token cannot be obtained, the URL to authenticate will be sent to stdout with exit code 1\n" + FormatExamples(
Example{
Description: "Ensure that the user is authenticated with GitHub before cloning.",
Command: `#!/usr/bin/env sh
@@ -49,7 +49,7 @@ else
fi
`,
},
example{
Example{
Description: "Obtain an extra property of an access token for additional metadata.",
Command: "coder external-auth access-token slack --extra \"authed_user.id\"",
},
+2
View File
@@ -81,6 +81,8 @@ var usageTemplate = func() *template.Template {
switch v := opt.Value.(type) {
case *serpent.Enum:
return strings.Join(v.Choices, "|")
case *serpent.EnumArray:
return fmt.Sprintf("[%s]", strings.Join(v.Choices, "|"))
default:
return v.Type()
}
+32 -27
View File
@@ -6,6 +6,7 @@ import (
"strconv"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
@@ -22,19 +23,21 @@ type workspaceListRow struct {
codersdk.Workspace `table:"-"`
// For table format:
Favorite bool `json:"-" table:"favorite"`
WorkspaceName string `json:"-" table:"workspace,default_sort"`
Template string `json:"-" table:"template"`
Status string `json:"-" table:"status"`
Healthy string `json:"-" table:"healthy"`
LastBuilt string `json:"-" table:"last built"`
CurrentVersion string `json:"-" table:"current version"`
Outdated bool `json:"-" table:"outdated"`
StartsAt string `json:"-" table:"starts at"`
StartsNext string `json:"-" table:"starts next"`
StopsAfter string `json:"-" table:"stops after"`
StopsNext string `json:"-" table:"stops next"`
DailyCost string `json:"-" table:"daily cost"`
Favorite bool `json:"-" table:"favorite"`
WorkspaceName string `json:"-" table:"workspace,default_sort"`
OrganizationID uuid.UUID `json:"-" table:"organization id"`
OrganizationName string `json:"-" table:"organization name"`
Template string `json:"-" table:"template"`
Status string `json:"-" table:"status"`
Healthy string `json:"-" table:"healthy"`
LastBuilt string `json:"-" table:"last built"`
CurrentVersion string `json:"-" table:"current version"`
Outdated bool `json:"-" table:"outdated"`
StartsAt string `json:"-" table:"starts at"`
StartsNext string `json:"-" table:"starts next"`
StopsAfter string `json:"-" table:"stops after"`
StopsNext string `json:"-" table:"stops next"`
DailyCost string `json:"-" table:"daily cost"`
}
func workspaceListRowFromWorkspace(now time.Time, workspace codersdk.Workspace) workspaceListRow {
@@ -53,20 +56,22 @@ func workspaceListRowFromWorkspace(now time.Time, workspace codersdk.Workspace)
}
workspaceName := favIco + " " + workspace.OwnerName + "/" + workspace.Name
return workspaceListRow{
Favorite: workspace.Favorite,
Workspace: workspace,
WorkspaceName: workspaceName,
Template: workspace.TemplateName,
Status: status,
Healthy: healthy,
LastBuilt: durationDisplay(lastBuilt),
CurrentVersion: workspace.LatestBuild.TemplateVersionName,
Outdated: workspace.Outdated,
StartsAt: schedRow.StartsAt,
StartsNext: schedRow.StartsNext,
StopsAfter: schedRow.StopsAfter,
StopsNext: schedRow.StopsNext,
DailyCost: strconv.Itoa(int(workspace.LatestBuild.DailyCost)),
Favorite: workspace.Favorite,
Workspace: workspace,
WorkspaceName: workspaceName,
OrganizationID: workspace.OrganizationID,
OrganizationName: workspace.OrganizationName,
Template: workspace.TemplateName,
Status: status,
Healthy: healthy,
LastBuilt: durationDisplay(lastBuilt),
CurrentVersion: workspace.LatestBuild.TemplateVersionName,
Outdated: workspace.Outdated,
StartsAt: schedRow.StartsAt,
StartsNext: schedRow.StartsNext,
StopsAfter: schedRow.StopsAfter,
StopsNext: schedRow.StopsNext,
DailyCost: strconv.Itoa(int(workspace.LatestBuild.DailyCost)),
}
}
+31 -2
View File
@@ -58,6 +58,21 @@ func promptFirstUsername(inv *serpent.Invocation) (string, error) {
return username, nil
}
func promptFirstName(inv *serpent.Invocation) (string, error) {
name, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "(Optional) What " + pretty.Sprint(cliui.DefaultStyles.Field, "name") + " would you like?",
Default: "",
})
if err != nil {
if errors.Is(err, cliui.Canceled) {
return "", nil
}
return "", err
}
return name, nil
}
func promptFirstPassword(inv *serpent.Invocation) (string, error) {
retry:
password, err := cliui.Prompt(inv, cliui.PromptOptions{
@@ -130,6 +145,7 @@ func (r *RootCmd) login() *serpent.Command {
var (
email string
username string
name string
password string
trial bool
useTokenForSession bool
@@ -191,6 +207,7 @@ func (r *RootCmd) login() *serpent.Command {
_, _ = fmt.Fprintf(inv.Stdout, "Attempting to authenticate with %s URL: '%s'\n", urlSource, serverURL)
// nolint: nestif
if !hasFirstUser {
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Your Coder deployment hasn't been set up!\n")
@@ -212,6 +229,10 @@ func (r *RootCmd) login() *serpent.Command {
if err != nil {
return err
}
name, err = promptFirstName(inv)
if err != nil {
return err
}
}
if email == "" {
@@ -239,7 +260,7 @@ func (r *RootCmd) login() *serpent.Command {
if !inv.ParsedFlags().Changed("first-user-trial") && os.Getenv(firstUserTrialEnv) == "" {
v, _ := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Start a 30-day trial of Enterprise?",
Text: "Start a trial of Enterprise?",
IsConfirm: true,
Default: "yes",
})
@@ -249,6 +270,7 @@ func (r *RootCmd) login() *serpent.Command {
_, err = client.CreateFirstUser(ctx, codersdk.CreateFirstUserRequest{
Email: email,
Username: username,
Name: name,
Password: password,
Trial: trial,
})
@@ -287,7 +309,8 @@ func (r *RootCmd) login() *serpent.Command {
}
sessionToken, err = cliui.Prompt(inv, cliui.PromptOptions{
Text: "Paste your token here:",
Text: "Paste your token here:",
Secret: true,
Validate: func(token string) error {
client.SetSessionToken(token)
_, err := client.User(ctx, codersdk.Me)
@@ -352,6 +375,12 @@ func (r *RootCmd) login() *serpent.Command {
Description: "Specifies a username to use if creating the first user for the deployment.",
Value: serpent.StringOf(&username),
},
{
Flag: "first-user-full-name",
Env: "CODER_FIRST_USER_FULL_NAME",
Description: "Specifies a human-readable name for the first user of the deployment.",
Value: serpent.StringOf(&name),
},
{
Flag: "first-user-password",
Env: "CODER_FIRST_USER_PASSWORD",
+154 -16
View File
@@ -18,6 +18,7 @@ import (
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/coder/v2/testutil"
)
func TestLogin(t *testing.T) {
@@ -89,10 +90,11 @@ func TestLogin(t *testing.T) {
matches := []string{
"first user?", "yes",
"username", "testuser",
"email", "user@coder.com",
"password", "SomeSecurePassword!",
"password", "SomeSecurePassword!", // Confirm.
"username", coderdtest.FirstUserParams.Username,
"name", coderdtest.FirstUserParams.Name,
"email", coderdtest.FirstUserParams.Email,
"password", coderdtest.FirstUserParams.Password,
"password", coderdtest.FirstUserParams.Password, // confirm
"trial", "yes",
}
for i := 0; i < len(matches); i += 2 {
@@ -103,6 +105,64 @@ func TestLogin(t *testing.T) {
}
pty.ExpectMatch("Welcome to Coder")
<-doneChan
ctx := testutil.Context(t, testutil.WaitShort)
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
Email: coderdtest.FirstUserParams.Email,
Password: coderdtest.FirstUserParams.Password,
})
require.NoError(t, err)
client.SetSessionToken(resp.SessionToken)
me, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
assert.Equal(t, coderdtest.FirstUserParams.Username, me.Username)
assert.Equal(t, coderdtest.FirstUserParams.Name, me.Name)
assert.Equal(t, coderdtest.FirstUserParams.Email, me.Email)
})
t.Run("InitialUserTTYNameOptional", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
// The --force-tty flag is required on Windows, because the `isatty` library does not
// accurately detect Windows ptys when they are not attached to a process:
// https://github.com/mattn/go-isatty/issues/59
doneChan := make(chan struct{})
root, _ := clitest.New(t, "login", "--force-tty", client.URL.String())
pty := ptytest.New(t).Attach(root)
go func() {
defer close(doneChan)
err := root.Run()
assert.NoError(t, err)
}()
matches := []string{
"first user?", "yes",
"username", coderdtest.FirstUserParams.Username,
"name", "",
"email", coderdtest.FirstUserParams.Email,
"password", coderdtest.FirstUserParams.Password,
"password", coderdtest.FirstUserParams.Password, // confirm
"trial", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
pty.ExpectMatch("Welcome to Coder")
<-doneChan
ctx := testutil.Context(t, testutil.WaitShort)
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
Email: coderdtest.FirstUserParams.Email,
Password: coderdtest.FirstUserParams.Password,
})
require.NoError(t, err)
client.SetSessionToken(resp.SessionToken)
me, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
assert.Equal(t, coderdtest.FirstUserParams.Username, me.Username)
assert.Equal(t, coderdtest.FirstUserParams.Email, me.Email)
assert.Empty(t, me.Name)
})
t.Run("InitialUserTTYFlag", func(t *testing.T) {
@@ -119,10 +179,11 @@ func TestLogin(t *testing.T) {
pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with flag URL: '%s'", client.URL.String()))
matches := []string{
"first user?", "yes",
"username", "testuser",
"email", "user@coder.com",
"password", "SomeSecurePassword!",
"password", "SomeSecurePassword!", // Confirm.
"username", coderdtest.FirstUserParams.Username,
"name", coderdtest.FirstUserParams.Name,
"email", coderdtest.FirstUserParams.Email,
"password", coderdtest.FirstUserParams.Password,
"password", coderdtest.FirstUserParams.Password, // confirm
"trial", "yes",
}
for i := 0; i < len(matches); i += 2 {
@@ -132,6 +193,18 @@ func TestLogin(t *testing.T) {
pty.WriteLine(value)
}
pty.ExpectMatch("Welcome to Coder")
ctx := testutil.Context(t, testutil.WaitShort)
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
Email: coderdtest.FirstUserParams.Email,
Password: coderdtest.FirstUserParams.Password,
})
require.NoError(t, err)
client.SetSessionToken(resp.SessionToken)
me, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
assert.Equal(t, coderdtest.FirstUserParams.Username, me.Username)
assert.Equal(t, coderdtest.FirstUserParams.Name, me.Name)
assert.Equal(t, coderdtest.FirstUserParams.Email, me.Email)
})
t.Run("InitialUserFlags", func(t *testing.T) {
@@ -139,13 +212,56 @@ func TestLogin(t *testing.T) {
client := coderdtest.New(t, nil)
inv, _ := clitest.New(
t, "login", client.URL.String(),
"--first-user-username", "testuser", "--first-user-email", "user@coder.com",
"--first-user-password", "SomeSecurePassword!", "--first-user-trial",
"--first-user-username", coderdtest.FirstUserParams.Username,
"--first-user-full-name", coderdtest.FirstUserParams.Name,
"--first-user-email", coderdtest.FirstUserParams.Email,
"--first-user-password", coderdtest.FirstUserParams.Password,
"--first-user-trial",
)
pty := ptytest.New(t).Attach(inv)
w := clitest.StartWithWaiter(t, inv)
pty.ExpectMatch("Welcome to Coder")
w.RequireSuccess()
ctx := testutil.Context(t, testutil.WaitShort)
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
Email: coderdtest.FirstUserParams.Email,
Password: coderdtest.FirstUserParams.Password,
})
require.NoError(t, err)
client.SetSessionToken(resp.SessionToken)
me, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
assert.Equal(t, coderdtest.FirstUserParams.Username, me.Username)
assert.Equal(t, coderdtest.FirstUserParams.Name, me.Name)
assert.Equal(t, coderdtest.FirstUserParams.Email, me.Email)
})
t.Run("InitialUserFlagsNameOptional", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
inv, _ := clitest.New(
t, "login", client.URL.String(),
"--first-user-username", coderdtest.FirstUserParams.Username,
"--first-user-email", coderdtest.FirstUserParams.Email,
"--first-user-password", coderdtest.FirstUserParams.Password,
"--first-user-trial",
)
pty := ptytest.New(t).Attach(inv)
w := clitest.StartWithWaiter(t, inv)
pty.ExpectMatch("Welcome to Coder")
w.RequireSuccess()
ctx := testutil.Context(t, testutil.WaitShort)
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
Email: coderdtest.FirstUserParams.Email,
Password: coderdtest.FirstUserParams.Password,
})
require.NoError(t, err)
client.SetSessionToken(resp.SessionToken)
me, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
assert.Equal(t, coderdtest.FirstUserParams.Username, me.Username)
assert.Equal(t, coderdtest.FirstUserParams.Email, me.Email)
assert.Empty(t, me.Name)
})
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
@@ -167,10 +283,11 @@ func TestLogin(t *testing.T) {
matches := []string{
"first user?", "yes",
"username", "testuser",
"email", "user@coder.com",
"password", "MyFirstSecurePassword!",
"password", "MyNonMatchingSecurePassword!", // Confirm.
"username", coderdtest.FirstUserParams.Username,
"name", coderdtest.FirstUserParams.Name,
"email", coderdtest.FirstUserParams.Email,
"password", coderdtest.FirstUserParams.Password,
"password", "something completely different",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
@@ -183,9 +300,9 @@ func TestLogin(t *testing.T) {
pty.ExpectMatch("Passwords do not match")
pty.ExpectMatch("Enter a " + pretty.Sprint(cliui.DefaultStyles.Field, "password"))
pty.WriteLine("SomeSecurePassword!")
pty.WriteLine(coderdtest.FirstUserParams.Password)
pty.ExpectMatch("Confirm")
pty.WriteLine("SomeSecurePassword!")
pty.WriteLine(coderdtest.FirstUserParams.Password)
pty.ExpectMatch("trial")
pty.WriteLine("yes")
pty.ExpectMatch("Welcome to Coder")
@@ -304,4 +421,25 @@ func TestLogin(t *testing.T) {
// This **should not be equal** to the token we passed in.
require.NotEqual(t, client.SessionToken(), sessionFile)
})
t.Run("KeepOrganizationContext", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, client)
root, cfg := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken())
err := cfg.Organization().Write(first.OrganizationID.String())
require.NoError(t, err, "write bad org to config")
err = root.Run()
require.NoError(t, err)
sessionFile, err := cfg.Session().Read()
require.NoError(t, err)
require.NotEqual(t, client.SessionToken(), sessionFile)
// Organization config should be deleted since the org does not exist
selected, err := cfg.Organization().Read()
require.NoError(t, err)
require.Equal(t, selected, first.OrganizationID.String())
})
}
+13 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/healthsdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/serpent"
)
@@ -34,11 +35,21 @@ func (r *RootCmd) netcheck() *serpent.Command {
_, _ = fmt.Fprint(inv.Stderr, "Gathering a network report. This may take a few seconds...\n\n")
var report derphealth.Report
report.Run(ctx, &derphealth.ReportOptions{
var derpReport derphealth.Report
derpReport.Run(ctx, &derphealth.ReportOptions{
DERPMap: connInfo.DERPMap,
})
ifReport, err := healthsdk.RunInterfacesReport()
if err != nil {
return xerrors.Errorf("failed to run interfaces report: %w", err)
}
report := healthsdk.ClientNetcheckReport{
DERP: healthsdk.DERPHealthReport(derpReport),
Interfaces: ifReport,
}
raw, err := json.MarshalIndent(report, "", " ")
if err != nil {
return err
+5 -5
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
@@ -27,12 +26,13 @@ func TestNetcheck(t *testing.T) {
b := out.Bytes()
t.Log(string(b))
var report healthsdk.DERPHealthReport
var report healthsdk.ClientNetcheckReport
require.NoError(t, json.Unmarshal(b, &report))
assert.True(t, report.Healthy)
require.Len(t, report.Regions, 1+1) // 1 built-in region + 1 test-managed STUN region
for _, v := range report.Regions {
// We do not assert that the report is healthy, just that
// it has the expected number of reports per region.
require.Len(t, report.DERP.Regions, 1+1) // 1 built-in region + 1 test-managed STUN region
for _, v := range report.DERP.Regions {
require.Len(t, v.NodeReports, len(v.Region.Nodes))
}
}
+85
View File
@@ -0,0 +1,85 @@
package cli
import (
"fmt"
"golang.org/x/xerrors"
"github.com/coder/serpent"
"github.com/coder/coder/v2/codersdk"
)
func (r *RootCmd) notifications() *serpent.Command {
cmd := &serpent.Command{
Use: "notifications",
Short: "Manage Coder notifications",
Long: "Administrators can use these commands to change notification settings.\n" + FormatExamples(
Example{
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding).",
Command: "coder notifications pause",
},
Example{
Description: "Resume Coder notifications",
Command: "coder notifications resume",
},
),
Aliases: []string{"notification"},
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.pauseNotifications(),
r.resumeNotifications(),
},
}
return cmd
}
func (r *RootCmd) pauseNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "pause",
Short: "Pause notifications",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{
NotifierPaused: true,
})
if err != nil {
return xerrors.Errorf("unable to pause notifications: %w", err)
}
_, _ = fmt.Fprintln(inv.Stderr, "Notifications are now paused.")
return nil
},
}
return cmd
}
func (r *RootCmd) resumeNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "resume",
Short: "Resume notifications",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{
NotifierPaused: false,
})
if err != nil {
return xerrors.Errorf("unable to resume notifications: %w", err)
}
_, _ = fmt.Fprintln(inv.Stderr, "Notifications are now resumed.")
return nil
},
}
return cmd
}
+112
View File
@@ -0,0 +1,112 @@
package cli_test
import (
"bytes"
"context"
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
func createOpts(t *testing.T) *coderdtest.Options {
t.Helper()
dt := coderdtest.DeploymentValues(t)
dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
return &coderdtest.Options{
DeploymentValues: dt,
}
}
func TestNotifications(t *testing.T) {
t.Parallel()
tests := []struct {
name string
command string
expectPaused bool
}{
{
name: "PauseNotifications",
command: "pause",
expectPaused: true,
},
{
name: "ResumeNotifications",
command: "resume",
expectPaused: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// given
ownerClient, db := coderdtest.NewWithDatabase(t, createOpts(t))
_ = coderdtest.CreateFirstUser(t, ownerClient)
// when
inv, root := clitest.New(t, "notifications", tt.command)
clitest.SetupConfig(t, ownerClient, root)
var buf bytes.Buffer
inv.Stdout = &buf
err := inv.Run()
require.NoError(t, err)
// then
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
settingsJSON, err := db.GetNotificationsSettings(ctx)
require.NoError(t, err)
var settings codersdk.NotificationsSettings
err = json.Unmarshal([]byte(settingsJSON), &settings)
require.NoError(t, err)
require.Equal(t, tt.expectPaused, settings.NotifierPaused)
})
}
}
func TestPauseNotifications_RegularUser(t *testing.T) {
t.Parallel()
// given
ownerClient, db := coderdtest.NewWithDatabase(t, createOpts(t))
owner := coderdtest.CreateFirstUser(t, ownerClient)
anotherClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
// when
inv, root := clitest.New(t, "notifications", "pause")
clitest.SetupConfig(t, anotherClient, root)
var buf bytes.Buffer
inv.Stdout = &buf
err := inv.Run()
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
assert.Equal(t, http.StatusForbidden, sdkError.StatusCode())
assert.Contains(t, sdkError.Message, "Forbidden.")
// then
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
t.Cleanup(cancel)
settingsJSON, err := db.GetNotificationsSettings(ctx)
require.NoError(t, err)
var settings codersdk.NotificationsSettings
err = json.Unmarshal([]byte(settingsJSON), &settings)
require.NoError(t, err)
require.False(t, settings.NotifierPaused) // still running
}
+37 -188
View File
@@ -1,212 +1,40 @@
package cli
import (
"errors"
"fmt"
"os"
"slices"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/config"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/pretty"
"github.com/coder/serpent"
)
func (r *RootCmd) organizations() *serpent.Command {
orgContext := NewOrganizationContext()
cmd := &serpent.Command{
Annotations: workspaceCommand,
Use: "organizations [subcommand]",
Short: "Organization related commands",
Aliases: []string{"organization", "org", "orgs"},
Hidden: true, // Hidden until these commands are complete.
Use: "organizations [subcommand]",
Short: "Organization related commands",
Aliases: []string{"organization", "org", "orgs"},
Hidden: true, // Hidden until these commands are complete.
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.currentOrganization(),
r.switchOrganization(),
r.showOrganization(orgContext),
r.createOrganization(),
r.organizationMembers(orgContext),
r.organizationRoles(orgContext),
},
}
cmd.Options = serpent.OptionSet{}
orgContext.AttachOptions(cmd)
return cmd
}
func (r *RootCmd) switchOrganization() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "set <organization name | ID>",
Short: "set the organization used by the CLI. Pass an empty string to reset to the default organization.",
Long: "set the organization used by the CLI. Pass an empty string to reset to the default organization.\n" + formatExamples(
example{
Description: "Remove the current organization and defer to the default.",
Command: "coder organizations set ''",
},
example{
Description: "Switch to a custom organization.",
Command: "coder organizations set my-org",
},
),
Middleware: serpent.Chain(
r.InitClient(client),
serpent.RequireRangeArgs(0, 1),
),
Options: serpent.OptionSet{},
Handler: func(inv *serpent.Invocation) error {
conf := r.createConfig()
orgs, err := client.OrganizationsByUser(inv.Context(), codersdk.Me)
if err != nil {
return xerrors.Errorf("failed to get organizations: %w", err)
}
// Keep the list of orgs sorted
slices.SortFunc(orgs, func(a, b codersdk.Organization) int {
return strings.Compare(a.Name, b.Name)
})
var switchToOrg string
if len(inv.Args) == 0 {
// Pull switchToOrg from a prompt selector, rather than command line
// args.
switchToOrg, err = promptUserSelectOrg(inv, conf, orgs)
if err != nil {
return err
}
} else {
switchToOrg = inv.Args[0]
}
// If the user passes an empty string, we want to remove the organization
// from the config file. This will defer to default behavior.
if switchToOrg == "" {
err := conf.Organization().Delete()
if err != nil && !errors.Is(err, os.ErrNotExist) {
return xerrors.Errorf("failed to unset organization: %w", err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Organization unset\n")
} else {
// Find the selected org in our list.
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
return org.Name == switchToOrg || org.ID.String() == switchToOrg
})
if index < 0 {
// Using this error for better error message formatting
err := &codersdk.Error{
Response: codersdk.Response{
Message: fmt.Sprintf("Organization %q not found. Is the name correct, and are you a member of it?", switchToOrg),
Detail: "Ensure the organization argument is correct and you are a member of it.",
},
Helper: fmt.Sprintf("Valid organizations you can switch to: %s", strings.Join(orgNames(orgs), ", ")),
}
return err
}
// Always write the uuid to the config file. Names can change.
err := conf.Organization().Write(orgs[index].ID.String())
if err != nil {
return xerrors.Errorf("failed to write organization to config file: %w", err)
}
}
// Verify it worked.
current, err := CurrentOrganization(r, inv, client)
if err != nil {
// An SDK error could be a permission error. So offer the advice to unset the org
// and reset the context.
var sdkError *codersdk.Error
if errors.As(err, &sdkError) {
if sdkError.Helper == "" && sdkError.StatusCode() != 500 {
sdkError.Helper = `If this error persists, try unsetting your org with 'coder organizations set ""'`
}
return sdkError
}
return xerrors.Errorf("failed to get current organization: %w", err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Current organization context set to %s (%s)\n", current.Name, current.ID.String())
return nil
},
}
return cmd
}
// promptUserSelectOrg will prompt the user to select an organization from a list
// of their organizations.
func promptUserSelectOrg(inv *serpent.Invocation, conf config.Root, orgs []codersdk.Organization) (string, error) {
// Default choice
var defaultOrg string
// Comes from config file
if conf.Organization().Exists() {
defaultOrg, _ = conf.Organization().Read()
}
// No config? Comes from default org in the list
if defaultOrg == "" {
defIndex := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
return org.IsDefault
})
if defIndex >= 0 {
defaultOrg = orgs[defIndex].Name
}
}
// Defer to first org
if defaultOrg == "" && len(orgs) > 0 {
defaultOrg = orgs[0].Name
}
// Ensure the `defaultOrg` value is an org name, not a uuid.
// If it is a uuid, change it to the org name.
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
return org.ID.String() == defaultOrg || org.Name == defaultOrg
})
if index >= 0 {
defaultOrg = orgs[index].Name
}
// deselectOption is the option to delete the organization config file and defer
// to default behavior.
const deselectOption = "[Default]"
if defaultOrg == "" {
defaultOrg = deselectOption
}
// Pull value from a prompt
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select an organization below to set the current CLI context to:"))
value, err := cliui.Select(inv, cliui.SelectOptions{
Options: append([]string{deselectOption}, orgNames(orgs)...),
Default: defaultOrg,
Size: 10,
HideSearch: false,
})
if err != nil {
return "", err
}
// Deselect is an alias for ""
if value == deselectOption {
value = ""
}
return value, nil
}
// orgNames is a helper function to turn a list of organizations into a list of
// their names as strings.
func orgNames(orgs []codersdk.Organization) []string {
names := make([]string, 0, len(orgs))
for _, org := range orgs {
names = append(names, org.Name)
}
return names
}
func (r *RootCmd) currentOrganization() *serpent.Command {
func (r *RootCmd) showOrganization(orgContext *OrganizationContext) *serpent.Command {
var (
stringFormat func(orgs []codersdk.Organization) (string, error)
client = new(codersdk.Client)
@@ -225,8 +53,29 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
onlyID = false
)
cmd := &serpent.Command{
Use: "show [current|me|uuid]",
Short: "Show the organization, if no argument is given, the organization currently in use will be shown.",
Use: "show [\"selected\"|\"me\"|uuid|org_name]",
Short: "Show the organization. " +
"Using \"selected\" will show the selected organization from the \"--org\" flag. " +
"Using \"me\" will show all organizations you are a member of.",
Long: FormatExamples(
Example{
Description: "coder org show selected",
Command: "Shows the organizations selected with '--org=<org_name>'. " +
"This organization is the organization used by the cli.",
},
Example{
Description: "coder org show me",
Command: "List of all organizations you are a member of.",
},
Example{
Description: "coder org show developers",
Command: "Show organization with name 'developers'",
},
Example{
Description: "coder org show 90ee1875-3db5-43b3-828e-af3687522e43",
Command: "Show organization with the given ID.",
},
),
Middleware: serpent.Chain(
r.InitClient(client),
serpent.RequireRangeArgs(0, 1),
@@ -241,7 +90,7 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
},
},
Handler: func(inv *serpent.Invocation) error {
orgArg := "current"
orgArg := "selected"
if len(inv.Args) >= 1 {
orgArg = inv.Args[0]
}
@@ -249,14 +98,14 @@ func (r *RootCmd) currentOrganization() *serpent.Command {
var orgs []codersdk.Organization
var err error
switch strings.ToLower(orgArg) {
case "current":
case "selected":
stringFormat = func(orgs []codersdk.Organization) (string, error) {
if len(orgs) != 1 {
return "", xerrors.Errorf("expected 1 organization, got %d", len(orgs))
}
return fmt.Sprintf("Current CLI Organization: %s (%s)\n", orgs[0].Name, orgs[0].ID.String()), nil
}
org, err := CurrentOrganization(r, inv, client)
org, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
+5 -98
View File
@@ -12,11 +12,8 @@ import (
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/coder/v2/testutil"
)
func TestCurrentOrganization(t *testing.T) {
@@ -32,8 +29,10 @@ func TestCurrentOrganization(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode([]codersdk.Organization{
{
ID: orgID,
Name: "not-default",
MinimalOrganization: codersdk.MinimalOrganization{
ID: orgID,
Name: "not-default",
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
IsDefault: false,
@@ -43,7 +42,7 @@ func TestCurrentOrganization(t *testing.T) {
defer srv.Close()
client := codersdk.New(must(url.Parse(srv.URL)))
inv, root := clitest.New(t, "organizations", "show", "current")
inv, root := clitest.New(t, "organizations", "show", "selected")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t).Attach(inv)
errC := make(chan error)
@@ -53,98 +52,6 @@ func TestCurrentOrganization(t *testing.T) {
require.NoError(t, <-errC)
pty.ExpectMatch(orgID.String())
})
t.Run("OnlyID", func(t *testing.T) {
t.Parallel()
ownerClient := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, ownerClient)
// Owner is required to make orgs
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, first.OrganizationID, rbac.RoleOwner())
ctx := testutil.Context(t, testutil.WaitMedium)
orgs := []string{"foo", "bar"}
for _, orgName := range orgs {
_, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: orgName,
})
require.NoError(t, err)
}
inv, root := clitest.New(t, "organizations", "show", "--only-id")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t).Attach(inv)
errC := make(chan error)
go func() {
errC <- inv.Run()
}()
require.NoError(t, <-errC)
pty.ExpectMatch(first.OrganizationID.String())
})
t.Run("UsingFlag", func(t *testing.T) {
t.Parallel()
ownerClient := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, ownerClient)
// Owner is required to make orgs
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, first.OrganizationID, rbac.RoleOwner())
ctx := testutil.Context(t, testutil.WaitMedium)
orgs := map[string]codersdk.Organization{
"foo": {},
"bar": {},
}
for orgName := range orgs {
org, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: orgName,
})
require.NoError(t, err)
orgs[orgName] = org
}
inv, root := clitest.New(t, "organizations", "show", "current", "--only-id", "-z=bar")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t).Attach(inv)
errC := make(chan error)
go func() {
errC <- inv.Run()
}()
require.NoError(t, <-errC)
pty.ExpectMatch(orgs["bar"].ID.String())
})
}
func TestOrganizationSwitch(t *testing.T) {
t.Parallel()
t.Run("Switch", func(t *testing.T) {
t.Parallel()
ownerClient := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, ownerClient)
// Owner is required to make orgs
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, first.OrganizationID, rbac.RoleOwner())
ctx := testutil.Context(t, testutil.WaitMedium)
orgs := []string{"foo", "bar"}
for _, orgName := range orgs {
_, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: orgName,
})
require.NoError(t, err)
}
exp, err := client.OrganizationByName(ctx, "foo")
require.NoError(t, err)
inv, root := clitest.New(t, "organizations", "set", "foo")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t).Attach(inv)
errC := make(chan error)
go func() {
errC <- inv.Run()
}()
require.NoError(t, <-errC)
pty.ExpectMatch(exp.ID.String())
})
}
func must[V any](v V, err error) V {
+176
View File
@@ -0,0 +1,176 @@
package cli
import (
"fmt"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func (r *RootCmd) organizationMembers(orgContext *OrganizationContext) *serpent.Command {
cmd := &serpent.Command{
Use: "members",
Aliases: []string{"member"},
Short: "Manage organization members",
Children: []*serpent.Command{
r.listOrganizationMembers(orgContext),
r.assignOrganizationRoles(orgContext),
r.addOrganizationMember(orgContext),
r.removeOrganizationMember(orgContext),
},
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
}
return cmd
}
func (r *RootCmd) removeOrganizationMember(orgContext *OrganizationContext) *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "remove <username | user_id>",
Short: "Remove a new member to the current organization",
Middleware: serpent.Chain(
r.InitClient(client),
serpent.RequireNArgs(1),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
organization, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
user := inv.Args[0]
err = client.DeleteOrganizationMember(ctx, organization.ID, user)
if err != nil {
return xerrors.Errorf("could not remove member from organization %q: %w", organization.HumanName(), err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Organization member removed from %q\n", organization.HumanName())
return nil
},
}
return cmd
}
func (r *RootCmd) addOrganizationMember(orgContext *OrganizationContext) *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "add <username | user_id>",
Short: "Add a new member to the current organization",
Middleware: serpent.Chain(
r.InitClient(client),
serpent.RequireNArgs(1),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
organization, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
user := inv.Args[0]
_, err = client.PostOrganizationMember(ctx, organization.ID, user)
if err != nil {
return xerrors.Errorf("could not add member to organization %q: %w", organization.HumanName(), err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Organization member added to %q\n", organization.HumanName())
return nil
},
}
return cmd
}
func (r *RootCmd) assignOrganizationRoles(orgContext *OrganizationContext) *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "edit-roles <username | user_id> [roles...]",
Aliases: []string{"edit-role"},
Short: "Edit organization member's roles",
Middleware: serpent.Chain(
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
organization, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
if len(inv.Args) < 1 {
return xerrors.Errorf("user_id or username is required as the first argument")
}
userIdentifier := inv.Args[0]
roles := inv.Args[1:]
member, err := client.UpdateOrganizationMemberRoles(ctx, organization.ID, userIdentifier, codersdk.UpdateRoles{
Roles: roles,
})
if err != nil {
return xerrors.Errorf("update member roles: %w", err)
}
updatedTo := make([]string, 0)
for _, role := range member.Roles {
updatedTo = append(updatedTo, role.String())
}
_, _ = fmt.Fprintf(inv.Stdout, "Member roles updated to [%s]\n", strings.Join(updatedTo, ", "))
return nil
},
}
return cmd
}
func (r *RootCmd) listOrganizationMembers(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]codersdk.OrganizationMemberWithUserData{}, []string{"username", "organization roles"}),
cliui.JSONFormat(),
)
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "list",
Short: "List all organization members",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
organization, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
res, err := client.OrganizationMembers(ctx, organization.ID)
if err != nil {
return xerrors.Errorf("fetch members: %w", err)
}
out, err := formatter.Format(inv.Context(), res)
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return cmd
}
+36
View File
@@ -0,0 +1,36 @@
package cli_test
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/testutil"
)
func TestListOrganizationMembers(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
ownerClient := coderdtest.New(t, &coderdtest.Options{})
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleUserAdmin())
ctx := testutil.Context(t, testutil.WaitMedium)
inv, root := clitest.New(t, "organization", "members", "list", "-c", "user id,username,organization roles")
clitest.SetupConfig(t, client, root)
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
require.Contains(t, buf.String(), user.Username)
require.Contains(t, buf.String(), owner.UserID.String())
})
}
+417
View File
@@ -0,0 +1,417 @@
package cli
import (
"encoding/json"
"fmt"
"io"
"slices"
"strings"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func (r *RootCmd) organizationRoles(orgContext *OrganizationContext) *serpent.Command {
cmd := &serpent.Command{
Use: "roles",
Short: "Manage organization roles.",
Aliases: []string{"role"},
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Hidden: true,
Children: []*serpent.Command{
r.showOrganizationRoles(orgContext),
r.editOrganizationRole(orgContext),
},
}
return cmd
}
func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]roleTableRow{}, []string{"name", "display name", "site permissions", "organization permissions", "user permissions"}),
func(data any) (any, error) {
inputs, ok := data.([]codersdk.AssignableRoles)
if !ok {
return nil, xerrors.Errorf("expected []codersdk.AssignableRoles got %T", data)
}
tableRows := make([]roleTableRow, 0)
for _, input := range inputs {
tableRows = append(tableRows, roleToTableView(input.Role))
}
return tableRows, nil
},
),
cliui.JSONFormat(),
)
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "show [role_names ...]",
Short: "Show role(s)",
Middleware: serpent.Chain(
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
org, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
roles, err := client.ListOrganizationRoles(ctx, org.ID)
if err != nil {
return xerrors.Errorf("listing roles: %w", err)
}
if len(inv.Args) > 0 {
// filter roles
filtered := make([]codersdk.AssignableRoles, 0)
for _, role := range roles {
if slices.ContainsFunc(inv.Args, func(s string) bool {
return strings.EqualFold(s, role.Name)
}) {
filtered = append(filtered, role)
}
}
roles = filtered
}
out, err := formatter.Format(inv.Context(), roles)
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return cmd
}
func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]roleTableRow{}, []string{"name", "display name", "site permissions", "organization permissions", "user permissions"}),
func(data any) (any, error) {
typed, _ := data.(codersdk.Role)
return []roleTableRow{roleToTableView(typed)}, nil
},
),
cliui.JSONFormat(),
)
var (
dryRun bool
jsonInput bool
)
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "edit <role_name>",
Short: "Edit an organization custom role",
Long: FormatExamples(
Example{
Description: "Run with an input.json file",
Command: "coder roles edit --stdin < role.json",
},
),
Options: []serpent.Option{
cliui.SkipPromptOption(),
{
Name: "dry-run",
Description: "Does all the work, but does not submit the final updated role.",
Flag: "dry-run",
Value: serpent.BoolOf(&dryRun),
},
{
Name: "stdin",
Description: "Reads stdin for the json role definition to upload.",
Flag: "stdin",
Value: serpent.BoolOf(&jsonInput),
},
},
Middleware: serpent.Chain(
serpent.RequireRangeArgs(0, 1),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
org, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
createNewRole := true
var customRole codersdk.Role
if jsonInput {
// JSON Upload mode
bytes, err := io.ReadAll(inv.Stdin)
if err != nil {
return xerrors.Errorf("reading stdin: %w", err)
}
err = json.Unmarshal(bytes, &customRole)
if err != nil {
return xerrors.Errorf("parsing stdin json: %w", err)
}
if customRole.Name == "" {
arr := make([]json.RawMessage, 0)
err = json.Unmarshal(bytes, &arr)
if err == nil && len(arr) > 0 {
return xerrors.Errorf("the input appears to be an array, only 1 role can be sent at a time")
}
return xerrors.Errorf("json input does not appear to be a valid role")
}
existingRoles, err := client.ListOrganizationRoles(ctx, org.ID)
if err != nil {
return xerrors.Errorf("listing existing roles: %w", err)
}
for _, existingRole := range existingRoles {
if strings.EqualFold(customRole.Name, existingRole.Name) {
// Editing an existing role
createNewRole = false
break
}
}
} else {
if len(inv.Args) == 0 {
return xerrors.Errorf("missing role name argument, usage: \"coder organizations roles edit <role_name>\"")
}
interactiveRole, newRole, err := interactiveOrgRoleEdit(inv, org.ID, client)
if err != nil {
return xerrors.Errorf("editing role: %w", err)
}
customRole = *interactiveRole
createNewRole = newRole
preview := fmt.Sprintf("permissions: %d site, %d org, %d user",
len(customRole.SitePermissions), len(customRole.OrganizationPermissions), len(customRole.UserPermissions))
_, err = cliui.Prompt(inv, cliui.PromptOptions{
Text: "Are you sure you wish to update the role? " + preview,
Default: "yes",
IsConfirm: true,
})
if err != nil {
return xerrors.Errorf("abort: %w", err)
}
}
var updated codersdk.Role
if dryRun {
// Do not actually post
updated = customRole
} else {
switch createNewRole {
case true:
updated, err = client.CreateOrganizationRole(ctx, customRole)
default:
updated, err = client.UpdateOrganizationRole(ctx, customRole)
}
if err != nil {
return xerrors.Errorf("patch role: %w", err)
}
}
output, err := formatter.Format(ctx, updated)
if err != nil {
return xerrors.Errorf("formatting: %w", err)
}
_, err = fmt.Fprintln(inv.Stdout, output)
return err
},
}
formatter.AttachOptions(&cmd.Options)
return cmd
}
func interactiveOrgRoleEdit(inv *serpent.Invocation, orgID uuid.UUID, client *codersdk.Client) (*codersdk.Role, bool, error) {
newRole := false
ctx := inv.Context()
roles, err := client.ListOrganizationRoles(ctx, orgID)
if err != nil {
return nil, newRole, xerrors.Errorf("listing roles: %w", err)
}
// Make sure the role actually exists first
var originalRole codersdk.AssignableRoles
for _, r := range roles {
if strings.EqualFold(inv.Args[0], r.Name) {
originalRole = r
break
}
}
if originalRole.Name == "" {
_, err = cliui.Prompt(inv, cliui.PromptOptions{
Text: "No organization role exists with that name, do you want to create one?",
Default: "yes",
IsConfirm: true,
})
if err != nil {
return nil, newRole, xerrors.Errorf("abort: %w", err)
}
originalRole.Role = codersdk.Role{
Name: inv.Args[0],
OrganizationID: orgID.String(),
}
newRole = true
}
// Some checks since interactive mode is limited in what it currently sees
if len(originalRole.SitePermissions) > 0 {
return nil, newRole, xerrors.Errorf("unable to edit role in interactive mode, it contains site wide permissions")
}
if len(originalRole.UserPermissions) > 0 {
return nil, newRole, xerrors.Errorf("unable to edit role in interactive mode, it contains user permissions")
}
role := &originalRole.Role
allowedResources := []codersdk.RBACResource{
codersdk.ResourceTemplate,
codersdk.ResourceWorkspace,
codersdk.ResourceUser,
codersdk.ResourceGroup,
}
const done = "Finish and submit changes"
const abort = "Cancel changes"
// Now starts the role editing "game".
customRoleLoop:
for {
selected, err := cliui.Select(inv, cliui.SelectOptions{
Message: "Select which resources to edit permissions",
Options: append(permissionPreviews(role, allowedResources), done, abort),
})
if err != nil {
return role, newRole, xerrors.Errorf("selecting resource: %w", err)
}
switch selected {
case done:
break customRoleLoop
case abort:
return role, newRole, xerrors.Errorf("edit role %q aborted", role.Name)
default:
strs := strings.Split(selected, "::")
resource := strings.TrimSpace(strs[0])
actions, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Message: fmt.Sprintf("Select actions to allow across the whole deployment for resources=%q", resource),
Options: slice.ToStrings(codersdk.RBACResourceActions[codersdk.RBACResource(resource)]),
Defaults: defaultActions(role, resource),
})
if err != nil {
return role, newRole, xerrors.Errorf("selecting actions for resource %q: %w", resource, err)
}
applyOrgResourceActions(role, resource, actions)
// back to resources!
}
}
// This println is required because the prompt ends us on the same line as some text.
_, _ = fmt.Println()
return role, newRole, nil
}
func applyOrgResourceActions(role *codersdk.Role, resource string, actions []string) {
if role.OrganizationPermissions == nil {
role.OrganizationPermissions = make([]codersdk.Permission, 0)
}
// Construct new site perms with only new perms for the resource
keep := make([]codersdk.Permission, 0)
for _, perm := range role.OrganizationPermissions {
perm := perm
if string(perm.ResourceType) != resource {
keep = append(keep, perm)
}
}
// Add new perms
for _, action := range actions {
keep = append(keep, codersdk.Permission{
Negate: false,
ResourceType: codersdk.RBACResource(resource),
Action: codersdk.RBACAction(action),
})
}
role.OrganizationPermissions = keep
}
func defaultActions(role *codersdk.Role, resource string) []string {
if role.OrganizationPermissions == nil {
role.OrganizationPermissions = []codersdk.Permission{}
}
defaults := make([]string, 0)
for _, perm := range role.OrganizationPermissions {
if string(perm.ResourceType) == resource {
defaults = append(defaults, string(perm.Action))
}
}
return defaults
}
func permissionPreviews(role *codersdk.Role, resources []codersdk.RBACResource) []string {
previews := make([]string, 0, len(resources))
for _, resource := range resources {
previews = append(previews, permissionPreview(role, resource))
}
return previews
}
func permissionPreview(role *codersdk.Role, resource codersdk.RBACResource) string {
if role.OrganizationPermissions == nil {
role.OrganizationPermissions = []codersdk.Permission{}
}
count := 0
for _, perm := range role.OrganizationPermissions {
if perm.ResourceType == resource {
count++
}
}
return fmt.Sprintf("%s :: %d permissions", resource, count)
}
func roleToTableView(role codersdk.Role) roleTableRow {
return roleTableRow{
Name: role.Name,
DisplayName: role.DisplayName,
OrganizationID: role.OrganizationID,
SitePermissions: fmt.Sprintf("%d permissions", len(role.SitePermissions)),
OrganizationPermissions: fmt.Sprintf("%d permissions", len(role.OrganizationPermissions)),
UserPermissions: fmt.Sprintf("%d permissions", len(role.UserPermissions)),
}
}
type roleTableRow struct {
Name string `table:"name,default_sort"`
DisplayName string `table:"display name"`
OrganizationID string `table:"organization id"`
SitePermissions string ` table:"site permissions"`
// map[<org_id>] -> Permissions
OrganizationPermissions string `table:"organization permissions"`
UserPermissions string `table:"user permissions"`
}
+51
View File
@@ -0,0 +1,51 @@
package cli_test
import (
"bytes"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"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/dbgen"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/testutil"
)
func TestShowOrganizationRoles(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{})
owner := coderdtest.CreateFirstUser(t, ownerClient)
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleUserAdmin())
const expectedRole = "test-role"
dbgen.CustomRole(t, db, database.CustomRole{
Name: expectedRole,
DisplayName: "Expected",
SitePermissions: nil,
OrgPermissions: nil,
UserPermissions: nil,
OrganizationID: uuid.NullUUID{
UUID: owner.OrganizationID,
Valid: true,
},
})
ctx := testutil.Context(t, testutil.WaitMedium)
inv, root := clitest.New(t, "organization", "roles", "show")
clitest.SetupConfig(t, client, root)
buf := new(bytes.Buffer)
inv.Stdout = buf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
require.Contains(t, buf.String(), expectedRole)
})
}
+82 -10
View File
@@ -2,10 +2,14 @@ package cli
import (
"context"
"errors"
"fmt"
"net/http"
"net/netip"
"time"
"golang.org/x/xerrors"
"tailscale.com/tailcfg"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
@@ -13,7 +17,9 @@ import (
"github.com/coder/pretty"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/healthsdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/serpent"
)
@@ -48,19 +54,21 @@ func (r *RootCmd) ping() *serpent.Command {
return err
}
logger := inv.Logger
opts := &workspacesdk.DialAgentOptions{}
if r.verbose {
logger = logger.AppendSinks(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug)
opts.Logger = inv.Logger.AppendSinks(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug)
}
if r.disableDirect {
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
opts.BlockEndpoints = true
}
conn, err := workspacesdk.New(client).
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
Logger: logger,
BlockEndpoints: r.disableDirect,
})
if !r.disableNetworkTelemetry {
opts.EnableTelemetry = true
}
client := workspacesdk.New(client)
conn, err := client.DialAgent(ctx, workspaceAgent.ID, opts)
if err != nil {
return err
}
@@ -137,11 +145,56 @@ func (r *RootCmd) ping() *serpent.Command {
)
if n == int(pingNum) {
diags := conn.GetPeerDiagnostics()
cliui.PeerDiagnostics(inv.Stdout, diags)
return nil
break
}
}
diagCtx, diagCancel := context.WithTimeout(inv.Context(), 30*time.Second)
defer diagCancel()
diags := conn.GetPeerDiagnostics()
cliui.PeerDiagnostics(inv.Stdout, diags)
ni := conn.GetNetInfo()
connDiags := cliui.ConnDiags{
PingP2P: didP2p,
DisableDirect: r.disableDirect,
LocalNetInfo: ni,
Verbose: r.verbose,
}
awsRanges, err := cliutil.FetchAWSIPRanges(diagCtx, cliutil.AWSIPRangesURL)
if err != nil {
opts.Logger.Debug(inv.Context(), "failed to retrieve AWS IP ranges", slog.Error(err))
}
connDiags.ClientIPIsAWS = isAWSIP(awsRanges, ni)
connInfo, err := client.AgentConnectionInfoGeneric(diagCtx)
if err != nil || connInfo.DERPMap == nil {
return xerrors.Errorf("Failed to retrieve connection info from server: %w\n", err)
}
connDiags.ConnInfo = connInfo
ifReport, err := healthsdk.RunInterfacesReport()
if err == nil {
connDiags.LocalInterfaces = &ifReport
} else {
_, _ = fmt.Fprintf(inv.Stdout, "Failed to retrieve local interfaces report: %v\n", err)
}
agentNetcheck, err := conn.Netcheck(diagCtx)
if err == nil {
connDiags.AgentNetcheck = &agentNetcheck
connDiags.AgentIPIsAWS = isAWSIP(awsRanges, agentNetcheck.NetInfo)
} else {
var sdkErr *codersdk.Error
if errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound {
_, _ = fmt.Fprint(inv.Stdout, "Could not generate full connection report as the workspace agent is outdated\n")
} else {
_, _ = fmt.Fprintf(inv.Stdout, "Failed to retrieve connection report from agent: %v\n", err)
}
}
connDiags.Write(inv.Stdout)
return nil
},
}
@@ -169,3 +222,22 @@ func (r *RootCmd) ping() *serpent.Command {
}
return cmd
}
func isAWSIP(awsRanges *cliutil.AWSIPRanges, ni *tailcfg.NetInfo) bool {
if awsRanges == nil {
return false
}
if ni.GlobalV4 != "" {
ip, err := netip.ParseAddr(ni.GlobalV4)
if err == nil && awsRanges.CheckIP(ip) {
return true
}
}
if ni.GlobalV6 != "" {
ip, err := netip.ParseAddr(ni.GlobalV6)
if err == nil && awsRanges.CheckIP(ip) {
return true
}
}
return false
}
+1
View File
@@ -67,6 +67,7 @@ func TestPing(t *testing.T) {
pty.ExpectMatch("pong from " + workspace.Name)
pty.ExpectMatch("✔ received remote agent data from Coder networking coordinator")
pty.ExpectMatch("✔ You are connected directly (p2p)")
cancel()
<-cmdDone
})
+14 -12
View File
@@ -35,24 +35,24 @@ func (r *RootCmd) portForward() *serpent.Command {
Use: "port-forward <workspace>",
Short: `Forward ports from a workspace to the local machine. For reverse port forwarding, use "coder ssh -R".`,
Aliases: []string{"tunnel"},
Long: formatExamples(
example{
Long: FormatExamples(
Example{
Description: "Port forward a single TCP port from 1234 in the workspace to port 5678 on your local machine",
Command: "coder port-forward <workspace> --tcp 5678:1234",
},
example{
Example{
Description: "Port forward a single UDP port from port 9000 to port 9000 on your local machine",
Command: "coder port-forward <workspace> --udp 9000",
},
example{
Example{
Description: "Port forward multiple TCP ports and a UDP port",
Command: "coder port-forward <workspace> --tcp 8080:8080 --tcp 9000:3000 --udp 5353:53",
},
example{
Example{
Description: "Port forward multiple ports (TCP or UDP) in condensed syntax",
Command: "coder port-forward <workspace> --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012",
},
example{
Example{
Description: "Port forward specifying the local address to bind to",
Command: "coder port-forward <workspace> --tcp 1.2.3.4:8080:8080",
},
@@ -95,19 +95,21 @@ func (r *RootCmd) portForward() *serpent.Command {
return xerrors.Errorf("await agent: %w", err)
}
opts := &workspacesdk.DialAgentOptions{}
logger := inv.Logger
if r.verbose {
logger = logger.AppendSinks(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug)
opts.Logger = logger.AppendSinks(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug)
}
if r.disableDirect {
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
opts.BlockEndpoints = true
}
conn, err := workspacesdk.New(client).
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
Logger: logger,
BlockEndpoints: r.disableDirect,
})
if !r.disableNetworkTelemetry {
opts.EnableTelemetry = true
}
conn, err := workspacesdk.New(client).DialAgent(ctx, workspaceAgent.ID, opts)
if err != nil {
return err
}
+186
View File
@@ -0,0 +1,186 @@
package cli
import (
"fmt"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func (RootCmd) promptExample() *serpent.Command {
promptCmd := func(use string, prompt func(inv *serpent.Invocation) error, options ...serpent.Option) *serpent.Command {
return &serpent.Command{
Use: use,
Options: options,
Handler: func(inv *serpent.Invocation) error {
return prompt(inv)
},
}
}
var useSearch bool
useSearchOption := serpent.Option{
Name: "search",
Description: "Show the search.",
Required: false,
Flag: "search",
Value: serpent.BoolOf(&useSearch),
}
cmd := &serpent.Command{
Use: "prompt-example",
Short: "Example of various prompt types used within coder cli.",
Long: "Example of various prompt types used within coder cli. " +
"This command exists to aid in adjusting visuals of command prompts.",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
promptCmd("confirm", func(inv *serpent.Invocation) error {
value, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Basic confirmation prompt.",
Default: "yes",
IsConfirm: true,
})
_, _ = fmt.Fprintf(inv.Stdout, "%s\n", value)
return err
}),
promptCmd("validation", func(inv *serpent.Invocation) error {
value, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Input a string that starts with a capital letter.",
Default: "",
Secret: false,
IsConfirm: false,
Validate: func(s string) error {
if len(s) == 0 {
return xerrors.Errorf("an input string is required")
}
if strings.ToUpper(string(s[0])) != string(s[0]) {
return xerrors.Errorf("input string must start with a capital letter")
}
return nil
},
})
_, _ = fmt.Fprintf(inv.Stdout, "%s\n", value)
return err
}),
promptCmd("secret", func(inv *serpent.Invocation) error {
value, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Input a secret",
Default: "",
Secret: true,
IsConfirm: false,
Validate: func(s string) error {
if len(s) == 0 {
return xerrors.Errorf("an input string is required")
}
return nil
},
})
_, _ = fmt.Fprintf(inv.Stdout, "Your secret of length %d is safe with me\n", len(value))
return err
}),
promptCmd("select", func(inv *serpent.Invocation) error {
value, err := cliui.Select(inv, cliui.SelectOptions{
Options: []string{
"Blue", "Green", "Yellow", "Red", "Something else",
},
Default: "",
Message: "Select your favorite color:",
Size: 5,
HideSearch: !useSearch,
})
if value == "Something else" {
_, _ = fmt.Fprint(inv.Stdout, "I would have picked blue.\n")
} else {
_, _ = fmt.Fprintf(inv.Stdout, "%s is a nice color.\n", value)
}
return err
}, useSearchOption),
promptCmd("multiple", func(inv *serpent.Invocation) error {
_, _ = fmt.Fprintf(inv.Stdout, "This command exists to test the behavior of multiple prompts. The survey library does not erase the original message prompt after.")
thing, err := cliui.Select(inv, cliui.SelectOptions{
Message: "Select a thing",
Options: []string{
"Car", "Bike", "Plane", "Boat", "Train",
},
Default: "Car",
})
if err != nil {
return err
}
color, err := cliui.Select(inv, cliui.SelectOptions{
Message: "Select a color",
Options: []string{
"Blue", "Green", "Yellow", "Red",
},
Default: "Blue",
})
if err != nil {
return err
}
properties, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Message: "Select properties",
Options: []string{
"Fast", "Cool", "Expensive", "New",
},
Defaults: []string{"Fast"},
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(inv.Stdout, "Your %s %s is awesome! Did you paint it %s?\n",
strings.Join(properties, " "),
thing,
color,
)
return err
}),
promptCmd("multi-select", func(inv *serpent.Invocation) error {
values, err := cliui.MultiSelect(inv, cliui.MultiSelectOptions{
Message: "Select some things:",
Options: []string{
"Code", "Chair", "Whale", "Diamond", "Carrot",
},
Defaults: []string{"Code"},
})
_, _ = fmt.Fprintf(inv.Stdout, "%q are nice choices.\n", strings.Join(values, ", "))
return err
}),
promptCmd("rich-parameter", func(inv *serpent.Invocation) error {
value, err := cliui.RichSelect(inv, cliui.RichSelectOptions{
Options: []codersdk.TemplateVersionParameterOption{
{
Name: "Blue",
Description: "Like the ocean.",
Value: "blue",
Icon: "/logo/blue.png",
},
{
Name: "Red",
Description: "Like a clown's nose.",
Value: "red",
Icon: "/logo/red.png",
},
{
Name: "Yellow",
Description: "Like a bumblebee. ",
Value: "yellow",
Icon: "/logo/yellow.png",
},
},
Default: "blue",
Size: 5,
HideSearch: useSearch,
})
_, _ = fmt.Fprintf(inv.Stdout, "%s is a good choice.\n", value.Name)
return err
}, useSearchOption),
},
}
return cmd
}
+1 -1
View File
@@ -31,7 +31,7 @@ func (r *RootCmd) rename() *serpent.Command {
_, _ = fmt.Fprintf(inv.Stdout, "%s\n\n",
pretty.Sprint(cliui.DefaultStyles.Wrap, "WARNING: A rename can result in data loss if a resource references the workspace name in the template (e.g volumes). Please backup any data before proceeding."),
)
_, _ = fmt.Fprintf(inv.Stdout, "See: %s\n\n", "https://coder.com/docs/coder-oss/latest/templates/resource-persistence#%EF%B8%8F-persistence-pitfalls")
_, _ = fmt.Fprintf(inv.Stdout, "See: %s\n\n", "https://coder.com/docs/templates/resource-persistence#%EF%B8%8F-persistence-pitfalls")
_, err = cliui.Prompt(inv, cliui.PromptOptions{
Text: fmt.Sprintf("Type %q to confirm rename:", workspace.Name),
Validate: func(s string) error {
+1 -1
View File
@@ -21,7 +21,7 @@ func TestRename(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+5 -5
View File
@@ -38,7 +38,7 @@ func TestRestart(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
ctx := testutil.Context(t, testutil.WaitLong)
@@ -69,7 +69,7 @@ func TestRestart(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
inv, root := clitest.New(t, "restart", workspace.Name, "--build-options")
@@ -123,7 +123,7 @@ func TestRestart(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
inv, root := clitest.New(t, "restart", workspace.Name,
@@ -202,7 +202,7 @@ func TestRestartWithParameters(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
{
Name: immutableParameterName,
@@ -250,7 +250,7 @@ func TestRestartWithParameters(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, mutableParamsResponse)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
{
Name: mutableParameterName,
+142 -119
View File
@@ -52,20 +52,20 @@ var (
)
const (
varURL = "url"
varToken = "token"
varAgentToken = "agent-token"
varAgentTokenFile = "agent-token-file"
varAgentURL = "agent-url"
varHeader = "header"
varHeaderCommand = "header-command"
varNoOpen = "no-open"
varNoVersionCheck = "no-version-warning"
varNoFeatureWarning = "no-feature-warning"
varForceTty = "force-tty"
varVerbose = "verbose"
varOrganizationSelect = "organization"
varDisableDirect = "disable-direct-connections"
varURL = "url"
varToken = "token"
varAgentToken = "agent-token"
varAgentTokenFile = "agent-token-file"
varAgentURL = "agent-url"
varHeader = "header"
varHeaderCommand = "header-command"
varNoOpen = "no-open"
varNoVersionCheck = "no-version-warning"
varNoFeatureWarning = "no-feature-warning"
varForceTty = "force-tty"
varVerbose = "verbose"
varDisableDirect = "disable-direct-connections"
varDisableNetworkTelemetry = "disable-network-telemetry"
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
@@ -82,11 +82,14 @@ const (
func (r *RootCmd) CoreSubcommands() []*serpent.Command {
// Please re-sort this list alphabetically if you change it!
return []*serpent.Command{
r.completion(),
r.dotfiles(),
r.externalAuth(),
r.login(),
r.logout(),
r.netcheck(),
r.notifications(),
r.organizations(),
r.portForward(),
r.publickey(),
r.resetPassword(),
@@ -95,7 +98,6 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
r.tokens(),
r.users(),
r.version(defaultVersionInfo),
r.organizations(),
// Workspace Commands
r.autoupdate(),
@@ -117,13 +119,14 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
r.stop(),
r.unfavorite(),
r.update(),
r.whoami(),
// Hidden
r.expCmd(),
r.gitssh(),
r.support(),
r.vscodeSSH(),
r.workspaceAgent(),
r.expCmd(),
r.support(),
}
}
@@ -181,12 +184,12 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
`
cmd := &serpent.Command{
Use: "coder [global-flags] <subcommand>",
Long: fmt.Sprintf(fmtLong, buildinfo.Version()) + formatExamples(
example{
Long: fmt.Sprintf(fmtLong, buildinfo.Version()) + FormatExamples(
Example{
Description: "Start a Coder server",
Command: "coder server",
},
example{
Example{
Description: "Get started by creating a template from an example",
Command: "coder templates init",
},
@@ -436,6 +439,13 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
Value: serpent.BoolOf(&r.disableDirect),
Group: globalGroup,
},
{
Flag: varDisableNetworkTelemetry,
Env: "CODER_DISABLE_NETWORK_TELEMETRY",
Description: "Disable network telemetry. Network telemetry is collected when connecting to workspaces using the CLI, and is forwarded to the server. If telemetry is also enabled on the server, it may be sent to Coder. Network telemetry is used to measure network quality and detect regressions.",
Value: serpent.BoolOf(&r.disableNetworkTelemetry),
Group: globalGroup,
},
{
Flag: "debug-http",
Description: "Debug codersdk HTTP requests.",
@@ -451,15 +461,6 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
Value: serpent.StringOf(&r.globalConfig),
Group: globalGroup,
},
{
Flag: varOrganizationSelect,
FlagShorthand: "z",
Env: "CODER_ORGANIZATION",
Description: "Select which organization (uuid or name) to use This overrides what is present in the config file.",
Value: serpent.StringOf(&r.organizationSelect),
Hidden: true,
Group: globalGroup,
},
{
Flag: "version",
// This was requested by a customer to assist with their migration.
@@ -476,24 +477,24 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
// RootCmd contains parameters and helpers useful to all commands.
type RootCmd struct {
clientURL *url.URL
token string
globalConfig string
header []string
headerCommand string
agentToken string
agentTokenFile string
agentURL *url.URL
forceTTY bool
noOpen bool
verbose bool
organizationSelect string
versionFlag bool
disableDirect bool
debugHTTP bool
clientURL *url.URL
token string
globalConfig string
header []string
headerCommand string
agentToken string
agentTokenFile string
agentURL *url.URL
forceTTY bool
noOpen bool
verbose bool
versionFlag bool
disableDirect bool
debugHTTP bool
noVersionCheck bool
noFeatureWarning bool
disableNetworkTelemetry bool
noVersionCheck bool
noFeatureWarning bool
}
// InitClient authenticates the client with files from disk
@@ -549,44 +550,7 @@ func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc {
// HeaderTransport creates a new transport that executes `--header-command`
// if it is set to add headers for all outbound requests.
func (r *RootCmd) HeaderTransport(ctx context.Context, serverURL *url.URL) (*codersdk.HeaderTransport, error) {
transport := &codersdk.HeaderTransport{
Transport: http.DefaultTransport,
Header: http.Header{},
}
headers := r.header
if r.headerCommand != "" {
shell := "sh"
caller := "-c"
if runtime.GOOS == "windows" {
shell = "cmd.exe"
caller = "/c"
}
var outBuf bytes.Buffer
// #nosec
cmd := exec.CommandContext(ctx, shell, caller, r.headerCommand)
cmd.Env = append(os.Environ(), "CODER_URL="+serverURL.String())
cmd.Stdout = &outBuf
cmd.Stderr = io.Discard
err := cmd.Run()
if err != nil {
return nil, xerrors.Errorf("failed to run %v: %w", cmd.Args, err)
}
scanner := bufio.NewScanner(&outBuf)
for scanner.Scan() {
headers = append(headers, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, xerrors.Errorf("scan %v: %w", cmd.Args, err)
}
}
for _, header := range headers {
parts := strings.SplitN(header, "=", 2)
if len(parts) < 2 {
return nil, xerrors.Errorf("split header %q had less than two parts", header)
}
transport.Header.Add(parts[0], parts[1])
}
return transport, nil
return headerTransport(ctx, serverURL, r.header, r.headerCommand)
}
func (r *RootCmd) configureClient(ctx context.Context, client *codersdk.Client, serverURL *url.URL, inv *serpent.Invocation) error {
@@ -632,52 +596,68 @@ func (r *RootCmd) createAgentClient() (*agentsdk.Client, error) {
return client, nil
}
// CurrentOrganization returns the currently active organization for the authenticated user.
func CurrentOrganization(r *RootCmd, inv *serpent.Invocation, client *codersdk.Client) (codersdk.Organization, error) {
conf := r.createConfig()
selected := r.organizationSelect
if selected == "" && conf.Organization().Exists() {
org, err := conf.Organization().Read()
if err != nil {
return codersdk.Organization{}, xerrors.Errorf("read selected organization from config file %q: %w", conf.Organization(), err)
}
selected = org
}
type OrganizationContext struct {
// FlagSelect is the value passed in via the --org flag
FlagSelect string
}
// Verify the org exists and the user is a member
func NewOrganizationContext() *OrganizationContext {
return &OrganizationContext{}
}
func (*OrganizationContext) optionName() string { return "Organization" }
func (o *OrganizationContext) AttachOptions(cmd *serpent.Command) {
cmd.Options = append(cmd.Options, serpent.Option{
Name: o.optionName(),
Description: "Select which organization (uuid or name) to use.",
// Only required if the user is a part of more than 1 organization.
// Otherwise, we can assume a default value.
Required: false,
Flag: "org",
FlagShorthand: "O",
Env: "CODER_ORGANIZATION",
Value: serpent.StringOf(&o.FlagSelect),
})
}
func (o *OrganizationContext) ValueSource(inv *serpent.Invocation) (string, serpent.ValueSource) {
opt := inv.Command.Options.ByName(o.optionName())
if opt == nil {
return o.FlagSelect, serpent.ValueSourceNone
}
return o.FlagSelect, opt.ValueSource
}
func (o *OrganizationContext) Selected(inv *serpent.Invocation, client *codersdk.Client) (codersdk.Organization, error) {
// Fetch the set of organizations the user is a member of.
orgs, err := client.OrganizationsByUser(inv.Context(), codersdk.Me)
if err != nil {
return codersdk.Organization{}, err
return codersdk.Organization{}, xerrors.Errorf("get organizations: %w", err)
}
// User manually selected an organization
if selected != "" {
if o.FlagSelect != "" {
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
return org.Name == selected || org.ID.String() == selected
return org.Name == o.FlagSelect || org.ID.String() == o.FlagSelect
})
if index < 0 {
return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization?", selected)
var names []string
for _, org := range orgs {
names = append(names, org.Name)
}
return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization? "+
"Valid options for '--org=' are [%s].", o.FlagSelect, strings.Join(names, ", "))
}
return orgs[index], nil
}
// User did not select an organization, so use the default.
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
return org.IsDefault
})
if index < 0 {
if len(orgs) == 1 {
// If there is no "isDefault", but only 1 org is present. We can just
// assume the single organization is correct. This is mainly a helper
// for cli hitting an old instance, or a user that belongs to a single
// org that is not the default.
return orgs[0], nil
}
return codersdk.Organization{}, xerrors.Errorf("unable to determine current organization. Use 'coder org set <org>' to select an organization to use")
if len(orgs) == 1 {
return orgs[0], nil
}
return orgs[index], nil
// No org selected, and we are more than 1? Return an error.
return codersdk.Organization{}, xerrors.Errorf("Must select an organization with --org=<org_name>.")
}
func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) {
@@ -753,16 +733,16 @@ func isTTYWriter(inv *serpent.Invocation, writer io.Writer) bool {
return isatty.IsTerminal(file.Fd())
}
// example represents a standard example for command usage, to be used
// with formatExamples.
type example struct {
// Example represents a standard example for command usage, to be used
// with FormatExamples.
type Example struct {
Description string
Command string
}
// formatExamples formats the examples as width wrapped bulletpoint
// FormatExamples formats the examples as width wrapped bulletpoint
// descriptions with the command underneath.
func formatExamples(examples ...example) string {
func FormatExamples(examples ...Example) string {
var sb strings.Builder
padStyle := cliui.DefaultStyles.Wrap.With(pretty.XPad(4, 0))
@@ -1256,3 +1236,46 @@ type roundTripper func(req *http.Request) (*http.Response, error)
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return r(req)
}
// HeaderTransport creates a new transport that executes `--header-command`
// if it is set to add headers for all outbound requests.
func headerTransport(ctx context.Context, serverURL *url.URL, header []string, headerCommand string) (*codersdk.HeaderTransport, error) {
transport := &codersdk.HeaderTransport{
Transport: http.DefaultTransport,
Header: http.Header{},
}
headers := header
if headerCommand != "" {
shell := "sh"
caller := "-c"
if runtime.GOOS == "windows" {
shell = "cmd.exe"
caller = "/c"
}
var outBuf bytes.Buffer
// #nosec
cmd := exec.CommandContext(ctx, shell, caller, headerCommand)
cmd.Env = append(os.Environ(), "CODER_URL="+serverURL.String())
cmd.Stdout = &outBuf
cmd.Stderr = io.Discard
err := cmd.Run()
if err != nil {
return nil, xerrors.Errorf("failed to run %v: %w", cmd.Args, err)
}
scanner := bufio.NewScanner(&outBuf)
for scanner.Scan() {
headers = append(headers, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, xerrors.Errorf("scan %v: %w", cmd.Args, err)
}
}
for _, header := range headers {
parts := strings.SplitN(header, "=", 2)
if len(parts) < 2 {
return nil, xerrors.Errorf("split header %q had less than two parts", header)
}
transport.Header.Add(parts[0], parts[1])
}
return transport, nil
}
+4 -4
View File
@@ -45,7 +45,7 @@ func Test_formatExamples(t *testing.T) {
tests := []struct {
name string
examples []example
examples []Example
wantMatches []string
}{
{
@@ -55,7 +55,7 @@ func Test_formatExamples(t *testing.T) {
},
{
name: "Output examples",
examples: []example{
examples: []Example{
{
Description: "Hello world.",
Command: "echo hello",
@@ -72,7 +72,7 @@ func Test_formatExamples(t *testing.T) {
},
{
name: "No description outputs commands",
examples: []example{
examples: []Example{
{
Command: "echo hello",
},
@@ -87,7 +87,7 @@ func Test_formatExamples(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := formatExamples(tt.examples...)
got := FormatExamples(tt.examples...)
if len(tt.wantMatches) == 0 {
require.Empty(t, got)
} else {
+6 -6
View File
@@ -140,8 +140,8 @@ func (r *RootCmd) scheduleStart() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "start <workspace-name> { <start-time> [day-of-week] [location] | manual }",
Long: scheduleStartDescriptionLong + "\n" + formatExamples(
example{
Long: scheduleStartDescriptionLong + "\n" + FormatExamples(
Example{
Description: "Set the workspace to start at 9:30am (in Dublin) from Monday to Friday",
Command: "coder schedule start my-workspace 9:30AM Mon-Fri Europe/Dublin",
},
@@ -189,8 +189,8 @@ func (r *RootCmd) scheduleStop() *serpent.Command {
client := new(codersdk.Client)
return &serpent.Command{
Use: "stop <workspace-name> { <duration> | manual }",
Long: scheduleStopDescriptionLong + "\n" + formatExamples(
example{
Long: scheduleStopDescriptionLong + "\n" + FormatExamples(
Example{
Command: "coder schedule stop my-workspace 2h30m",
},
),
@@ -234,8 +234,8 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
overrideCmd := &serpent.Command{
Use: "override-stop <workspace-name> <duration from now>",
Short: "Override the stop time of a currently running workspace instance.",
Long: scheduleOverrideDescriptionLong + "\n" + formatExamples(
example{
Long: scheduleOverrideDescriptionLong + "\n" + FormatExamples(
Example{
Command: "coder schedule override-stop my-workspace 90m",
},
),

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