Compare commits

...

1351 Commits

Author SHA1 Message Date
Steven Masley 27c8345ef2 chore: Add linter rule to prevent breaking of sse (#4144)
* chore: Add linter rule to prevent breaking of sse
2022-09-27 11:14:58 -04:00
Bruno Quaresma 5a449bf86f chore: Add user autocomplete (#4210)
* chore: Add user autocomplete

* Update value type

* fix initial load and option updates

* cleaned up styling

* PR comments

* prettier

Co-authored-by: Kira Pilot <kira.pilot23@gmail.com>
2022-09-27 10:23:38 -04:00
Ben Potter a7e08db16d docs: fix link 404 in port-forwarding (#4211) 2022-09-27 08:24:49 -05:00
Ben Potter b6426083b9 fix: inline code blocks in template README (#4141) 2022-09-27 07:54:29 -05:00
Kyle Carberry 4f453544d4 chore: Update hero image (#4216) 2022-09-27 07:38:18 -05:00
Ammar Bandukwala 47a53ce6c5 coderd: treat email case insensitively (#4215) 2022-09-27 03:51:58 +00:00
Garrett Delfosse 20bcb04e8a fix: use correct interval for healthcheck loop (#4212) 2022-09-26 21:00:58 +00:00
Ben Potter c86fc6e976 chore: examples/lima: bump terraform version (#4205)
Download terraform binary directly instead of using Hashicorp APT
repo.
Workaround for https://github.com/hashicorp/terraform/issues/31826

Signed-off-by: Cian Johnston <cian@coder.com>
Co-authored-by: Ben Potter <ben@coder.com>
2022-09-26 20:04:59 +00:00
Steven Masley 2f0d30d7b5 chore: Reduce the amount of bytes allocated for Filter (#4209)
Reuse parsed data structure for subsequent queries
2022-09-26 15:16:46 -04:00
Steven Masley 48c0b59447 fix: Log out of legacy cookie (#4202) 2022-09-26 14:20:38 -04:00
Kyle Carberry 39cf329404 fix: Replace access URL for built-in DERP servers (#4197)
Fixes #4195.
2022-09-26 12:56:04 -05:00
Ammar Bandukwala ee4b934601 Add Users Last Seen At (#4192) 2022-09-26 15:31:03 +00:00
Kyle Carberry b8ec5c786d fix: Ensure tailnet coordinations are sent orderly (#4198) 2022-09-26 10:16:04 -05:00
Bruno Quaresma c37ecdb9ff feat: Add port forward button (#4167) 2022-09-26 14:56:17 +00:00
Kyle Carberry 413bfb8d58 fix: Retry reporting agent version (#4190)
It's possible that an agent starts before a build is reported
as complete. This ensures the version is successfully sent
before the startup completes.

Fixes #4151.
2022-09-25 11:11:36 -05:00
Kyle Carberry 112eaf80d1 fix: Add logging to Terraform install (#4191)
Fixes #4129.
2022-09-24 14:55:17 -05:00
Kyle Carberry 4054a9c7cb Fix permissions for welcome message 2022-09-24 02:27:23 +00:00
Ryan Merolle 6571e52f17 Add coder binary to Dockerfile $PATH (#4189) 2022-09-24 02:25:15 +00:00
Kyle Carberry 28428d1294 feat: Add custom version names (#4186)
Fixes #4137.
2022-09-23 20:17:36 -05:00
Kyle Carberry 3c215a83b6 feat: Allow admins to create workspaces (#4183)
Fixes #3263.

This is now possible via the API, but still isn't possible via the UI.
2022-09-23 20:17:10 -05:00
Kyle Carberry 266a3b24e7 fix: Replace getFormHelpers (#4181)
Fixes #3209.
2022-09-23 16:37:44 -05:00
Kyle Carberry f9075cab0e fix: Hide agent status when a workspace is stopped (#4185)
Fixes #4033.
2022-09-23 21:36:33 +00:00
Kyle Carberry b64f624d17 fix: Remove unused scopes from parameter computation (#4171) 2022-09-23 16:09:45 -05:00
Kyle Carberry ea115c981d fix: Make entire row clickable in responsive navbar (#4182)
Fixes #3235.
2022-09-23 20:38:24 +00:00
Kyle Carberry 1c85799be5 fix: Update Terraform to v1.3.0 (#4180)
Contributes to #3202.
2022-09-23 15:31:26 -05:00
Colin Adler 15b9a59786 chore: only trace rbac.Filter (#4177) 2022-09-23 15:21:56 -05:00
Colin Adler 95aea104c7 chore: ignore traces from (*API).workspaceAgentCoordinate after accept (#4178) 2022-09-23 15:21:44 -05:00
Garrett Delfosse 4c8be34d81 feat: add health check monitoring to workspace apps (#4114) 2022-09-23 15:51:04 -04:00
Kyle Carberry f160830226 fix: Update default cache directory (#4175)
Fixes #2534.
2022-09-23 14:26:29 -05:00
Bruno Quaresma 38e2a28ada chore: Pin site deps (#4173) 2022-09-23 16:09:35 -03:00
Bruno Quaresma 189c562826 chore: Use Vite instead of Webpack for development (#4156) 2022-09-23 15:22:48 -03:00
Joe Previte ee00a1d886 chore(site): fix material ui warning (#4161)
* chore(deps): upgrade @material-ui/core to 4.12.4

This is the latest version which includes a fix for the warning we were
seeing while running our tests about `css` function being deprecated.

* refactor: use alpha() instead of fade

`fade()` was deprecated in favor of `alpha()` in a previous version of
`@material-ui/core/styles`.

* refactor: rows -> minRows

This was deprecated in a previous version of `@material-ui/core`.

* refactor: overlap circle -> circular

overlap="circle" was deprecated in favor of overlap="circular".

* refactor: createMuiTheme -> createTheme

This was deprecated and changed to `createTheme`.

* fixup!: chore(deps): upgrade @material-ui/core to 4.12.4

* fixup!: refactor: createMuiTheme -> createTheme

* fix: add SvgIconProps on icons

I couldn't find any release notes or breaking changes related to this
but it seems `props` can no longer be inferred on `SvgIcon`s so I had to
manually add the type.

* Revert "refactor: rows -> minRows"

This reverts commit 94dae6fea8.

* chore(deps): downgrade @material-ui/core to 4.12.0

* fixup!: fix: add SvgIconProps on icons

* fix: pass {} to useStyles

Looks like we may need to pass an empty object if some components in a
file use `props` in styles and some don't.

* fix: update types in Pill.tsx

We need to use generics so that `makeStyles` correctly infers the types
for the `Pill.tsx` styles.

I also updated the types to use `PillProps` directly to make sure they
stay in sync.
2022-09-23 18:14:02 +00:00
Kyle Carberry 99013b3aed chore: Close dials in tailnet conn on close (#4174)
Fixes a race seen in: https://github.com/coder/coder/actions/runs/3114263658/jobs/5049905647
2022-09-23 12:10:47 -05:00
Kira Pilot 8cd5aeaf25 cleanup workspace machine (#4160)
* removed dead build states

* removed dead code

* removed guards

* not calling events from actions

* simplified timeline

* simplify refresh template
2022-09-23 13:06:48 -04:00
Bruno Quaresma 1214022c5a Improve DAU chart view (#4172) 2022-09-23 13:58:00 -03:00
Kyle Carberry 8738755ffc chore: Compile rego once to save CPU cycles in testing (#4169)
Compiling rego isn't very fast, so this should speed up tests in CI!
2022-09-23 16:26:04 +00:00
Mathias Fredriksson 1e1967e0db fix: Avoid using hijacked http.ResponseWriter in workspaceAgentReportStats (#4165) 2022-09-23 19:08:56 +03:00
Bruno Quaresma 7898581e50 feat: Show a full screen loader while is loading a lazy loading page (#4168) 2022-09-23 10:32:28 -05:00
Mathias Fredriksson 6b365f46f5 fix: Ensure coordinator is closed and freed in agent (#4164)
* fix: Close coordinator on context cancellation

* fix: Refactor runCoordinator so that previous is closed/freed
2022-09-23 18:08:13 +03:00
Steven Masley 2e30d0512e chore: Move scope into the same auth call (#4162)
Scopes now are enforced in the same Authorize call as the roles. 
Vs 2 `Authorize()` calls
2022-09-23 11:07:30 -04:00
Kyle Carberry 4183c5e1d0 chore: Clean up unused and outdated dependencies from go.mod (#4163) 2022-09-23 00:58:19 +00:00
Dean Sheather 6deef06ad2 feat: secure and cross-domain subdomain-based proxying (#4136)
Co-authored-by: Kyle Carberry <kyle@carberry.com>
2022-09-22 22:30:32 +00:00
Kyle Carberry 80b45f1aa1 fix: Buffer tailnet nodes from connection initialization (#4159)
* fix: Don't use StatusAbnormalClosure

This is reserved for WASM use, and might be the cause of some weird leaks.

* Add close to provisioner logs
2022-09-22 20:22:49 +00:00
Kyle Carberry a7ee8b31e0 fix: Don't use StatusAbnormalClosure (#4155) 2022-09-22 18:26:05 +00:00
Ben Potter 9e099b543f chore: revert open in coder docs for now (#4154)
* chore: revert open in coder docs for now

* remove in dogfood
2022-09-22 18:02:11 +00:00
Kira Pilot 5fd90471fc Cleanup dead states in workspace machine
* removed dead build states

* removed dead code

* removed guards
2022-09-22 13:32:40 -04:00
Colin Adler 57c84d6446 chore: add option for specifically disabling Coder tracing (#4153) 2022-09-22 11:53:08 -05:00
Kyle Carberry b77d6bdd91 fix: Panic when loading coordination override (#4152)
This was broken because of browser-only. This should fix it!

Signed-off-by: Kyle Carberry <kyle@carberry.com>

Signed-off-by: Kyle Carberry <kyle@carberry.com>
2022-09-22 11:03:49 -05:00
Joe Previte 764600003b feat: add open in coder docs, fix missing templates (#4124)
* docs: add open in coder

This adds new documentation for the "Open in Coder" button that admins
can use to get their developers up and running faster.

* fix: display error if template not found

Previously, we weren't handling a case where we tried to get a template
that returned a 404 from the backend.

Now we handle that case in our state machine and display the error
message from the API on the frontend.

* feat: support template query param in index

This adds support to navigate directly to a template from the index by
using the `?template=<name>` query  param.

* Revert "feat: support template query param in index"

This reverts commit bad7ffb677.

We decided to use the `/template/path` route instead.

* fixup!: docs: add open in coder

* docs: add open in coder to dogfood readme

* Update docs/admin/open-in-coder.md

Co-authored-by: Ben Potter <ben@coder.com>

* Update docs/admin/open-in-coder.md

Co-authored-by: Ben Potter <ben@coder.com>

* Update docs/admin/open-in-coder.md

Co-authored-by: Ben Potter <ben@coder.com>
2022-09-22 08:48:03 -07:00
Kyle Carberry 7ad4276224 feat: Add browser-only connections to Enterprise (#4135)
* feat: Add browser-only connections to Enterprise

Fixes #4131.

* Fix formatting
2022-09-22 15:14:22 +00:00
Mohammed Agboola 656dcc0050 fix: typo (#4149) 2022-09-21 17:38:51 -05:00
Colin Adler 5de6f86959 feat: trace httpapi.{Read,Write} (#4134) 2022-09-21 17:07:00 -05:00
Joe Previte 1bf2dc0cc3 chore: add explicit-length-check eslint rule (#4147)
* chore: add eslint rule explicit-length-check

* fix: add explicit-length-check
2022-09-21 15:42:10 -04:00
Kira Pilot 5698b9d706 feat: use sse for workspace page (#4122)
* added error handling

* workspace machine cleanup

* renaming callback

* general cleanup

* fixed tests

* PR comments
2022-09-21 14:32:00 -04:00
Joe Previte 3db9ea9dd2 fix: disable inspect xstate in develop (#4145) 2022-09-21 11:08:54 -07:00
Ben Potter 93475453d8 chore: sync autostart helpers+values when toggled (#4143) 2022-09-21 12:59:06 -05:00
Ben Potter ceef283bfd chore: minor changes to SSH dialog (#4142) 2022-09-20 23:02:50 -05:00
Ammar Bandukwala d30945c5c5 feat: bump workspace deadline on user activity (#4119)
Resolves #2995
2022-09-20 21:17:24 +00:00
Presley Pizzo 0899548208 feat: have user type name of thing to delete for extra safety (#4080)
* Add info and text field to delete dialog

* Format

* Use DeleteDialog for Users, nix info except for Workspaces

* Format

* Update storybook

* Add and update tests

* Fix the worst of the UsersPage test bugs

* Fix users page tests

* Fix workspace tests

* Format
2022-09-20 17:13:48 -04:00
Colin Adler eb71053e56 chore: update wireguard-go (#4139) 2022-09-20 16:02:49 -05:00
Colin Adler 5e2efb68f1 feat: add SCIM provisioning via Okta (#4132)
Co-authored-by: Ben Potter <ben@coder.com>
2022-09-20 15:16:26 -05:00
Bruno Quaresma 50321ba2aa docs: Add missing audit logs filtering fields (#4133)
* docs: Add missing audit logs filtering fields

* Update docs/admin/audit-logs.md

Co-authored-by: Ben Potter <ben@coder.com>

Co-authored-by: Ben Potter <ben@coder.com>
2022-09-20 17:44:00 +00:00
Bruno Quaresma bc47d7ce69 feat: Add extra fields to the audit filter (#4123) 2022-09-20 13:07:21 -03:00
Kyle Carberry 3618b098cb fix: Return deprecation error when using WebRTC endpoint (#4130)
Fixes #4126.
2022-09-20 09:56:19 -05:00
Mathias Fredriksson 2ca7214259 fix: Produce unknown subcommand errors for bad command names (#4089)
Fixes #1616
2022-09-20 15:31:38 +03:00
Colin Adler 8d7954b015 fix: ignore context canceled error on server (#4128) 2022-09-19 23:56:51 -05:00
Colin Adler 67230babc0 fix: properly shutdown tracers (#4127) 2022-09-19 23:35:18 -05:00
Colin Adler 3993f66997 chore: bump github.com/open-policy-agent/opa from 0.41.0 to 0.44.0 (#4094) 2022-09-20 04:16:03 +00:00
Kyle Carberry db0ba8588e chore: Refactor Enterprise code to layer on top of AGPL (#4034)
* chore: Refactor Enterprise code to layer on top of AGPL

This is an experiment to invert the import order of the Enterprise
code to layer on top of AGPL.

* Fix Garrett's comments

* Add pointer.Handle to atomically obtain references

This uses a context to ensure the same value persists through
multiple executions to `Load()`.

* Remove entitlements API from AGPL coderd

* Remove AGPL Coder entitlements endpoint test

* Fix warnings output

* Add command-line flag to toggle audit logging

* Fix hasLicense being set

* Remove features interface

* Fix audit logging default

* Add bash as a dependency

* Add comment

* Add tests for resync and pubsub, and add back previous exp backoff retry

* Separate authz code again

* Add pointer loading example from comment

* Fix duplicate test, remove pointer.Handle

* Fix expired license

* Add entitlements struct

* Fix context passing
2022-09-19 23:11:01 -05:00
Kyle Carberry 714c366d16 chore: Remove WebRTC networking (#3881)
* chore: Remove WebRTC networking

* Fix race condition

* Fix WebSocket not closing
2022-09-19 19:46:29 -05:00
Bruno Quaresma 1186e643ec feat: Add audit logs filtering to the UI (#4120) 2022-09-19 21:28:23 -03:00
Garrett Delfosse 7fe7ffea6d chore: make fmt (#4121) 2022-09-19 20:22:46 +00:00
Kyle Carberry 72d6731924 fix: Only update workspace LastUsed when the connection payload has changed (#4115)
This was causing every workspace to update last used to time.Now() when
coderd was restarted!
2022-09-19 14:11:18 -05:00
Colin Adler 153e96f574 fix: use consistent tracer name (#4117) 2022-09-19 13:46:26 -05:00
Ammar Bandukwala 794b88fab4 Fix wireguard dependency (#4116)
The old commit disappeared(?).
2022-09-19 18:23:44 +00:00
Dean Sheather 29d804e692 feat: add API key scopes and application_connect scope (#4067) 2022-09-19 17:39:02 +00:00
Bruno Quaresma adad347902 refactor: Refactor audit logs count to support filtering (#4113) 2022-09-19 17:08:25 +00:00
Kyle Carberry 6f82ad09c8 fix: Improve consistency on CLI help (#4112)
This makes the english consistent on flags, and improves
the contrast for the placeholder color on dark themes.
2022-09-19 11:36:18 -05:00
Ben Potter 353fb8724a add docs: "docker in docker" and "systemd in docker" (#4051) 2022-09-19 16:33:31 +00:00
Bruno Quaresma 3e4b67893e fix: Workspace default filter on search bar (#4111) 2022-09-19 13:27:41 -03:00
James Ottaway 9196b3978d Fix kubectl get pods command in k8s install docs (#4053) 2022-09-19 15:55:32 +00:00
Ben Potter 732bc5910c fix: docs: remove reference to fixed issue (#4104) 2022-09-19 10:23:41 -05:00
Ben Potter 64e4ea73c0 fix: docs: use diff view in Docker docs (#4110) 2022-09-19 09:43:54 -05:00
Bruno Quaresma bf8d823ae3 feat: Add audit log filters in the API (#4078) 2022-09-19 10:37:33 -03:00
Geoffrey Huntley f314f30ebc housekeeping(gitignore): update gitignore/eslintignore/prettierignore (#4108) 2022-09-19 17:16:19 +10:00
Denbeigh Stevens 36a599ea9a docs: fix ephemeral resources link (#4101)
[This link](https://coder.com/docs/coder-oss/latest/templates#persistent-and-ephemeral-resources)
directs to the top of the Templates page, we should use
[this link](https://coder.com/docs/coder-oss/latest/templates#persistent-vs-ephemeral-resources) instead.
2022-09-19 02:23:17 +00:00
Kyle Carberry 68ee82437e fix: Remove hiding Tailscale flags (#4103)
Now that Tailscale is defualt, we shouldn't be hiding these!

Fixes #4083.
2022-09-18 20:24:26 -05:00
Geoffrey Huntley d499416024 housekeeping(branding): be consistent (#4075) 2022-09-19 09:57:18 +10:00
Kyle Carberry b3d07ffd87 fix: Test race for TestPostWorkspaceBuild (#4102) 2022-09-18 16:40:24 -05:00
Garrett Delfosse 63fd4945a2 chore: watch workspace endpoint (#4060) 2022-09-16 18:54:23 +00:00
Colin Adler b340634aaa feat: add rbac tracing (#4093) 2022-09-16 18:32:15 +00:00
Joe Previte 1bca269b90 refactor: add type safety in utils.test.ts (#4091)
This makes a few changes to the typings in
site/src/components/GlobalSnackbar/utils.test.ts to more accurately
represent the types we're using. It allows us to remove from type
assertion and one eslin-disable comment..
2022-09-16 10:11:37 -07:00
Colin Adler 77acf0c340 feat: provisionerd tracing, add public trace ingestion (#4070) 2022-09-16 11:43:22 -05:00
Dean Sheather fc841898cd fix: remove path-based port proxying (#4063) 2022-09-16 16:31:08 +00:00
Dean Sheather 6e9c05f859 chore: use zstd -6 in dev (#4092) 2022-09-16 16:03:16 +00:00
Mathias Fredriksson 21664c5c58 fix: Revert change from zstd level 22 to level 6 compression (#4086) 2022-09-16 18:36:11 +03:00
Mathias Fredriksson 9e12850f38 fix: Remove TestWorkspaceBuildResources/ListRunning (#4088) 2022-09-16 16:39:57 +03:00
Colin Adler 86fdafda23 fix: data races in databasefake (#4084) 2022-09-16 00:06:39 +00:00
Kyle Carberry b2bc74e3af chore: Skip TestPortForward due to flakes (#4081)
We'll have to fix this in a future PR... it's unfortunate but
these are *really* flakey.
2022-09-15 21:05:43 +00:00
Colin Adler 87ab6ae8a0 fix: incorrect templates list test assert (#4079) 2022-09-15 15:03:29 -05:00
Joe Previte b8bd3208ca chore: update cSpell and fix isNotificationTextPrefixed (#4076)
* chore: update cSpell words

* chore: add ignorePaths for cSpell

* fix: update isNotificationTextPrefixed

This removes an eslint-disable rule and adds two new tests to ensure
isNotificationTextPrefixed is working as expected.

* fix(e2e): remove filter in workspacesPage
2022-09-15 16:59:22 -03:00
Bruno Quaresma 9e9a9e0cd2 fix: Setup redirect (#4064) 2022-09-15 13:26:24 +00:00
Bruno Quaresma 40c0fc285c refactor: Remove users redirect to active filter (#4056) 2022-09-15 10:05:33 -03:00
Bruno Quaresma b78ab9e028 Fix form tab (#4066) 2022-09-15 09:59:13 -03:00
Andrei Kondratiev 938bd7341b helm: added service annotations (#4062) 2022-09-15 00:01:40 -05:00
Eric Paulsen 45f39ba488 chore: rename AWS ECS template & fix docker template var (#4068) 2022-09-14 20:59:31 -05:00
Kyle Carberry e847e7386a fix: Resolve flake in TestPortForward (#4069) 2022-09-14 20:21:53 -05:00
Kyle Carberry ec453f01e4 fix: Wait for connections before port-forwarding (#4057)
UDP packets were being dropped if a connection was started
before the Tailscale connection has been established.
2022-09-14 21:57:42 +00:00
Joe Previte 22e49c4316 feat(cli): add error message for bad login URL (#4042) 2022-09-14 20:15:47 +00:00
Bruno Quaresma 62d97b18f4 refactor: Typography, action hover and table head colors (#4046)
* Adjust primary text color

* refactor: Typography and table head colors
2022-09-14 15:09:06 -03:00
Geoffrey Huntley a01ab27751 docs(contributing): enable contribution via devcontainer (#3970) 2022-09-14 10:30:12 -07:00
Bruno Quaresma b20ecfdf37 refactor: Minor improvements and fixes for the page headers (#4045) 2022-09-14 11:04:01 -03:00
Presley Pizzo b6712ffbee chore: add wrapper components for conditional rendering (#4047)
* Add conditional wrappers

* Use wrappers in TemplatesPageView
2022-09-14 09:55:00 -04:00
Kyle Carberry 4f0417c6ad Revert "feat: Add portforward to the UI (#3812)" (#4048)
This reverts commit 0552c36e29.
2022-09-13 17:18:27 -05:00
Kyle Carberry 0f8c2f592e feat: Use Tailscale networking by default (#4003)
* feat: Use Tailscale networking by default

Removal of WebRTC code will happen in another PR, but it
felt dangerious to default and remove in a single commit.

Ideally, we can release this version and collect final
thoughts and  feedback before a full commitment.

* Remove UNIX forwarding

Tailscale doesn't support this, and adding support
for it shouldn't block our rollout. Customers can
always forward over SSH.

* Update cli/portforward_test.go

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

Co-authored-by: Dean Sheather <dean@deansheather.com>
2022-09-13 15:55:56 -05:00
Bruno Quaresma 478d49c19c docs: Custom resource icon (#4041)
* Fix missed unresolved conflict

* docs: Custom resource icons

* Fix title

* Apply suggestions from code review

Co-authored-by: Ben Potter <ben@coder.com>

Co-authored-by: Ben Potter <ben@coder.com>
2022-09-13 20:45:17 +00:00
Bruno Quaresma 0552c36e29 feat: Add portforward to the UI (#3812)
* feat: Add portforward to the UI

* Update site/src/components/PortForwardButton/PortForwardButton.tsx

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>

* Add CODER_ENABLE_WILDCARD_APPS env var

* Fix portforward link

* Remove t file

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>
2022-09-13 17:41:40 -03:00
Steven Masley 9b5ee8f267 feat: Implement (but not enforce) CSRF for FE requests (#3786)
Future work is to enforce CSRF

Co-authored-by: Presley Pizzo <presley@coder.com>
2022-09-13 15:26:46 -04:00
Steven Masley 9ab437d6e2 feat: Add serving applications on subdomains and port-based proxying (#3753)
Co-authored-by: Dean Sheather <dean@deansheather.com>
2022-09-14 03:31:33 +10:00
Presley Pizzo 99a7a8dd22 chore: Turn predictable action arguments on (#3964)
* Turn predictable action arguments on

* Remove layout strings
2022-09-13 12:54:04 -04:00
Ben Potter f16dd5acb4 docs: explain SSH key behavior (#3990) 2022-09-13 11:36:39 -05:00
Eric Paulsen d57c181aad Delete template docs (#4029)
* add: delete template docs

* add: RBAC context

* fix: caps

Co-authored-by: Ben Potter <ben@coder.com>

* add: deletion note

Co-authored-by: Ben Potter <ben@coder.com>
2022-09-13 10:51:50 -05:00
Ben Potter 3ded910cca Add support for coder tunnel in docker-compose (#4027) 2022-09-13 14:53:41 +00:00
Bruno Quaresma 214e59452f feat: Show custom resource icons in the UI (#4020) 2022-09-13 11:32:59 -03:00
Presley Pizzo 83c35bb916 feat: display specific errors if templates page fails (#4023)
* Surface templates page errors

* Format

* Separate error messages

* Fix story

* Format

* Format

* Fix imports

* Remove unnecessary check

* Format
2022-09-13 10:26:58 -04:00
Steven Masley 21e8fb243b fix: Allow develop.sh to host docker workspaces (#3802) 2022-09-13 09:21:05 -04:00
Kyle Carberry 57c7fcf27f fix: Ignore deleted users when signing up with OAuth (#4036)
This prevented a deleted user from signing up again when they
were already linked with a previous account.
2022-09-13 07:33:35 -05:00
Christian Feldkirchner 1ee1db9664 Update docker.md (#4004)
Added a more detailed description on how to create the initial user (via the web ui)
2022-09-13 05:17:01 +00:00
Kyle Carberry a4980446c5 fix: Update Tailscale to resolve race condition (#4032)
This is being fixed upstream here: https://github.com/tailscale/tailscale/pull/5611
2022-09-13 03:32:51 +00:00
Geoffrey Huntley 708bdbc134 docs(contributing): add macos homebrew commands (#3968) 2022-09-13 13:13:30 +10:00
Kyle Carberry 850a83097c feat: Allow deleting users (#4028)
* Add deleted column to the users table

* Fix user indexes

* Add frontend

* Add test
2022-09-12 23:24:20 +00:00
Kyle Carberry a2098254cd feat: Support --header for CLI commands to support proxies (#4008)
Fixes #3527.
2022-09-12 16:22:05 -05:00
Bruno Quaresma 846dd999b7 refactor: Remove cli example from the Audit page (#4031) 2022-09-12 17:17:59 -03:00
Ammar Bandukwala 7e54413d3b docs: add networking (#4030) 2022-09-12 19:07:03 +00:00
dependabot[bot] e9efb7e253 chore: bump github.com/go-chi/httprate from 0.6.0 to 0.7.0 (#4018)
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.6.0...v0.7.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 18:51:44 +00:00
dependabot[bot] 34a2d40f27 chore: bump github.com/prometheus/client_golang from 1.12.2 to 1.13.0 (#4025)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.12.2 to 1.13.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.12.2...v1.13.0)

---
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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 18:08:48 +00:00
Bruno Quaresma 184e7dbce0 docs: Add docs about coder_metadata hide attribute (#3985) 2022-09-12 14:57:53 -03:00
dependabot[bot] 0e59cb21ce chore: bump go.uber.org/atomic from 1.9.0 to 1.10.0 (#3793)
Bumps [go.uber.org/atomic](https://github.com/uber-go/atomic) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/uber-go/atomic/releases)
- [Changelog](https://github.com/uber-go/atomic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/atomic/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: go.uber.org/atomic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 12:47:59 -05:00
Kyle Carberry 5c0d63d31f fix: Only hold tailnet.*Conn.Close() for a short duration (#4015)
* fix: Only hold `tailnet.*Conn.Close()` for a short duration

The long duration could be cause to a test deadlock.

* Add closed chan to listener struct
2022-09-12 17:46:45 +00:00
dependabot[bot] d4f0a6fecf chore: bump github.com/hashicorp/hcl/v2 from 2.13.0 to 2.14.0 (#4026)
Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.13.0 to 2.14.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.13.0...v2.14.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 17:44:14 +00:00
dependabot[bot] 4db98b2b9f chore: bump cloud.google.com/go/compute from 1.7.0 to 1.9.0 (#4012)
Bumps [cloud.google.com/go/compute](https://github.com/googleapis/google-cloud-go) from 1.7.0 to 1.9.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/video/v1.7.0...pubsub/v1.9.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/compute
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 12:34:28 -05:00
dependabot[bot] cab6fe9482 chore: bump github.com/moby/moby (#4021)
Bumps [github.com/moby/moby](https://github.com/moby/moby) from 20.10.17+incompatible to 20.10.18+incompatible.
- [Release notes](https://github.com/moby/moby/releases)
- [Changelog](https://github.com/moby/moby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moby/moby/compare/v20.10.17...v20.10.18)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 12:33:51 -05:00
dependabot[bot] edec39baef chore: bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#4016)
Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.7.0 to 1.9.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.7.0...v1.9.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 12:10:55 -05:00
dependabot[bot] a7a56f9a26 chore: bump github.com/unrolled/secure from 1.12.0 to 1.13.0 (#4017)
Bumps [github.com/unrolled/secure](https://github.com/unrolled/secure) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/unrolled/secure/releases)
- [Commits](https://github.com/unrolled/secure/compare/v1.12.0...v1.13.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 12:10:38 -05:00
Kyle Carberry 0551a6cba2 chore: Automatically approve dependabot PRs (#4014)
Dependabot is annoying but now it makes merging it's PRs a
little bit easier!
2022-09-12 16:56:38 +00:00
dependabot[bot] 42d1b5e4ba chore: bump go.uber.org/goleak from 1.1.12 to 1.2.0 (#4010)
Bumps [go.uber.org/goleak](https://github.com/uber-go/goleak) from 1.1.12 to 1.2.0.
- [Release notes](https://github.com/uber-go/goleak/releases)
- [Changelog](https://github.com/uber-go/goleak/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/goleak/compare/v1.1.12...v1.2.0)

---
updated-dependencies:
- dependency-name: go.uber.org/goleak
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 11:55:46 -05:00
dependabot[bot] 4f78368403 chore: bump github.com/coreos/go-oidc/v3 from 3.2.0 to 3.4.0 (#4013)
Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.2.0 to 3.4.0.
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.2.0...v3.4.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 11:55:34 -05:00
dependabot[bot] 31f25002a6 chore: bump github.com/charmbracelet/lipgloss from 0.5.0 to 0.6.0 (#4011)
Bumps [github.com/charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/charmbracelet/lipgloss/releases)
- [Commits](https://github.com/charmbracelet/lipgloss/compare/v0.5.0...v0.6.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 11:55:04 -05:00
Kyle Carberry 2b8223bdd5 fix: Use command property when launching an application (#3998)
Fixes #3777.
2022-09-12 16:46:13 +00:00
dependabot[bot] 07e2565a4f chore: bump go.opentelemetry.io/otel/trace from 1.8.0 to 1.9.0 (#3794)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 11:40:58 -05:00
dependabot[bot] 761f1e7c1a chore: bump google.golang.org/api from 0.94.0 to 0.95.0 (#3921)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.94.0 to 0.95.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.94.0...v0.95.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 11:38:07 -05:00
Mathias Fredriksson 09da3858ce fix: Terminal emulation used by SSH sessions (#3473)
Fixes #3371
2022-09-12 19:27:51 +03:00
Kyle Carberry b4c29f34c3 fix: Always use UTC time when inserting stats (#4009)
Fixes a flake reported by @mafredri
2022-09-12 16:01:42 +00:00
Mathias Fredriksson d0b02e581d feat: Improve experience with local SSH keys (#3835)
* feat: Improve experience with local SSH keys

This change means that users can place SSH keys in the default locations
for OpenSSH, like `~/.ssh/id_rsa` and it will be automatically picked
up (as per a default OpenSSH experience).

Fixes #3126

* fix: Ensure gitssh cleans up temporary file on interrupt

Co-authored-by: Dean Sheather <dean@deansheather.com>
2022-09-12 17:26:04 +03:00
Kyle Carberry 66ad86a755 fix: Update workspace wasn't using the latest build (#4001)
This was an oversight in a prior contribution. It broke the update
button, but fixed the other cases.
2022-09-12 08:22:29 -03:00
Bruno Quaresma 43f368dfc4 docs: Add audit logs docs (#3975)
* docs: Add audit logs docs

* Apply suggestions from code review

Co-authored-by: Joe Previte <jjprevite@gmail.com>

* Add contact link

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-09-12 01:04:56 +00:00
Ben Potter e5e1ed2f9c chore: minor clarifications to install docs (#3983) 2022-09-12 10:50:20 +10:00
Joe Previte 067069d2e2 docs: add jsjoeio/coder-templates to community (#3986) 2022-09-12 10:49:05 +10:00
Kyle Carberry 5b5bc1da56 feat: Add local configuration option for DERP mapping (#3996)
This allows entirely airgapped geodistributed deployments of Coder!
2022-09-11 16:45:49 -05:00
Kyle Carberry 6e20f9c729 fix: Recursively ignore hidden folders (#3997)
Fixes #3938.
2022-09-11 15:13:20 -05:00
Kyle Carberry 9e148a5cac fix: Update embedded DERP server default name (#3995)
* fix: Update embedded DERP server default name

This is still configurable, but exposing the name DERP
seemed awkward.

* Update relay name
2022-09-11 13:06:07 -05:00
Kyle Carberry f5bbbdf638 chore: Fix VSCode configuration to hide visual test overlay (#3994)
This made it impossible to code 😅
2022-09-11 10:50:50 -05:00
Denbeigh Stevens 522fde47dc docs: fix incorrect terraform providers docs link (#3991) 2022-09-10 16:20:28 -05:00
Colin Adler 29bac36816 feat: add workspace auditing (#3966) 2022-09-10 11:07:45 -05:00
J Bruni 442df9e132 Fix phrase at templates.md (#3987) 2022-09-10 16:07:51 +10:00
Kyle Carberry 849e389388 Update manifest.json 2022-09-09 15:53:00 -05:00
Presley Pizzo 20d950d1b3 feat: Update template page automatically (#3962)
* Update template page automatically

* Remove misleading test
2022-09-09 16:27:21 -04:00
Spike Curtis ba6a868a80 Licensed features docs (#3934)
* Licensed features docs

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

* Licensed features -> Enterprise features

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-09-09 20:10:39 +00:00
Bruno Quaresma ce211fd8f5 fix: Do not update workspace on start (#3984) 2022-09-09 12:56:41 -07:00
Bruno Quaresma 8a94b72c7d feat: Allow hide resources (#3977) 2022-09-09 16:38:00 -03:00
Ammar Bandukwala f6aa025a01 feat: use active users instead of total users in Template views (#3900) 2022-09-09 19:30:31 +00:00
Bruno Quaresma 346583f13e fix: Audit log human parse message and nullable diffs (#3978)
* fix: Audit log human parse message and nullable diffs

* Fix diff values
2022-09-09 13:53:38 -03:00
Colin Adler abb804f2de feat: add template/template version auditing (#3965) 2022-09-09 11:34:23 -05:00
Ben Potter d380c9494d fix: broken docker-compose link (#3976) 2022-09-09 11:04:02 -05:00
Colin Adler 4e26e325a6 feat: add auditing to user routes (#3961) 2022-09-08 21:16:16 -05:00
sharkymark c026464375 chore: add uninstall steps to remove a Coder OSS deployment from docs (#3742)
Co-authored-by: Ben <ben@coder.com>
2022-09-09 00:31:29 +00:00
Ben Potter 3610f09c77 chore: separate install docs (#3859) 2022-09-08 14:41:00 -05:00
Geoffrey Huntley d38e645492 housekeeping(welcome): notify employees when it is someones first PR (#3884) 2022-09-08 14:35:51 -05:00
Eric Paulsen 9c5b879b16 add: ECS example template (#3915)
* add: ECS example template

* fix: empty main.tf

* cleanup

* rm: cluster & compute

* set CPU & memory vars

Co-authored-by: Ben Potter <ben@coder.com>

Co-authored-by: Ben Potter <ben@coder.com>
2022-09-08 15:27:27 +00:00
Kyle Carberry 2c41343ce5 fix: Show audit log in production if allowed (#3960) 2022-09-08 14:58:53 +00:00
Colin Adler 7dc73ed6c6 feat: add description to audit log responses (#3949) 2022-09-08 09:36:34 -05:00
Kyle Carberry 5e04a2f800 chore: Remove DataDog test reporting (#3958)
It was costing a lot of money, and it wasn't being used very much.
2022-09-08 14:29:30 +00:00
Kyle Carberry e1afec6db4 fix: Optionally consume email_verified if it's provided (#3957)
This reduces our OIDC requirement claims to only `email`. If `email_verified`
is provided and is `false`, we will block authentication.

Fixes #3954.
2022-09-08 14:06:00 +00:00
Dean Sheather bb4a681833 fix: don't check buildinfo or entitlements in agent (#3956) 2022-09-08 23:59:28 +10:00
Dean Sheather 6a3876d6df chore: hide template check 404 error from develop.sh (#3942) 2022-09-08 15:22:08 +10:00
Kyle Carberry 8596023e31 chore: Update PR template to mention checking for docs (#3913)
This arose from a conversation Presley and I had about developers
maintaining docs, and that this little reminder could be useful!
2022-09-07 22:20:02 -05:00
Kyle Carberry 7718fa53c9 fix: Use a channel for bufferring tailnet connection updates (#3940) 2022-09-07 22:18:35 -05:00
Kyle Carberry 519d724ca4 fix: Sort resources by name (#3941)
Fixes #3489.
2022-09-08 03:16:26 +00:00
Ben Potter 332056af29 dogfood: remove folder from code-server (#3944) 2022-09-07 17:37:30 -05:00
Kyle Carberry 2b0fcf3ece fix: Show the users workspaces by default on coder ls (#3947)
Fixes #3945.
2022-09-07 17:30:49 -05:00
Kyle Carberry c8d9c44aba fix: Sort workspaces by last used then name (#3943) 2022-09-07 21:16:53 +00:00
Kyle Carberry f510f01768 fix: Require an argument for speedtest (#3946) 2022-09-07 21:10:17 +00:00
Presley Pizzo 2a085d1936 chore: refactor dialogs (#3935)
* Move dialogs

* Repurpose WorkspaceDeleteDialog

* Rename to DeleteDialog

Pausing on the typing part for now, leaving this as a refactor

* Rename handlers
2022-09-07 17:04:42 -04:00
dependabot[bot] 47ee44e5ca chore: bump msw from 0.45.0 to 0.47.0 in /site (#3917)
Bumps [msw](https://github.com/mswjs/msw) from 0.45.0 to 0.47.0.
- [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/v0.45.0...v0.47.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 20:58:30 +00:00
dependabot[bot] 9a07d5de6e chore: bump eslint-plugin-jest from 26.7.0 to 27.0.1 in /site (#3828)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 26.7.0 to 27.0.1.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v26.7.0...v27.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 13:51:12 -07:00
Kyle Carberry 11abb85df5 fix: Rename IncludeProvisionerD to IncludeProvisionerDaemon in test
This was an artifact from merging!
2022-09-07 20:29:26 +00:00
Bruno Quaresma a00fdd699f feat: Add Audit page in the UI (#3782) 2022-09-07 17:26:12 -03:00
Joe Previte 1359850715 feat(cli): validate name length on template create (#3823)
* feat(cli): add template create validation test

This adds a test to validate that `template create` prints an error
message if called with a template name exceeding the 32-char limit.

* fixup

* fixup test

* feat(cli): add name validation to templatecreate

This adds a validation step to ensure the template name is less than 32
characters.

* fixup!: use utf8.RuneCountInString

* fixup!: remove pty from test
2022-09-07 15:01:18 -05:00
Kyle Carberry 720c9dadcf fix: Remove name from workspace builds (#3937)
Fixes #1561.
2022-09-07 19:49:57 +00:00
Colin Adler 762063ed8f fix: add avatar_url to user object in audit log response (#3939) 2022-09-07 19:22:04 +00:00
dependabot[bot] 87379f413f chore: bump @typescript-eslint/parser from 5.31.0 to 5.36.2 in /site (#3912)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.31.0 to 5.36.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.36.2/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 15:59:18 -03:00
dependabot[bot] c880263926 chore: bump eslint from 8.21.0 to 8.23.0 in /site (#3920)
Bumps [eslint](https://github.com/eslint/eslint) from 8.21.0 to 8.23.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.21.0...v8.23.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 15:58:58 -03:00
Dean Sheather a79e34c0c7 chore: build releases on a single Linux runner (switch to rcodesign) (#3890)
* chore: build, sign and notarize darwin binaries on linux

* chore: download rcodesign during release

* chore: change nfpm install to be a download instead of compile

* chore: delete apple cert secrets after build

* fix: fix dependencies in archive.sh and build_go.sh

* chore: reduce output from rcodesign
2022-09-07 18:56:46 +00:00
Spike Curtis ac279b3483 Add periods to end of license warning text. (#3933)
* Add periods to end of license warning text.

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

* Fix tests

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-09-07 17:27:42 +00:00
Spike Curtis d46b04cb1e Add Enterprise License text (#3932)
Signed-off-by: Spike Curtis <spike@coder.com>

Signed-off-by: Spike Curtis <spike@coder.com>
2022-09-07 16:55:57 +00:00
Dean Sheather 819622182b chore: parallel makefile attempt 3 (#3926)
* Revert "chore: Revert parallel Makefile builds (#3918)"

This reverts commit b077f71015.

* fix: fix release workflow with parallel makefile

* fix: mark generated files as fresh during releases
2022-09-08 02:40:17 +10:00
Colin Adler 3d6d51fbd0 feat: audit log api (#3898) 2022-09-07 16:38:19 +00:00
Jon Ayers ad24404018 fix: fix creating users with wrong login type (#3929) 2022-09-07 10:37:15 -05:00
Presley Pizzo 69f430257c chore: remove unused sql-formatter (#3903) 2022-09-07 11:06:43 -04:00
dependabot[bot] cd85be52de chore: bump eslint-import-resolver-typescript in /site (#3925)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.3.0 to 3.5.0.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.3.0...v3.5.0)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 11:06:01 -04:00
Kyle Carberry 3db927bc09 fix: Add contents permission for release CI (#3927) 2022-09-07 14:53:31 +00:00
Kyle Carberry 00104096c2 fix: Resolve CI flakes for tailnet agent (#3924) 2022-09-07 09:24:58 -05:00
dependabot[bot] 73ec618aff chore: bump @testing-library/react from 13.3.0 to 13.4.0 in /site (#3905)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 13.3.0 to 13.4.0.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v13.3.0...v13.4.0)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 10:12:42 -04:00
Kyle Carberry f9ef4b148b fix: Add package write permission to releases (#3923) 2022-09-07 13:58:22 +00:00
Kyle Carberry 80352656e9 fix: Improve speedtest by adding direct connection toggle (#3919)
It's weird to test connection speeds over DERP, because most
connections will eventually migrate to direct.
2022-09-07 03:21:08 +00:00
dependabot[bot] 0f0e3d1068 chore: bump uuid from 8.3.2 to 9.0.0 in /site (#3914)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.2 to 9.0.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.3.2...v9.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 21:57:32 -05:00
Kyle Carberry b077f71015 chore: Revert parallel Makefile builds (#3918)
This was breaking the release process. Namely it was running
the `gen` targets due to the dependency tree, which was failing
on macOS and Linux runners. This revert can be reverted once
we fix that up.
2022-09-07 01:56:51 +00:00
Kyle Carberry d2e6f305b1 Lower protoc version requirement for easy CI install 2022-09-06 20:35:52 -05:00
Kyle Carberry 502a7370c8 Pin to a static version of protoc 2022-09-06 20:32:24 -05:00
Kyle Carberry d970d2d3da Install protoc in release build 2022-09-06 20:13:08 -05:00
Kyle Carberry bb17fe5398 Use go run when executing goimports in gen 2022-09-06 19:59:14 -05:00
Kyle Carberry 65d63f9167 Use go run for executing goimports 2022-09-06 19:53:39 -05:00
Bruno Quaresma b1bdf10e38 feat: Add table support and syntax highlights for markdowns (#3910) 2022-09-06 22:20:23 +00:00
Kyle Carberry dca24bd15d fix: Don't clear out peers that haven't connected yet (#3916)
This was causing parallel connections to fail, because they wouldn't
be established yet.
2022-09-06 21:27:59 +00:00
Joe Previte 18af9426c0 chore: add no implicit coercion eslint rule (#3909)
* chore: add no-implicit-coercion ESLint rule

This adds a new ESLint rule to prevent us from using implicit coercion
in the codebase. See https://eslint.org/docs/latest/rules/no-implicit-coercion

* chore: fix implicit coercion errors

* fixup: formatting
2022-09-06 21:27:10 +00:00
dependabot[bot] bb0e79eb88 chore: bump prettier-plugin-organize-imports in /site (#3891)
Bumps [prettier-plugin-organize-imports](https://github.com/simonhaenisch/prettier-plugin-organize-imports) from 3.0.0 to 3.1.1.
- [Release notes](https://github.com/simonhaenisch/prettier-plugin-organize-imports/releases)
- [Commits](https://github.com/simonhaenisch/prettier-plugin-organize-imports/compare/v3.0.0...v3.1.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 17:21:16 -04:00
dependabot[bot] 5301d36027 chore: bump canvas from 2.9.3 to 2.10.0 in /site (#3904)
Bumps [canvas](https://github.com/Automattic/node-canvas) from 2.9.3 to 2.10.0.
- [Release notes](https://github.com/Automattic/node-canvas/releases)
- [Changelog](https://github.com/Automattic/node-canvas/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/node-canvas/compare/v2.9.3...v2.10.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 13:47:12 -07:00
Andrei Kondratiev f5ba90b963 Home folder can be empty, so copying default bash settings (#3897) 2022-09-06 15:11:53 -05:00
dependabot[bot] 30ce62b5b4 chore: bump @playwright/test from 1.24.1 to 1.25.1 in /site (#3843)
Bumps [@playwright/test](https://github.com/Microsoft/playwright) from 1.24.1 to 1.25.1.
- [Release notes](https://github.com/Microsoft/playwright/releases)
- [Commits](https://github.com/Microsoft/playwright/compare/v1.24.1...v1.25.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 15:07:48 -04:00
Spike Curtis a7cdec5d39 Feature server implementation (#3899)
* Feature server implementation

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

* Fix imports

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-09-06 18:59:10 +00:00
Dean Sheather 1b6f9e54a3 fix: fix ERRPIPE in scripts/lib.sh (#3908) 2022-09-07 04:42:45 +10:00
Kyle Carberry 3264960fb3 Change the primary UI font, darken the background, and show template icons for workspaces (#3863)
* Use darker colors in the dashboard

I think this looks a bit nicer. It's pretty subjective, but right now
we sit in-between a light and a dark mode, but more on the dark side.

This essentially transforms us into a dark mode.

* Add icons to workspaces rows and apge

* Add narrowed navbar to tighten up design

* Swap gray[3] for gray[4]
2022-09-06 18:26:36 +00:00
Bruno Quaresma 3c94ca9cbe fix: Skip empty values so Terraform can use the default value (#3902) 2022-09-06 15:15:19 -03:00
dependabot[bot] 94eb503aac chore: bump chromatic from 6.7.1 to 6.9.0 in /site (#3837)
Bumps [chromatic](https://github.com/chromaui/chromatic-cli) from 6.7.1 to 6.9.0.
- [Release notes](https://github.com/chromaui/chromatic-cli/releases)
- [Changelog](https://github.com/chromaui/chromatic-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chromaui/chromatic-cli/compare/v6.7.1...v6.9.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-06 14:05:13 -04:00
Dean Sheather 419d701927 chore: parallel builds with Makefile (#3854)
* Revert "revert: Makefile buff-ification (#3700) (#3848)"

This reverts commit e490bdd531.

* fix: fix slim targets in makefile

* fix: don't clobber slim binaries, make sure they're in the correct location
2022-09-06 17:27:06 +00:00
Ammar Bandukwala 4f0105ef7e feat: add orphan support (#3849)
* feat: add resource orphanage

* feat: deny custom state in build for regular users

* Minor protoc improvements
2022-09-06 17:07:00 +00:00
Bruno Quaresma 209e011404 fix: Escape # character on appName (#3895) 2022-09-06 15:16:03 +00:00
Presley Pizzo 1f55135765 Make color usage more consistent (#3842)
* Tweak overrides - should not cause visual change

* Use closest color for avatar

* Change hover color of contained buttons

* Change nav item color (matches avatar now)

* Format

* Use lighter border for contained button hover

This looks more clickable than lightening the background

* Delete unused component

* Make dropdown arrow consistent

Same up as down. Contrast text everywhere except nav, where it matches links and avatar.

* No need to fade right arrows

* Add hover color

* Consistent box shadows

* Format

* Delete unused DialogSearch

* Deleting unused button types to avoid confusion

* Use disabled arrow on disabled action buttons
2022-09-06 10:58:12 -04:00
dependabot[bot] 8e1dfc2763 chore: bump typescript from 4.7.4 to 4.8.2 in /site (#3836)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.7.4 to 4.8.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.7.4...v4.8.2)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bruno Quaresma <bruno@coder.com>
2022-09-06 14:15:29 +00:00
Geoffrey Huntley 1b56a8cccb docs(readme): use /chat link in the README.md (#3868) 2022-09-06 08:58:27 +00:00
dependabot[bot] e3bbc77c35 chore: bump google.golang.org/api from 0.90.0 to 0.94.0 (#3882)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.90.0 to 0.94.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.90.0...v0.94.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-05 18:02:17 -05:00
Kyle Carberry 1254e7a902 feat: Add speedtest command for tailnet (#3874) 2022-09-05 17:15:49 -05:00
Ammar Bandukwala 38825b9ab4 dogfood: keep image locally (#3878)
Avoid delete conflicts
2022-09-05 19:23:52 +00:00
Geoffrey Huntley d6812e0be8 housekeeping(codeowners): migrate to teams (#3867) 2022-09-05 13:38:29 -05:00
Kyle Carberry 2fa77a9bbd fix: Run status callbacks async to solve tailnet race (#3866) 2022-09-05 10:43:24 -05:00
Mathias Fredriksson 3ca6f1fcd4 fix: Prevent nil pointer deref in reconnectingPTY (#3871)
Related #3870
2022-09-05 16:45:10 +03:00
Ammar Bandukwala 1a5d3eace4 dogfood: dynamically pull image (#3864)
Previously, the template would never pull new image updates.
2022-09-04 21:06:36 +00:00
Kyle Carberry 00f05e798b Fix avatar_url dump.sql 2022-09-04 16:56:09 +00:00
Kyle Carberry d8f9537880 Fix avatar_url database type 2022-09-04 16:55:25 +00:00
Kyle Carberry 05e2806ff3 feat: Add profile pictures to OAuth users (#3855)
This supports GitHub and OIDC login for profile pictures!
2022-09-04 11:44:27 -05:00
Kyle Carberry 67c4605370 chore: Reduce test times (#3856)
* chore: Reduce test times

* Rename IncludeProvisionerD to IncludeProvisionerDaemon

* Make  TestTemplateDAUs use Tailnet
2022-09-04 11:28:09 -05:00
J Bruni 271d075667 Update Coder contact at ADOPTERS.md (#3861) 2022-09-04 09:15:25 -05:00
Ammar Bandukwala 0a7fad674a dogfood: remove github apt source (#3860) 2022-09-03 20:44:40 -05:00
Ammar Bandukwala 1b3e75c3ab add watchexec to dogfood image (#3858)
* add watchexec to dogfood image

This comes in handy quite frequently.

* Fix dogfood image
2022-09-03 18:38:13 -05:00
Geoffrey Huntley aae57476f1 docs(adopters): add ADOPTERS.md (#3825) 2022-09-03 06:18:04 +00:00
Geoffrey Huntley 0372586382 housekeeping(discord): use /chat instead of the discord.gg link (#3826) 2022-09-03 06:16:57 +00:00
Kyle Carberry a24f26c137 fix: Allow disabling built-in DERP server (#3852) 2022-09-02 23:47:25 +00:00
Kyle Carberry 4f4d470c7c feat: Add wireguard to port-forward (#3851)
This allows replacement of the WebRTC networking!
2022-09-02 18:26:01 -05:00
Ammar Bandukwala a09ffd6c0d feat: show better error on invalid template upload (#3847)
* feat: show better error on invalid template upload

* Fix tests
2022-09-02 22:48:40 +00:00
Kyle Carberry ac50070713 fix: Add omitempty for proper latency type (#3850)
This was causing an error on the frontend, because this value can be nil!
2022-09-02 22:05:27 +00:00
Kyle Carberry 2e1db6cc63 feat: Add latency indicator to the UI (#3846)
With Tailscale, we now get latency of all regions.
2022-09-02 20:09:05 +00:00
Kyle Carberry e490bdd531 revert: Makefile buff-ification (#3700) (#3848)
This caused the following issues:
- Slim binaries weren't being updated.
- The coder.tar.ztd was misplaced.
- There is no coder.sha1 file with proper filenames.

This should be reintroduced in a future change with those fixes.
2022-09-02 14:46:58 -05:00
Bruno Quaresma d350d9033c refactor: Remove extra line from table bottom (#3831) 2022-09-02 19:32:28 +00:00
Colin Adler ff0aa8d742 feat: add unique ids to all HTTP requests (#3845) 2022-09-02 13:04:29 -05:00
Kyle Carberry de219d966d fix: Run Tailnet SSH connections in a goroutine (#3838)
This was causing SSH connections in parallel to fail 🤦!
2022-09-02 11:58:15 -05:00
dependabot[bot] 3be7bb58b4 chore: bump @storybook/addon-essentials from 6.4.22 to 6.5.10 in /site (#3827)
Bumps [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/addons/essentials) from 6.4.22 to 6.5.10.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v6.5.10/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v6.5.10/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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-02 16:58:04 +00:00
Bruno Quaresma 6fe63ed358 refactor: Keep focused style when input is hovered (#3832) 2022-09-02 09:53:46 -07:00
Bruno Quaresma 5618640227 refactor: Remove duplicated title (#3829) 2022-09-02 16:49:41 +00:00
Colin Adler 55c13c8ff9 chore: fully implement enterprise audit pkg (#3821) 2022-09-02 16:42:28 +00:00
Dean Sheather fefdff4946 fix: install goimports in deploy build (#3841) 2022-09-03 02:38:33 +10:00
Dean Sheather e6699d25ca fix: fix CI calling script/version.sh instead of scripts (#3839) 2022-09-03 02:16:19 +10:00
Bruno Quaresma 8c70b6c360 refactor: Update table cell colors to match the ones in the Workspace (#3830)
page
2022-09-02 13:04:08 -03:00
Bruno Quaresma 21ae411237 refactor: Fix README spacing (#3833) 2022-09-02 13:03:59 -03:00
Bruno Quaresma b9e5cc97a1 refactor: Make user columns consistent (#3834) 2022-09-02 13:03:36 -03:00
dependabot[bot] f1976a086f chore: bump webpack-bundle-analyzer from 4.5.0 to 4.6.1 in /site (#3818)
Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 4.5.0 to 4.6.1.
- [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases)
- [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v4.5.0...v4.6.1)

---
updated-dependencies:
- dependency-name: webpack-bundle-analyzer
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-02 10:25:26 -05:00
dependabot[bot] e20ff62c9f chore: bump xstate from 4.32.1 to 4.33.5 in /site (#3817)
Bumps [xstate](https://github.com/statelyai/xstate) from 4.32.1 to 4.33.5.
- [Release notes](https://github.com/statelyai/xstate/releases)
- [Commits](https://github.com/statelyai/xstate/compare/xstate@4.32.1...xstate@4.33.5)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-02 13:11:20 +00:00
dependabot[bot] afd6834ff7 chore: bump @typescript-eslint/eslint-plugin in /site (#3804)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.31.0 to 5.36.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.36.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-02 10:08:26 -03:00
Dean Sheather e1a4f3a16b Makefile buff-ification (#3700)
Remove old go_build_matrix and go_build_slim scripts in favor of full makefile-ification.
2022-09-02 12:58:23 +00:00
Dean Sheather 46bf265e9b fix: prevent running helm chart without valid tag (#3770)
Co-authored-by: Eric Paulsen <ericpaulsen@coder.com>
2022-09-02 21:01:30 +10:00
Mathias Fredriksson 4c18034260 fix: Prevent autobuild executor from slowing down API requests (#3726)
With just a few workspaces, the autobuild executor can slow down API
requests every time it runs. This is because we started a long running
transaction and checked all eligible (for autostart) workspaces inside
that transaction. PostgreSQL doesn't know if we're modifying rows and as
such is locking the tables for read operations.

This commit changes the behavior so each workspace is checked in its own
transaction reducing the time the table/rows needs to stay locked.

For now concurrency has been arbitrarily limited to 10 workspaces at a
time, this could be made configurable or adjusted as the need arises.
2022-09-02 13:24:47 +03:00
Ammar Bandukwala 3f73243b37 feat: improve formatting of last used (#3824) 2022-09-01 23:03:02 -05:00
Ammar Bandukwala 2d347657dc site: correct documentation on gitsshkey (#3690)
* site: correct documentation on gitsshkey

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>
2022-09-02 02:29:57 +00:00
Joe Previte 3c91b92930 docs: add comment to ResourceAvatar (#3822) 2022-09-01 18:16:20 -07:00
Ammar Bandukwala 04b03792cb feat: add last used to Workspaces page (#3816) 2022-09-02 00:08:51 +00:00
Garrett Delfosse 80e9f24ac7 feat: add loaders to ssh and terminal buttons (#3820) 2022-09-01 19:58:43 -04:00
Kyle Carberry be273a20a7 fix: Update Tailscale to add HTTP(s) latency reporting (#3819)
This was broken in Tailscale, and I'll be sending an upstream PR
to resolve it. See: https://github.com/coder/tailscale/commit/2c5af585574d4e1432f0d5dc9d02c63db3f497b0
2022-09-01 22:02:05 +00:00
dependabot[bot] 081259314b chore: bump cron-parser from 4.5.0 to 4.6.0 in /site (#3809)
Bumps [cron-parser](https://github.com/harrisiirak/cron-parser) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/harrisiirak/cron-parser/releases)
- [Commits](https://github.com/harrisiirak/cron-parser/compare/4.5.0...4.6.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 21:25:53 +00:00
dependabot[bot] ff026d4890 chore: bump eslint-plugin-react from 7.30.1 to 7.31.1 in /site (#3806)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.30.1 to 7.31.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.30.1...v7.31.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 14:20:08 -07:00
Kyle Carberry cde036c1ab fix: Update to Go 1.19 for releases (#3814) 2022-09-01 20:10:53 +00:00
Ammar Bandukwala 30f8fd9b95 Daily Active User Metrics (#3735)
* agent: add StatsReporter

* Stabilize protoc
2022-09-01 14:58:23 -05:00
Kyle Carberry e0cb52ceea fix: Use an unnamed region instead of erroring for DERP (#3810) 2022-09-01 18:43:52 +00:00
Presley Pizzo 5f0b13795a feat: make scrollbars match color scheme (#3807) 2022-09-01 14:28:18 -04:00
dependabot[bot] 1efcd33d63 chore: bump jest-runner-eslint from 1.0.0 to 1.1.0 in /site (#3799)
Bumps [jest-runner-eslint](https://github.com/jest-community/jest-runner-eslint) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/jest-community/jest-runner-eslint/releases)
- [Changelog](https://github.com/jest-community/jest-runner-eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/jest-runner-eslint/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: jest-runner-eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 14:26:45 -04:00
Presley Pizzo 6d95145d3b Feat: delete template button (#3781)
* Add api call

* Extract DropDownButton

* Start adding DropdownButton to Template page

* Move stories to dropdown button

* Format

* Update xservice to delete

* Deletion flow

* Format

* Move ErrorSummary for consistency

* RBAC (unfinished) and style tweak

* Format

* Test rbac

* Format

* Move ErrorSummary under PageHeader in workspace and template

* Format

* Replace hook with onBlur

* Make style arg optional

* Format
2022-09-01 14:24:14 -04:00
Kyle Carberry 6826b976d7 fix: Add latency-check for DERP over HTTP(s) (#3788)
* fix: Add latency-check for DERP over HTTP(s)

This fixes scenarios where latency wasn't being reported if
a connection had UDP entirely blocked.

* Add inactivity ping

* Improve coordinator error reporting consistency
2022-09-01 16:41:47 +00:00
dependabot[bot] f4c8bfdc18 chore: bump webpack-dev-server from 4.9.3 to 4.10.1 in /site (#3801)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.9.3 to 4.10.1.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.9.3...v4.10.1)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 16:26:50 +00:00
dependabot[bot] 5b9573d7c1 chore: bump just-debounce-it from 3.0.1 to 3.1.1 in /site (#3800)
Bumps [just-debounce-it](https://github.com/angus-c/just) from 3.0.1 to 3.1.1.
- [Release notes](https://github.com/angus-c/just/releases)
- [Commits](https://github.com/angus-c/just/compare/just-debounce-it@3.0.1...just-pick@3.1.1)

---
updated-dependencies:
- dependency-name: just-debounce-it
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 09:15:42 -07:00
dependabot[bot] b57b8b887d chore: bump jest-websocket-mock from 2.3.0 to 2.4.0 in /site (#3797)
Bumps [jest-websocket-mock](https://github.com/romgain/jest-websocket-mock) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/romgain/jest-websocket-mock/releases)
- [Commits](https://github.com/romgain/jest-websocket-mock/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: jest-websocket-mock
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 09:14:57 -07:00
Mathias Fredriksson f4a78c976f docs: Update direnv docs for Nix and remove .envrc (#3790) 2022-09-01 20:24:08 +10:00
Kyle Carberry 567e750659 fix: Prepend STUN nodes for DERP (#3787)
This makes Tailscale prefer STUN over DERP when possible.
2022-09-01 02:21:21 +00:00
Kyle Carberry 9bd83e5ec7 feat: Add Tailscale networking (#3505)
* fix: Add coder user to docker group on installation

This makes for a simpler setup, and reduces the likelihood
a user runs into a strange issue.

* Add wgnet

* Add ping

* Add listening

* Finish refactor to make this work

* Add interface for swapping

* Fix conncache with interface

* chore: update gvisor

* fix tailscale types

* linting

* more linting

* Add coordinator

* Add coordinator tests

* Fix coordination

* It compiles!

* Move all connection negotiation in-memory

* Migrate coordinator to use net.conn

* Add closed func

* Fix close listener func

* Make reconnecting PTY work

* Fix reconnecting PTY

* Update CI to Go 1.19

* Add CLI flags for DERP mapping

* Fix Tailnet test

* Rename ConnCoordinator to TailnetCoordinator

* Remove print statement from workspace agent test

* Refactor wsconncache to use tailnet

* Remove STUN from unit tests

* Add migrate back to dump

* chore: Upgrade to Go 1.19

This is required as part of #3505.

* Fix reconnecting PTY tests

* fix: update wireguard-go to fix devtunnel

* fix migration numbers

* linting

* Return early for status if endpoints are empty

* Update cli/server.go

Co-authored-by: Colin Adler <colin1adler@gmail.com>

* Update cli/server.go

Co-authored-by: Colin Adler <colin1adler@gmail.com>

* Fix frontend entites

* Fix agent bicopy

* Fix race condition for the last node

* Fix down migration

* Fix connection RBAC

* Fix migration numbers

* Fix forwarding TCP to a local port

* Implement ping for tailnet

* Rename to ForceHTTP

* Add external derpmapping

* Expose DERP region names to the API

* Add global option to enable Tailscale networking for web

* Mark DERP flags hidden while testing

* Update DERP map on reconnect

* Add close func to workspace agents

* Fix race condition in upstream dependency

* Fix feature columns race condition

Co-authored-by: Colin Adler <colin1adler@gmail.com>
2022-08-31 20:09:44 -05:00
Colin Adler 00da01fdf7 chore: rearrange audit logging code into enterprise folder (#3741) 2022-08-31 21:12:54 +00:00
Mickael 9583e16a05 Update community-templates.md (#3785)
added kubernetes dind template
2022-08-31 15:40:41 -05:00
Cian Johnston 5362f4636e feat: show agent version in UI and CLI (#3709)
This commit adds the ability for agents to set their version upon start.
This is then reported in the UI and CLI.
2022-08-31 16:33:50 +01:00
Steven Masley aa9a1c3f56 fix: Prevent suspending owners (#3757) 2022-08-31 15:26:36 +00:00
Joe Previte e6802f0a56 refactor: use WidgetsIcon for null resources (#3754)
* refactor: replace HelpIcon w/WidgetsIcon

Based on user feedback, we believe the `WidgetsIcon` will cause less
confusion.

* fixup

* refactor: clean up types in ResourceAvatar.tsx

Before, we were using `string` for `type` in `ResourceAvatar`. This
meant it wasn't tied to the types generated from the backend.

Now it imports `WorkspaceResource` so that there is a single source of
truth and they always stay in sync.
2022-08-31 07:44:20 -07:00
Muhammad Atif Ali 774d7588dd docs: Update community-templates.md (#3778)
Added docker based deep learning and matlab coder-templates
2022-08-31 12:04:16 +00:00
Michael Eanes 126d71f41d Remove alpha warning from about (#3774)
The doc was outdated; I don't think the software is alpha anymore.
2022-08-31 03:23:56 +00:00
Kyle Carberry 6644e951d8 fix: Scope error to test functions to fix TestFeaturesService race (#3765)
Fixes #3747.
2022-08-30 19:17:57 -05:00
Bruno Quaresma 02c0100d4d fix: Use a select when parameter input has many options (#3762) 2022-08-30 15:56:36 -07:00
Garrett Delfosse 01a06e1213 feat: Add dedicated labels to agent status and OS (#3759) 2022-08-30 19:18:10 +00:00
Kyle Carberry a410ac42f5 fix: Use first user for telemetry email (#3761)
This was causing other users email to be sent, which isn't desired.
2022-08-30 19:00:23 +00:00
Bruno Quaresma f037aad456 fix: Accepts empty string for the icon prop to remove it (#3760) 2022-08-30 18:48:03 +00:00
Mathias Fredriksson 1dc0485027 fix: Use smarter quoting for ProxyCommand in config-ssh (#3755)
* fix: Use smarter quoting for ProxyCommand in config-ssh

This change takes better into account how OpenSSH executes
`ProxyCommand`s and applies quoting accordingly.

This supercedes #3664, which was reverted.

Fixes #2853

* fix: Ensure `~/.ssh` directory exists
2022-08-30 21:08:20 +03:00
Bruno Quaresma 0708e37a38 feat: Sort templates by workspaces count (#3734) 2022-08-30 17:27:33 +00:00
Muhammad Atif Ali 190310464d Update username in connecting to a workspace documenation (using JetBrains Gateway) (#3746)
if someone is not using coder-provided templates, they might not have coder as a user name.
2022-08-30 16:18:04 +00:00
Eric Paulsen 8a60ee0391 add: code-server to template examples (#3739)
* add: code-server to template examples

* add: code-server to gcp templates

* add: code-server to gcp-linux template

* update: READMEs

* update: boot disk version

* update: google provider version
2022-08-30 10:55:40 -05:00
Geoffrey Huntley 20086c1e77 feat(devenv): use direnv to invoke nix-shell (#3745) 2022-08-30 02:33:11 +00:00
Eric Paulsen c4a9be9c41 update: google provider to latest (#3743)
* update: google provider to latest

* rm: code-server
2022-08-29 19:12:26 -05:00
Spike Curtis cc346afce6 Use licenses to populate the Entitlements API (#3715)
* Use licenses for entitlements API

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

* Tests for entitlements API

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

* Add commentary about FeatureService

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

* Lint

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

* Quiet down the logs

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

* Tell revive it's ok

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-29 16:45:40 -07:00
Joe Previte 05f932b37e refactor(scripts): remove -P from ln calls (#3740) 2022-08-29 15:05:08 -07:00
Jon Ayers 053fe6ff61 feat: add panic recovery middleware (#3687) 2022-08-29 17:00:52 -05:00
Bruno Quaresma 3cf17d34e7 refactor: Redesign auth cli page and add workspaces link (#3737) 2022-08-29 16:57:54 -03:00
Spike Curtis 779c446a6e cli prints license warnings (#3716)
* cli prints license warnings

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

* Satisfy the linter

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-29 11:30:06 -07:00
Bruno Quaresma 62f686c003 fix: Templates table columns width (#3731) 2022-08-29 14:49:04 -03:00
Colin Adler 6285d65b6a fix: remove (http.Server).ReadHeaderTimeout (#3730)
* fix: remove `(http.Server).ReadHeaderTimeout`

Fixes https://github.com/coder/coder/issues/3710. It caused some race
condition for websockets where the server sent the first message.

* comment why disabled
2022-08-29 12:07:49 -05:00
Kyle Carberry 611ca55458 fix: Use "data" scheme when creating parameters from the site (#3732)
Fixes #3691.
2022-08-29 16:32:57 +00:00
Steven Masley 34d902ebf1 fix: Fix properly selecting workspace apps by agent (#3684) 2022-08-29 08:56:52 -04:00
Mathias Fredriksson dc9b4155e0 feat: Generate DB unique constraints as enums (#3701)
* feat: Generate DB unique constraints as enums

This fixes a TODO from #3409.
2022-08-29 14:56:51 +03:00
Mathias Fredriksson f4c5020f63 fix: Print postgres-builtin-url to stdout without formatting (#3727)
This allows use-cases like `eval $(coder server postgres-builtin-url)`.
2022-08-29 11:37:18 +00:00
Dean Sheather b9b9c2fb9f fix: mount TLS secret in helm chart (#3717) 2022-08-27 15:03:10 +00:00
Garrett Delfosse ccabec6dd1 fi stop tracing 4xx http status codes as errors (#3707) 2022-08-26 15:18:42 +00:00
Spike Curtis 23f61fce2a CLI: coder licensese delete (#3699)
Signed-off-by: Spike Curtis <spike@coder.com>

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-26 08:15:46 -07:00
Mathias Fredriksson 98a6958f10 Revert "fix: Avoid double escaping of ProxyCommand on Windows (#3664)" (#3704)
This reverts commit 123fe0131e.
2022-08-26 17:52:25 +03:00
Mathias Fredriksson 6a00baf235 fix: Transform branch name to valid Docker tag for dogfood (#3703) 2022-08-26 17:38:40 +03:00
Mathias Fredriksson c8f8c95f6a feat: Add support for renaming workspaces (#3409)
* feat: Implement workspace renaming

* feat: Add hidden rename command (and data loss warning)

* feat: Implement database.IsUniqueViolation
2022-08-26 12:28:38 +03:00
Presley Pizzo 623fc5baac feat: condition Audit log on licensing (#3685)
* Update XService

* Add simple wrapper

* Add selector

* Condition page

* Condition link

* Format and lint

* Integration test

* Add username to api call

* Format

* Format

* Fix link name

* Upgrade xstate/react to fix crashing tests

* Fix tests

* Format

* Abstract strings

* Debug test

* Increase timeout

* Add comments and try shorter timeout

* Use PropsWithChildren

* Undo PropsWithChildren, try lower timeout

* Format, lower timeout
2022-08-25 19:20:31 -04:00
Spike Curtis ca3811499e DELETE license API endpoint (#3697)
* DELETE license API endpoint

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

* Fix new lint stuff

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-25 14:04:31 -07:00
Dean Sheather 14a9576b77 Auto import kubernetes template in Helm charts (#3550) 2022-08-26 05:32:35 +10:00
Joe Previte 94e96fa40b chore: enable react/no-array-index-key eslint (#3696)
* chore: enable react/no-array-index-key eslint

* fix: add missing key to ResourcesTable
2022-08-25 11:20:24 -07:00
Dean Sheather 8a446837d4 chore: remove exa -> ls and bat -> cat replacements from dogfood img (#3695) 2022-08-26 04:03:27 +10:00
Garrett Delfosse 7a77e55bd4 fix: match term color (#3694) 2022-08-25 16:34:37 +00:00
Garrett Delfosse b412cc1a4b fix: use correct response writer for tracing middle (#3693) 2022-08-25 11:24:43 -05:00
Mathias Fredriksson 78a24941fe feat: Add codersdk.NullTime, change workspace build deadline (#3552)
Fixes #2015

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-08-25 19:10:42 +03:00
Roman Zubov a21a6d2f4a docs: replaced manual up next blocks with doc tag in workspaces.md (#3023)
* docs: replaced manual up next blocks with doc tag in workspaces.md

* replaced up next blocks with <doc page=""> tags

* revert back to markdown

now that we updated how these links work, we can have them as markdown on github and as cards on the docs website.

Co-authored-by: Anton Korzhuk <antonkorzhuk@gmail.com>
2022-08-25 08:26:04 -07:00
Spike Curtis 4de1fc8339 CLI: coder licenses list (#3686)
* Check GET license calls authz

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

* CLI: coder licenses list

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-25 08:24:39 -07:00
Garrett Delfosse a05fad4efd fix: stop tracing static file server (#3683) 2022-08-25 09:37:59 -04:00
Steven Masley 6e496077ae feat: Support search query and --me in workspace list (#3667) 2022-08-24 17:43:41 -04:00
Kira Pilot cf0d2c9bbc added react-i18next to FE (#3682)
* added react-i18next

* fixing typo

* snake case to camel case

* typo

* clearer error in catch block
2022-08-24 17:28:02 -04:00
Joe Previte e6b6b7f610 chore: upload playwright videos on failure (#3677) 2022-08-24 13:45:03 -07:00
Steven Masley 0b53b06fc6 chore: Make member role struct match site roles (#3671) 2022-08-24 15:58:57 -04:00
Spike Curtis 076c4a0aa8 Fix authz test for GET licenses (#3681)
Signed-off-by: Spike Curtis <spike@coder.com>

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-24 12:25:37 -07:00
Spike Curtis 9e35793b43 Enterprise rbac testing (#3653)
* WIP refactor Auth tests to allow enterprise

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

* enterprise RBAC testing

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

* Fix import ordering

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-24 12:05:46 -07:00
Joe Previte 254e91a08f Update stale.yaml (#3674)
- remove close-issue-reason (only valid in 5.1.0)
- add days-before-issue-stale 30
2022-08-24 12:02:12 -07:00
Garrett Delfosse 5d7c4092ac fix: end long lived connection traces (#3679) 2022-08-24 14:57:31 -04:00
Spike Curtis c9bce19d88 GET license endpoint (#3651)
* GET license endpoint

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

* SDK GetLicenses -> Licenses

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-24 18:44:22 +00:00
Kira Pilot da54874958 fixed users test (#3676) 2022-08-24 14:10:41 -04:00
Kira Pilot 57c202d112 Template settings fixes/kira pilot (#3668)
* using hours instead of seconds

* checking out

* added ttl tests

* added description validation  and tests

* added some helper text

* fix typing

* Update site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx

Co-authored-by: Cian Johnston <cian@coder.com>

* ran prettier

* added ttl of 0 test

* typo

* PR feedback

Co-authored-by: Cian Johnston <cian@coder.com>
2022-08-24 14:07:56 -04:00
Garrett Delfosse 4e3b212707 make agent 'connecting' visually different from 'connected' (#3675) 2022-08-24 17:54:45 +00:00
Kyle Carberry 4f8270d95b fix: Exclude time column when selecting build log (#3673)
Closes #2962.
2022-08-24 12:04:33 -05:00
Garrett Delfosse 1400d7cd84 fix: correctly link agent name in app urls (#3672) 2022-08-24 16:49:03 +00:00
Eric Paulsen ca3c0490e0 chore: k8s example persistence & coder images (#3619)
* add: persistence & coder images

* add: code-server

* chore: README updates

* chore: README example
2022-08-24 11:23:02 -05:00
Mathias Fredriksson 123fe0131e fix: Avoid double escaping of ProxyCommand on Windows (#3664)
Fixes #2853
2022-08-24 19:12:40 +03:00
Kyle Carberry 09142255e6 fix: Add consistent use of coder templates init (#3665)
Closes #2303.
2022-08-24 11:40:36 -04:00
Kyle Carberry 706bceb7e7 fix: Remove reference to coder rebuild command (#3670)
Closes #2464.
2022-08-24 15:35:46 +00:00
Cian Johnston eba753ba87 fix: template: enforce bounds of template max_ttl (#3662)
This PR makes the following changes:

- enforces lower and upper limits on template `max_ttl_ms`
- adds a migration to enforce 7-day cap on `max_ttl`
- allows setting template `max_ttl` to 0
- updates template edit CLI help to be clearer
2022-08-24 15:45:14 +01:00
Mathias Fredriksson 343d1184b2 fix: Clean up coder config-ssh dry-run behavior (#3660)
This commit also drops old deprecated code.

Fixes #2982
2022-08-24 16:58:46 +03:00
Mathias Fredriksson 7a71180ae6 chore: Enable comments for database dump / models (#3661) 2022-08-24 12:44:30 +00:00
Ammar Bandukwala 253e6cbffa web: fix template permission check (#3652)
Resolves #3582
2022-08-23 23:44:32 +00:00
Spike Curtis 184f0625e1 coder licenses add CLI command (#3632)
* coder licenses add CLI command

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

* Fix up lint

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

* Fix t.parallel call

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

* Code review improvements

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

* Lint

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-23 13:55:39 -07:00
Cian Johnston 6dacf70898 fix: disable AccountForm when user is not allowed edit users (#3649)
* RED: add unit tests for AccountForm username field
* GREEN: disable username field and button on account form when user edits are not allowed

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-08-23 20:19:26 +00:00
Garrett Delfosse b9dd566804 fix scrollbar on ssh key view (#3647) 2022-08-23 15:22:42 -04:00
Mathias Fredriksson e44f7adb7e feat: Set SSH env vars: SSH_CLIENT, SSH_CONNECTION and SSH_TTY (#3622)
Fixes #2339
2022-08-23 21:19:57 +03:00
Garrett Delfosse 9c0cd5287c fix: clarify we download templates on template select (#3296)
Co-authored-by: Joe Previte <jjprevite@gmail.com>
Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
2022-08-23 17:30:46 +00:00
Mathias Fredriksson 5025fe2fa0 fix: Protect circular buffer during close in reconnectingPTY (#3646) 2022-08-23 16:07:31 +00:00
Presley Pizzo 49de44c76d feat: Add LicenseBanner (#3568)
* Extract reusable Pill component

* Make icon optional

* Get pills in place

* Rough styling

* Extract Expander component

* Fix alignment

* Put it in action - type error

* Hide banner by default

* Use generated type

* Move PaletteIndex type

* Tweak colors

* Format, another color tweak

* Add stories

* Add tests

* Update site/src/components/Pill/Pill.tsx

Co-authored-by: Kira Pilot <kira@coder.com>

* Update site/src/components/Pill/Pill.tsx

Co-authored-by: Kira Pilot <kira@coder.com>

* Comments

* Remove empty story, improve empty test

* Lint

Co-authored-by: Kira Pilot <kira@coder.com>
2022-08-23 11:26:22 -04:00
Mathias Fredriksson f7ccfa2ab9 feat: Set CODER=true in workspaces (#3637)
Fixes #2340
2022-08-23 14:29:01 +03:00
Colin Adler 8343a4f199 chore: cleanup go.mod (#3636) 2022-08-22 22:40:11 -05:00
Jon Ayers a7b49788f5 chore: deduplicate OAuth login code (#3575) 2022-08-22 18:13:46 -05:00
Ammar Bandukwala a07ca946c3 Increase default auto-stop to 12h (#3631)
Resolves #3462.

And, clarify language to resolve #3509.
2022-08-22 17:24:15 -05:00
Ben Potter 8ca3fa9712 fix: use hardcoded "coder" user for AWS and Azure (#3625) 2022-08-22 22:19:30 +00:00
Spike Curtis b101a6f3f4 POST license API endpoint (#3570)
* POST license API

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

* Support interface{} types in generated Typescript

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

* Disable linting on empty interface any

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

* Code review updates

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

* Enforce unique licenses

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

* Renames from code review

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

* Code review renames and comments

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-22 15:02:50 -07:00
dependabot[bot] 85acfdf0dc chore: bump msw from 0.44.2 to 0.45.0 in /site (#3629)
Bumps [msw](https://github.com/mswjs/msw) from 0.44.2 to 0.45.0.
- [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/v0.44.2...v0.45.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-22 16:56:39 -04:00
Ammar Bandukwala 2ee6acb2ad Upgrade frontend to React 18 (#3353)
Co-authored-by: Kira Pilot <kira.pilot23@gmail.com>
2022-08-22 15:42:06 -05:00
Ammar Bandukwala 6fde537f9c web: use seconds in max TTL input (#3576)
Milliseconds are more difficult to deal with due to
all of the zeros.

Also, describe this feature as "auto-stop" to be
consistent with our Workspace page UI and CLI. "ttl"
is our backend lingo which should eventually be updated.
2022-08-22 20:35:17 +00:00
Ammar Bandukwala 5e36be8cbb docs: remove architecture diagram (#3615)
The diagram was more confusion than helpful.
2022-08-22 10:56:10 -05:00
Kyle Carberry 58d29264aa feat: Add template icon to the workspaces page (#3612)
This removes the last built by column from the page. It seemed
cluttered to have both on the page, and is simple enough to
click on the workspace to see additional info.
2022-08-22 09:42:11 -05:00
Dean Sheather 369a9fb535 fix: add writeable home dir to docker image (#3603) 2022-08-22 19:43:13 +10:00
Eric Paulsen 68e17921f0 fix: tooltip 404 (#3618) 2022-08-21 18:50:36 -05:00
Kyle Carberry b0fe9bcdd1 chore: Upgrade to Go 1.19 (#3617)
This is required as part of #3505.
2022-08-21 22:32:53 +00:00
Ammar Bandukwala d37fb054c8 docs: outdent remote desktop docs (#3614)
Resolves #3590
2022-08-21 01:59:40 +00:00
Bruno Quaresma 54b8e794ce feat: Add emoji picker for template icons (#3601) 2022-08-19 16:42:05 -04:00
Bruno Quaresma a4c90c591d feat: Add icon to the template page (#3604) 2022-08-19 15:37:16 -03:00
Spike Curtis 690e6c6585 Check AGPL code doesn't import enterprise (#3602)
* Check AGPL code doesn't import enterprise

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

* use error/log instead of echo/exit

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-19 17:49:08 +00:00
Joe Previte 91bfcca287 fix(ui): decrease WorkspaceActions popover padding (#3555)
There was too much padding on the WorkspaceActions dropdown. This fixes
that.
2022-08-19 09:58:31 -07:00
Bruno Quaresma c14a4b92ed feat: Display and edit template icons in the UI (#3598) 2022-08-19 13:09:07 -03:00
Joe Previte e938e8577f fix: add missing && \ in Dockerfile (#3594)
* fix: add missing && \ in Dockerfile

* fixup: add goboring after PATH goboring
2022-08-19 15:41:17 +00:00
Kyle Carberry 985eea6099 fix: Update icon when metadata is changed (#3587)
This was causing names to become empty! Fixes #3586.
2022-08-19 10:11:54 -05:00
Joe Previte c417115eb1 feat: add cmake, nfpm to dogfood dockerfile (#3558)
* feat: add cmake, nfpm to dogfood dockerfile

* fixup: formatting

* Update dogfood/Dockerfile

Co-authored-by: Cian Johnston <cian@coder.com>

Co-authored-by: Cian Johnston <cian@coder.com>
2022-08-19 15:10:56 +00:00
Mathias Fredriksson 544bf01fbb chore: Update coder/coder provider in example templates (#3581)
Additionally, a convenience script was added to
`examples/update_template_versions.sh` to keep the templates up-to-date.

Fixes #2966
2022-08-19 17:18:11 +03:00
Bruno Quaresma 80f042f01b feat: Add icon to templates (#3561) 2022-08-19 13:17:35 +00:00
Cian Johnston 57f3410009 cli: remove confirm prompt when starting a workspace (#3580) 2022-08-19 11:08:56 +01:00
Mathias Fredriksson 3fdae47b87 fix: Shadow err in TestProvision_Cancel to fix test race (#3579)
Fixes #3574
2022-08-19 11:56:28 +03:00
Eric Paulsen 4ba3573632 fix: quickstart 404 (#3564) 2022-08-18 18:47:12 -05:00
Jon Ayers f6b0835982 fix: avoid processing updates to usernames (#3571)
- With the support of OIDC we began processing updates to a user's
  email and username to stay in sync with the upstream provider. This
  can cause issues in templates that use the user's username as a stable
  identifier, potentially causing the deletion of user's home volumes.
- Fix some faulty error wrapping.
2022-08-18 17:56:17 -05:00
Cian Johnston 04c5f924d7 fix: ui: workspace bumpers now honour template max_ttl (#3532)
- chore: WorkspacePage: invert workspace schedule bumper logic for readibility
- fix: make workspace bumpers honour template max_ttl
- chore: refactor workspace schedule bumper logic to util/schedule.ts and unit test separately
2022-08-18 23:32:23 +01:00
Bruno Quaresma 7599ad4bf6 feat: Add template settings page (#3557) 2022-08-18 16:58:01 -03:00
Joe Previte aabb72783c docs: update CONTRIBUTING requirements (#3541)
* docs: update CONTRIBUTING requirements

* Update docs/CONTRIBUTING.md

* refactor: remove dev from Makefile

* fixup: add linux section
2022-08-18 17:11:58 +00:00
Dean Sheather 55890df6f1 feat: add helm README, install guide, linters (#3268) 2022-08-19 02:41:23 +10:00
Dean Sheather 3610402cd8 Use new table formatter everywhere (#3544) 2022-08-19 02:41:00 +10:00
Kyle Carberry c43297937b feat: Add Kubernetes and resource metadata telemetry (#3548)
Fixes #3524.
2022-08-18 15:57:46 +00:00
Mathias Fredriksson f1423450bd fix: Allow terraform provisions to be gracefully cancelled (#3526)
* fix: Allow terraform provisions to be gracefully cancelled

This change allows terraform commands to be gracefully cancelled on
Unix-like platforms by signaling interrupt on provision cancellation.

One implementation detail to note is that we do not necessarily kill a
running terraform command immediately even if the stream is closed. The
reason for this is to allow for graceful cancellation even in such an
event. Currently the timeout is set to 5 minutes by default.

Related: #2683

The above issue may be partially or fully fixed by this change.

* fix: Remove incorrect minimumTerraformVersion variable

* Allow init to return provision complete response
2022-08-18 17:03:55 +03:00
Mathias Fredriksson 6a0f8ae9cc fix: Add SIGHUP and SIGTERM handling to coder server (#3543)
* fix: Add `SIGHUP` and `SIGTERM` handling to `coder server`

To prevent additional signals from aborting program execution, signal
handling was moved to the beginning of the main function, this ensures
that signals stays registered for the entire shutdown procedure.

Fixes #1529
2022-08-18 16:25:32 +03:00
Jon Ayers 380022fe63 fix: update oauth token on each login (#3542) 2022-08-17 23:06:03 -05:00
Jon Ayers c3eea98db0 fix: use unique ID for linked accounts (#3441)
- move OAuth-related fields off of api_keys into a new user_links table
- restrict users to single form of login
- process updates to user email/usernames for OIDC
- added a login_type column to users
2022-08-17 18:00:53 -05:00
Cian Johnston 53d1fb36db update-alternatives to ensure gofmt is goboring gofmt (#3540) 2022-08-17 20:03:44 +00:00
whitney-coder d6351a6b9f Update README.md (#3539)
Minor grammatical change on line 14
2022-08-17 14:48:41 -05:00
Bruno Quaresma 546157b63e feat: Make template name editable (#3538) 2022-08-17 19:04:00 +00:00
Kira Pilot 4b646cc4fa fix: hiding agent status on stopped workspaces (#3512)
* hiding agent status on a stopped workspaace

resolves #3484

* run prettier and lint

* Update site/src/components/Resources/Resources.tsx

Co-authored-by: Joe Previte <jjprevite@gmail.com>

* running prettier

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-08-17 14:37:54 -04:00
Spike Curtis acd0cd66f6 coder features list CLI command (#3533)
* AGPL Entitlements API

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

* Generate typesGenerated.ts

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

* AllFeatures -> FeatureNames

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

* Features CLI command

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

* Validate columns

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

* Tests for features list CLI command

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

* Drop empty EntitlementsRequest

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

* Fix dump.sql generation

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-17 11:26:16 -07:00
Spike Curtis 5c898d0c83 Fix archive.sh for LICENSE files (#3535)
Signed-off-by: Spike Curtis <spike@coder.com>

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-17 10:27:52 -07:00
Kyle Carberry c3f946737c fix: Strip session_token cookie from app proxy requests (#3528)
Fixes coder/security#1.
2022-08-17 17:09:45 +00:00
Noah Huppert 000e1a5ef2 Fixed env block in Emacs IDE docs (#3534) 2022-08-17 16:30:45 +00:00
Dean Sheather a872330a8d feat: add generic table formatter (#3415) 2022-08-18 02:28:22 +10:00
Spike Curtis b1b2d1b2b2 AGPL Entitlements API (#3523)
* AGPL Entitlements API

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

* Generate typesGenerated.ts

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

* AllFeatures -> FeatureNames

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-17 09:02:36 -07:00
Spike Curtis 5817c6ac7f Build enterprise coder binary by default (#3517)
* Build enterprise coder binary by default

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

* Add --agpl to develop.sh

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

* Add --agpl flag to archive.sh

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

* shell format

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

* Move AGPL back to LICENSE, explain enterprise license is forthcoming

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

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-17 09:02:25 -07:00
Steven Masley 4be61d9250 fix: Role assign ui fixes (#3521)
Co-authored-by: Kira Pilot <kira@coder.com>
2022-08-16 10:39:42 -05:00
Ben Potter 4b6a82f92a chore: rename to "template push" in docs (#3525) 2022-08-16 14:52:31 +00:00
Steven Masley 01dd35f1ba chore: Rename 'admin' to 'owner' (#3498)
Co-authored-by: Colin Adler <colin1adler@gmail.com>
2022-08-15 14:40:19 -05:00
Steven Masley 2306d2c709 chore: Fix misspelled "referrer" in site.go (#3507) 2022-08-15 14:12:34 +00:00
Mathias Fredriksson e749070193 chore: Update readme with note about embedded database (#3488) 2022-08-15 12:32:22 +03:00
Jon Ayers 301727d1fc chore: improve dump error output (#3499)
* chore: improve dump error output

- Properly report the error that occurs during the DB connection retry
  loop.
- Fail fatally if migration is unsuccessful.
2022-08-12 22:15:13 -05:00
Ammar Bandukwala 8cf82112ad docs: document additional roles (#3496)
Co-authored-by: Steven Masley <stevenmasley@coder.com>
2022-08-12 22:42:16 +00:00
Steven Masley 40e68cb80b feat: Add template-admin + user-admin role for managing templates + users (#3490)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-08-12 17:27:48 -05:00
Bruno Quaresma c41261cf6e fix: Remove unexpected break lines when copy logs (#3492) 2022-08-12 19:18:41 +00:00
Bruno Quaresma 351d55e1f4 chore: Minor table design changes (#3494) 2022-08-12 16:18:03 -03:00
Kyle Carberry 3b951f77fb fix: Unskip SuspendAnotherUser test (#3430)
It wasn't clear why this was skipped, it seems accidental.
2022-08-12 19:12:44 +00:00
Oxylibrium 0a46b1e59d chore: remove swr and dead code (#3495) 2022-08-12 15:06:40 -04:00
Mathias Fredriksson 010f64e8e9 fix: Enable goleak for cli tests (#3370) 2022-08-12 21:02:10 +03:00
Bruno Quaresma 0e8c68ebc5 chore: Increase border radius (#3493) 2022-08-12 14:58:14 -03:00
Muhammad Atif Ali c3fcf7c953 chore: renamed coder template edit flags in coder CLI (#3471)
Use `-` over `_` for cli flags
2022-08-12 10:21:42 -05:00
Kyle Carberry b3d3b8ba0f fix: Stop multiple buttons from compounding in the workspace action dropdown (#3482)
The variadic function on an object doesn't clone the inner array.

This was causing the `secondary` property to accumulate more and
more button types as time went on!

Fixes #3154.
2022-08-12 13:19:52 +00:00
Kyle Carberry 16c12e976e chore: Improve agent logging (#3483) 2022-08-12 07:01:00 -05:00
Kyle Carberry ca342067b3 fix: Remove typo in policy.rego 2022-08-11 23:33:50 -05:00
Ammar Bandukwala d7b96f7d58 Correct spelling of macOS (#3478)
* Correct spelling of macOS

* fixup! Correct spelling of macOS

* fixup! Correct spelling of macOS
2022-08-11 21:22:06 -04:00
Jon Ayers 923c212960 chore: add zstd to dogfood image (#3479) 2022-08-11 17:48:49 -05:00
Steven Masley 3ae42f4de9 chore: Update rego to be partial execution friendly (#3449)
- Improves performance of batch authorization calls
- Enables possibility to convert rego auth calls into SQL WHERE clauses
2022-08-11 22:07:48 +00:00
Bruno Quaresma 4a17e0d91f feat: Add setup page (#3476) 2022-08-11 17:22:46 +00:00
Sagar Vora 604f211674 fix: replace broken link with Github contributors graph (#3472) 2022-08-11 14:35:51 +00:00
Kira Pilot 6122df6f1f feature: gate audit log by permissions (#3464)
* pairing

* restricting audit route

resolvees #3460

* updated tests

* fixing lint

* useSelector instead of useActor
2022-08-11 09:34:45 -04:00
Ammar Bandukwala 4e6645af50 docs: outdent generic quickstart (#3467) 2022-08-10 21:53:35 -05:00
Jon Ayers 426b30ed16 fix: add missing dependencies to dogfood image (#3470) 2022-08-11 01:24:56 +00:00
Eric Paulsen 272962cfae docs: add upgrade page & update getting started (#3439) 2022-08-10 17:56:21 -05:00
Presley Pizzo 5d40b1f0f4 feat: Add switches for auto-start and auto-stop (#3358)
* Add elements

* Add Loading story

* Make form show empty values when manual

* Make form depend on switches

* Fix style

* Format

* Update unit tests

* Tweaks

* Update storybook

* Move util files

* Pull out more util functions

* Pull out strings

* Add border to section

* Make min ttl 1

* Format

* Fix import

* Fix validation for falsey values

* Format and fix tests

* Put switches in form, persist form state

* Fix bug

* Remove helper text when disabled

* Fix storybook

* Revert "Remove helper text when disabled"

This reverts commit a6271ca6c4.

* Format

* Use nicer function to set values

* Format
2022-08-10 22:03:15 +00:00
Ben Potter cee0d1f848 chore: add metadata to example templates (#3451) 2022-08-10 16:34:17 -05:00
Mathias Fredriksson 95f26f74b6 fix: Close response body in cli server test (#3459) 2022-08-10 16:30:46 +00:00
Kyle Carberry d6d9cf9b30 fix: Downgrade embedded PostgreSQL (#3453)
This was causing a new data path to occur, which broke existing installs.
It needs to use the same path and upgrade instead.
2022-08-10 10:08:24 -05:00
Kyle Carberry fd73d6dd0d fix: Reduce variables needed for Docker template (#3442)
* fix: Reduce variables needed for Docker template

This should make initial setup a bit simpler!

* Fix for M2 Macbooks

PostgreSQL 13 doesn't support the M series architecture.

* Fix name <-> id swap

* Update Docker provider to remove host requirement

Co-authored-by: Kyle Carberry <kyle@air.local>
2022-08-10 14:45:05 +00:00
Bruno Quaresma 758eb21b36 feat: Support booleans for parameters input (#3437) 2022-08-10 10:41:26 -03:00
Ammar Bandukwala f28cd15706 docs: remove incorrect SSH key info (#3448) 2022-08-09 22:15:18 -05:00
Ammar Bandukwala 3ceee76784 docs: explain resource metadata (#3447) 2022-08-09 20:21:26 -05:00
Ammar Bandukwala c73f708678 docs: remove configuring prefix from IDEs (#3446) 2022-08-09 20:10:09 -05:00
Ammar Bandukwala 815bf1b668 docs: fix IDE icon (#3445) 2022-08-09 20:07:51 -05:00
Ammar Bandukwala 88c9f31007 docs: explain how to display secrets (#3443) 2022-08-09 23:45:30 +00:00
Ammar Bandukwala fd59e2e812 add metadata to dogfood template (#3444) 2022-08-09 23:40:12 +00:00
Steven Masley db665e7261 chore: Drop resource_id support in rbac system (#3426) 2022-08-09 18:16:53 +00:00
Mathias Fredriksson ccf6f4e7ed chore: Use contexts with timeout in coderd tests (#3381) 2022-08-09 20:17:00 +03:00
Bruno Quaresma 690ba661a7 feat: Add metadata support to the UI (#3431) 2022-08-09 16:49:06 +00:00
Kyle Carberry 53400c6205 fix: Check if an API error has data before checking the message (#3427)
This was causing the app to crash with an error. I found this manually
by looking through the obfuscated sources in DevTools. It's a
data-point for #3425 though!
2022-08-09 14:26:18 +00:00
Kyle Carberry e1da2b6467 fix: Don't fetch resources when a workspace is building (#3424)
Fixes #3423.
2022-08-09 14:07:01 +00:00
Mathias Fredriksson c0cc8b9935 fix: Improve friendly validation error messages (#3390)
* fix: Add validations to `(*codersdk.Error).Friendly`

* fix: Add named validators for template and workspace name
2022-08-09 14:25:23 +03:00
Kyle Carberry f62e1ede77 feat: Add support for GitHub Enterprise authentication (#3422)
This was manually tested with GitHub Enterprise v3.6.0-rc1.
2022-08-08 20:49:51 -05:00
Kyle Carberry 7bdb8ff9cf feat: Add workspace metrics export to Prometheus (#3421)
This adds workspace totals indexed by status. It could be any
codersdk.ProvisionerJobStatus.
2022-08-09 01:08:42 +00:00
Kira Pilot e62677efab feat: add audit page title, subtitle, and CLI snippet (#3419)
* resolves #3356

* scaffolded out new audit page header

resolves #3357

* added tests and stories

* run prettier
2022-08-08 21:08:36 -04:00
Spike Curtis 049e7cb5df azure-linux example template (#3348)
* azure-linux example template

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

* Use azurerm_linux_virtual_machine and wait for attachment

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

* Use azure-instance-identity

Signed-off-by: Spike Curtis <spike@coder.com>
2022-08-08 15:25:20 -07:00
Kyle Carberry a848e71f58 fix: Make the twitter handle lowercase in README (#3413)
The uppercase was bothering...
2022-08-08 13:17:38 -05:00
Kyle Carberry 42bac09c1a fix: Sort workspace agents by name (#3407)
Fixes #2778.
2022-08-08 12:25:29 -05:00
Kyle Carberry d275e52a41 fix: Add godoc badge to README (#3412)
This helps allude to the idea that Coder provides an API as
seen in #3411.

This also fixes the codecov badge from always being red ;p
2022-08-08 12:16:40 -05:00
Kira Pilot eb7d947d10 resolves #3356 (#3408) 2022-08-08 12:23:01 -04:00
Kyle Carberry 9c12b4ed8e chore: Add nix shell for simple development setup (#3399)
* chore: Add nix shell for simple development setup

This enables contributors using Nix to set up their environment with ease.

* improve nix style, flake output schema

* fix error message

* Update scripts/build_go_slim.sh

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

* Update scripts/build_go_slim.sh

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

* Add UTC default for timezone and remove unnecessary goreleaser dependency

* Skip TZ test if localtime does not exist

Co-authored-by: Charlie Moog <moogcharlie@gmail.com>
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-08-08 15:49:12 +00:00
Kyle Carberry 3279504cbe feat: Add active users prometheus metric (#3406)
This  allows deployments using our Prometheus export t determine
the number of active users in the past hour.

The interval is an hour to align with API key last used refresh times.

SSH connections poll to check shutdown time, so this will be accurate
even on long-running connections without dashboard requests.
2022-08-08 10:09:46 -05:00
Ammar Bandukwala 13a2014d7f docs: fix up port-forwarding (#3403)
- Improve English
- Make new page live in manifest.json
- Add icon
- Outdent page to root
2022-08-07 22:22:47 +00:00
mark-theshark 8d4b6086f6 chore: docs: add port-forwarding options (CLI & ssh) (#3394)
* chore: docs: add port-forwarding options

* fix: code type

Co-authored-by: Eric Paulsen <eric@Erics-MacBook-Air.local>
2022-08-07 17:30:15 -04:00
mark-theshark 44a826dc06 docs: fix address specification in Docker quickstart (#3396) 2022-08-07 14:52:55 -05:00
Mathias Fredriksson 1fb274cbda fix: Disallow args for config-ssh subcommand in cli (#3393) 2022-08-06 20:56:42 +03:00
Mathias Fredriksson b10a1b84e5 fix: Fix close in pty and ptytest (#3392) 2022-08-05 21:31:54 +03:00
Ben Potter f14efd1a2b chore: alphabetize template list (#3363) 2022-08-05 13:03:22 -05:00
Cian Johnston 854bb5dbeb fix: post-hoc testutil fix (#3391) 2022-08-05 16:09:20 +00:00
Abhineet Jain e7bc01383c fix: handle workspace errors (#3341) 2022-08-05 10:38:07 -05:00
Cian Johnston 01fe5e668e chore: add testutil.Eventually and friends (#3389)
This PR adds a `testutil` function aimed to replace `require.Eventually`.

Before:
```go
require.Eventually(t, func() bool { ... }, testutil.WaitShort, testutil.IntervalFast)
```

After:
```go
require.True(t, testutil.EventuallyShort(t, func(ctx context.Context) bool { ... }))

// or the full incantation if you need more control
ctx, cancel := context.WithTimeout(ctx.Background(), testutil.WaitLong)
require.True(t, testutil.Eventually(t, ctx, func(ctx context.Context) bool { ... }, testutil.IntervalSlow))
```

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-08-05 16:34:44 +01:00
Mathias Fredriksson 46d64c624a fix: Add ps.Kill/Wait to test cleanup in ptytest.Start (#3387) 2022-08-05 13:35:33 +03:00
Mathias Fredriksson fb9fca8bc9 fix: Ensure terraform tests have a cache path and logger (#3161)
* fix: Ensure terraform tests have a cache path and logger

* fix: Protect against concurrent `terraform init`
2022-08-04 20:37:07 +03:00
Kyle Carberry ad20b23178 fix: Move state pull output to stdout (#3382)
* fix: Move state pull output to stdout

Fixes #1645.

* Update cli/state.go

Co-authored-by: Abhineet Jain <AbhineetJain@users.noreply.github.com>

Co-authored-by: Abhineet Jain <AbhineetJain@users.noreply.github.com>
2022-08-04 15:33:59 +00:00
Kyle Carberry 303b280e0e fix: Associate spot instances with their instance IDs for auth (#3383)
Fixes #2162.
2022-08-04 10:20:56 -05:00
Ben Potter 075454cce8 chore: use consistent button type for settings (#3362) 2022-08-04 10:15:35 -05:00
David Wahler 9f54fa8e52 Make gcp-linux example template use a non-root user (#2480)
* make gcp-linux example template use a non-root user

* don't try to create user account if it already exists

* upgrade to debian-10 image since debian-9 is no longer available
2022-08-03 18:07:10 -05:00
Ben Potter fd4e2cc331 chore: improve contrast for terminal overlay (#3375) 2022-08-03 15:39:38 -05:00
Ammar Bandukwala 8a4438895b Add fish to dogfood (#3373) 2022-08-03 18:21:34 +00:00
dependabot[bot] b6774ead2c chore: bump eslint-plugin-jest from 26.6.0 to 26.7.0 in /site (#3334)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 26.6.0 to 26.7.0.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v26.6.0...v26.7.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  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>
2022-08-03 14:15:47 -04:00
Ben Potter 7e1caa7086 chore: add helper text to help admins create new templates (#3364) 2022-08-03 17:27:39 +00:00
Kyle Carberry 69664ed168 fix: Use "virtual_machine_id" for instance identity with Azure (#3355)
This was using the wrong property, causing automatic auth to break.
2022-08-03 12:19:13 -05:00
Ben Potter 420fae886a chore: link to the hosted docs site instead of GitHub (#3365) 2022-08-03 17:04:45 +00:00
Mathias Fredriksson 6e426cf47d fix: Fix goleak in cli TestSSH/ForwardAgent test (#3369) 2022-08-03 16:06:40 +03:00
Mathias Fredriksson 9a023dd63b fix: Skip waiting for exit output in SSH test (#3368)
This seems to have caused flakes on Windows, the reason could be that
the input is lost due to writing to stdin before the shell is ready, or
simply that the command wasn't echoed (for the same reason).

We no longer need to consume the output since #2122 has been fixed, so
this might remove the flake in the latter case.

Ideally we would wait for the prompt to be present, but since we are
spawning the users shell, we have no control of what the prompt looks
like. In CI we can make assumption but even then it could change in the
future.
2022-08-03 13:37:12 +03:00
Mathias Fredriksson 1d6283bdac fix: Improve debuggability of ptytest failures (#3367)
Since we were not failing tests with `require` the error output was
somewhat hidden in the stream of log messages. This change standardizes
`ptytest` logging and failing to improve visibility.
2022-08-03 13:27:20 +03:00
Ammar Bandukwala 8f338782db Make minor improvements to Dogfood README (#3361) 2022-08-02 21:14:12 +00:00
Ammar Bandukwala 81e292be44 Add dogfood image (#3350) 2022-08-02 20:20:54 +00:00
Abhineet Jain 8bcf23e60a fix: handle create workspace errors (#3346) 2022-08-02 13:19:00 -04:00
Mathias Fredriksson 83c63d4a63 fix: Improve shutdown procedure of ssh, portforward, wgtunnel cmds (#3354)
* fix: Improve shutdown procedure of ssh, portforward, wgtunnel cmds

We could turn it into a practice to wrap `cmd.Context()` so that we have
more fine-grained control of cancellation. Sometimes in tests we may be
running commands with a context that is never canceled.

Related to #3221

* fix: Set ssh session stderr to stderr
2022-08-02 17:44:59 +03:00
Abhineet Jain 5ae19f097e fix: chromatic workflow filter (#3352) 2022-08-02 04:23:32 -04:00
Ammar Bandukwala bd785ddd87 Fix docs links (#3351) 2022-08-02 01:22:14 -04:00
Jon Ayers c1885dab27 fix: NPE when no arg provided to 'coder update' (#3347)
- Add test suite for 'coder update'.
2022-08-01 19:46:50 -05:00
David Wahler 8a2811210a feat: Add backend API support for resource metadata (#3242)
* Initial support for metadata in provisioner API and Terraform provisioner

* add support for nullable metadata fields

* handle metadata fields in provisionerd and API
2022-08-01 16:53:05 -05:00
Noah Huppert 877519232c Added Emacs Tips Documentation (#3247) 2022-08-01 16:47:22 -05:00
Dean Sheather 66a5b0f7bc fix: don't use adduser and addgroup for docker images (#3344)
* fix: don't use adduser and addgroup for docker images

* Revert "fix: Remove alternative image architectures until we virtualize (#3336)"

This reverts commit 00c5116a2e.
2022-08-01 19:28:38 +00:00
Kyle Carberry 8f3727d05d fix: Update issue reporting link with body (#3339) 2022-08-01 17:03:02 +00:00
Anton Korzhuk 80223a5e41 replace inline svgs with svg icon_path (#3332) 2022-08-01 16:57:51 +00:00
Ben Potter 56ee105a2a chore: add Discord link to footer (#3239) 2022-08-01 10:03:52 -05:00
Kyle Carberry 00c5116a2e fix: Remove alternative image architectures until we virtualize (#3336)
With the addition of a command being executed inside the Docker build,
we could no longer build non-amd64 images on amd64. They will be added
back, but to allow for releases this temporarily removes them.
2022-08-01 08:37:37 -05:00
Ammar Bandukwala 0d93e9bde1 ci: move chromatic to coder workflow (#3330) 2022-08-01 09:36:00 -04:00
Ammar Bandukwala 19fcf60864 ci: add typo detection (#3327)
And fix them.
2022-08-01 09:29:52 -04:00
dependabot[bot] eb514357bb chore: bump eslint from 8.20.0 to 8.21.0 in /site (#3335)
Bumps [eslint](https://github.com/eslint/eslint) from 8.20.0 to 8.21.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.20.0...v8.21.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>
2022-08-01 08:22:53 -05:00
Mathias Fredriksson 4730c589fe chore: Use standardized test timeouts and delays (#3291) 2022-08-01 15:45:05 +03:00
Kyle Carberry 3d0febdd90 feat: Add OIDC authentication (#3314)
* feat: Add OIDC authentication

* Extract username into a separate package and add OIDC tests

* Add test case for invalid tokens

* Add test case for username as email

* Add OIDC to the frontend

* Improve comments from self-review

* Add authentication docs

* Add telemetry

* Update docs/install/auth.md

Co-authored-by: Ammar Bandukwala <ammar@ammar.io>

* Update docs/install/auth.md

Co-authored-by: Ammar Bandukwala <ammar@ammar.io>

* Remove username package

Co-authored-by: Ammar Bandukwala <ammar@ammar.io>
2022-07-31 23:05:35 -05:00
Jon Ayers 8b17bf98ea fix: prepend scheme to access url (#3317)
- Problems can arise spawning workspaces if a schemeless URL is passed
  as the access URL.

  If an access url is detected to not have an "http" or "https" scheme
  then it is prepended with "https". If the hostname is detected
  to be a loopback device then "http" is preferred.
2022-07-31 17:49:25 -05:00
Ammar Bandukwala f82df1bd78 docs: clean up English (#3324)
Fix issues from #3319 and #3320
2022-07-31 20:06:05 +00:00
dependabot[bot] 70bf66e030 chore: bump github.com/go-chi/httprate from 0.5.3 to 0.6.0 (#3311)
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.5.3 to 0.6.0.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.5.3...v0.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-31 13:44:38 -05:00
dependabot[bot] 921de16d98 chore: bump google.golang.org/api from 0.88.0 to 0.90.0 (#3310)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.88.0 to 0.90.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.88.0...v0.90.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>
2022-07-31 13:44:25 -05:00
Noah Huppert 16f0f1a2db 3293, cli: Updated Placeholder color to have a dark theme alt (#3294)
Co-authored-by: Ubuntu <ubuntu@ip-172-31-1-230.us-east-2.compute.internal>
2022-07-31 13:44:05 -05:00
mark-theshark c553829fbf chore: document startup_script and agent log location (#3319) 2022-07-30 17:57:16 -05:00
Ben Potter 52041becf7 Revert "chore: relax template name validation"
This reverts commit 7806f3bebe.
2022-07-30 22:34:59 +00:00
mark-theshark beed6c7222 chore: updated web ide screenshots to be current, and fix minor spelling errors. (#3153) 2022-07-30 22:31:22 +00:00
Ben Potter c8d7b38418 Merge branch 'main' of github.com:coder/coder into main 2022-07-30 22:31:08 +00:00
Ben Potter 7806f3bebe chore: relax template name validation 2022-07-30 22:31:06 +00:00
mark-theshark 7367253097 chore: update jetbrains gateway docs with screenshots (#3320)
* chore: update jetbrains gateway with screenshots

* organize & add to manifest

Co-authored-by: Ben <ben@coder.com>
2022-07-30 17:29:05 -05:00
whitney-coder d764b3d0c3 Update oauth.md 2 (#3312)
I messed up the order of the brackets/parenthesis on the first commit.  This time, it should be correct and in line with Markdown syntax which states: Markdown syntax for a hyperlink is square brackets followed by parentheses. The square brackets hold the text, the parentheses hold the link.
2022-07-29 16:34:49 -05:00
Ammar Bandukwala 09776f33dd docs: rm postgres (#3313)
This is not our job.
2022-07-29 19:55:40 +00:00
Spike Curtis 6ea9298656 Update Gateway 2022.2 RC docs (#3256)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-29 14:36:43 -05:00
Ammar Bandukwala 6e63487b27 Rename template update to template push (#3307)
Before, there was a `template edit` AND a `template update`. The
distinction between both commands was easy to forget. `push` more
clearly indicates that the template's source code is being updated.

It is also complimentary to existing `template pull`.
2022-07-29 19:21:48 +00:00
whitney-coder 4b9daf5777 Update oauth.md (#3308)
Markdown syntax on line 9 caused a funky looking link on the website
2022-07-29 14:18:53 -05:00
Kira Pilot f49328bee5 chore: fix yaml file config error (#3306) 2022-07-29 15:18:22 -04:00
dependabot[bot] 9614bfea6b chore: bump @xstate/cli from 0.2.1 to 0.3.0 in /site (#3262)
Bumps @xstate/cli from 0.2.1 to 0.3.0.

---
updated-dependencies:
- dependency-name: "@xstate/cli"
  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>
2022-07-29 14:59:00 -04:00
Ammar Bandukwala 29eccbe4da ci: revert skips of required checks (#3303)
These were putting certain PRs in an unmergeable state.
2022-07-29 18:49:51 +00:00
dependabot[bot] d12e6b394f chore: bump typescript from 4.6.4 to 4.7.4 in /site (#2941)
* chore: bump typescript from 4.6.4 to 4.7.4 in /site

Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.6.4 to 4.7.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.6.4...v4.7.4)

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

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

* Remove unnecessary React imports

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Presley Pizzo <presley@coder.com>
2022-07-29 14:48:58 -04:00
mark-theshark 1f2ead80c6 chore: remove duplicative kube config info in projector section (#3290) 2022-07-29 13:40:14 -05:00
Kyle Carberry 183b2e80b9 fix: Increase zoom of hero for README (#3300)
This was pretty small before which made it difficult to see
what was going on.
2022-07-29 13:39:30 -05:00
Kira Pilot aaa2db6f8b feat: add pagination component to components directory (#3295)
* proof of concept

* added tests

* fixed tests

* wrote unit tests

* preettier
2022-07-29 14:37:53 -04:00
Kira Pilot b9936d2310 updated dependabot (#3297) 2022-07-29 13:31:49 -04:00
Abhineet Jain e94fe20b6b fix: handle getUser error (#3285) 2022-07-29 13:10:22 -04:00
Cian Johnston 4658b3f0d2 fix: coderd: putExtendWorkspace: move error from validation to message (#3289)
* refactor: coderd: extract error messages to variables
* fix: putExtendWorkspace: return validation error in message field
2022-07-29 15:01:17 +01:00
Abhineet Jain 74c87664c1 fix: handle more auth API errors (#3241) 2022-07-28 17:14:05 -04:00
Presley Pizzo 6b82fdd0c0 Surface backend error when extending schedule (#3275) 2022-07-28 17:13:46 -04:00
Spike Curtis d6faf8f524 remove character limit on instance ids (#3274)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-28 13:52:03 -07:00
dependabot[bot] 6d14dcb1ee chore: bump eslint-plugin-jest from 26.5.3 to 26.6.0 in /site (#3204)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 26.5.3 to 26.6.0.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v26.5.3...v26.6.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  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>
2022-07-28 15:24:59 -04:00
dependabot[bot] 7ba69739f6 chore: bump ts-node from 10.8.2 to 10.9.1 in /site (#3213)
Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 10.8.2 to 10.9.1.
- [Release notes](https://github.com/TypeStrong/ts-node/releases)
- [Commits](https://github.com/TypeStrong/ts-node/compare/v10.8.2...v10.9.1)

---
updated-dependencies:
- dependency-name: ts-node
  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>
2022-07-28 15:11:44 -04:00
dependabot[bot] 736084ca5d chore: bump @typescript-eslint/eslint-plugin in /site (#3214)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.30.6 to 5.31.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.31.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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>
2022-07-28 15:09:51 -04:00
Mathias Fredriksson 29d44b6283 fix: Guard pty window resize after close (#3270)
Could help alleviate #3236.
2022-07-28 19:07:11 +00:00
Denbeigh Stevens 43b8cf04f0 fix: remove pipefail from standard shell options (#3269)
This isn't well-supported by every POSIX shell anyways.
2022-07-28 18:50:04 +00:00
Presley Pizzo 73f145e45f fix: error messages from workspaceScheduleXService (#3255)
* Update color palette

* Edit dialog error colors

* Format

* Lighten links

* Lighten link just in ErrorSummary

* Format

* Fix errors in schedule xservice

* Add error summary to form for generic message

* Format

* Extend getFormHelpers to remap field name

* Add mock error and use in storybook

* Format
2022-07-28 13:18:51 -04:00
Bruno Quaresma 1a8cce27ae fix: Workspace schedule button on responsive (#3264) 2022-07-28 16:17:50 +00:00
Bruno Quaresma 2805d86ba9 chore: Replace stop icon to use pause icon (#3261) 2022-07-28 11:02:14 -03:00
dependabot[bot] 663d0475b9 chore: bump @typescript-eslint/parser from 5.30.6 to 5.31.0 in /site (#3212)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.30.6 to 5.31.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.31.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  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>
2022-07-28 09:48:58 -04:00
Spike Curtis 043768076f Explain pty Process abstraction (#3254)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-27 17:03:55 -07:00
Steven Masley 6230d5512e chore: Remove line numbers from auto-gen typescript (#3258)
* chore: Remove line numbers from auto-gen typescript

The line numbers are just extra noise that change when things shift
around. They are not required and usually make CI fail when you
forget to run 'make gen'.
2022-07-27 21:36:15 +00:00
Cian Johnston 27ea415b6c fix: remove string TTL from workspace error responses (#3257)
- Rewrites some error messages to better integrate with the frontend (ttl_ms -> time until shutdown)
- Makes codersdk.ValidationError implement the error interface
- Only return validations if the error was a validation error, return detail otherwise (e.g. database error)
2022-07-27 21:20:02 +00:00
Spike Curtis 36ffdce065 Return proper exit code on ssh with TTY (#3192)
* Return proper exit code on ssh with TTY

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

* Fix revive lint

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

* Fix Windows exit code for missing command

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

* Fix close error handling on agent TTY

Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-27 14:23:28 -05:00
Presley Pizzo a37e61a099 fix: make text colors legible (#3250)
* Update color palette

* Edit dialog error colors

* Format

* Lighten links

* Lighten link just in ErrorSummary

* Format
2022-07-27 13:49:03 -04:00
Mathias Fredriksson 46564fb470 fix: Fix goleak in cli TestSSH tests (#3253)
Commands are now also run with contexts that time out.

Work towards #3221.
2022-07-27 17:33:00 +00:00
Mathias Fredriksson a0320f455a fix: Close notifier Poll goroutine on stop (#3252)
Fix towards #3221.
2022-07-27 20:26:13 +03:00
Cian Johnston 6377f17fda chore: update terraform to 1.2.1 (#3243)
* chore: update terraform to 1.2.1

* allow terraform version equal to max
2022-07-27 17:11:38 +01:00
Mathias Fredriksson d27076cac7 fix: Improve coder server shutdown procedure (#3246)
* fix: Improve `coder server` shutdown procedure

This commit improves the `coder server` shutdown procedure so that all
triggers for shutdown do so in a graceful way without skipping any
steps.

We also improve cancellation and shutdown of services by ensuring
resources are cleaned up at the end.

Notable changes:
- We wrap `cmd.Context()` to allow us to control cancellation better
- We attempt graceful shutdown of the http server (`server.Shutdown`)
  because it's less abrupt (compared to `shutdownConns`)
- All exit paths share the same shutdown procedure (except for early
  exit)
- `provisionerd`s are now shutdown concurrently instead of one at a
  time, the also now get a new context for shutdown because
  `cmd.Context()` may be cancelled
- Resources created by `newProvisionerDaemon` are cleaned up
- Lifecycle `Executor` exits its goroutine on context cancellation

Fixes #3245
2022-07-27 18:21:21 +03:00
Mathias Fredriksson bb05b1f749 fix: Use slog for devtunnel logging (#3248)
Ensures standardized logging for server.
2022-07-27 18:05:47 +03:00
Mathias Fredriksson cef622d77c fix: Order database queries for templates (#3249)
* fix: Order database queries for templates

Fixes a race in a test where the order of templates varies.

* fix: Add sorting to databasefake as well
2022-07-27 15:04:29 +00:00
Ammar Bandukwala 5802c29c38 docs: add versions (#3147)
Resolves #3111
2022-07-27 10:52:18 -04:00
Spike Curtis f310aeb4cb Disable skipping job acquire log (#3240)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-26 16:36:45 -07:00
Abhineet Jain b1e0d69789 Implement basic templates versions CLI (#3145) 2022-07-26 18:31:17 -04:00
Dean Sheather df20dd7374 feat: improve coder users show output, add json format (#3176) 2022-07-26 15:47:12 -05:00
Bruno Quaresma aaf0da27ef chore: Update viewport to support responsive (#3233) 2022-07-26 17:33:46 -03:00
Abhineet Jain 6f93acd964 feat: make template pages responsive (#3232) 2022-07-26 16:31:58 -04:00
Bruno Quaresma 991b4f7480 feat: Make settings page responsive (#3228) 2022-07-26 19:48:41 +00:00
Bruno Quaresma 509a601efe feat: Make users page responsive (#3229) 2022-07-26 16:46:43 -03:00
Abhineet Jain 0128ca6bd1 fix: manage backend authXService errors (#3190) 2022-07-26 15:39:45 -04:00
Dean Sheather b19cf701c5 feat: change docker to use "coder" user and add basic Helm chart (#2746) 2022-07-26 13:19:29 -05:00
Bruno Quaresma d2aa75dd0d fix: Responsive for workspaces and workspace page (#3189) 2022-07-26 15:05:00 -03:00
David Wahler fbd1a272fe fix: Fix dangling references in provisioner/terraform/testdata (#3193) 2022-07-26 12:04:21 -05:00
dependabot[bot] 8115a11e58 chore: bump webpack from 5.73.0 to 5.74.0 in /site (#3208)
Bumps [webpack](https://github.com/webpack/webpack) from 5.73.0 to 5.74.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.73.0...v5.74.0)

---
updated-dependencies:
- dependency-name: webpack
  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>
2022-07-26 10:53:45 -04:00
dependabot[bot] c8d2254028 chore: bump chromatic from 6.7.0 to 6.7.1 in /site (#3206)
Bumps [chromatic](https://github.com/chromaui/chromatic-cli) from 6.7.0 to 6.7.1.
- [Release notes](https://github.com/chromaui/chromatic-cli/releases)
- [Changelog](https://github.com/chromaui/chromatic-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chromaui/chromatic-cli/commits)

---
updated-dependencies:
- dependency-name: chromatic
  dependency-type: direct:development
  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>
2022-07-26 10:53:21 -04:00
dependabot[bot] f49b015fc7 chore: bump cronstrue from 2.5.0 to 2.11.0 in /site (#2943)
Bumps [cronstrue](https://github.com/bradymholt/cronstrue) from 2.5.0 to 2.11.0.
- [Release notes](https://github.com/bradymholt/cronstrue/releases)
- [Changelog](https://github.com/bradymholt/cRonstrue/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bradymholt/cronstrue/compare/v2.5.0...v2.11.0)

---
updated-dependencies:
- dependency-name: cronstrue
  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>
2022-07-26 14:52:17 +00:00
Kira Pilot ef260faf27 fix: remove flaking test (#3207) 2022-07-26 10:35:13 -04:00
Mathias Fredriksson 159137dc10 fix: Use stdin/out defined in command (#3199) 2022-07-26 17:23:32 +03:00
dependabot[bot] 9fe260d5ea chore: bump eslint from 8.15.0 to 8.20.0 in /site (#3205)
Bumps [eslint](https://github.com/eslint/eslint) from 8.15.0 to 8.20.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.15.0...v8.20.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>
2022-07-26 10:22:52 -04:00
dependabot[bot] 8d6949a0b1 chore: bump @fontsource/ibm-plex-mono from 4.5.9 to 4.5.10 in /site (#2944)
Bumps [@fontsource/ibm-plex-mono](https://github.com/fontsource/fontsource/tree/HEAD/fonts/google/ibm-plex-mono) from 4.5.9 to 4.5.10.
- [Release notes](https://github.com/fontsource/fontsource/releases)
- [Changelog](https://github.com/fontsource/fontsource/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fontsource/fontsource/commits/HEAD/fonts/google/ibm-plex-mono)

---
updated-dependencies:
- dependency-name: "@fontsource/ibm-plex-mono"
  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>
2022-07-26 10:12:18 -04:00
dependabot[bot] 3f2cbc9b85 chore: bump @playwright/test from 1.23.2 to 1.24.1 in /site (#3203)
Bumps [@playwright/test](https://github.com/Microsoft/playwright) from 1.23.2 to 1.24.1.
- [Release notes](https://github.com/Microsoft/playwright/releases)
- [Commits](https://github.com/Microsoft/playwright/compare/v1.23.2...v1.24.1)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  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>
2022-07-26 10:11:04 -04:00
dependabot[bot] 9a3baffe43 chore: bump @pmmmwh/react-refresh-webpack-plugin in /site (#3184)
Bumps [@pmmmwh/react-refresh-webpack-plugin](https://github.com/pmmmwh/react-refresh-webpack-plugin) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/pmmmwh/react-refresh-webpack-plugin/releases)
- [Changelog](https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pmmmwh/react-refresh-webpack-plugin/compare/v0.5.6...v0.5.7)

---
updated-dependencies:
- dependency-name: "@pmmmwh/react-refresh-webpack-plugin"
  dependency-type: direct:development
  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>
2022-07-26 09:54:28 -04:00
dependabot[bot] 100584d95c chore: bump github.com/klauspost/compress from 1.15.8 to 1.15.9 (#3162)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.15.8 to 1.15.9.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml)
- [Commits](https://github.com/klauspost/compress/compare/v1.15.8...v1.15.9)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  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>
2022-07-26 16:46:30 +03:00
Mathias Fredriksson d1d89210b8 fix: Disable telemetry by default in tests (#3200)
I also noticed we don't have `goleak` enabled for CLI tests, this commit
adds it, but commented out. The reason being that we're nowhere near
being able to enable it yet.

Co-authored-by: Cian Johnston <cian@coder.com>
2022-07-26 16:27:48 +03:00
dependabot[bot] 122c6f06d8 chore: bump github.com/unrolled/secure from 1.11.0 to 1.12.0 (#3017)
Bumps [github.com/unrolled/secure](https://github.com/unrolled/secure) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/unrolled/secure/releases)
- [Commits](https://github.com/unrolled/secure/compare/v1.11.0...v1.12.0)

---
updated-dependencies:
- dependency-name: github.com/unrolled/secure
  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>
2022-07-26 16:26:07 +03:00
Cian Johnston 2c0d57e8c0 fix: update reference to agent.dev in examples and docs (#3198)
* fix: update agent ID in example templates
* fix: update agent ID in dogfood template
* chore: update default agent ID in documentation
* fix: develop.sh: start FE after template is created; leave template dir around if template creation fails
2022-07-26 14:09:09 +01:00
Mathias Fredriksson 9a9912c8ce fix: Add go.mod to prcontext and use build vs go run (#3197) 2022-07-26 16:04:00 +03:00
Mathias Fredriksson 0b86c8047c fix: Close connections in agent tests (#3196) 2022-07-26 13:24:54 +03:00
Mathias Fredriksson f34b5000cb fix: Avoid logging to stdout in devtunnel test (#3194)
The device keeps logging to the logger even after `dev.Close()` but
doing that with `t.Log` is unsafe (test has ended). This is why
`slogtest` was used.

`dev.Close()` has a wait on encryption and decryption routines, however,
these are left running even after the wait. The implementation uses the
WaitGroups in a weird way.
2022-07-26 12:20:21 +03:00
Kira Pilot 9bf5537b0f feat: showcase workspace state in actions dropdown (#3133)
* show progress indicator within workspace dropdown

resolves #2020

* wrote tests

* fix loading button

* PR feedback

* added stories for dropdown content

* PR feedbac
2022-07-25 18:12:59 -04:00
Bruno Quaresma b0957f32e3 feat: Add mobile navbar (#3186) 2022-07-25 17:54:11 +00:00
Mathias Fredriksson 173ab297be chore: Increase style/gen CI test timeout (#3187) 2022-07-25 17:10:53 +00:00
Mathias Fredriksson 92a95fbd5f fix: Rewrite ptytest to buffer stdout (#3170)
Fixes #2122
2022-07-25 20:02:34 +03:00
Mathias Fredriksson d7dee2c069 fix: Improve code coverage reporting in codecov (#2715)
* fix: Remove explicit coverpkg github.com/coder/coder/codersdk

This package is already covered by ./...

* fix: Ignore test utils in coverage (clitest, coderdtest, ptytest)
2022-07-25 19:55:19 +03:00
dependabot[bot] 6c5a142674 chore: bump dayjs from 1.11.3 to 1.11.4 in /site (#3180)
Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.11.3 to 1.11.4.
- [Release notes](https://github.com/iamkun/dayjs/releases)
- [Changelog](https://github.com/iamkun/dayjs/blob/v1.11.4/CHANGELOG.md)
- [Commits](https://github.com/iamkun/dayjs/compare/v1.11.3...v1.11.4)

---
updated-dependencies:
- dependency-name: dayjs
  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>
2022-07-25 12:35:42 -04:00
dependabot[bot] 1859ca568d chore: bump eslint-plugin-jsx-a11y from 6.6.0 to 6.6.1 in /site (#3179)
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.6.0 to 6.6.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsx-a11y
  dependency-type: direct:development
  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>
2022-07-25 12:35:17 -04:00
Mathias Fredriksson 1c04b20fde fix: Set cache dir for coderd tests (#3160)
* fix: Set cache dir for coderd in codedtest

* fix: Ensure server cli tests have a cache path

To avoid sharing default path.
2022-07-25 19:24:32 +03:00
Mathias Fredriksson 6916d34458 fix: Fix cleanup in test helpers, prefer defer in tests (#3113)
* fix: Change uses of t.Cleanup -> defer in test bodies

Mixing t.Cleanup and defer can lead to unexpected order of execution.

* fix: Ensure t.Cleanup is not aborted by require

* chore: Add helper annotations
2022-07-25 19:22:02 +03:00
dependabot[bot] c2cd51d8b8 chore: bump sql-formatter from 8.0.2 to 8.2.0 in /site (#3178)
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 8.0.2 to 8.2.0.
- [Release notes](https://github.com/sql-formatter-org/sql-formatter/releases)
- [Changelog](https://github.com/sql-formatter-org/sql-formatter/blob/master/.release-it.json)
- [Commits](https://github.com/sql-formatter-org/sql-formatter/compare/v8.0.2...v8.2.0)

---
updated-dependencies:
- dependency-name: sql-formatter
  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>
2022-07-25 12:12:58 -04:00
dependabot[bot] 456318cbd8 chore: bump eslint-import-resolver-typescript in /site (#3177)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.2.5 to 3.3.0.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.2.5...v3.3.0)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-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>
2022-07-25 12:09:30 -04:00
dependabot[bot] 4a0b8440bc chore: bump @types/node from 14.18.21 to 14.18.22 in /site (#3174)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.18.21 to 14.18.22.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  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>
2022-07-25 12:04:51 -04:00
dependabot[bot] 3c38a23e27 chore: bump eslint-plugin-react from 7.30.0 to 7.30.1 in /site (#3172)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.30.0 to 7.30.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.30.0...v7.30.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  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>
2022-07-25 11:55:48 -04:00
Bruno Quaresma 821ae5dbd7 chore: Add colors object with the Coder color palette (#3173) 2022-07-25 12:49:00 -03:00
Mathias Fredriksson 4d53934eb0 fix: (Re-)enable TestPasswordTerminalState test (#3169) 2022-07-25 18:42:20 +03:00
David Wahler 5312296283 fix: Add a slightly better error message for dropped SSH connection (#3131) 2022-07-25 10:25:34 -05:00
dependabot[bot] f0f0aebdbb chore: bump @testing-library/user-event from 14.2.0 to 14.3.0 in /site (#3163)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 14.2.0 to 14.3.0.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.2...v14.3)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  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>
2022-07-25 10:35:21 -04:00
Mathias Fredriksson d7ec407a7c fix: Improve coder list and show CLI help (#3167) 2022-07-25 16:56:20 +03:00
Mathias Fredriksson 233aa17848 fix: Avoid dirtying stdout/stderr in test (#3165)
* fix: Default all clitest commands to io.Discard stdout/err

* fix: Never write to stdout or stderr in tests
2022-07-25 16:55:53 +03:00
Mathias Fredriksson ad2b29a571 fix: Remove hardcoded /tmp path in test (#3168) 2022-07-25 16:55:06 +03:00
Mathias Fredriksson 2c67a2f30b fix: Close bug in pty (#3166) 2022-07-25 16:31:30 +03:00
Mathias Fredriksson 592340c6ce fix: Data race in cliui.Styles without clone (#3164) 2022-07-25 16:30:52 +03:00
Ammar Bandukwala 54547a4e9a ci: fix postgres skipper (#3157) 2022-07-24 19:58:20 +00:00
Ammar Bandukwala 60de8d0279 ci: add skip directives for long tests (#3151)
This PR introduces many CI optimizations:

1. The `[ci-skip]` PR body directive to skip the Postgres and end to end tests
2. Improved caching that cuts the Go test matrix in half
3. Increasing Go test parallelism for ~20% gains
4. Enable caching in webpack (4x frontend build)
2022-07-24 14:33:58 -05:00
Ammar Bandukwala 5578facf8f Fix stalebot (#3156) 2022-07-24 19:32:41 +00:00
Ammar Bandukwala ecb6301cab docs: make small style improvements (#3065) 2022-07-23 16:37:54 -05:00
Ammar Bandukwala e4251af8f3 ci: configure stale bot some more (#3148) 2022-07-23 16:37:19 -05:00
Ammar Bandukwala 3eb6f28d81 ci: fix master build 2022-07-23 21:36:15 +00:00
Ammar Bandukwala d10513f43a ci: optimize jobs with path filtering (#3074) 2022-07-23 21:33:25 +00:00
mark-theshark 1ddff0abcd chore: docs to create admin user and workspace creation from UI screenshot (#3149) 2022-07-23 20:44:28 +00:00
Ammar Bandukwala f28d14197a Rename default agent to "main" instead of "dev" (#3150)
Resolves #3143
2022-07-23 20:26:56 +00:00
Ammar Bandukwala 257e52e014 ci: aggressively close stale PRs (#3146) 2022-07-23 14:57:35 -05:00
Spike Curtis 5e32468a73 Add JetBrains Gateway doc (#3104)
* Add JetBrains Gateway doc

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

* Added GitHub issue to track Gateway failure

Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-22 15:32:16 -07:00
Kyle Carberry c6016d247d docs: Update hero image to the dashboard (#3132) 2022-07-22 21:00:21 +00:00
Bruno Quaresma ca93614c3f refactor: Make workspace status more visible (#3130) 2022-07-22 19:18:52 +00:00
Abhineet Jain 1b19a09a37 feat: New static error summary component (#3107) 2022-07-22 19:10:40 +00:00
Kyle Carberry fd4954b4e5 fix: Use membership endpoint to ensure user exists in team (#3129)
This was using the incorrect GitHub endpoint prior, which fetched a team
by slug. Any user in a GitHub organization can view all teams, so this
didn't block signups like intended.

I've verified this API returns an error when the calling user is not a
member  of the team requested.

Fixes #3105.
2022-07-22 13:54:08 -05:00
Kira Pilot 471564df7d feat: improve update button visibility (#3115)
* feat: give update button primary focus when applicable

resolves #3024

* added update tooltip

* cleanup

* prettier

* PR feedback
2022-07-22 14:28:52 -04:00
Joe Previte 2dd98c7ec8 docs: add dogfooding guide (#3099) 2022-07-22 18:22:11 +00:00
Mathias Fredriksson 51dd1fde3b fix: Remove use of require in require.Eventually in tests (#3110)
* fix: Remove use of `require` in `require.Eventually` in tests

Because require uses `t.FailNow()` and `require.Eventually` runs the
function in a goroutine, which is not allowed.

* feat: Add ruleguard for require.Eventually

Co-authored-by: Cian Johnston <cian@coder.com>
2022-07-22 20:02:49 +03:00
Bruno Quaresma 3bb760576b fix: Add resource icons into template page (#3124) 2022-07-22 11:46:51 -05:00
Spike Curtis fa4361db76 restore devtunnel test (#3050)
* Dev tunnel test uses local fake server; fixed port

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

* Remove parallel for test

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

* Fix segfault
2022-07-22 08:26:39 -07:00
Kira Pilot 882ee55fd0 fix: storybook should use absolute paths (#3119) 2022-07-22 11:02:54 -04:00
Ben Potter f43eb0e77c fix: minor fixes to templates docs (#3117) 2022-07-22 09:59:19 -05:00
Cian Johnston 1140e29a17 chore: autobuild/executor: refactor big switch statement for legibility (#3116) 2022-07-22 15:45:12 +01:00
Mathias Fredriksson ef7d357e19 fix: Move timeout ctx closer to use in tests, increase timeout (#3109)
Some contexts were moved closer to use so that test setup doesn't affect
timeout. And timeout was increased for some others to avoid flakyness
due to slow test runners.
2022-07-22 17:42:09 +03:00
Bruno Quaresma e874d538fb feat: Add resource icons (#3118) 2022-07-22 11:38:38 -03:00
Mathias Fredriksson 7d07e670ca chore: Improve test cleanup (#3112) 2022-07-22 15:14:45 +03:00
Mathias Fredriksson 75ff579051 fix: Decrease postgres test timeout (make test-postgres) (#3108)
This commit lowers the postgres test timeout from 30m to 20m, currently
our postgres tests seem to take 8-10m, a 2x factor should suffice.
Comments were updated in both places to reflect the reasoning and
necessity of keeping these values in sync.

They used to take longer but the `count` was lowered in
3d40cb85b7.

The actual timeout value of `make test-postgres` got overlooked in
https://github.com/coder/coder/pull/3079.
2022-07-22 12:47:03 +03:00
Kira Pilot 0aa8c2efeb fix: set a failed canceled job status correctly (#3101)
* set a failed canceled job status correctly

resolves #1374

* added unit test for convertProvisionerJob

* Update coderd/provisionerjobs_internal_test.go

Co-authored-by: Cian Johnston <cian@coder.com>

* PR feedback

Co-authored-by: Cian Johnston <cian@coder.com>
2022-07-21 16:47:06 -04:00
mark-theshark 77f4ab16a4 feat: update IDE docs with advanced examples with pods and custom images (#3002) 2022-07-21 20:03:03 +00:00
David Wahler 7f54628848 config-ssh: always support agent name in host alias (#3036) 2022-07-21 14:49:32 -05:00
dependabot[bot] c9d7cbca48 chore: bump github.com/nhatthm/otelsql from 0.3.4 to 0.4.0 (#3069)
Bumps [github.com/nhatthm/otelsql](https://github.com/nhatthm/otelsql) from 0.3.4 to 0.4.0.
- [Release notes](https://github.com/nhatthm/otelsql/releases)
- [Commits](https://github.com/nhatthm/otelsql/compare/v0.3.4...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/nhatthm/otelsql
  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>
2022-07-21 14:39:15 -05:00
dependabot[bot] 06e0a5b1e4 chore: bump google.golang.org/api from 0.86.0 to 0.88.0 (#3070)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.86.0 to 0.88.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.86.0...v0.88.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>
2022-07-21 14:38:56 -05:00
Cian Johnston 59b04c154e fix: coderdtest: increase ForceCancelInterval (#3085)
Two coderd unit tests (TestPatchCancelTemplateVersion/Success and TestPatchCancelWorkspaceBuild) implied erroneously that the job was canceled successfully.

This is not the case, as these unit tests do not include a Provision_Complete response in the input to the
echo provisioner. Now explicitly checking the job error and bumping the force cancel interval to be longer.

Fixes #3083.
2022-07-21 19:29:45 +00:00
Jon Ayers e01905821f fix: avoid emitting version warning when connection error encountered (#3082) 2022-07-21 14:28:24 -05:00
Bruno Quaresma 5b78251592 refactor: Initial color palette changes (#3087) 2022-07-21 17:56:16 +00:00
Mathias Fredriksson e33a74975e fix: Deadlock and race in peer, test improvements (#3086)
* fix: Potential deadlock in peer.Channel dc.OnOpen

* fix: Potential send on closed channel

* fix: Improve robustness of waitOpened during close

* chore: Simplify statements

* fix: Improve teardown and timeout of peer tests

* fix: Improve robustness of TestConn/Buffering test

* Update peer/channel.go

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
2022-07-21 18:47:17 +03:00
Jon Ayers 62e685669f feat: add verbose error messaging (#3053) 2022-07-20 15:17:51 -05:00
Mathias Fredriksson 4a7d067c6c fix: Increase CI timeout for postgres test (#3079)
The Go test timeout uses 20m, if we want to get a stack trace, we must
allow the actions worker to run longer than that.
2022-07-20 19:09:26 +00:00
Mathias Fredriksson 96edc8af9a fix: Add continue-on-error to codecov action step (#3081)
Avoid relying on codecov to manage action step failure, hopefully works
around:
https://github.com/codecov/codecov-action/issues/788
2022-07-20 19:04:40 +00:00
Mathias Fredriksson 3e5affd28a fix: Increase test timeout for TestCreate/CreateFromListWithSkip (#3077)
Considering database load and CI performance during testing, we should
avoid failing too early.
2022-07-20 17:51:33 +00:00
Ammar Bandukwala b0c26745fb ci: fix stale issue workflow (#3073) 2022-07-20 17:24:30 +00:00
Bruno Quaresma 916c388d8d fix: Statuses breaking line in the UI (#3071)
* fix: Fix statuses breaking line in the UI

* fix: AppLink stories
2022-07-20 17:11:20 +00:00
dependabot[bot] 82f159b8c3 chore: bump terser from 4.8.0 to 4.8.1 in /site (#3068)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 12:58:43 -04:00
Ammar Bandukwala cf9bc71c03 ci: skip long jobs when only docs change (#3072) 2022-07-20 16:47:41 +00:00
Mathias Fredriksson 4fde5366be fix: Improve TestSSH reliability on macOS (#3067)
Related issue: https://github.com/coder/coder/issues/2122
2022-07-20 19:24:15 +03:00
dependabot[bot] 6199e6a060 chore: bump github.com/spf13/afero from 1.9.0 to 1.9.2 (#3046)
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.9.0 to 1.9.2.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.9.0...v1.9.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  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>
2022-07-20 11:20:48 -05:00
Kira Pilot 0c18a2313f clarify start validation in schedule (#3052)
resolves #2792
2022-07-20 11:54:57 -04:00
Mathias Fredriksson 034416f141 chore: Speed up port-forward tests (#3062)
* chore: Speed up port-forward tests

* chore: Add t.Helper and ensure listener closure on error
2022-07-20 18:11:25 +03:00
Mathias Fredriksson cd74afcccc fix: Increase randomness for names used in tests (#3063)
We are starting to run into test flakes due to lack of randomness in CI,
this change simply bumps randomness by additional suffix numbers.

See: https://github.com/coder/coder/issues/3038#issuecomment-1190283608
2022-07-20 18:03:04 +03:00
Ammar Bandukwala 87b0b4b1ea docs: add secrets (#3057) 2022-07-20 07:31:33 -05:00
David Wahler f7ea016494 Pass git configuration variables via terraform (#3034)
* Pass workspace owner email address to provisioner

* Remove owner_email and owner_username fields from agent metadata

* Add Git environment variables to example templates

* Remove "owner_name" field from provisioner metadata, use username instead

* Remove Git configuration from most templates, add documentation

* Proofreading/typo fixes from @mafredri

* Update example templates to latest version of terraform-provider-coder
2022-07-19 13:24:06 -05:00
Alon David b9847c18f4 solves #2535. (#2557)
remove leading V in coder container image tag
2022-07-19 17:06:03 +00:00
dependabot[bot] a69bd47b3a chore: bump github.com/jedib0t/go-pretty/v6 from 6.3.3 to 6.3.5 (#3028)
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.3.3 to 6.3.5.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.3.3...v6.3.5)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  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>
2022-07-19 11:41:51 -05:00
dependabot[bot] caf2478cf6 chore: bump github.com/pion/webrtc/v3 from 3.1.42 to 3.1.43 (#3027)
Bumps [github.com/pion/webrtc/v3](https://github.com/pion/webrtc) from 3.1.42 to 3.1.43.
- [Release notes](https://github.com/pion/webrtc/releases)
- [Commits](https://github.com/pion/webrtc/compare/v3.1.42...v3.1.43)

---
updated-dependencies:
- dependency-name: github.com/pion/webrtc/v3
  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>
2022-07-19 11:27:41 -05:00
Ammar Bandukwala c86a623ff8 Remove Joe from docs CODEOWNERS (#3037) 2022-07-19 16:27:13 +00:00
Kira Pilot 1830a18565 feat: amend schedule form valiidation string (#3043)
resolves #2792
2022-07-19 12:04:06 -04:00
Ben Potter b6ad5623a3 example: add a bare/custom template (#2965) 2022-07-19 13:29:24 +00:00
Ammar Bandukwala a2f6b25110 Add new Dogfood template (#2959)
* Setup base template

* Add sysbox

* Run code-server in background

* Fix small typo
2022-07-18 22:44:09 +00:00
Ammar Bandukwala a66b852c81 ci: fix stale (#3030)
- Add necessary runs-on
- Use lowercase labels for consistency
2022-07-18 19:58:51 +00:00
Ammar Bandukwala 5919e96ac2 ci: add stale workflow (#3029) 2022-07-18 19:34:04 +00:00
Mathias Fredriksson 54cf677e80 chore: Switch back to upstream for hashicorp/yamux (#3026)
All our fixes have been upstreamed, so we are switching back.
2022-07-18 20:27:26 +03:00
dependabot[bot] 4f6b2cff83 chore: bump github.com/spf13/afero from 1.8.2 to 1.9.0 (#3014)
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.8.2 to 1.9.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.8.2...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  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>
2022-07-18 12:12:10 -05:00
Mathias Fredriksson 3a692a6cdb fix: Sort ComputedValue according to parameter schema index (#3022)
This fixes a test-flake in TestTemplateVersionParameters/List and gives
us consistent sorting for parameters.
2022-07-18 19:39:24 +03:00
Kira Pilot c0d19ebea2 chore: configure absolute paths with webpack (#3011)
* coonfigure absolute paths with webpack

resolves #1855

* fixed jest config
2022-07-18 10:43:11 -04:00
dependabot[bot] 6d1ec409d0 chore: bump github.com/klauspost/compress from 1.15.7 to 1.15.8 (#3015)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.15.7 to 1.15.8.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml)
- [Commits](https://github.com/klauspost/compress/compare/v1.15.7...v1.15.8)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  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>
2022-07-18 10:08:43 -04:00
Abhineet Jain ccdf82dd7e feat: show template versions (#3003) 2022-07-15 15:25:47 -07:00
Kira Pilot 9a5fa3f050 fix: border fixes for workspace schedule button (#3010)
* border fixes for workspace schedule button

* fixing chromatic snapshots

* chromatic fix
2022-07-15 18:04:33 -04:00
Abhineet Jain d04ba2cc02 feat: add template version creator (#3001) 2022-07-15 14:12:39 -07:00
Bruno Quaresma d26b3b7ba1 refactor: Remove avatar from workspace name (#3006) 2022-07-15 15:49:18 +00:00
Colin Adler 680e24a14b Revert "feat: add template version creator (#2991)" (#2999)
This reverts commit aea3b3b83e.
2022-07-14 21:57:42 +00:00
Colin Adler 1033e02d79 feat: add coder server postgres-builtin-serve to run the built-in DB (#2997) 2022-07-14 21:51:44 +00:00
Kira Pilot eebf0dd736 feat: consolidate workspace buttons/kira pilot (#2996)
* added workspace cta dropdown

resolves #2748

* added tests

* fixed failing tests

* clean up snapshots
2022-07-14 16:47:10 -04:00
Abhineet Jain aea3b3b83e feat: add template version creator (#2991) 2022-07-14 20:44:33 +00:00
Ali Diamond 6ef8a625d5 Update workspaces.md (#2993)
adding missing command word
2022-07-14 18:31:01 +00:00
Bruno Quaresma adcd6f5cf1 refactor: Make the workspace panels more light (#2984) 2022-07-14 15:09:07 -03:00
Bruno Quaresma c8d04aff6b feat: Add status badge to the favicon (#2978) 2022-07-14 14:45:03 +00:00
Abhineet Jain bf1af216e1 fix: remove access column header (#2976) 2022-07-13 17:59:09 -07:00
Kyle Carberry 8e17254785 fix: Add test for wrapping init script with single quotes (#2979)
This ensures our initialization script works with single  uotes.
2022-07-13 17:43:48 -05:00
David Wahler b5f5e909bd Return template parameters in consistent order (#2975)
* return parameters from Terraform provisioner in sorted order

* persist parameter indices in database and return them in correct order from API

* don't re-sort parameters by name when creating templates
2022-07-13 15:29:34 -05:00
Abhineet Jain b692b7ea14 fix: remove system user highlighting (#2973) 2022-07-13 11:48:26 -07:00
Bruno Quaresma 000bc50258 refactor: Refactor last built by column (#2968) 2022-07-13 12:37:12 -03:00
Bruno Quaresma 02129332d7 fix: Loading state in the workspaces page (#2967) 2022-07-13 11:49:07 -03:00
Cian Johnston 0f5f30b6f6 fix: make agent scripts easier to troubleshoot (#2922)
- Adds distinct exit statuses to the bootstrap scripts
- Makes the bootstrap scripts loop forever trying to download the coder agent
- Surfaces and logs the status codes returned by the download tool
2022-07-13 10:17:40 +01:00
Kyle Carberry 6f34cbff1e fix: Use double quotes for trap signal (#2956)
Frequently callers will wrap our shell script in `sh -c ''`.
Having single quotes on our `trap` led to a syntax error when
doing this.
2022-07-13 01:09:59 +00:00
Kyle Carberry 8b76e40629 fix: Fetch GitHub teams by name for performance (#2955)
In large organizations with thousands of teams, looping took >5s.
This fetches organizations by team name, which should be very fast!
2022-07-13 00:45:43 +00:00
Jon Ayers 7e9819f2a8 ref: move httpapi.Reponse into codersdk (#2954) 2022-07-12 19:15:02 -05:00
Kyle Carberry dde51f1caa fix: Force trap to always succeed due to incompatibility (#2953)
There are some instances of Linux that don't support trap. We should
ignore the failure in those cases.
2022-07-12 23:31:25 +00:00
Kyle Carberry 5ee112bc00 fix: Fetch all GitHub teams on login (#2951)
This wasn't looping prior, so organizations with >100 teams
couldn't login. Contributes to #2848.
2022-07-12 23:06:27 +00:00
Mathias Fredriksson 59facdd8dc fix: Show schedule commands in help, improve template (#2923)
* fix: Show schedule commands in help, improve template

* chore: Remove schedule long help, fixed by listing missing commands

* chore: Clean up annotation usage with template function

* fix: Drive-by fix for trailing whitespace for flags

Introduced in c7681370b5.
2022-07-12 23:24:53 +03:00
Mathias Fredriksson 2d048803c8 chore: Switch drpc from fork to upstream (#2949)
The https://github.com/storj/drpc/pull/31 PR was not merged, but was
replaced by:

https://github.com/storj/drpc/commit/9206537a4db76809da6ec768a0c5e45ddb618ef5

This fixes the underlying issue fixed in the fork.
2022-07-12 22:20:22 +03:00
Mathias Fredriksson e035b642b8 chore: Update yamux fork (#2948) 2022-07-12 22:10:24 +03:00
Cian Johnston 5e6320163d change default aws linux instance type to t3.micro, reduce default template TTL (#2776)
- make default template max TTL 24 hours (still less than 168)
- make default workspace autostop 2 hours unless specified otherwise
- add instance type selector to aws templates
2022-07-12 19:37:59 +01:00
Steven Masley c07a45e610 fix: Fix workspace count to exclude deleted workspaces (#2916) 2022-07-12 12:52:28 -05:00
Abhineet Jain 61c52b3090 feat: default confirm to no for cli delete (#2919) 2022-07-12 10:36:07 -07:00
Abhineet Jain b0bab3e432 feat: show last build initiator for workspaces (#2921) 2022-07-12 10:14:36 -07:00
Bruno Quaresma e172a40a91 fix: Add links to the SSH popover (#2945) 2022-07-12 16:45:53 +00:00
Bruno Quaresma 166bc273b3 feature: Add SSH button in the agent access column (#2931) 2022-07-12 13:10:38 -03:00
dependabot[bot] 0645176e66 chore: bump webpack-dev-server from 4.9.0 to 4.9.3 in /site (#2939)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.9.0 to 4.9.3.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.9.0...v4.9.3)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-type: direct:development
  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>
2022-07-12 12:07:47 -04:00
dependabot[bot] 8df4212bbb chore: bump ts-loader from 9.3.0 to 9.3.1 in /site (#2940)
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 9.3.0 to 9.3.1.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/main/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v9.3.0...v9.3.1)

---
updated-dependencies:
- dependency-name: ts-loader
  dependency-type: direct:development
  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>
2022-07-12 11:58:11 -04:00
dependabot[bot] 18a9d070af chore: bump @playwright/test from 1.22.1 to 1.23.2 in /site (#2934)
Bumps [@playwright/test](https://github.com/Microsoft/playwright) from 1.22.1 to 1.23.2.
- [Release notes](https://github.com/Microsoft/playwright/releases)
- [Commits](https://github.com/Microsoft/playwright/compare/v1.22.1...v1.23.2)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  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>
Co-authored-by: Kira Pilot <kira@coder.com>
2022-07-12 11:22:03 -04:00
dependabot[bot] 919e3a5fb5 chore: bump @testing-library/react-hooks from 8.0.0 to 8.0.1 in /site (#2936)
Bumps [@testing-library/react-hooks](https://github.com/testing-library/react-hooks-testing-library) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/testing-library/react-hooks-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-hooks-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-hooks-testing-library/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: "@testing-library/react-hooks"
  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>
Co-authored-by: Kira Pilot <kira@coder.com>
2022-07-12 11:21:49 -04:00
dependabot[bot] 8acae4b5aa chore: bump chromatic from 6.5.4 to 6.7.0 in /site (#2935)
Bumps [chromatic](https://github.com/chromaui/chromatic-cli) from 6.5.4 to 6.7.0.
- [Release notes](https://github.com/chromaui/chromatic-cli/releases)
- [Changelog](https://github.com/chromaui/chromatic-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chromaui/chromatic-cli/compare/v6.5.4...v6.7.0)

---
updated-dependencies:
- dependency-name: chromatic
  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>
Co-authored-by: Kira Pilot <kira@coder.com>
2022-07-12 11:19:17 -04:00
dependabot[bot] 516dc190ad chore: bump @typescript-eslint/eslint-plugin in /site (#2933)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.27.0 to 5.30.6.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.30.6/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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>
2022-07-12 10:50:45 -04:00
dependabot[bot] 4cfa240065 chore: bump eslint-plugin-jest from 26.2.2 to 26.5.3 in /site (#2926)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 26.2.2 to 26.5.3.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v26.2.2...v26.5.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  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>
Co-authored-by: Kira Pilot <kira@coder.com>
2022-07-12 10:46:46 -04:00
dependabot[bot] 516d955219 chore: bump webpack from 5.72.0 to 5.73.0 in /site (#2932)
Bumps [webpack](https://github.com/webpack/webpack) from 5.72.0 to 5.73.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.72.0...v5.73.0)

---
updated-dependencies:
- dependency-name: webpack
  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>
2022-07-12 10:46:15 -04:00
dependabot[bot] 453d6ff75d chore: bump eslint-plugin-jsx-a11y from 6.5.1 to 6.6.0 in /site (#2930)
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.5.1 to 6.6.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.5.1...v6.6.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsx-a11y
  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>
2022-07-12 10:31:35 -04:00
dependabot[bot] 701821ab28 chore: bump webpack-cli from 4.9.2 to 4.10.0 in /site (#2929)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.9.2 to 4.10.0.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.9.2...webpack-cli@4.10.0)

---
updated-dependencies:
- dependency-name: webpack-cli
  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>
2022-07-12 10:25:49 -04:00
dependabot[bot] b4bee421e9 chore: bump xterm from 4.18.0 to 4.19.0 in /site (#2927)
Bumps [xterm](https://github.com/xtermjs/xterm.js) from 4.18.0 to 4.19.0.
- [Release notes](https://github.com/xtermjs/xterm.js/releases)
- [Commits](https://github.com/xtermjs/xterm.js/compare/4.18.0...4.19.0)

---
updated-dependencies:
- dependency-name: xterm
  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>
2022-07-12 10:18:11 -04:00
dependabot[bot] c178f37a3e chore: bump eslint-import-resolver-typescript in /site (#2928)
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 2.7.1 to 3.2.5.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v2.7.1...v3.2.5)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-12 10:13:38 -04:00
dependabot[bot] 3070ef8903 chore: bump @xstate/cli from 0.1.7 to 0.2.1 in /site (#2908)
Bumps @xstate/cli from 0.1.7 to 0.2.1.

---
updated-dependencies:
- dependency-name: "@xstate/cli"
  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>
2022-07-12 09:49:07 -04:00
dependabot[bot] d497e1ce8d chore: bump cron-parser from 4.4.0 to 4.5.0 in /site (#2924)
Bumps [cron-parser](https://github.com/harrisiirak/cron-parser) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/harrisiirak/cron-parser/releases)
- [Commits](https://github.com/harrisiirak/cron-parser/compare/4.4.0...4.5.0)

---
updated-dependencies:
- dependency-name: cron-parser
  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>
2022-07-12 09:45:22 -04:00
dependabot[bot] 146473cafd chore: bump prettier-plugin-organize-imports in /site (#2910)
Bumps [prettier-plugin-organize-imports](https://github.com/simonhaenisch/prettier-plugin-organize-imports) from 2.3.4 to 3.0.0.
- [Release notes](https://github.com/simonhaenisch/prettier-plugin-organize-imports/releases)
- [Commits](https://github.com/simonhaenisch/prettier-plugin-organize-imports/compare/v2.3.4...v3.0.0)

---
updated-dependencies:
- dependency-name: prettier-plugin-organize-imports
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-12 09:45:09 -04:00
dependabot[bot] dcf5d57357 chore: bump @typescript-eslint/parser from 5.25.0 to 5.30.6 in /site (#2911)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.25.0 to 5.30.6.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.30.6/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  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>
2022-07-12 09:26:53 -04:00
Colin Adler 92ebdaec5a feat: force legacy tunnels to new version (#2914) 2022-07-12 00:33:35 +00:00
Ammar Bandukwala 59de95b8bb Minor docs fixes (#2920)
* Fix image links in quickstart

* Add myself to CODEOWNERS for docs
2022-07-11 22:35:24 +00:00
Abhineet Jain df13b9dfea fix: open multiple app windows (#2912) 2022-07-11 12:24:28 -07:00
Kyle Carberry 2c89e07e12 fix: Redirect to login when unauthenticated and requesting a workspace app (#2903)
Fixes #2884.
2022-07-11 13:46:01 -05:00
dependabot[bot] 08d90f7b4f chore: bump mini-css-extract-plugin from 2.6.0 to 2.6.1 in /site (#2906)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
  dependency-type: direct:development
  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>
2022-07-11 14:45:16 -04:00
dependabot[bot] 00fee2e501 chore: bump jest-junit from 13.2.0 to 14.0.0 in /site (#2907)
Bumps [jest-junit](https://github.com/jest-community/jest-junit) from 13.2.0 to 14.0.0.
- [Release notes](https://github.com/jest-community/jest-junit/releases)
- [Commits](https://github.com/jest-community/jest-junit/compare/v13.2.0...v14.0.0)

---
updated-dependencies:
- dependency-name: jest-junit
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-11 14:45:05 -04:00
Ketan Gangatirkar 536c77af5d fix: confirm when deleting template (#2866)
* prompt for confirmation before deleting templates (#2830)

* populate templateNames from the interactive picker too

* allow skipping delete confirmation prompt with --yes flag

* eliminate unnecessary newline

* test both confirmation of delete and `--yes` with no confirmation

* fix failing test that needed --yes

* remove unnecessary empty line the linter disliked

* make the tests correct
2022-07-11 13:13:56 -05:00
dependabot[bot] fa7dcf615a chore: bump prettier from 2.6.2 to 2.7.1 in /site (#2896)
Bumps [prettier](https://github.com/prettier/prettier) from 2.6.2 to 2.7.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.6.2...2.7.1)

---
updated-dependencies:
- dependency-name: prettier
  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>
2022-07-11 14:13:42 -04:00
dependabot[bot] 7d8b092af9 chore: bump @types/node from 14.18.16 to 14.18.21 in /site (#2905)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.18.16 to 14.18.21.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  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>
2022-07-11 14:12:26 -04:00
dependabot[bot] 312a19c270 chore: bump github.com/hashicorp/go-version from 1.5.0 to 1.6.0 (#2818)
Bumps [github.com/hashicorp/go-version](https://github.com/hashicorp/go-version) from 1.5.0 to 1.6.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.5.0...v1.6.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>
2022-07-11 18:06:28 +00:00
dependabot[bot] a585a986d8 chore: bump eslint-plugin-react-hooks from 4.5.0 to 4.6.0 in /site (#2864)
Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/HEAD/packages/eslint-plugin-react-hooks)

---
updated-dependencies:
- dependency-name: eslint-plugin-react-hooks
  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>
2022-07-11 13:01:50 -05:00
dependabot[bot] 420a07762a chore: bump go.opentelemetry.io/otel/exporters/otlp/otlptrace (#2895)
Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://github.com/open-telemetry/opentelemetry-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace
  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>
2022-07-11 13:01:30 -05:00
dependabot[bot] ef691f297a chore: bump @storybook/addon-actions from 6.4.22 to 6.5.9 in /site (#2519)
Bumps [@storybook/addon-actions](https://github.com/storybookjs/storybook/tree/HEAD/addons/actions) from 6.4.22 to 6.5.9.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v6.5.9/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v6.5.9/addons/actions)

---
updated-dependencies:
- dependency-name: "@storybook/addon-actions"
  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>
2022-07-11 13:54:11 -04:00
Ammar Bandukwala 13d7466ebc docs: add Docker quickstart (#2875)
* Fix docker-compose file

* Add docker quickstart
2022-07-11 12:05:05 -05:00
Ketan Gangatirkar 5eecbaa534 fix: trim leading and trailing spaces from template parameters (#2829) (#2879) 2022-07-11 11:46:03 -05:00
Mathias Fredriksson 749694b7de fix: Standardize and wrap example descriptions at 80 chars (#2894) 2022-07-11 19:08:09 +03:00
Kira Pilot 50e8a27d04 fix: removing noisy shutdown snapshots (#2899)
* fix: removing noisy shutdown snapshots

resolves #2685

* removing workspaceSchedule stories
2022-07-11 11:34:58 -04:00
Nicholas Pease 74d484eacf Update docs in reference to command change in #2530 (#2902) 2022-07-11 15:30:54 +00:00
dependabot[bot] 6d0aab4d2c chore: bump go.opentelemetry.io/otel/sdk from 1.7.0 to 1.8.0 (#2900)
Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/sdk
  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>
2022-07-11 10:22:59 -05:00
Ben Potter 71cb223564 chore: add matlab icon (#2891) 2022-07-11 17:19:35 +02:00
dependabot[bot] daadb9a532 chore: bump github.com/nhatthm/otelsql from 0.3.3 to 0.3.4 (#2898)
Bumps [github.com/nhatthm/otelsql](https://github.com/nhatthm/otelsql) from 0.3.3 to 0.3.4.
- [Release notes](https://github.com/nhatthm/otelsql/releases)
- [Commits](https://github.com/nhatthm/otelsql/compare/v0.3.3...v0.3.4)

---
updated-dependencies:
- dependency-name: github.com/nhatthm/otelsql
  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>
2022-07-11 09:36:05 -05:00
dependabot[bot] 8f55254167 chore: bump github.com/elastic/go-sysinfo from 1.8.0 to 1.8.1 (#2889)
Bumps [github.com/elastic/go-sysinfo](https://github.com/elastic/go-sysinfo) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/elastic/go-sysinfo/releases)
- [Changelog](https://github.com/elastic/go-sysinfo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elastic/go-sysinfo/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/elastic/go-sysinfo
  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>
2022-07-11 09:11:11 -05:00
Kyle Carberry 1973786335 fix: Add trap to agent startup script to sleep on failure (#2873)
* fix: Add `trap` to agent startup script to sleep on failure

The Docker Terraform provider removes containers immediately on exit, making
it difficult to debug a failed container start with Coder. This will sleep on
exit and output a friendly log, which should assist with debugging failures.

* Update provisionersdk/agent.go

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

* Update provisionersdk/agent.go

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

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-07-11 14:10:06 +00:00
dependabot[bot] 3e279b6d23 chore: bump tailscale.com from 1.26.1 to 1.26.2 (#2890)
Bumps [tailscale.com](https://github.com/tailscale/tailscale) from 1.26.1 to 1.26.2.
- [Release notes](https://github.com/tailscale/tailscale/releases)
- [Commits](https://github.com/tailscale/tailscale/compare/v1.26.1...v1.26.2)

---
updated-dependencies:
- dependency-name: tailscale.com
  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>
2022-07-11 09:09:17 -05:00
Mathias Fredriksson c7681370b5 fix: Wrap help flags at 100 chars (#2893)
Because the actual flags take quite a bit of space, wrapping at 80
characters creates a very cramped output for e.g. `coder server`, for
this reasons, flags are wrapped at 100 chars (vs. standard 80).

The `Consumes $ENV_FLAG` message was put on a newline for consistency,
this should allow users to learn where to look for the informations.

Side note: we should perhaps stop adding period (`.`) at the end of flag
descriptions to be consistent, for instance, command helps usually don't
have one.

This change fixes the biggest issue in #2363, but not all `--help`
output is guaranteed (yet) to wrap at 80-100 chars.

Fixes #2363
2022-07-11 17:07:25 +03:00
dependabot[bot] 2bf78aa548 chore: bump go.opentelemetry.io/otel/trace from 1.7.0 to 1.8.0 (#2887)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  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>
2022-07-11 08:54:26 -05:00
Mathias Fredriksson 41de2d8b67 fix: Replace github.com/hashicorp/yamux with our fork (#2883) 2022-07-11 16:51:03 +03:00
dependabot[bot] c99c15232c chore: bump xterm-addon-web-links from 0.5.1 to 0.6.0 in /site (#2892)
Bumps [xterm-addon-web-links](https://github.com/xtermjs/xterm.js) from 0.5.1 to 0.6.0.
- [Release notes](https://github.com/xtermjs/xterm.js/releases)
- [Commits](https://github.com/xtermjs/xterm.js/commits/0.6)

---
updated-dependencies:
- dependency-name: xterm-addon-web-links
  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>
2022-07-11 08:48:22 -05:00
dependabot[bot] 70d394f6a1 chore: bump @storybook/addon-links from 6.4.22 to 6.5.9 in /site (#2861)
Bumps [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/addons/links) from 6.4.22 to 6.5.9.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v6.5.9/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v6.5.9/addons/links)

---
updated-dependencies:
- dependency-name: "@storybook/addon-links"
  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>
2022-07-11 08:56:33 -04:00
Cian Johnston 8a59178e7e provisionersdk: extract and embed agent bootstrap scripts (#2886)
This commit extracts the existing hard-coded agent scripts to their own files and embeds them using go:embed.
We can more easily use e.g. shellcheck to validate our agent scripts.
2022-07-11 12:43:14 +01:00
Cian Johnston 8d8c1a1927 develop.sh: add missing embed tag (#2885) 2022-07-11 12:20:54 +01:00
Kyle Carberry 4f1df88529 fix: Always output job failure reason in provisioner daemon tests (#2850)
This flake can be seen here: https://github.com/coder/coder/runs/7186604615?check_suite_focus=true
2022-07-10 14:52:33 -05:00
mark-theshark 08a781f401 docs: expand web IDE documentation
* docker group for coder user and code-server xterm issue and port-forward web IDEs

* add screenshots of jupyterlab, rstudio and airflow in Coder

* Clean up English in install and minor edits

* Integrate Jupyter

Co-authored-by: ammario <ammar@ammar.io>
2022-07-09 02:40:05 +00:00
Kyle Carberry dff6e97f83 feat: Add allowlist of GitHub teams for OAuth (#2849)
Fixes #2848.
2022-07-08 21:37:18 -05:00
Kyle Carberry c801da45f3 fix: Add https: to image CSP to allow external images (#2870)
This broke external application icons.
2022-07-08 21:35:59 -05:00
Kyle Carberry 411caa20df fix: Refactor preinstall script to use useradd if adduser is not available (#2858)
Fixes #2800 preventing installation on Alpine.
2022-07-08 16:09:19 -05:00
Kyle Carberry 52fa1f2464 fix: Handle all method types for app proxying (#2868)
All methods need to be accepted on app routes. Some apps
may POST (like Jupyter).
2022-07-08 15:45:28 -05:00
dependabot[bot] 8589eb693a chore: bump sql-formatter from 6.1.1 to 8.0.2 in /site (#2862)
Bumps [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) from 6.1.1 to 8.0.2.
- [Release notes](https://github.com/sql-formatter-org/sql-formatter/releases)
- [Changelog](https://github.com/sql-formatter-org/sql-formatter/blob/master/.release-it.json)
- [Commits](https://github.com/sql-formatter-org/sql-formatter/compare/v6.1.1...v8.0.2)

---
updated-dependencies:
- dependency-name: sql-formatter
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-08 14:54:43 -05:00
dependabot[bot] ff5930c7fe chore: bump ts-node from 10.7.0 to 10.8.2 in /site (#2823)
Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 10.7.0 to 10.8.2.
- [Release notes](https://github.com/TypeStrong/ts-node/releases)
- [Commits](https://github.com/TypeStrong/ts-node/compare/v10.7.0...v10.8.2)

---
updated-dependencies:
- dependency-name: ts-node
  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>
2022-07-08 14:29:15 -05:00
Ketan Gangatirkar 2609be767d feat: add timestamps to output at end of some workspace and template subcommands (#2831) 2022-07-08 14:27:56 -05:00
Kyle Carberry 584448e089 fix: worksapces -> workspaces in template create CLI (#2857)
Fixes #2846.
2022-07-08 14:22:30 -05:00
Ketan Gangatirkar ca90189a9b fix: Upload the Windows .exe in CI (#2833)
Co-authored-by: kylecarbs <kyle@carberry.com>
2022-07-08 18:32:16 +00:00
dependabot[bot] c2bb5ee2b1 chore: bump github.com/klauspost/compress from 1.15.6 to 1.15.7 (#2816)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.15.6 to 1.15.7.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml)
- [Commits](https://github.com/klauspost/compress/compare/v1.15.6...v1.15.7)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  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>
2022-07-08 13:13:50 -05:00
dependabot[bot] 5df5507cf3 chore: bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (#2817)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.5 to 1.8.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.5...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  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>
2022-07-08 13:13:18 -05:00
dependabot[bot] a7b73fe001 chore: bump google.golang.org/api from 0.85.0 to 0.86.0 (#2819)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.85.0 to 0.86.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.85.0...v0.86.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>
2022-07-08 13:13:07 -05:00
dependabot[bot] 7ae1878c51 chore: bump github.com/unrolled/secure from 1.10.0 to 1.11.0 (#2820)
Bumps [github.com/unrolled/secure](https://github.com/unrolled/secure) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/unrolled/secure/releases)
- [Commits](https://github.com/unrolled/secure/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/unrolled/secure
  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>
2022-07-08 13:12:56 -05:00
Kyle Carberry bacfd630fb fix: Disable random workspace filter tests due to flakes (#2855)
Contributes towards #2854.
2022-07-08 13:01:00 -05:00
Kyle Carberry 3d40cb85b7 fix: Reduce count to 1 for PostgreSQL tests (#2852)
It's unnecessary for these to run twice. It increases CI times  without
providing much additional assurance tests don't have race conditions.
This already runs with `-race` too.
2022-07-07 23:14:35 -05:00
Kyle Carberry dc58d1b734 fix: Update text in logout tests (#2851)
This fixes CI!
2022-07-07 21:46:31 -05:00
Ketan Gangatirkar 4f1e9dae27 Ketan/cli help tweak (#2803)
* fix CLI help text for logout

"log out" is verb, "logout" is a noun

* add CLI help for port-forward command (#2802)

* found another noun where a verb should be
2022-07-04 15:48:08 -05:00
Ketan Gangatirkar 88f852b42f restore windows builds to CI (#2827)
restore windows builds to CI
2022-07-04 15:37:48 -05:00
Spike Curtis b1e4cfe6c8 fix pubsub/poll race on provisioner job logs (#2783)
* fix pubsub/poll race on provisioner job logs

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

* only cancel on non-error

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

* Improve logging & comments

Signed-off-by: spikecurtis <spike@spikecurtis.com>
2022-07-01 14:07:18 -07:00
Abhineet Jain c1b3080162 fix: restrict edit schedule access (#2698) 2022-07-01 20:43:51 +00:00
Bruno Quaresma ea5c2cd09b refactor: Downsize the search bar a bit (#2789) 2022-07-01 17:37:08 -03:00
Kira Pilot ead3516fb5 fixing searchBar style type (#2785) 2022-07-01 15:09:44 -04:00
Bruno Quaresma 2d0ea00ffd refactor: Move schedule to the header (#2775) 2022-07-01 18:26:27 +00:00
Spike Curtis 22febc749a provisionerd sends failed or complete last (#2732)
* provisionerd sends failed or complete last

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

* Move runner into package

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

* Remove jobRunner interface

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

* renames and slight reworking from code review

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

* Reword comment about okToSend

Signed-off-by: Spike Curtis <spike@coder.com>
2022-07-01 09:55:46 -07:00
Jon Ayers e5d5fa7706 fix: reprompt for matching passwords on mismatch (#2758)
- Previously we only re-prompted for the password confirmation.
2022-07-01 11:49:39 -05:00
Jon Ayers 554d9917c0 feat: make 'templates update [name]' optional (#2761)
- If the name is not specified the current working directory
  name is used or the name specified by "--directory". This
  reflects 'templates create" behavior.
2022-07-01 11:49:29 -05:00
David Wahler 0dbfd265fb chore: clean up scripts for internal godoc site that's no longer used (#2770) 2022-07-01 10:55:05 -05:00
Abhineet Jain de1fc40000 fix: consistent workspace status b/w CLI and UI (#2743) 2022-07-01 14:40:03 +00:00
Bruno Quaresma 9776e66ff9 refactor: Make the applications more notable in the resources table (#2774) 2022-07-01 10:48:48 -03:00
Cian Johnston e14953461c fix: develop.sh: do not clobber existing login, pre-build coder binary for speed (#2750) 2022-07-01 11:09:19 +01:00
Colin Adler 482feef373 feat(devtunnel): support geodistributed tunnels (#2711) 2022-06-30 19:11:13 -05:00
Kira Pilot ae59f166fd chore: cleaning up workspaces table code (#2765)
* cleaning up workspace table code

* Update site/src/components/WorkspacesTable/WorkspacesTableBody.tsx

Co-authored-by: Joe Previte <jjprevite@gmail.com>

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-06-30 16:50:15 -04:00
David Wahler 29be359f3d Clarify wording of install.sh --dry-run output (#2751) 2022-06-30 13:01:54 -05:00
Jon Ayers 6ad0f31687 fix: don't check version on gitssh cmds (#2757) 2022-06-30 12:03:41 -05:00
Kira Pilot 64997705ab feat: adding active and hover states to search input (#2741)
* feat: adding active and hover states to search input

* adding error state

* cleaning up error state

* adding input value
2022-06-30 11:57:38 -04:00
Cian Johnston 8ad35c7353 feature: allow editing workspace deadline in UI (#2721)
This PR adds two buttons to edit the workspace deadline.

- These buttons only appear when a workspace is running and has a non-zero deadline
- Clicking the  button increases the deadline by one hour, to a max of 24 hours in the future
- Clicking the  button decreases the deadline by one hour, to a minimum of 30 minutes in the future (when the warning banner appears)
2022-06-30 16:45:14 +01:00
Abhineet Jain 9df6bc7ba1 fix: update template updated_at value (#2729)
* fix: update template updated_at value

* use Go time for all updated_at updates
2022-06-30 12:14:51 +00:00
Jon Ayers 7df5827767 feat: add version checking to CLI (#2725) 2022-06-29 17:49:40 -05:00
dependabot[bot] 45328ec0f1 chore: bump github.com/AlecAivazis/survey/v2 from 2.3.4 to 2.3.5 (#2277)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-29 16:49:24 -05:00
Steven Masley 38fb6cb4b4 test: Try again in unit test if user already exists (#2730) 2022-06-29 14:17:32 -05:00
Kira Pilot 03fd063d20 chore: adding some quality of life Playwright comments (#2726) 2022-06-29 14:25:56 -04:00
Kira Pilot d9668f7a4e add debounced search on type to the search bar (#2703)
* debounced search on type

* loading workspaces on page entry

* fixing e2e test

* removing boilerplate
2022-06-29 13:43:41 -04:00
Colin Adler 6a55889362 fix: disable wireguard in portforward and gitssh tests (#2728) 2022-06-29 17:37:26 +00:00
Steven Masley baa36182c0 fix: Allow spaces in searches (#2723) 2022-06-29 11:59:38 -05:00
Steven Masley 889e2e68ea security: Tighten csp connect-src to prevent external websockets (#2705) 2022-06-29 16:42:17 +00:00
Steven Masley ea7f9e2d47 chore: Parameter listing cmd default adding scope column (#2718) 2022-06-29 11:29:21 -05:00
Ammar Bandukwala a06bea7a3f docs: improve providers illustration (#2713)
* Remove unused providers.png

* Add beautiful providers-compute
2022-06-28 22:11:43 -05:00
Colin Adler 2b6dcb842d Revert "feat: add version checking to CLI" (#2712) 2022-06-29 02:42:23 +00:00
Jon Ayers 7ee7be3391 feat: add version checking to CLI (#2643)
* feat: add version checking to CLI
2022-06-28 20:55:34 -05:00
Jon Ayers 4b6189c9e9 fix: fix panic in template pull (#2710) 2022-06-28 19:54:28 -05:00
Abhineet Jain 0d25e1752f feat: Add filter on Users page (#2653)
This commit adds a new filter feature to the Users page.

- adds a filter to the getUsers API call and users state machine.
- adds filter UI to Users page view.
- addresses error handling in the filter component, users page and machine.
- refactors user table code.
- refactors common code for workspace filter.
- adds and updates unit tests and stories.
2022-06-28 19:12:15 -04:00
Spike Curtis cb2d1f488a fix: ci uses a migrated DB template (#2696)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-28 14:42:19 -07:00
Steven Masley 576aef40f2 chore: Add linter rule to catch missing return after http writes (#2702) 2022-06-28 14:13:37 -05:00
Katie Horne 09cb778620 chore: add info re: always updating images (#2635) 2022-06-28 14:06:54 -05:00
Abhineet Jain 37f9dffc02 fix: remove gotests.xml from .gitignore (#2704) 2022-06-28 15:20:30 +00:00
Cian Johnston 0052e6a21b add CAP_NET_BIND_SERVICE to coder.service (#2699)
* add CAP_NET_BIND_SERVICE to systemd unit

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-06-28 16:02:42 +01:00
Colin Adler a494489ffa fix: use valid ip mask in api keys when remote address is ipv6 (#2695) 2022-06-27 20:31:18 +00:00
Kyle Carberry 69f27efead fix: Close parameter file before test exit (#2694)
Flake seen here:
https://github.com/coder/coder/runs/7079404499?check_suite_focus=true
2022-06-27 14:42:26 -05:00
Kyle Carberry abfae1b4aa fix: Add coder user to docker group on installation (#2693)
This makes for a simpler setup, and reduces the likelihood
a user runs into a strange issue.
2022-06-27 14:12:43 -05:00
Timo 752d6096a1 example: Added docker volume to docker-code-server (#2592) 2022-06-27 14:07:30 -05:00
Jon Ayers 2353687610 feat: unexpose coderdtest.NewWithAPI (#2613)
* feat: unexpose coderdtest.NewWithAPI
2022-06-27 13:50:52 -05:00
Timo 7dfec821f5 example: Added code-server icon (#2591) 2022-06-27 14:46:41 -04:00
dependabot[bot] 2d3d822273 chore: bump tailscale.com from 1.26.0 to 1.26.1 (#2677)
Bumps [tailscale.com](https://github.com/tailscale/tailscale) from 1.26.0 to 1.26.1.
- [Release notes](https://github.com/tailscale/tailscale/releases)
- [Commits](https://github.com/tailscale/tailscale/compare/v1.26.0...v1.26.1)

---
updated-dependencies:
- dependency-name: tailscale.com
  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>
2022-06-27 12:29:16 -05:00
Jon Ayers 3a3aa493f1 fix: use absolute path for config-ssh coder binary (#2647) 2022-06-27 12:15:55 -05:00
Kyle Carberry 6429dfee1f test: Use a template to prevent migrations from running for every test (#2462)
* test: Use a template to prevent migrations from running for every test

* Create a single makefile target

* Fix built-in race

* Extend timeout of built-in PostgreSQL fetch
2022-06-27 17:07:39 +00:00
Kyle Carberry d9da96cad0 fix: Add test for SCP (#2692)
* fix: Elongate agent disconnect timeout in tests

This will fix the flake seen here:
https://github.com/coder/coder/runs/7071719863?check_suite_focus=true

* fix: Add test for SCP

This was hanging due to the stdin pipe never being closed.
A test has been added to make sure it works!
2022-06-27 17:41:53 +01:00
dependabot[bot] a805565cd4 chore: bump github.com/hashicorp/hcl/v2 from 2.12.0 to 2.13.0 (#2680)
Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.12.0 to 2.13.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.12.0...v2.13.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>
2022-06-27 11:36:32 -05:00
Steven Masley f41b50a253 feat: Updating workspace prompts new parameters (#2598) 2022-06-27 16:19:10 +00:00
Katie Horne 407c47fd65 chore: change Coder v2 to Coder OSS in docs (#2630) 2022-06-27 11:07:17 -05:00
dependabot[bot] 68b5f0a35a chore: bump github.com/jedib0t/go-pretty/v6 from 6.3.2 to 6.3.3 (#2689)
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.3.2 to 6.3.3.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.3.2...v6.3.3)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  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>
2022-06-27 15:54:30 +00:00
dependabot[bot] 998e75feb3 chore: bump github.com/hashicorp/hc-install from 0.3.2 to 0.4.0 (#2691)
Bumps [github.com/hashicorp/hc-install](https://github.com/hashicorp/hc-install) from 0.3.2 to 0.4.0.
- [Release notes](https://github.com/hashicorp/hc-install/releases)
- [Changelog](https://github.com/hashicorp/hc-install/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/hc-install/compare/v0.3.2...v0.4.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>
2022-06-27 15:48:41 +00:00
dependabot[bot] 5c8b09fee7 chore: bump github.com/stretchr/testify from 1.7.3 to 1.7.5 (#2690)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.3 to 1.7.5.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.3...v1.7.5)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  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>
2022-06-27 10:37:43 -05:00
dependabot[bot] 975b4f6df2 chore: bump github.com/pion/webrtc/v3 from 3.1.41 to 3.1.42 (#2688)
Bumps [github.com/pion/webrtc/v3](https://github.com/pion/webrtc) from 3.1.41 to 3.1.42.
- [Release notes](https://github.com/pion/webrtc/releases)
- [Commits](https://github.com/pion/webrtc/compare/v3.1.41...v3.1.42)

---
updated-dependencies:
- dependency-name: github.com/pion/webrtc/v3
  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>
2022-06-27 15:12:22 +00:00
Kyle Carberry 08f4b193e1 fix: Elongate agent disconnect timeout in tests (#2687)
This will fix the flake seen here:
https://github.com/coder/coder/runs/7071719863?check_suite_focus=true
2022-06-27 15:06:51 +00:00
dependabot[bot] 4a2d29948e chore: bump github.com/spf13/cobra from 1.4.0 to 1.5.0 (#2679)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  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>
2022-06-27 09:58:02 -05:00
Kyle Carberry 33a04f661f fix: Check if start is nil before consuming in echo provisioner (#2686)
This caused a race seen here:
https://github.com/coder/coder/runs/7074123929?check_suite_focus=true#step:10:217
2022-06-27 09:57:37 -05:00
Abhineet Jain 82938944e7 refactor: update Prettier printWidth to 100 (#2684) 2022-06-27 10:53:44 -04:00
Camdon 09722ae1ef Bump version number for coder terraform provider (#2673) 2022-06-27 08:49:26 -05:00
Cian Johnston bbbd5241c3 develop.sh: attempt to create a Docker template automatically (#2627)
This commit makes the following changes:

- Adds two variables docker_host and docker_arch to the example docker-code-server template
- Adds an example params.yaml to docker-code-server and updates the README.md to reference these parameters
- scripts/develop.sh will now attempt to create a template using docker-code-server with the appropriate parameters for the environment
- Updated Lima example to make use of the template parameters for docker-code-server


Additional drive-bys:

- webpack.dev.ts references CODER_HOST and not CODERV2_HOST; updated develop.sh accordingly
- develop.sh should now terminate child processes upon error.
2022-06-27 09:59:08 +01:00
dependabot[bot] f9d830a2b6 chore: bump google.golang.org/api from 0.82.0 to 0.85.0 (#2632)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.82.0 to 0.85.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.82.0...v0.85.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>
2022-06-26 18:43:21 -05:00
Kyle Carberry 16ac54cbd9 fix: Wait for workspace build to complete before running SSH tests (#2671)
This was causing a race shown here:
https://github.com/coder/coder/runs/7063713786?check_suite_focus=true
2022-06-26 23:10:52 +00:00
Kyle Carberry dac6838fc3 fix: Await workspace build job before waiting for CLI output (#2670)
This was causing occasional flakes seen here:
https://github.com/coder/coder/runs/7063142245?check_suite_focus=true
2022-06-26 22:05:37 +00:00
Kyle Carberry 4851d932c4 fix: Split host and port before storing IP (#2594)
The IP was always nil prior, and this fixes the test to
check for that as well!
2022-06-26 21:22:03 +00:00
Kyle Carberry 545a9f3435 fix: Increase wait time for agent connection tests (#2667)
This was causing a test flake seen here:
https://github.com/coder/coder/runs/7063032150?check_suite_focus=true
2022-06-26 21:15:59 +00:00
Kyle Carberry 01c31b47a3 fix: Adjust pagination limit to be zero-based (#2663)
There isn't a use-case for querying a limit of zero. Using
-1 led to issues when using default parameters for querying.
2022-06-26 20:23:25 +00:00
Kyle Carberry 95e854d144 fix: Update database fake to check for nil time when streaming logs (#2664)
This caused a test flake seen here: https://github.com/coder/coder/runs/7056544834?check_suite_focus=true
2022-06-26 19:52:15 +00:00
Steven Masley 47796211d7 fix: Properly remove non matched workspaces (#2649) 2022-06-25 16:37:21 -05:00
Steven Masley 3312c814bd feat: Workspace filters case insensitive (#2646) 2022-06-25 06:22:59 -05:00
Abhineet Jain 90815e5119 feat: improve Users filter API (#2645) 2022-06-24 23:55:28 +00:00
Jon Ayers d1c69866e8 fix: provide environment variable for CLI session token (#2648) 2022-06-24 18:50:35 -05:00
Colin Adler 6aed58f486 feat: add ssh support over wireguard (#2642) 2022-06-24 16:21:46 -05:00
Colin Adler 26e85b0bbc fix: use typed wireguard public keys in database structs (#2639) 2022-06-24 15:45:28 -05:00
Oxylibrium 115730341e fix: ensure gcp resource names are lowercase (#2619) 2022-06-24 15:11:24 -04:00
Colin Adler 46c6b9ee27 fix: use correct default wireguard public key (#2638) 2022-06-24 17:16:36 +00:00
Abhineet Jain bd07284a68 feat: Add success messages for CLI commands (#2634) 2022-06-24 16:30:22 +00:00
Colin Adler 05b67ab1cf feat: peer wireguard (#2445) 2022-06-24 10:25:01 -05:00
Steven Masley d21ab2115d feat: Backend api for filtering users using filter query string (#2553)
* User search query string
2022-06-24 10:02:23 -05:00
Oxylibrium 981fb2764f fix: add copy fallback for insecure contexts (#2044) 2022-06-23 16:35:12 -04:00
Oxylibrium 885e7fd03e chore: update tsconfig target to es2018 (#2616) 2022-06-23 15:52:42 -04:00
Garrett Delfosse 0bcdfd584f fix: order apps by name (#2614) 2022-06-23 19:18:03 +00:00
Ben Potter a39a8563cc docs: use simplified path for dotfiles (#2615) 2022-06-23 14:12:35 -05:00
Abhineet Jain 9c8079b25e refactor: Extract workspace filter into a separate component (#2601) 2022-06-23 11:30:53 -04:00
Kira Pilot 929227d0f8 bug: fix chromatic schedule bug (#2481) 2022-06-23 11:25:07 -04:00
Presley Pizzo 65870e65ce feat: Move agent status (#2593)
* Move agent status

* Format
2022-06-23 10:18:56 -04:00
Cian Johnston ac557e02b8 clean site/out and enforce make bin (#2604) 2022-06-23 15:16:27 +01:00
Katie Horne 4eda7034ee chore: fix broken links to use full path (#2606) 2022-06-23 09:04:31 -05:00
Kyle Carberry b55fca4904 fix: Increase timeout for streaming logs (#2596)
One second wasn't long enough, and was causing flakes in CI.
2022-06-23 09:00:00 -05:00
Abhineet Jain c6b1daabc5 feat: Download default terraform version when minor version mismatches (#1775) 2022-06-22 23:11:52 +00:00
Garrett Delfosse 6a2a145545 fix: simplify terminal link (#2597) 2022-06-22 17:10:58 -05:00
Kira Pilot 97d1d2f4f0 added a default app icon (#2595)
resolves #2268
2022-06-22 17:05:21 -04:00
Eric Paulsen 7dc3f5f92b init: oauth docs (#2565)
* init: oauth docs

* chore: update directories

* update: feedback
2022-06-22 15:25:06 -05:00
Mathias Fredriksson 69b7eed7ed feat: Check decompressed coder-slim binaries via SHA1 (#2556) 2022-06-22 21:33:23 +03:00
Cian Johnston a0c8e70d1b scripts/develop.sh: remove nc dependency (#2590) 2022-06-22 18:04:12 +00:00
Kyle Carberry 3f9776784c fix: Subtract a second when listening in TestWorkspaceBuildLogs (#2588)
This allowed a test flake seen here:
https://github.com/coder/coder/runs/7009119403?check_suite_focus=true#step:9:151
2022-06-22 17:48:03 +00:00
Abhineet Jain cfbda57990 fix: Parse 24h time format from schedule cron in CLI (#2586)
* fix: parse 24h time format from schedule cron in cli

* add unit test
2022-06-22 17:45:00 +00:00
Kyle Carberry b7eeb436ad feat: Add ip_address to API keys (#2580)
Fixes #2561.
2022-06-22 17:32:21 +00:00
Kyle Carberry caf9c41a9e fix: Stop sending before logs when after is specified (#2585)
This fixes duplicate logs appearing in completed jos!
2022-06-22 17:09:28 +00:00
Kyle Carberry 437066ce20 fix: Stop sending additional signals in Shutdown test (#2582)
Coder was exiting before the additional signals were handled,
which caused occasional CI failures.
2022-06-22 11:32:34 -05:00
Abhineet Jain f72a6d09fc fix: Open new windows for terminals (#2568) 2022-06-22 12:29:08 -04:00
David Wahler c366725472 Revert changes to scripts/build_go_matrix.sh from 1778db2 (#2581) 2022-06-22 15:59:20 +00:00
Mathias Fredriksson 11c47e0d3b feat: Rename config-ssh --diff to --dry-run (#2575)
* feat: Rename config-ssh `--diff` to `--dry-run`

Since the intent between diff and dry-run are different, this change
allows for interactive prompts to be shown during `--dry-run`,
previously prompts were disabled. Dry-run can also be chanied with
`--yes` and `--use-previous-options` for non-interactive modes.

Dry-run is like a normal run with changes replaced by diff.

Fixes #2530

Co-authored-by: Cian Johnston <cian@coder.com>
2022-06-22 18:33:08 +03:00
Abhineet Jain bd19fcbae1 Wrap code text in template readme files (#2562) 2022-06-22 11:01:43 -04:00
Cian Johnston 92bcacebde cli/templateinit: add links to template READMEs (#2576)
- template init: add links to template docs
- examples: add URL field to examples, ensure that example fields are always non-empty
- cliui: bump wrap width to 80 from 58
2022-06-22 14:15:04 +00:00
Katie Horne 34222b2260 chore: add doc on roles, user management (#2548) 2022-06-22 13:36:48 +00:00
Kyle Carberry 1778db23cb fix: Use WebSockets to stream workspace build logs (#2569)
* fix: Use WebSockets to stream workspace build logs

This was using a streaming HTTP request before, which didn't work
on my version of Chrome. This method seemed less reliable and standard
than a WebSocket, so figured switching would be best.

* Update site/src/xServices/workspaceBuild/workspaceBuildXService.ts

Co-authored-by: Abhineet Jain <AbhineetJain@users.noreply.github.com>

* Update site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx

Co-authored-by: Abhineet Jain <AbhineetJain@users.noreply.github.com>

* Update site/src/api/api.ts

Co-authored-by: Abhineet Jain <AbhineetJain@users.noreply.github.com>

* Remove unused prop

Co-authored-by: Abhineet Jain <AbhineetJain@users.noreply.github.com>
2022-06-22 13:23:14 +00:00
Cian Johnston dc7d6def8e improve develop.sh (#2572)
- Running make dev now prompts you to run ./scripts/develop.sh manually, as GNU make does not appear to pass SIGINT to subprocesses.
- Added checks to develop.sh to ensure that coderd is listening before running our initial setup steps
- Add some more troubleshooting/debugging output to develop.sh
2022-06-22 14:02:31 +01:00
Kyle Carberry 7f778316ac fix: Remove duplicate logs from WorkspaceBuildPage (#2564) 2022-06-21 18:30:40 -05:00
Ketan Gangatirkar 5d2368cb1e remove incorrect coder open invocation from README.md screenshot (#2566) 2022-06-21 23:22:32 +00:00
Jon Ayers ee5918217b fix: cleanup reaper implementation (#2563)
- Clean up the agent/reaper API to be a more isolated and reusable package.
2022-06-21 18:01:34 -05:00
Ben Potter 0585372170 add enhanced docs for creating & troubleshooting templates (#2546)
Co-authored-by: Katie Horne <katie@coder.com>
2022-06-21 15:17:07 -05:00
Ammar Bandukwala 9d02a37ba9 Condense footer (#2555)
Since the footer is included in every page, we should try extra
hard to use little vertical space.

Also, I add an icon to the version text for balance.
2022-06-21 19:46:43 +00:00
Kyle Carberry 06ea7c8388 test: Remove max processes on Windows runner (#2457)
This was added because the runner was running out of memory.
It has potential to reduce our CI time significantly, so we'll
see if it still happens.
2022-06-21 12:04:27 -05:00
Mathias Fredriksson e2785ada5e feat: Compress and extract slim binaries with zstd (#2533)
Fixes #2202

Co-authored-by: Dean Sheather <dean@deansheather.com>
2022-06-21 19:53:36 +03:00
Mathias Fredriksson 64f0473499 chore: Prefer [[ over [ in bash build scripts (#2543) 2022-06-21 19:51:32 +03:00
Kyle Carberry fe81b0b859 fix: Wait for TestServer/Telemetry to close before exit (#2554)
This was causing occasional test failures due to leakage!
2022-06-21 19:50:29 +03:00
Kyle Carberry a48a838c9e fix: Wrap TableCell with Link for native browser routing (#2532)
Tables were previously using an onClick handler which replicated some
Link behavior, but not natively through the browser.

Fixes #2525.
2022-06-21 16:31:16 +00:00
dependabot[bot] 1ce28836d1 chore: bump github.com/stretchr/testify from 1.7.2 to 1.7.3 (#2518)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.2...v1.7.3)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  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>
2022-06-21 10:32:32 -05:00
Kira Pilot 5d2579fcda fix: adjusting language and icon for feedback link (#2483)
* fix: adjusting language and icon for feedback link

* adjust classname
2022-06-21 09:56:08 -04:00
Abhineet Jain a40089c22a Update template users language (#2523) 2022-06-21 09:38:03 -04:00
Katie Horne f476a4ad37 chore: fix broken links and formatting issues (#2547) 2022-06-21 13:13:38 +00:00
Ketan Gangatirkar 93b78755a6 change Docker main.tf to be multiline and make the README.md wrap more nicely (#2537)
see issue #2385
2022-06-21 05:34:14 -05:00
Abhineet Jain 7a4fd12911 Alias users CLI subcommand as user (#2522) 2022-06-20 10:40:08 -04:00
Abhineet Jain 8a853a64a5 Show build initiator on Workspace Build page (#2446)
* show build initiator in ui

* update autostop story
2022-06-20 10:38:57 -04:00
Katie Horne 6d0579d6b6 chore: sync readme and install (#2442) 2022-06-19 15:49:42 -05:00
Ben Potter a19493bd53 add docs for web IDEs (code-server, JetBrains Projector, VNC) (#2448)
* add "configuring web IDEs" doc

* no jupyterhub for now

* change location of web IDE page

* add Dockerfile example

* add run instructions
2022-06-19 20:45:14 +00:00
Dean Sheather 9bdaec6a21 fix: use armhf architecture in linux packages (#2514) 2022-06-20 06:12:38 +10:00
Dean Sheather 153ffc0ee9 fix: include architecture and version information in linux packages (#2511) 2022-06-19 18:56:07 +00:00
Dean Sheather 97348b1c9d fix: replace underscores with hyphens in slim binary name (#2509) 2022-06-19 18:15:20 +00:00
Dean Sheather 8d6faa3c1a fix: login before pushing docker images in release pipeline (#2496) 2022-06-19 13:12:09 +10:00
Dean Sheather 4b3608b628 fix: run git fetch --tags --force during release (#2495) 2022-06-19 10:39:01 +10:00
Dean Sheather dc115688b8 fix: checkout tags in deploy job (#2493) 2022-06-18 22:16:14 +00:00
Dean Sheather 167ab281e4 fix: fix ERRPIPE in build scripts, fix deploy (#2492) 2022-06-18 21:47:37 +00:00
Dean Sheather 075e891f28 Remove goreleaser in favor of build scripts (#2143) 2022-06-19 05:47:10 +10:00
Dean Sheather a9c166491d fix: synchronize terraform log output, fix init coloring (#2482) 2022-06-19 05:26:43 +10:00
Spike Curtis 54a585dbf6 Log provisioner outputs from TestProvision_ExtraEnv (#2427)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-17 14:47:05 -07:00
Spike Curtis 0aa66b4296 Lock the fake database during transactions (#2478)
* Lock the fake database during transactions

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

* Add ut for fake database transactions

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

* fix lint

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

* Fix lint macOS

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-17 13:50:11 -07:00
Cian Johnston 1455603505 fix: cli: create: use new autostart format, opt-in by default (#2472) 2022-06-17 20:38:10 +00:00
Steven Masley edd1083176 fix: Fix test flake based on same update time (#2484) 2022-06-17 15:20:21 -05:00
Steven Masley 4616499030 chore: Reuse ComputedParmeter, remove duplicated codersdk type (#2477)
* chore: Reuse ComputedParmeter instead of custom type
2022-06-17 15:20:13 -05:00
Cian Johnston 0b6efce466 add lima template for coder (#2452)
This commit adds a lima example for Coder.
You can now run limactl start --name=coder ./examples/lima/coder.yaml and have a "prod-like" Coder instance up and running within a minute or so.

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-06-17 20:12:42 +00:00
Steven Masley a9d62cc992 fix: Fix nested transactions should function correctly (#2470)
* fix: Fix nested transactions should function correctly

Inner tx should reuse outer tx
2022-06-17 15:10:44 -05:00
Cian Johnston 0d2f0d7f8c chore: apply non-durability settings to test postgres container (#2479)
This commit applies some recommended settings to sacrifice durability
for speed for our testing database:
  - Mount PGDATA dir on a tmpfs (--tmpfs /tmp)
  - Turn off fsync
  - Turn off synchronous_commit
  - Turn off full_page_writes

  Ref: https://www.postgresql.org/docs/current/non-durability.html
2022-06-17 20:57:38 +01:00
Mathias Fredriksson 17ba4c8e88 fix: Allow template names to be re-used after deletion (#2454)
Fixes #2152
2022-06-17 19:18:07 +00:00
Abhineet Jain 289b98978f Add reason field for workspace builds (#2438)
* add reason field for workspace build

* add the reason field to FE via API

* update BuildReasonMember to BuildReasonInitiator

* add unit tests

* add more unit tests

* add error for unknown transition

* fix lint

* add documentation

* fix unit tests

* fix generated types

* remove nested transaction

* rename migration file
2022-06-17 13:41:11 -04:00
Kira Pilot 7dcfea10dc feat: add feedback link to footer (#2447)
* add ability to activate users

resolves #2254

* added test

* PR feedback

* guarding against null validation_contains field

* fixing type for ParameterSchema

resolves #2161

* added report link to footer

resolves #1885

* added test

* Footer story

* fix broken test
2022-06-17 13:26:13 -04:00
Steven Masley 64b92eea67 feat: Allow inheriting parameters from previous template_versions when updating a template (#2397)
* WIP: feat: Update templates also updates parameters
* Insert params for template version update
* Working implementation of inherited params
* Add "--always-prompt" flag and logging info
2022-06-17 12:22:28 -05:00
Jon Ayers 18973a65c1 fix: Add reaper to coder agent (#2441)
* fix: Add reaper to coder agent

- The coder agent runs as PID 1 in some of our Docker workspaces.
  In such cases it is the responsibility of the init process to
  reap dead processes. Failing to do so can result in an inability
  to create new processes by running out of PIDs.

  This PR adds a reaper to our agent that is only spawned if it
  detects that it is PID1.
2022-06-17 11:51:46 -05:00
Mathias Fredriksson 6c1208e3db feat: Clean up coder agent path in ps listing (#2453)
This commit changes the `coder agent` path in `ps` listing from
`/tmp/tmp.coderwWs87Y/coder agent` to `./coder agent`.

The path is also updated to `/tmp/coder.wWs87Y`.

There were two options considered for turning `./coder agent` into
`coder agent`:

1. Run `exec -a coder /path/to/coder agent`
2. Run `PATH=/path/to:$PATH exec coder agent`

Option 1 is not supported by `dash`, and thus discarded.

Option 2 duplicates functionality in `coder agent` which _appends_ the
path, here we would want to _prepend_ it to ensure we're starting the
downloaded `coder` binary in case there is a binary with a conflicting
name on the system.

Fixes #2407
2022-06-17 19:37:47 +03:00
Mathias Fredriksson 18b0effa83 fix: Add --yes and --use-previous-options to config-ssh (#2458) 2022-06-17 18:03:15 +03:00
Kyle Carberry 7cce7a9c69 test: Write URL after signal listen to fix flake (#2456)
The URL could be read before the signal was listening, causing
this test to flake: https://github.com/coder/coder/runs/6936820170?check_suite_focus=true
2022-06-17 14:16:45 +00:00
Kyle Carberry f09ab03baf fix: Add flag to toggle telemetry (#2455)
* fix: Add flag to toggle telemetry

This allows users to entirely disable tracking from Coder!
Telemetry is enabled by default, so this is opt-out.

* Update cli/server.go

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

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-06-17 14:02:44 +00:00
Bruno Quaresma d0aca86657 feat: Show help tooltip on hover (#2423) 2022-06-17 12:50:54 +00:00
Bruno Quaresma 3415b9daef feat: Pop out all apps (#2346) 2022-06-17 12:36:48 +00:00
Bruno Quaresma 9fdee5d391 fix: Storybook error on complex args (#2424) 2022-06-17 09:19:41 -03:00
Kyle Carberry b9f3fe49cb fix: Start login shells on macOS and Linux (#2437)
This appends `-l` to the shell command on macOS and Linux.
It also adds environment variable expansion to allow for
chaining from `coder_agent.env`.
2022-06-17 05:54:45 +00:00
kylecarbs be02d87f22 fix: Swap migration numbers to fix deployment 2022-06-17 05:47:13 +00:00
Kyle Carberry 4cce969018 feat: Add anonymized telemetry to report product usage (#2273)
* feat: Add anonymized telemetry to report product usage

This adds a background service to report telemetry to a Coder
server for usage data. There will be realtime event data sent
in the future, but for now usage will report on a CRON.

* Fix flake and requested changes

* Add reporting options for setup

* Add reporting for workspaces

* Add resources as they are reported

* Track API key usage

* Ensure telemetry is tracked prior to exit
2022-06-17 00:26:40 -05:00
Ben Potter af8a1e3fea package app icons (#2449) 2022-06-17 05:13:37 +00:00
Ammar Bandukwala 40ef1546e1 docs: add architecture page (#2450) 2022-06-16 23:46:02 -05:00
Ammar Bandukwala de213934d1 docs: expand dotfiles section (#2444) 2022-06-16 18:48:18 -05:00
Katie Horne 535481139a chore: add IDEs page to sidebar (#2443) 2022-06-16 18:09:40 -05:00
Katie Horne a09d2af977 chore: add basic postgres instructions for byo databases (#2430) 2022-06-16 17:54:15 -05:00
Katie Horne 0c9ff3a2ac chore: change git link so that it uses https instead of ssh (#2431) 2022-06-16 16:21:52 -05:00
Katie Horne da9009bd3e chore: add link from install.md to Docker example README (#2435) 2022-06-16 16:06:10 -05:00
Spike Curtis 93b1425d85 Stop showing persistent vs ephemeral for resources (#2333)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-16 18:36:11 +00:00
Spike Curtis 552dad6919 Remove tfexec, allow TF_ environment vars and log them (#2264)
* Remove tfexec, allow TF_ environment vars and log them

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

* fixup: commented code, long lines

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

* rename executor methods to remove get

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

* don't log terraform environment variables we don't know are safe

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

* Disable linting of fake secret

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

* drop parse support and move logger into terraform package

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

* disable testpackage linter on internal package test

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-16 17:50:39 +00:00
Ammar Bandukwala 82c4b80c67 Docs touchups (#2421)
* Make secrets docs louder

* docs: a bunch of small touchups
2022-06-16 17:47:10 +00:00
Cian Johnston c9691eafcb feat: cli: consolidate schedule-related commands (#2402)
* feat: cli: consolidate schedule-related commands

This commit makes the following changes:
- renames autostart -> schedule starat
- renames ttl -> schedule stop
- renames bump -> schedule override
- adds schedule show command
- moves some cli-related stuff to util.go
2022-06-16 18:24:10 +01:00
Colin Adler c36b0d892b fix(devtunnel): use 1280 mtu (#2420)
This should be more compatible with cloud VMs and VPNs.
2022-06-16 12:12:04 -05:00
dependabot[bot] ba451b569a chore: bump github.com/gohugoio/hugo from 0.100.2 to 0.101.0 (#2416)
Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.100.2 to 0.101.0.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/goreleaser.yml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.100.2...v0.101.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>
2022-06-16 17:06:41 +00:00
dependabot[bot] c570501662 chore: bump github.com/nhatthm/otelsql from 0.3.0 to 0.3.3 (#2415)
Bumps [github.com/nhatthm/otelsql](https://github.com/nhatthm/otelsql) from 0.3.0 to 0.3.3.
- [Release notes](https://github.com/nhatthm/otelsql/releases)
- [Commits](https://github.com/nhatthm/otelsql/compare/v0.3.0...v0.3.3)

---
updated-dependencies:
- dependency-name: github.com/nhatthm/otelsql
  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>
2022-06-16 11:51:16 -05:00
Mathias Fredriksson b3f2b7c80a feat: Add section for community templates (#2401)
* feat: Add section for community templates

* Add ntimo/coder-hetzner-cloud-template
2022-06-16 19:48:44 +03:00
dependabot[bot] edaa3f5fc3 chore: bump github.com/pkg/sftp from 1.13.4 to 1.13.5 (#2276)
Bumps [github.com/pkg/sftp](https://github.com/pkg/sftp) from 1.13.4 to 1.13.5.
- [Release notes](https://github.com/pkg/sftp/releases)
- [Commits](https://github.com/pkg/sftp/compare/v1.13.4...v1.13.5)

---
updated-dependencies:
- dependency-name: github.com/pkg/sftp
  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>
2022-06-16 11:30:34 -05:00
dependabot[bot] fda856d293 chore: bump github.com/jedib0t/go-pretty/v6 from 6.3.1 to 6.3.2 (#2314)
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.3.1 to 6.3.2.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.3.1...v6.3.2)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  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>
2022-06-16 11:29:54 -05:00
Katie Horne 6c1a111fa9 chore: add IDEs page (#2388) 2022-06-16 11:22:14 -05:00
Spike Curtis a82c0eb560 Fix socket leak, clean up single use postgres databases (#2413)
* Fix socket leak, clean up single use postgres databases

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

* Move migrate close defer until after we know it is not nil

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-16 09:01:33 -07:00
Katie Horne eab5c15062 chore: edit CLI/UI copy (#2247) 2022-06-16 10:28:41 -05:00
David Wahler 2d7e6d6530 disable TestPasswordTerminalState until we can make it run reliably (#2409) 2022-06-16 10:18:07 -05:00
Kyle Carberry 024ab6df57 fix: Use in-memory filesystem for echo provisioner tests (#2408)
* fix: Use in-memory filesystem for echo provisioner tests

This should reduce IO in CI to shave some time off tests!

* test: Increase timeouts to reduce flakes

It's difficult to understand what's timing out due to a lock
vs. taking a long time. This should help resolve! 🕵️
2022-06-16 15:09:22 +00:00
Kyle Carberry 5e673cc544 test: Increase timeouts to reduce flakes (#2406)
It's difficult to understand what's timing out due to a lock
vs. taking a long time. This should help resolve! 🕵️
2022-06-16 14:52:45 +00:00
Katie Horne a95d9b17f6 chore: remove index.md (#2403)
* chore: fix broken link

* chore: remove index.md
2022-06-16 14:46:01 +00:00
Ammar Bandukwala 29c9c1d928 docs: small improvements to install (#2400)
* Simplify install docs

* docs: clarify security policy
2022-06-16 12:30:56 +00:00
Kyle Carberry 10dc9e3876 fix: Force keeping old files to prevent dpkg failure on update (#2399)
Updating a release if system files failed would result in failure from
the install script. This fixes it!
2022-06-15 20:04:20 -05:00
Steven Masley 75205f5978 feat: Implement parameters list + more template list columns (#2359)
* feat: Implement parameters list

- Allow more columns on template list

* Hide param list by default for now
2022-06-15 18:21:01 -05:00
Katie Horne f5e558c4ec chore: convert citations to hyperlinks (#2392) 2022-06-15 16:11:44 -05:00
Kyle Carberry ccd061652b feat: Add built-in PostgreSQL for simple production setup (#2345)
* feat: Add built-in PostgreSQL for simple production setup

Fixes #2321.

* Use fork of embedded-postgres for cache path
2022-06-15 16:02:18 -05:00
Katie Horne bb4ecd72c5 chore: update quickstart (docs) (#2381) 2022-06-15 14:52:48 -05:00
Kira Pilot 0f44048fcc fix: adjust ParameterSchema type for workspace creation (#2384)
* add ability to activate users

resolves #2254

* added test

* PR feedback

* guarding against null validation_contains field

* fixing type for ParameterSchema

resolves #2161
2022-06-15 15:12:57 -04:00
Ammar Bandukwala 2e625c1d9b docs: use about as home page (#2382)
* docs: use About as home page

* docs: format install.md
2022-06-15 14:03:13 -05:00
Jon Ayers 961f5110ca fix: fix deleted workspace banner 404 (#2386)
- When a workspace is deleted the user is nudged
  to create a new workspace. The page to which they
  are routed 404s. This PR simply routes them to the
  /templates page where they can pick a template for their
  new workspace.
2022-06-15 13:34:26 -05:00
Dean Sheather 45eb1b4980 feat: improve terraform template parsing errors (#2331) 2022-06-16 04:12:17 +10:00
Jon Ayers 6cf483bf37 fix: allow server startup without tunnel (#2380)
- Previously, specifying 'no' to the tunnel prompt just killed
  the process. It should be possible to start the server without
  a tunnel and not have the process killed.
2022-06-15 12:54:01 -05:00
Jon Ayers 9b3b6418a2 feat: Add template pull cmd (#2329) 2022-06-15 12:42:43 -05:00
Kira Pilot a6a06d4e9c feat: ability to activate suspended users (#2344)
* add ability to activate users

resolves #2254

* added test

* PR feedback
2022-06-15 13:29:38 -04:00
Kira Pilot d48ab96511 trying to resolve Chromatic CI failures (#2350) 2022-06-15 13:04:47 -04:00
Ben Potter 8f7dbee813 fix: flaky install.sh upgrade on OSX (zsh killed) (#2309)
* remove binary exists

* fix lint and format errors

Co-authored-by: ben@coder.com <benpotter@bens-mbp.lan>
2022-06-15 11:09:52 -05:00
Abhineet Jain 55e538e854 fix: break word to wrap long strings in stats, add media query (#2310)
* break word to wrap long strings, add media query

* add stories for smaller screens
2022-06-15 11:16:03 -04:00
Cian Johnston 12a664fa9a fix: coderd: fix flaky test (#2343) 2022-06-15 14:32:02 +00:00
Katie Horne afa5443180 chore: update docs manifest to reflect current Coder version number (#2342) 2022-06-15 14:25:12 +00:00
Mathias Fredriksson 7808593a25 fix: Revert to old SSH config section management in config-ssh (#2341) 2022-06-15 17:22:30 +03:00
Katie Horne d0794910d9 chore: update link to go to Coder docs instead of GitHub (#2330) 2022-06-15 09:16:43 -05:00
Bruno Quaresma b225953f68 feat: Add "Outdated" tooltip and "Update version" button in the Workspaces page (#2322) 2022-06-15 13:52:05 +00:00
Colin Adler e9f87f12ec chore: skip devtunnel test (#2336) 2022-06-14 20:32:40 -05:00
Cian Johnston 02ad60fd75 fix: allow setting workspace deadline as early as now plus 30 minutes (#2328)
This PR makes the following changes:

- coderd: /api/v2/workspaces/:workspace/extend now accepts any time at least 30 minutes in the future.
- coder bump command also allows the above. Some small copy changes to command.
- coder bump now actually enforces template-level maxima.
2022-06-14 22:39:15 +01:00
Steven Masley 4734636b17 fix: compilation error on merge with Authorize call (#2319)
Merge caused compilation errors.
- Authorize call having too many arguments
- `workspaces_test.go` missing "fmt" import
2022-06-14 16:21:30 +00:00
Cian Johnston c28b7ecdf2 fix: coderd: decouple ttl and deadline (#2282)
This commit makes the following changes:

- Partially reverts the changes of feat: update workspace deadline when workspace ttl updated #2165, making the deadline of a running workspace build independant of TTL, once started.
- CLI: updating a workspace TTL no longer updates the deadline of the workspace.
- UI: updating a workspace TTL no longer updates the deadline of the workspace.
- Drive-by: API: When creating a workspace, default TTL to min(12 hours, template max_ttl) if not instructed otherwise.
- Drive-by: CLI: list: measure workspace extension correctly (+X in last column) from the time the provisioner job was completed
- Drive-by: WorkspaceSchedule: show timezone of schedule if it is set, defaulting to dayjs guess otherwise.
- Drive-by: WorkspaceScheduleForm: fixed an issue where deleting the "TTL" value in the form would show the text "Your workspace will shut down a few seconds after start".
2022-06-14 17:09:24 +01:00
Steven Masley 251316751e feat: Return more 404s vs 403s (#2194)
* feat: Return more 404s vs 403s
* Return vague 404 in all cases
2022-06-14 10:14:05 -05:00
Steven Masley dc1de58857 feat: workspace filter query supported in backend (#2232)
* feat: add support for template in workspace filter
* feat: Implement workspace search filter to support names
* Use new query param parser for pagination fields
* Remove excessive calls, use filters on a single query

Co-authored-by: Garrett <garrett@coder.com>
2022-06-14 08:46:33 -05:00
dependabot[bot] 5be52de593 chore: bump github.com/gohugoio/hugo from 0.100.1 to 0.100.2 (#2274)
Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.100.1 to 0.100.2.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/goreleaser.yml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.100.1...v0.100.2)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hugo
  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>
2022-06-13 21:39:16 -05:00
Colin Adler 961ddad925 fix: use command -v instead of which in agent bootstrap (#2307)
Certain distros don't ship with `which` (arch) and `command -v` is
built-in to the shell, so this is much more compatible.
2022-06-13 21:20:15 -05:00
Cian Johnston 0a949aaff2 cli: streamline autostart ux (#2251)
This commit adds the following changes:

- autostart enable|disable => autostart set|unset
- autostart enable now accepts a more natual schedule format: <time> <days-of-week> <location>
- autostart show now shows configured timezone
- 🎉 automatic timezone detection across mac, windows, linux 🎉

Fixes #1647
2022-06-13 22:09:36 +01:00
Bruno Quaresma 9d155843dd refactor: Replace PageHeaderText by PageHeaderSubtitle (#2287) 2022-06-13 17:12:47 -03:00
Steven Masley 1863da4ff4 chore: Add some more error context in cli (#2301) 2022-06-13 14:39:35 -05:00
Colin Adler dad42fe712 feat: gzip static http server assets (#2272) 2022-06-13 13:14:22 -05:00
Ben Potter d057e8cc03 docs: fix: use absolute link for examples (#2288)
* docs: fix: use absolute link for examples

* fix ugh
2022-06-13 12:50:05 -05:00
Abhineet Jain a91482cb25 fix: populate default created_by and add not-null constraint in templates (#2290) 2022-06-13 17:25:06 +00:00
Steven Masley 49f857806f fix: Do not write 2 errors to api on template fetch error (#2285) 2022-06-13 15:42:14 +00:00
Katie Horne cbde8e8b91 chore: add hero image to OSS docs homepage (#2241) 2022-06-13 09:16:26 -05:00
Colin Adler e3a1cd34b7 fix: ensure agentResource is non-nil (#2261) 2022-06-11 00:02:49 +00:00
Colin Adler 8415022bf9 fix(devtunnel): close http.Server before wireguard interface (#2263) 2022-06-10 23:40:33 +00:00
Colin Adler de6f86bf7a fix: ensure config dir exists before reading tunnel config (#2259) 2022-06-10 21:42:55 +00:00
Kira Pilot ec0bb7b330 feat: update language on workspace page (#2220) 2022-06-10 16:42:21 -04:00
Abhineet Jain 02d2aea7f2 feat: store and display template creator (#2228)
* design commit

* add owner_id to templates table

* add owner information in apis and ui

* update minWidth for statItem

* rename owner to created_by

* missing refactor to created_by

* handle errors in fetching created_by names
2022-06-10 19:24:21 +00:00
Garrett Delfosse 46da59a6b5 fix: use correct link in create from template button (#2253) 2022-06-10 13:38:43 -05:00
Colin Adler f562b74fa1 feat: use custom wireguard reverse proxy for dev tunnel (#1975) 2022-06-10 13:38:11 -05:00
David Wahler 71fd19631a feat: Warn on coderd startup if access URL is localhost (#2248) 2022-06-10 13:35:51 -05:00
Kyle Carberry f79ab7f87e fix: Remove easter egg mentioning competitor (#2250)
This is more confusing than helpful!
2022-06-10 18:14:06 +00:00
G r e y 928958c94c fix: workspace schedule time displays (#2249)
Summary:

Various time displays weren't quite right.

Details:

- Display date (not just time) of upcoming workspace stop in workspace
page
- Fix ttlShutdownAt for various cases + tests
  - manual to non-manual
  - unchanged/unmodified
  - isBefore --> isSameOrBefore
  - use the delta (off by _ error)
- pluralize units in dayjs.add
2022-06-10 17:26:20 +00:00
Mathias Fredriksson 1a9e57296c feat: Show template description in coder template init (#2238) 2022-06-10 19:54:28 +03:00
Joe Previte fcc52846da fix: update icon (#2216) 2022-06-10 11:23:20 -05:00
Abhineet Jain b2833c694b feat: update build url to @username/workspace/builds/buildnumber (#2234)
* update build url to @username/workspace/builds/buildnumber

* update errors thrown from the API

* add unit tests for the new API

* add t.parallel

* get username and workspace name from params
2022-06-10 12:08:50 -04:00
Kyle Carberry f9290b016e fix: Use explicit resource order when assocating agents (#2219)
This cleans up agent association code to explicitly map a single
agent to a single resource. This will fix #1884, and unblock
a prospect from beginning a POC.
2022-06-10 15:47:36 +00:00
Steven Masley 6bee180bb3 fix: Sort workspace by name by created_at (#2214)
* fix: Sort workspace by name by created_at

Fix bug where deleting workspaces with the same name returns the
oldest deleted workspace
2022-06-10 09:58:42 -05:00
Abhineet Jain 953e8c8fe6 feat: Allow admins to access member workspace terminals (#2114)
* allow workspace update permissions to access agents

* do not show app links to users without workspace update access

* address CR comments

* initialize machine context in the hook

* revert scoped connected status check
2022-06-10 10:46:48 -04:00
Mathias Fredriksson 0260e39d11 fix: Accept CODER_CACHE_DIRECTORY with CACHE_DIRECTORY fallback (#2236)
Fixes #2199
2022-06-10 17:00:00 +03:00
Ammar Bandukwala 06021bdc92 Make coder bump idempotent (#2230)
Resolves #2223

In addition to solving what's outlined in the issue,
I remove the client-side minute check because it had no
clear purpose when the API already returns an error.
2022-06-10 09:31:47 +01:00
ammario 6ea86c831b Revert "Make coder bump idempotent (#2225)"
This reverts commit 0df75f9176.

I merged on accident.
2022-06-10 03:31:13 +00:00
Ammar Bandukwala 0df75f9176 Make coder bump idempotent (#2225)
Resolves #2223

In addition to solving what's outlined in the issue,
I remove the client-side minute check because it had no
clear purpose when the API already returns an error.
2022-06-09 22:30:43 -05:00
Garrett Delfosse 92bda0d2c1 fix: allow admins to reset their own pass without old_password (#2222) 2022-06-10 11:43:54 +10:00
Garrett Delfosse b7234a6ce1 fix: push create workspace UX to templates page (#2142) 2022-06-09 18:43:49 -05:00
Cian Johnston 119db78bff feat: update workspace deadline when workspace ttl updated (#2165)
This commit adds the following changes to workspace scheduling behaviour:

* CLI: updating a workspace TTL updates the deadline of the workspace.
  * If the TTL is being un-set, the workspace deadline is set to zero.
  * If the TTL is being set, the workspace deadline is updated to be the last updated time of the workspace build plus the requested TTL. Additionally, the user is prompted to confirm interactively (can be bypassed with -y).
* UI: updating the workspace schedule behaves similarly to the CLI, showing a message to the user if the updated TTL/time to shutdown would effect changes to the lifetime of the running workspace.
2022-06-09 22:10:24 +01:00
G r e y 411d7da661 fix: ws schedule as 12-hour format (#2209)
This does not finish all tasks in #2175 but is one of the asks.
2022-06-09 16:20:29 -04:00
G r e y 377f17c292 fix: initialValues for ws schedule (#2213)
Summary:

When a schedule is not set, we default to M-F, 5 hours ttl
2022-06-09 15:20:53 -04:00
Bruno Quaresma d04d527f2c chore: Update docs manifest home page and icons (#2133)
* chore: Update docs manifest home page and icons

* RRemove contributors

* Update template icon

* fixup: manifest.json changes

* fix: add missing readme to root

* fix: add readme to /docs with toc

* fix: add quickstart to manifest

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-06-09 18:46:16 +00:00
Ben Potter 0ec1e8f89b example: aws-linux: resize and use non-root user (#2186) 2022-06-09 18:10:01 +00:00
G r e y 92db80cadc fix: sort time zones (#2210)
Summary:

The list of time zones in the edit workspace schedule form is not sorted
alphabetically.
2022-06-09 18:42:27 +01:00
Kira Pilot 518495a6c5 feat: show deleted workspace after delete action (#2208)
* added deleted workspace banner

* x state pass

* added include_deleted param

* clean up x state

* added teests

* cleaning up unneeded xstate service
2022-06-09 11:43:49 -04:00
dependabot[bot] d0ac4d9e74 chore: bump eslint-plugin-react from 7.29.4 to 7.30.0 in /site (#2076)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.29.4 to 7.30.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.29.4...v7.30.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  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>
2022-06-09 11:17:52 -04:00
G r e y 857d83750a ci: storybook flake for auto-stop display (#2184)
Summary:

Uncaught from the change in https://github.com/coder/coder/pull/2171
2022-06-09 15:03:28 +00:00
Kyle Carberry fff59ef6ad fix: Add tests for instance and app association (#2198)
This was regressed in #2187. There was bad testing around this
before, and this should prevent a similiar situation from happening
again!
2022-06-09 13:34:24 +00:00
Mathias Fredriksson 567e4afdfc example: Re-enable Digital Ocean project assignment (#2196)
The issue tracked in #1750 was fixed by #2187, we can now re-enable the
project resource.
2022-06-09 12:17:44 +03:00
Mathias Fredriksson 2621093452 fix: Stop showing resources after coder create (#2155)
Fixes #1036
2022-06-09 10:44:41 +03:00
Steven Masley 74fe38eb3d feat: Add initiator_username to workspace builds in apis (#2174)
* feat: Add initiator_username to workspace builds in apis
2022-06-08 20:23:35 -05:00
Kyle Carberry 14701498c9 fix: Improve Terraform agent<->resource association testing (#2187) 2022-06-08 17:40:34 -05:00
Ben Potter 42c6b0849d example: add and document dotfiles usage (#2046) 2022-06-08 21:23:54 +00:00
dependabot[bot] c33113786e chore: bump dayjs from 1.11.2 to 1.11.3 in /site (#2088)
Bumps [dayjs](https://github.com/iamkun/dayjs) from 1.11.2 to 1.11.3.
- [Release notes](https://github.com/iamkun/dayjs/releases)
- [Changelog](https://github.com/iamkun/dayjs/blob/v1.11.3/CHANGELOG.md)
- [Commits](https://github.com/iamkun/dayjs/compare/v1.11.2...v1.11.3)

---
updated-dependencies:
- dependency-name: dayjs
  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>
2022-06-08 20:05:43 +00:00
Spike Curtis a86c957871 feat: set /Users/spike for coder agent in gcp-linux template (#2147)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-08 11:22:18 -07:00
Kira Pilot 3bc122b7d5 feat: added include_deleted to getWorkspaceByOwnerAndName (#2164)
* feat: added include_deleted

relates to #1955

* Update coderd/workspaces.go

defining vars in the scope of conditional

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

* Update coderd/workspaces.go

avoid newline

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

* Update coderd/workspaces.go

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

* PR feedback

* wrote test, added type

* Update coderd/workspaces_test.go

shortening test name

Co-authored-by: Cian Johnston <cian@coder.com>

* taking out api.ts change for now

* casing

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
Co-authored-by: Cian Johnston <cian@coder.com>
2022-06-08 14:04:05 -04:00
Spike Curtis 85821568a9 feat: set $HOME for coder agent in aws-linux template (#2150)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-08 09:51:33 -07:00
G r e y 9cc6d7b3b2 fix: show explicit schedule stop time (#2171)
This does not fully resolve all requests in #2141, but just the piece of
when the workspace is actually stopping.

Next, we will adjust the default extension from 90 minutes to 4 hours.
Lastly, we can look at customizing the extension time in the extension
flow or with a pre-emptive prompt next to the stop time.
2022-06-08 12:00:22 -04:00
G r e y b390250e2e fix: increase default extension time (#2172)
This does not fully close #2141, but builds upon #2171 to fulfill each
of the UX requests.

A next step (probably as a separate ticket) is to allow customizing
extension time pre-emptively.
2022-06-08 12:00:09 -04:00
Joe Previte b6899e2c18 docs: slim down readme (#2140)
* docs: move docs table of contents to docs/index.md

* docs: move how it works and IDE support to about.md

* docs: move readme steps to walkthrough

* docs: slim down readme

* refactor: walkthrough -> quickstart

* docs: minor edits
2022-06-08 08:37:03 -07:00
Cian Johnston 8cfe223192 feat: cli: allow editing template metadata (#2159)
This PR adds a CLI command template edit which allows updating the following metadata fields of a template:
- Description
- Max TTL
- Min Autostart Interval
2022-06-08 15:14:57 +01:00
Mathias Fredriksson b65259f95e feat: Refactor CLI config-ssh to improve UX (#1900)
- Magic block is replaced by Include statement
- Writes are only done on changes
- Inform user of changes via prompt
- Allow displaying changes via `--diff`
- Remove magic block if present
- Safer config writing via tmp-file + rename
- Parse previous `config-ssh` options, compare to new options and ask to use new (otherwise old ones are used)
- Tests the new functionality

Fixes #1326
2022-06-08 11:45:29 +03:00
Garrett Delfosse 945fa9dacf fix: bug where all workspaces filter defaulted to 'me' (#2145)
* fix: bug where all workspaces filter defaulted to 'me'
2022-06-07 22:15:50 +00:00
Dean Sheather 7b76afb069 Revert split release (#2139)
* Revert "chore: ignore artifacts dir (#2132)"

This reverts commit 27acb98571.

* Revert "chore: split release workflow so the majority happens on Linux (#2092)"

This reverts commit b87096b500.
2022-06-07 20:57:32 +00:00
Abhineet Jain 4b82509922 feat: Make workspaces, timeline, templates rows obviously clickable (#2047)
* add right arrow to build table rows

* Add clickable rows to template and workspace list

* Specify 1% width for chevron right
2022-06-07 19:11:56 +00:00
Garrett Delfosse 7258d6acc8 fix: Show correct 'no results' message on workspace filters (#2103) 2022-06-07 13:54:59 -05:00
Dean Sheather 27acb98571 chore: ignore artifacts dir (#2132) 2022-06-07 16:41:38 +00:00
Bruno Quaresma 3a79759405 fix: Docs paths (#2131) 2022-06-07 16:29:18 +00:00
Bruno Quaresma 2b0662bf48 fix: Add .md extension to the docs routes (#2128) 2022-06-07 15:56:05 +00:00
Steven Masley a391572690 test: Pagination api query param parsing (#2127)
* test: Pagination api query param parsing
2022-06-07 10:48:08 -05:00
Dean Sheather b87096b500 chore: split release workflow so the majority happens on Linux (#2092) 2022-06-07 15:24:46 +00:00
Presley Pizzo b4645b2d11 Clear error on cancel (#2107) 2022-06-07 11:02:20 -04:00
Steven Masley cc30d42473 chore: Fix some 'Message' linting errors on main (#2129) 2022-06-07 14:52:44 +00:00
Steven Masley af401e3fe1 chore: Linter rule for properly formatted api errors (#2123)
* chore: Linter rule for properly formatted api errors
* Add omitempty to 'Detail' field
2022-06-07 14:33:06 +00:00
Katie Horne 3f1e885d21 chore: add manifest.json (#2100) 2022-06-07 09:30:34 -05:00
Kyle Carberry 7e8692b0fd fix: Update routing for workspace schedule (#2113)
* fix: Update routing for workspace schedule

This was broken as part of #2101. It was a silly mistake,
but unfortunate our tests didn't catch it.

This is a rare change so unlikely to occur again, so I won't
make an issue adding tests.

* Update site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>
2022-06-07 14:28:47 +00:00
Bruno Quaresma a4e259e14b fix: Page header width in the workspace page (#2125) 2022-06-07 14:24:01 +00:00
Bruno Quaresma 3752f4c401 fix: Update tooltip messages for workspaces and resources (#2126) 2022-06-07 14:20:44 +00:00
Mathias Fredriksson 6da4810a5e chore: Add (skipped) ptytest test that hangs on Intel Mac (and Windows) (#1629)
Co-authored-by: Steven Masley <stevenmasley@coder.com>
2022-06-07 17:08:11 +03:00
Bruno Quaresma eedd293ad5 feat: Add helpful tooltips for the key features (#2097) 2022-06-07 13:43:46 +00:00
Presley Pizzo 6d966963da refactor: rename errors to validations (#2105)
* Update validation error unpacking

* Rename validations on backend

* Format
2022-06-07 09:31:15 -04:00
Bruno Quaresma 3616c629c7 refactor: Update status for build logs (#2087) 2022-06-07 08:24:27 -05:00
Cian Johnston 3e419ddb3d feat: enforce template-level constraints for TTL and autostart (#2018)
This PR adds fields to templates that constrain values for workspaces derived from that template.

- Autostop: Adds a field max_ttl on the template which limits the maximum value of ttl on all workspaces derived from that template. Defaulting to 168 hours, enforced on edits to workspace metadata. New workspaces will default to the templates's `max_ttl` if not specified.
- Autostart: Adds a field min_autostart_duration which limits the minimum duration between successive autostarts of a template, measured from a single reference time. Defaulting to 1 hour, enforced on edits to workspace metadata.
2022-06-07 13:37:45 +01:00
dependabot[bot] 3878e6434a chore: bump github.com/moby/moby (#2112)
Bumps [github.com/moby/moby](https://github.com/moby/moby) from 20.10.16+incompatible to 20.10.17+incompatible.
- [Release notes](https://github.com/moby/moby/releases)
- [Changelog](https://github.com/moby/moby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moby/moby/compare/v20.10.16...v20.10.17)

---
updated-dependencies:
- dependency-name: github.com/moby/moby
  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>
2022-06-07 01:53:04 +00:00
Kyle Carberry 94ed081a77 fix: Dependabot uses arrays for labels (#2111) 2022-06-07 01:35:44 +00:00
Kyle Carberry 16e66c61d9 fix: Remove dependabot labels (#2110)
This kept adding unnecessary labels to our tracker.
Once we need to filter for specific dependabot PRs,
we can add this back.
2022-06-07 01:17:56 +00:00
Ammar Bandukwala 8f1380774a Remove issue templates (#2109)
Per our product management conversation, we want to make
giving feedback as easy as possible initially.
2022-06-07 01:14:24 +00:00
Kyle Carberry 74d9fee444 fix: Make the workspace URL pretty (#2101)
This adds the `@username/workspacename` format to the
workspace page!
2022-06-06 17:53:39 -05:00
Steven Masley e2b2580196 chore: version sub command remove --version and -v flag (#2090)
* test: Add unit test for version cmd
2022-06-06 17:38:51 -05:00
Spike Curtis a7a7e7561d K8s template uses an authenticated environment (#2104)
* feat: K8s template uses authenticated environment

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

* fmt

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-06 14:39:23 -07:00
Garrett Delfosse a860b86256 fix: support substring search on workspace name (#2096) 2022-06-06 19:43:16 +00:00
Kyle Carberry 66cf59bbe1 fix: Apply environment variables to startup script (#2099)
This was stopping `coder` from being in the path, and allowed
applications started in the script to bypass injected environmnet
variables like `GIT_SSH_COMMAND`.
2022-06-06 14:20:25 -05:00
Garrett Delfosse 1a39931d56 fix: correct localhost url in dev webpack (#2098) 2022-06-06 18:38:39 +00:00
Katie Horne 318e9792ad expand README files for examples (#1946) 2022-06-06 13:03:07 -05:00
Garrett Delfosse 37b0aaa018 fix: add workspace option 'deleted' to options type (#2095)
* fix: add workspace option 'deleted' to options type

* dead code
2022-06-06 17:23:02 +00:00
Garrett Delfosse 367897ef6b Specify number of builds before reporting status on codecov (#2094) 2022-06-06 11:25:51 -05:00
Steven Masley cf477ffbf0 chore: bump github.com/open-policy-agent/opa from 0.40.0 to 0.41.0 (#2093) 2022-06-06 11:16:46 -05:00
dependabot[bot] d7be7a840a chore: bump msw from 0.39.2 to 0.42.0 in /site (#2075)
Bumps [msw](https://github.com/mswjs/msw) from 0.39.2 to 0.42.0.
- [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/v0.39.2...v0.42.0)

---
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>
2022-06-06 10:11:50 -05:00
dependabot[bot] 9f496435da chore: bump @fontsource/inter from 4.5.10 to 4.5.11 in /site (#2084)
Bumps [@fontsource/inter](https://github.com/fontsource/fontsource/tree/HEAD/fonts/google/inter) from 4.5.10 to 4.5.11.
- [Release notes](https://github.com/fontsource/fontsource/releases)
- [Changelog](https://github.com/fontsource/fontsource/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fontsource/fontsource/commits/HEAD/fonts/google/inter)

---
updated-dependencies:
- dependency-name: "@fontsource/inter"
  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>
2022-06-06 10:11:28 -05:00
Kyle Carberry ab8235f53e feat: Add links to the resource card for workspace applications (#2067)
* fix: Use proper webpack config for dev mode

This was broken when improving the build times. The typechecker
unfortunately missed it!

* feat: Add links to the resource card for workspace applications

Fixes #1907 and #805.

I'll make this pretty in another PR!

* Improve style
2022-06-06 09:50:07 -05:00
dependabot[bot] 722dbab337 chore: bump @typescript-eslint/eslint-plugin in /site (#2079)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.23.0 to 5.27.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.27.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  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>
2022-06-06 14:46:38 +00:00
Garrett Delfosse 3be3bc261e fix: handle owner but no name syntax (#2045) 2022-06-06 10:38:38 -04:00
dependabot[bot] 77d068215a chore: bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#2073)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  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>
2022-06-06 09:38:23 -05:00
dependabot[bot] 2824521d67 chore: bump google.golang.org/api from 0.81.0 to 0.82.0 (#2072)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.81.0 to 0.82.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.81.0...v0.82.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>
2022-06-06 09:38:13 -05:00
dependabot[bot] ffe25c3442 chore: bump github.com/gohugoio/hugo from 0.99.1 to 0.100.1 (#2071)
Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.99.1 to 0.100.1.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/goreleaser.yml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.99.1...v0.100.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>
2022-06-06 09:37:55 -05:00
Mathias Fredriksson 59a6826920 feat: Add support for pprof in coder agent (#1985)
* feat: Allow USR1 signal to start pprof
2022-06-06 16:38:33 +03:00
Kyle Carberry 0ac37b146d feat: Add page titles (#2070) 2022-06-06 08:34:10 -05:00
Spike Curtis 3f3ecbf8b3 feat: Authenticate Digital Ocean via environment variable (#2051)
* Digital Ocean example uses environment variable auth

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-06 07:05:16 -05:00
Kyle Carberry 1634f2cddd fix: Use proper webpack config for dev mode (#2061)
This was broken when improving the build times. The typechecker
unfortunately missed it!
2022-06-05 18:23:44 +00:00
Kyle Carberry 013f028e55 feat: Add workspace application support (#1773)
* feat: Add app support

This adds apps as a property to a workspace agent.

The resource is added to the Terraform provider here:
https://github.com/coder/terraform-provider-coder/pull/17

Apps will be opened in the dashboard or via the CLI
with `coder open <name>`. If `command` is specified, a
terminal will appear locally and in the web. If `target`
is specified, the browser will open to an exposed instance
of that target.

* Compare fields in apps test

* Update Terraform provider to use relative path

* Add some basic structure for routing

* chore: Remove interface from coderd and lift API surface

Abstracting coderd into an interface added misdirection because
the interface was never intended to be fulfilled outside of a single
implementation.

This lifts the abstraction, and attaches all handlers to a root struct
named `*coderd.API`.

* Add basic proxy logic

* Add proxying based on path

* Add app proxying for wildcards

* Add wsconncache

* fix: Race when writing to a closed pipe

This is such an intermittent race it's difficult to track,
but regardless this is an improvement to the code.

* fix: Race when writing to a closed pipe

This is such an intermittent race it's difficult to track,
but regardless this is an improvement to the code.

* fix: Race when writing to a closed pipe

This is such an intermittent race it's difficult to track,
but regardless this is an improvement to the code.

* fix: Race when writing to a closed pipe

This is such an intermittent race it's difficult to track,
but regardless this is an improvement to the code.

* Add workspace route proxying endpoint

- Makes the workspace conn cache concurrency-safe
- Reduces unnecessary open checks in `peer.Channel`
- Fixes the use of a temporary context when dialing a workspace agent

* Add embed errors

* chore: Refactor site to improve testing

It was difficult to develop this package due to the
embed build tag being mandatory on the tests. The logic
to test doesn't require any embedded files.

* Add test for error handler

* Remove unused access url

* Add RBAC tests

* Fix dial agent syntax

* Fix linting errors

* Fix gen

* Fix icon required

* Adjust migration number

* Fix proxy error status code

* Fix empty db lookup
2022-06-04 15:13:37 -05:00
dependabot[bot] 2c089d5a99 chore: bump github.com/ory/dockertest/v3 from 3.9.0 to 3.9.1 (#1892)
Bumps [github.com/ory/dockertest/v3](https://github.com/ory/dockertest) from 3.9.0 to 3.9.1.
- [Release notes](https://github.com/ory/dockertest/releases)
- [Commits](https://github.com/ory/dockertest/compare/v3.9.0...v3.9.1)

---
updated-dependencies:
- dependency-name: github.com/ory/dockertest/v3
  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>
2022-06-04 15:12:54 -05:00
Kyle Carberry 5edd086b69 chore: Reduce deployment times by excluding Docker images (#1945)
* chore: Reduce deployment times by excluding Docker images

Only the Windows and Linux binaries are build during deploy, so we
can save many minutes by excluding Docker images.

* Stop docker image builds on snapshot

* Fix artifact upload

* Skip typecheck for release

* Flag deploy
2022-06-04 19:56:45 +00:00
Steven Masley c9a4642a12 chore: Update BE http errors to be ui friendly (#1994)
* chore: More UI friendly errors

Mainly capitlization + messages prefix error
2022-06-03 21:48:09 +00:00
Spike Curtis 847e2b18da Don't use parameters to pass secrets to GCP or AWS (#2039)
* Don't use parameters to pass secrets to GCP or AWS

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

* Fix fmt

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-03 14:29:22 -07:00
Kyle Carberry 43f622a52d fix: Remove unused workspace routes in favor of list with filter (#2038)
* fix: Remove unused workspace routes in favor of list with filter

This consolidates the workspace routes into a single place.
It allows users to fetch a workspace by their username and
workspace name, which will be used by the frontend for routing.

* Fix RBAC

* Fix CLI usages
2022-06-03 14:36:08 -05:00
Spike Curtis d8c440188e feat: Remove organization and user scoped parameters (#2007)
* feat: Remove organization and user scoped parameters

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

* Fixup dump.sql

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

* Fix dump.sql again

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

* Fix down migration

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-03 11:49:58 -07:00
David Wahler 582d636e54 feat: support fully-qualified workspace names in CLI (#2036) 2022-06-03 12:47:56 -05:00
Abhineet Jain fc38b61819 feat: use ConfirmDialog for ResetPasswordDialog (#2035)
* feat: use ConfirmDialog for ResetPasswordDialog

* fix lint

* make description typography a div

* use paragraph for string description, div otherwise

* fix lint
2022-06-03 17:35:09 +00:00
Colin Adler 60102cb22f chore: fix codecov ignore for queries.sql.go (#2037) 2022-06-03 12:26:59 -05:00
Garrett Delfosse 8b03e2b0e1 feat: Workspaces filtering (#1972)
Co-authored-by: G r e y <grey@coder.com>
Co-authored-by: Kira Pilot <kira@coder.com>
2022-06-03 17:20:28 +00:00
G r e y ac6cb269db feat: ws schedule timezone select (#2032)
Resolves: #1959

Summary:

The package tzdata is used to create a meaningful select-list for
timezone in the workspace schedule form.

Impact:

Improved UX. Furthermore, we guess your timezone if the form is being
initialized from scratch.
2022-06-03 12:52:02 -04:00
Abhineet Jain 2b12beef98 feat: Update success confirmation dialog and snackbar (#2005)
* feat: update success confirmation dialog and snackbar

* add success variants to confirm dialog and snackbar

* update story name

* use success variant for snackbar
2022-06-03 11:42:46 -04:00
G r e y 37aff0c8a9 fix: FE parsing of schedule with day strings (#2006)
Resolves: #1901

Summary:

We had a homegrown parser that only understood numbers, not strings like
MON or TUES. We replace the homegrown parser with cron-parser.

Details:

This was nearly a straight drop-in.

Impact:

Much less code/maintenance burden :D

What I learned:

Don't trust the README, sometimes you just gotta read the code or import
it and try it out. The `fields` representation of the parsed expression
was missing from their docs. I might open an issue or PR to update them!
2022-06-03 11:08:57 -04:00
Abhineet Jain 7e89d91ce3 feat: link to timezone database spells out timezone (#2026) 2022-06-03 14:57:09 +00:00
G r e y c2720577cb fix: ws schedule top-down restriction (#2008)
Resolves: #1958

Summary:

The workspace schedule form no longer disables certain fields based on
whether or not a start time is filled out. Instead, we validate that a
start time is provided if any of the days are checked.
2022-06-03 10:50:36 -04:00
Bruno Quaresma 88e8c96ddd feature: Load workspace build logs from streaming (#1997) 2022-06-03 09:23:45 -05:00
Abhineet Jain d6e9eab258 feat: Use consistent colors for links and highlighting (#1989)
* feat: consistent highlight colors

* update user dropdown menu border

* update borderedmenurow active color
2022-06-03 10:16:50 -04:00
Bruno Quaresma 6bb76782a6 feat: Open terminal in a new window (#2017) 2022-06-03 09:06:44 -05:00
Mathias Fredriksson b4f5920df5 fix: Avoid use of r.Context() after r.Hijack() (#1978) 2022-06-03 12:50:10 +03:00
Kyle Carberry 61aacff444 chore: Refactor site to improve testing (#2014)
It was difficult to develop this package due to the
embed build tag being mandatory on the tests. The logic
to test doesn't require any embedded files.
2022-06-03 04:27:21 +00:00
Colin Adler 89dde21837 fix: ensure listen websocket isn't opened for non-latest agents (#2002)
Exponential backoff is only enabled if the websocket fails to open. If
the websocket is opened but immediately killed, the agent will try to
immediately reconnect. This is desireable in cases where coderd is being
replaced or network conditions cause the connection to die, but not for
permanent errors.
2022-06-02 15:03:01 -05:00
Ketan Gangatirkar 0e1f868f5f tweak README.md headings around one liner 2022-06-02 14:48:22 -05:00
Ketan Gangatirkar 597994548d added one liner to run Coder at very top of README.md 2022-06-02 14:47:34 -05:00
G r e y 0b59ed30d0 feat: ui autostop extension (#1987)
Resolves: #1460

Summary:

An 'Extend' CTA on workspace schedule banner is added so that a user can
extend their workspace lease from the UI.

Details:

* feat: putWorkspaceExtension handler

* refactor: TypesGen dflt import in workspace.ts

* feat: defaultWorkspaceExtension util

Impact:

This completes the UI<-->CLI parity epic in an MVP way. Of course, a
future improvement to make is extending by times other than the default
90 minutes.
2022-06-02 15:44:11 -04:00
Oxylibrium 1a07d021fe ux: change colors for inflight workspace actions (#1986) 2022-06-02 12:52:20 -04:00
Abhineet Jain e09cd3e9cf feat: Update UI for error dialog and snackbar (#1971)
* feat: update ui for error dialog and snackbar

* update padding for buttons
2022-06-02 11:23:52 -04:00
Abhineet Jain 47c7eda670 feat: add a divider after Account menu item (#1927)
* add a divider after Account menu item

* test: improve Storybook tests

* add closed and open userdropdown tests

* add default isOpen

* extract UserDropdownContent into a single component

* remove the isOpen prop

* address nit comments

* update test name
2022-06-02 11:09:19 -04:00
Steven Masley e6ee7dd652 chore: Add linting rule to help catch InTx misuse (#1980)
* chore: Add linting rule to help catch InTx misuse

This isn't perfect, as if you nest your misuse in another code block
like an if statement, it won't catch it :/. It is better
than nothing
2022-06-02 14:50:15 +00:00
Abhineet Jain c463e7801c feat: Update TTL language to Time until shutdown (#1948)
* feat: update ttl language in frontend

* Update TTL Helper text

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>

* update TTL helper string

Co-authored-by: Presley Pizzo <1290996+presleyp@users.noreply.github.com>
2022-06-02 10:15:36 -04:00
G r e y ab69c22ddc fix: missing FE ttl constraint validation (#1952)
Resolves: #1908
2022-06-02 10:14:42 -04:00
Steven Masley b9983e417f feat: Handle pagination cases where after_id does not exist (#1947)
* feat: Handle pagination cases where after_id does not exist

Throw an error to the user in these cases
- Templateversions
- Workspacebuilds

User pagination does not need it as suspended users still
have rows in the database
2022-06-02 09:01:45 -05:00
Kira Pilot 419dc6b036 feat: flexbox updates on workspace page (#1963)
* feat: flexbox work on workspace page

resolves 1910

* fixing cancel text

* chromatic fixes

* resolves #1953

no overflox text on smaller screens
2022-06-02 09:57:36 -04:00
Bruno Quaresma 3fd4dcd9d5 fix: Display member role when user has no role (#1965) 2022-06-02 08:46:06 -05:00
Cian Johnston dcf03d8ba3 chore: refactor time.Duration -> int64 milliseconds for FE consumption (#1944)
* Changes all public-facing codersdk types to use a plain int64 (milliseconds) instead of time.Duration.
* Makes autostart_schedule a *string as it may not be present.
* Adds a utils/ptr package with some useful methods.
2022-06-02 11:23:34 +01:00
Mathias Fredriksson 51c420c90a feat: Add support for --identity-agent in coder ssh (#1954) 2022-06-02 11:13:38 +03:00
Spike Curtis 9e3a625898 Show workspace name in WorkspaceBuildStats component (#1933)
* Show workspace name in WorkspaceBuildStats component

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

* Fix WorkspaceBuildPage tests

Signed-off-by: Spike Curtis <spike@coder.com>
2022-06-01 16:49:43 -07:00
Cian Johnston b203d40123 fix: fix duplicate migrations (#1968) 2022-06-01 20:58:22 +00:00
Steven Masley 913c0f5e7f feat: Longer lived api keys for cli (#1935)
* feat: Longer lived api keys for cli
* feat: Refresh tokens based on their lifetime set in the db
* test: Add unit test for refreshing
2022-06-01 14:58:55 -05:00
Presley Pizzo bb400a4e82 fix: Show error message from backend on create existing user (#1964)
* Show error message from backend on create existing user

* Format
2022-06-01 15:52:54 -04:00
Ben Potter 46ffb67d60 feat: one-line install script (#1924)
* feat: one-line install script

* remove homebrew support

* remove arch linux

* use proper filename for packages

* fix variable format

* fix systemd instructions

* fixes to standalone script

* fix missing var bugs

* fix standalone install

* fix for MacOS

* format

* fix armv7 assets and zips

* remove windows

* update install docs

* support external sources with shellcheck

* shfmt

* add external sources to GitHub action & unfold

* change wording

* first template docs

* default to /usr/local instead

* add option for binary name
2022-06-01 14:15:09 -05:00
Bruno Quaresma f5a8d17aa8 feat: Add copy button to the SSH Page (#1962) 2022-06-01 19:01:36 +00:00
Bruno Quaresma b85de3ee79 feat: Improve empty states for workspaces and templates (#1950) 2022-06-01 17:32:55 +00:00
Dean Sheather 6be8a373e0 feat: run a terraform plan before creating workspaces with the given template parameters (#1732) 2022-06-02 00:44:53 +10:00
Steven Masley cc87a0cf6b feat: Implied 'member' roles for site and organization (#1917)
* feat: Member roles are implied and never exlpicitly added
* Rename "GetAllUserRoles" to "GetAuthorizationRoles"
* feat: Add migration to remove implied roles
* rename user auth role middleware
2022-06-01 09:07:50 -05:00
Presley Pizzo 2878346f19 Use backend error if possible (#1938) 2022-06-01 09:09:58 -04:00
Kyle Carberry 1fa50a9da1 fix: Race when writing to a closed pipe (#1916) 2022-06-01 07:59:03 -05:00
Cian Johnston 1c5d94ed5b fix: add all regions to aws examples (#1934) 2022-06-01 11:20:14 +01:00
Cian Johnston 7b40c692eb fix: coderd: dev mode should show verbose output by default (#1898)
* check buildinfo for devel prerelease tag and show verbose output if so
2022-06-01 11:00:42 +01:00
Steven Masley 7acb742218 feat: Prevent role changing on yourself. (#1931)
* feat: Prevent role changing on yourself.

Only allow changing roles on other users. Not much value in self changing
at the moment
2022-05-31 15:50:38 -05:00
Spike Curtis 4b0ed06a26 Remove set -u on yarn_install.sh to allow it to run on zsh (#1930)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-05-31 13:50:18 -07:00
G r e y 56ec53d04b fix: derive running ws stop time from deadline (#1920)
* refactor: isWorkspaceOn utility

Summary:

A utility is function is added that answers the question if a workspace
is on.

Impact:

This is a shared piece of logic in workspace scheduling presentations.
In particular it unblocks work in 1779, or at least allows an
implementation that shares details with the WorkspaceScheduleBanner.

Notes:

We could possibly instead return whether the workspace is "ON",
"UNKNOWN", or "OFF". Maybe a future improvement for that could be made
as the neds arrises.

* fix: derive running ws stop time from deadline

Summary:

When a workspace is on, the remaining time until shutdown needs to be
derived from the deadline timestamp, not implied from the TTL
2022-05-31 15:50:03 -04:00
G r e y c6167a94ef refactor: remove dangling comment (#1929) 2022-05-31 19:39:29 +00:00
Bruno Quaresma 65c17a04df feat: Add selected template link at the template select field (#1918) 2022-05-31 18:28:22 +00:00
Bruno Quaresma 75bcb739f9 refactor: Make login headline one line and add auth method section (#1922) 2022-05-31 16:40:56 +00:00
Kira Pilot 555bf2461a fix: change color of time icon for dark mode (#1923)
resolves #1791
2022-05-31 12:33:15 -04:00
G r e y bdacbd4989 refactor: mock provisioner job typings (#1919)
An unnecessary type assertion was being made on the status property;
instead we just type the object as a ProvisionerJob
2022-05-31 15:16:15 +00:00
Presley Pizzo 6f7b7f0248 feat: Delete workspace (#1822)
* Add delete button

* Add confirmation dialog

* Extract dialog, storybook it, and test it

* Fix cancel and redirect

* Remove fragment
2022-05-31 10:43:31 -04:00
Abhineet Jain 9b19dc9154 refactor: rename SettingsPages directory to UserSettingsPage (#1877) 2022-05-31 14:16:17 +00:00
Bruno Quaresma 83edbee2e1 fix: Replace yes by true and add set -x (#1914) 2022-05-31 14:14:14 +00:00
Kira Pilot dd55d4577d chore: remove react imports (#1867)
reolves #1856
2022-05-31 10:01:37 -04:00
Steven Masley 26a2a169df fix: Suspended users cannot authenticate (#1849)
* fix: Suspended users cannot authenticate

- Merge roles and apikey extract httpmw
- Add member account to make dev
- feat: UI Shows suspended error logging into suspended account
- change 'active' route to 'activate'
2022-05-31 08:06:42 -05:00
Cian Johnston e02ef6f228 chore: executor_test: reduce test execution time (#1876)
Removes 5-second wait in autobuild.executor unit tests:

- Adds a write-only channel to Executor and plumbs through to unit tests
- Modifies runOnce to return an executor.RunStats struct and write to statsCh if not nil
2022-05-30 20:23:36 +01:00
Ketan Gangatirkar ae4b2d88cd added links to our issues to reduce necessary thinking to report issues 2022-05-30 14:19:48 -05:00
Cian Johnston a8ae9b39b3 feat: enforce upper bounds on workspace TTL and Deadline (#1902)
* Enforces upper bound for workspace TTL
* Enforces upper bound for workspace deadline
2022-05-30 20:19:17 +01:00
Ketan Gangatirkar 17a57a44eb added community links 2022-05-30 14:16:02 -05:00
Ketan Gangatirkar 02692402d8 added #coder in the most prominent least awkward place 2022-05-30 14:12:33 -05:00
Ben Potter 6850db2a47 chore: fix additional typo in templates doc 2022-05-28 08:14:46 -05:00
Ben Potter 80ec67f3fd chore: fix typo in templates docs 2022-05-28 08:13:50 -05:00
Ben Potter 7ad68ca36b example: docker: support Windows hosts (#1880) 2022-05-28 01:09:29 +00:00
Kyle Carberry da7ed8b292 chore: Ignore scripts from code coverage (#1878)
Our CI scripts don't need to have thorough tests, and aren't
in the hot path of the product.
2022-05-27 22:25:24 +00:00
Garrett Delfosse 5598ac05dc fix: prevent email from being altered (#1863) 2022-05-27 22:25:04 +00:00
Asher cfa316be89 fix: incomplete message when intercepting console logger (#1875)
I was getting a message like "Warning: Failed type %s: %s%s".
2022-05-27 17:16:19 -05:00
Asher dd1484e24f fix: add missing key to resource row (#1874) 2022-05-27 17:16:04 -05:00
Garrett Delfosse 8222bdc3bc feat: add user password change page (#1866) 2022-05-27 18:08:28 -04:00
Ben 8cd7d4fa9c chore: update hero 2022-05-27 20:48:52 +00:00
Abhineet Jain d623eeb8d1 feat: delete API token in /logout API (#1770)
* delete API token in logout api

* add deleteapikeybyid to databasefake

* set blank cookie on logout always

* refactor logout flow, add unit tests

* update logout messsage

* use read-only file mode for windows

* fix file mode on windows for cleanup

* change file permissions on windows

* assert error is not nil

* refactor cli

* try different file mode on windows

* try different file mode on windows

* try keeping the files open on Windows

* fix the error message on Windows
2022-05-27 16:47:03 -04:00
Kyle Carberry d0ed107b08 fix: Add command to reconnecting PTY (#1860)
This fixes #1708 and opens the door for PTYs to execute
non-shell commands!
2022-05-27 14:51:20 -05:00
Kira Pilot 6052607936 feat: add user roles to menu (#1862)
* view user roles in menu

resolves #1524

* fix stories

* PR feedback
2022-05-27 15:27:51 -04:00
G r e y 8d7499feb7 feat: ui alert <= 30mins from deadline (#1825)
Summary:

When a workspace build is <= 30 minutes from auto-scheduled shutdown,
then an alert banner is displayed on the workspace page.
2022-05-27 15:23:56 -04:00
Cian Johnston ff542afe87 feat: allow bumping workspace deadline (#1828)
* Adds a `bump` command to extend workspace build deadline
 * Reduces WARN-level logging spam from autobuild executor
 * Modifies `cli/ssh` notifications to read from workspace build deadline and to notify relative time instead (sidestepping the problem of figuring out a user's timezone across multiple OSes)
 * Shows workspace extension time in `coder list` output e.g.
    ```
    WORKSPACE        TEMPLATE  STATUS   LAST BUILT  OUTDATED  AUTOSTART        TTL        
    developer/test1  docker    Running  4m          false     0 9 * * MON-FRI  15m (+5m)  
    ```
2022-05-27 20:04:33 +01:00
Ben Potter bde3779fec chore: clarify install options in README (#1844)
* chore: clarify install options in README

* clarify the path is an example, not a requirement

* Update README.md

Co-authored-by: Katie Horne <katie@coder.com>

* Update README.md

Co-authored-by: Katie Horne <katie@coder.com>

* Update README.md

Co-authored-by: Katie Horne <katie@coder.com>

* Update README.md

Co-authored-by: Katie Horne <katie@coder.com>

* Update README.md

Co-authored-by: Katie Horne <katie@coder.com>

Co-authored-by: Katie Horne <katie@coder.com>
2022-05-27 18:10:54 +00:00
Ben Potter 5000edbfe0 example: docker warning on Coder host (#1842) 2022-05-27 13:02:59 -05:00
Kyle Carberry 984dc2bffd fix: Close peer negotiate mutex if we haven't negotiated (#1774)
Closes #1706 and #1644.
2022-05-27 17:34:13 +00:00
Garrett Delfosse 24d1a6744a fix: Add route for user to change own password (#1812) 2022-05-27 17:29:55 +00:00
Mathias Fredriksson 608eb322a8 chore: Add .editorconfig, shfmt, shellcheck and subshell dir changes (#1649) 2022-05-27 20:15:19 +03:00
Mathias Fredriksson 1a70298b5c feat: Add examples/templates/do-linux for Digital Ocean Droplets (#1749)
Co-authored-by: Cian Johnston <cian@coder.com>
2022-05-27 20:04:43 +03:00
Oxylibrium 14cdd85b66 fix(site): username validation in forms (#1851)
* refactor(site): move name validation to utils
* fix(site): username validation in forms
2022-05-27 17:02:56 +00:00
Garrett Delfosse 8a5277e291 fix: restore previous session on coder server --dev (#1821) 2022-05-27 17:02:02 +00:00
Bruno Quaresma 7eacab82a2 refactor: Update users page to looks like others (#1850) 2022-05-27 16:47:11 +00:00
Ammar Bandukwala e2030bba38 Move competitive comparison to README
And rewrite a bit.

Resolves #1365.
2022-05-27 11:38:25 -05:00
Steven Masley ec1fe46138 feat: Move create organizations route (#1831)
* feat: last rbac routes
- move create organization to /organizations.
2022-05-27 11:19:13 -05:00
ketang d73a0f4f23 fixed grammar 2022-05-27 11:09:53 -05:00
Ben Potter 655f348812 chore: change README to fancy alpha note 2022-05-27 10:56:35 -05:00
Bruno Quaresma 2b2d0291c2 fix: Suspend user in the UI (#1841) 2022-05-27 15:23:56 +00:00
Bruno Quaresma 4125863226 fix: Fix template README when has front-matter notation (#1840) 2022-05-27 15:19:32 +00:00
Steven Masley a409a34819 fix: Open csp-images to allow external (#1835)
External images are required for the README parts of templates.
Only allowing https right now
2022-05-27 14:59:13 +00:00
Abhineet Jain 7a5c8734ee test: Fix unit test in 'TestWorkspaceExtend' (#1836) 2022-05-27 14:45:22 +00:00
Abhineet Jain 9929189c45 feat: add tag and value in validation error details (#1760)
* add tag and value in validation error details

* fix unit tests and linter

* add quotes around value

* fix unit tests
2022-05-27 10:13:13 -04:00
Ammar Bandukwala c5f06acb01 Add alpha disclaimer to README 2022-05-27 09:08:35 -05:00
Steven Masley ebaae75993 test: Unit test to assert role capabilities (#1781)
* test: Unit test to assert role permissions

This unit test allows for asserting which roles can perform
actions on various objects. This is much easier than making
unit tests to hit the api.
2022-05-27 08:48:19 -05:00
Mathias Fredriksson 12227874a8 fix: Detect changes to examples/templates in Makefile (#1829) 2022-05-27 16:34:32 +03:00
Garrett Delfosse 1361c1357a feat: inject USER into shells (#1818) 2022-05-26 18:01:47 -05:00
ketang 951dc2d8b0 update tagline 2022-05-26 17:00:09 -05:00
Joe Previte d01a687caa fix: typo in docker terraform template (#1811) 2022-05-26 21:28:17 +00:00
Joe Previte 4d79b806c0 docs: clarify installing Coder instructions (#1809) 2022-05-26 14:11:58 -07:00
G r e y b6d6276149 ci: disable chromatic on forks (#1806) 2022-05-26 20:27:32 +00:00
Colin Adler 5833e37354 fix: macos flake (#1804)
https://github.com/coder/coder/runs/6614638495?check_suite_focus=true#step:9:104
2022-05-26 15:21:48 -05:00
Colin Adler d135f85f69 fix: use correct devnull device on windows for proxy logs (#1803) 2022-05-26 15:21:36 -05:00
G r e y 7467bfe4ed chore: organize ws stats, schedule stories (#1790)
Resolves: #1681

Summary:

- Moves WorkspaceSchedule out of WorkspaceStats
- Adds WorkspaceScheduleForm directory

Impact:

Improves breadth of our chromatic visual regression tests since the
examples for WorkspaceStats were non-representative of the component
2022-05-26 16:14:08 -04:00
Kira Pilot d4c26d534c chore: remove admin dropdown (#1802)
resolves #1748
2022-05-26 16:04:51 -04:00
Presley Pizzo 07ebd59e94 fix: Remove workspace Settings button and page (#1807) 2022-05-26 20:02:37 +00:00
Garrett Delfosse 4d6e8526a8 chore: tolerate codecov failures in CI (#1798) 2022-05-26 14:48:34 -05:00
Kira Pilot b4c41d3904 chore: add users link to nav bar (#1797)
* chore: add users link to nav bar

resolves #1746

* fix test names
2022-05-26 15:25:13 -04:00
Garrett Delfosse 781f3d0641 fix: use dir over full path for coder bin (#1795) 2022-05-26 19:05:46 +00:00
Bruno Quaresma 7b393526c5 fix: Fix sensitive parameters being displayed in the new workspace form (#1796) 2022-05-26 13:42:25 -05:00
Presley Pizzo d2ff5904c0 fix: hide New user button if no permission (#1794) 2022-05-26 14:25:23 -04:00
Bruno Quaresma e1b0cb0bca Remove create template button from the UI (#1793) 2022-05-26 18:22:47 +00:00
Garrett Delfosse 3052a6d88e Add coder executable to PATH (#1771) 2022-05-26 12:59:41 -05:00
Presley Pizzo fc67c6efb1 fix: remove unused pages from Admin dropdown (org and settings) (#1788)
* Delete Orgs Page

* Delete Admin Settings page
2022-05-26 13:10:54 -04:00
Cian Johnston 8f0a5a81f1 feat: add API/SDK support for autostop extension (#1778)
* Adds deadline column to workspace_builds, associated DB/API plumbing
* database: Upon inserting a row into workspace_builds, deadline will 
  initially be zero.
* autobuild: Executor now checks the Deadline field of the workspace_build
  for the purpose of autostop logic.
* coderd: Adds a new route /api/v2/workspaces/:workspace/extend which allows
  updating the deadline of the currently active workspace build. The new
  deadline must be after the existing deadline, and not the zero time.
* provisionerd: updates workspace_build.deadline upon successful workspace 
  build completion (equal to now plus workspace TTL, if it exists).
2022-05-26 18:08:11 +01:00
Steven Masley c04d045279 feat: RBAC provisionerdaemons and parameters (#1755)
* chore: Remove org_id from provisionerdaemons
2022-05-26 11:20:54 -05:00
Bruno Quaresma 104d07f659 feat: Add the template page (#1754) 2022-05-26 16:19:11 +00:00
G r e y 7c59ec4a2b feat: edit workspace schedule page (#1701)
Resolves: #1455 
Resolves: #1456

Summary:

Adds a page (accessible from Workspace Schedule section on a workspace) to edit a schedule.

Impact:

General parity with CLI for autostart/autostop: that is you can update your schedule from the UI
2022-05-26 12:11:30 -04:00
Kira Pilot 9a70c345c7 fix: update workspace form fields when switching templates (#1761)
resolves #1716
2022-05-26 08:43:07 -04:00
Kyle Carberry 31b819e83f chore: Remove interface from coderd and lift API surface (#1772)
Abstracting coderd into an interface added misdirection because
the interface was never intended to be fulfilled outside of a single
implementation.

This lifts the abstraction, and attaches all handlers to a root struct
named `*coderd.API`.
2022-05-26 03:14:08 +00:00
Abhineet Jain c78f947e09 feat: Upgrade terraform version to 1.1.9 (#1745)
* upgrade terraform version to 1.1.9

* Fix docs typo

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2022-05-25 19:35:41 -04:00
Presley Pizzo 841d9f277c feat: UI for canceling workspace builds (#1735)
* Start hooking up cancel

* Update xservice

* Render cancel

Changes behavior of other buttons too

* Make outdated workspace story show max buttons

* Remove retry code

* Remove loading button state

* Fix type, extend tests

* Update story
2022-05-25 17:58:00 -04:00
Garrett Delfosse 35ccb88f60 feat: add dotfiles command (#1723) 2022-05-25 16:43:20 -05:00
Ben Potter 47ef03fea4 example: fix: properly tag aws-windows workspaces (#1744) 2022-05-25 22:11:29 +01:00
Colin Adler b5d615367e chore: update cdr.dev/slog (#1759)
Fixes #1626
2022-05-25 20:22:38 +00:00
Mathias Fredriksson 527f1f3bc3 feat: Add SSH agent forwarding support to coder agent (#1548)
* feat: Add SSH agent forwarding support to coder agent

* feat: Add forward agent flag to `coder ssh`

* refactor: Share setup between SSH tests, sync goroutines

* feat: Add test for `coder ssh --forward-agent`

* fix: Fix test flakes and implement Deans suggestion for helpers

* fix: Add example to config-ssh

* fix: Allow forwarding agent via -A

Co-authored-by: Cian Johnston <cian@coder.com>
2022-05-25 21:28:10 +03:00
dependabot[bot] 22ef456164 chore: bump github.com/gohugoio/hugo from 0.98.0 to 0.99.1 (#1699)
* chore: bump github.com/gohugoio/hugo from 0.98.0 to 0.99.1

Bumps [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) from 0.98.0 to 0.99.1.
- [Release notes](https://github.com/gohugoio/hugo/releases)
- [Changelog](https://github.com/gohugoio/hugo/blob/master/goreleaser.yml)
- [Commits](https://github.com/gohugoio/hugo/compare/v0.98.0...v0.99.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>

* fixup! chore: bump github.com/gohugoio/hugo from 0.98.0 to 0.99.1

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Colin Adler <colin1adler@gmail.com>
2022-05-25 12:44:23 -05:00
dependabot[bot] 088f842e17 chore: bump github.com/hashicorp/terraform-json from 0.13.0 to 0.14.0 (#1736)
Bumps [github.com/hashicorp/terraform-json](https://github.com/hashicorp/terraform-json) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/hashicorp/terraform-json/releases)
- [Commits](https://github.com/hashicorp/terraform-json/compare/v0.13.0...v0.14.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-25 12:20:28 -05:00
dependabot[bot] 29175d3158 chore: bump github.com/ory/dockertest/v3 from 3.8.1 to 3.9.0 (#1738)
Bumps [github.com/ory/dockertest/v3](https://github.com/ory/dockertest) from 3.8.1 to 3.9.0.
- [Release notes](https://github.com/ory/dockertest/releases)
- [Commits](https://github.com/ory/dockertest/compare/v3.8.1...v3.9.0)

---
updated-dependencies:
- dependency-name: github.com/ory/dockertest/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>
2022-05-25 12:20:11 -05:00
dependabot[bot] cd6fdc7832 chore: bump google.golang.org/api from 0.79.0 to 0.81.0 (#1737)
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.79.0 to 0.81.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.79.0...v0.81.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>
2022-05-25 11:51:21 -05:00
dependabot[bot] 3c21b070d7 chore: bump github.com/pion/webrtc/v3 from 3.1.39 to 3.1.41 (#1697)
Bumps [github.com/pion/webrtc/v3](https://github.com/pion/webrtc) from 3.1.39 to 3.1.41.
- [Release notes](https://github.com/pion/webrtc/releases)
- [Commits](https://github.com/pion/webrtc/compare/v3.1.39...v3.1.41)

---
updated-dependencies:
- dependency-name: github.com/pion/webrtc/v3
  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>
2022-05-25 16:35:02 +00:00
Steven Masley eea8dc6c16 feat: Add rbac to templateversion+orgmember endpoints (#1713) 2022-05-25 11:00:59 -05:00
David Wahler f8410dee3a fix: include subdirectories in example templates (#1715) 2022-05-25 10:34:28 -05:00
Bruno Quaresma 5492ab75c2 feat: Add resource type and restyle terminal link (#1722) 2022-05-25 13:32:01 +00:00
Ammar Bandukwala c5f4d80eda Shorten README before Install docs 2022-05-24 20:20:08 -05:00
Kyle Carberry 74329f479f fix: Use Terraform address to index resource + agent association (#1727)
Closes #1705.

There was an issue in the implementation brought by #1577 by not trimming
the array value when resources use counts. This should fix it, and adds
a test to be sure!
2022-05-24 20:00:34 -05:00
Ammar Bandukwala 95d7e39c80 Rewrite README for launch (#1731) 2022-05-25 00:40:57 +00:00
Kyle Carberry 4d9168c076 fix: Increase release timeout (#1729)
This is unfortunate, but with the containers it can take a while.
We should spend some time making these parallel in the future,
but for now this is fine!
2022-05-24 16:54:27 -05:00
Colin Adler 4543a3b277 fix: log after test exit in TestAgent/StartupScript (#1726)
```
$ go test ./agent/ -v -run TestAgent/StartupScript -count 1
=== RUN   TestAgent
=== PAUSE TestAgent
=== CONT  TestAgent
=== RUN   TestAgent/StartupScript
=== PAUSE TestAgent/StartupScript
=== CONT  TestAgent/StartupScript
    t.go:56: 2022-05-24 20:22:39.648 [INFO]	<agent.go:112>	connected
--- PASS: TestAgent (0.00s)
    --- PASS: TestAgent/StartupScript (0.17s)
PASS
panic: Log in goroutine after TestAgent/StartupScript has completed: 2022-05-24 20:22:39.651 [WARN]	<agent.go:130>	agent script failed ...
"error": run:
             github.com/coder/coder/agent.(*agent).runStartupScript
                 /home/colin/Projects/coder/coder/agent/agent.go:183
           - signal: killed
```
2022-05-24 16:03:42 -05:00
Oxylibrium 99c79c79db docs(README): fix links to subpages (#1724) 2022-05-24 19:26:25 +00:00
G r e y 104c76b8bc ci: limit chromatic to site (#1700) 2022-05-24 14:30:15 -04:00
Joe Previte 0ade49b758 docs: rephrase value statement in README (#1711) 2022-05-24 10:59:20 -07:00
Abhineet Jain 7ba6449054 Improve CLI logout flow (#1692)
* Improve CLI logout flow

* Fix lint error

* Make notLoggedInMessage a const

* successful logout with a msg when cfg files are absent

* use require, os.remove, show only one message, add prompt
2022-05-24 13:11:01 -04:00
Ammar Bandukwala 33e2e40942 Expand stalebot to issues (#1672)
Removing old, stale issues is essential to keeping a workable tracker.
2022-05-24 10:03:43 -07:00
Steven Masley d3a0578fe1 feat: Allow regen-ssh and fetching a single user from the cli (#1619)
* feat: Allow regen-ssh and fetching a single user from the cli
2022-05-24 16:53:04 +00:00
Steven Masley 363b16af38 fix: Add template read permission node to members (#1712) 2022-05-24 16:35:34 +00:00
Joe Previte 61ffd03aaf docs: update contribution guidelines (#1691)
* docs(contributing): add subheading backend under styling

* docs: add styling for frontend
2022-05-24 15:30:15 +00:00
G r e y b0d52039f9 refactor: resource strings in WorkspaceSchedule (#1702) 2022-05-24 09:55:30 -04:00
Steven Masley c7ca86d374 feat: Implement RBAC checks on /templates endpoints (#1678)
* feat: Generic Filter method for rbac objects
2022-05-24 08:43:34 -05:00
Bruno Quaresma fcd610ee7b refactor: Update create workspace flow to allow creation from the workspaces page (#1684) 2022-05-24 08:37:44 -05:00
Steven Masley 5f8d0e5dad feat: Add RBAC to /files endpoints (#1664)
* feat: Add RBAC to /files endpoints
2022-05-24 08:25:02 -05:00
Bruno Quaresma f763472609 fix: Fix template label (#1685) 2022-05-24 12:38:31 +00:00
Mathias Fredriksson 34b1e19338 fix: Try to fix cli portforward test flakes (#1650)
* fix: Try to fix cli portforward test flakes

* fix: Guard against agent exit outside test func

* fix: Improve test teardown in setupTestListener, cleanup
2022-05-24 11:15:06 +03:00
Cian Johnston c2f74f3cc2 chore: avoid concurrent usage of t.FailNow (#1683)
* chore: golangci: add linter rule to report usage of t.FailNow inside goroutines
* chore: avoid t.FailNow in goroutines to appease the race detector
2022-05-24 08:58:39 +01:00
Presley Pizzo 9b70a9b2eb Fix: fix Workspace storybook and remove unnecessary fetching from xService (#1682)
* Make workspace machine ephemeral to limit polling

* Fix Workspace storybook

* Lint

* Remove breadcrumb from workspaceXService
2022-05-23 20:04:38 -04:00
dependabot[bot] 4ba3eedb70 chore: bump github.com/lib/pq from 1.10.5 to 1.10.6 (#1653)
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.5...v1.10.6)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  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>
2022-05-23 18:02:12 -05:00
dependabot[bot] 62acfc9a07 chore: bump goreleaser/goreleaser-action from 2 to 3 (#1652)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-23 22:55:40 +00:00
dependabot[bot] 98345e3d24 chore: bump github.com/hashicorp/go-version from 1.4.0 to 1.5.0 (#1654)
Bumps [github.com/hashicorp/go-version](https://github.com/hashicorp/go-version) from 1.4.0 to 1.5.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.4.0...v1.5.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>
2022-05-23 17:49:21 -05:00
dependabot[bot] e9818d79da chore: bump jaxxstorm/action-install-gh-release from 1.6.0 to 1.7.1 (#1651)
Bumps [jaxxstorm/action-install-gh-release](https://github.com/jaxxstorm/action-install-gh-release) from 1.6.0 to 1.7.1.
- [Release notes](https://github.com/jaxxstorm/action-install-gh-release/releases)
- [Commits](https://github.com/jaxxstorm/action-install-gh-release/compare/v1.6.0...v1.7.1)

---
updated-dependencies:
- dependency-name: jaxxstorm/action-install-gh-release
  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>
2022-05-23 17:47:38 -05:00
dependabot[bot] 2de47ef9f0 chore: bump github.com/quasilyte/go-ruleguard/dsl from 0.3.19 to 0.3.21 (#1655)
Bumps [github.com/quasilyte/go-ruleguard/dsl](https://github.com/quasilyte/go-ruleguard) from 0.3.19 to 0.3.21.
- [Release notes](https://github.com/quasilyte/go-ruleguard/releases)
- [Commits](https://github.com/quasilyte/go-ruleguard/compare/dsl/v0.3.19...dsl/v0.3.21)

---
updated-dependencies:
- dependency-name: github.com/quasilyte/go-ruleguard/dsl
  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>
2022-05-23 22:45:40 +00:00
Cian Johnston b2020761d9 feat: add default autostart and ttl for new workspaces (#1632)
* database: add autostart_schedule and ttl to InsertWorkspace; make gen
* coderd: workspaces: consume additional fields of CreateWorkspaceRequest
* cli: update: add support for TTL and autostart_schedule
* cli: create: add unit tests
* coder: import  `time/tzdata` for embedded timezone database
* autobuild: fix unit test that only runs with a real db
2022-05-23 23:31:41 +01:00
G r e y c465f8a8a3 feat: add retry to ErrorSummary (#1690)
Summary:

The ErrorSummary accepts a retry callback and received improvements to
style and product copy

Impact:

This allows xstate-controlled pages to send re-fetch events
2022-05-23 21:07:52 +00:00
Asher dd4bb07193 feat: add terminal links (#1636) 2022-05-23 15:49:02 -05:00
Oxylibrium 80f8f605fd chore: Add self to CONTRIBUTORS.md (#1680) 2022-05-23 16:46:03 -04:00
Bruno Quaresma 57c6d887a1 chore: Ignore last built value on Chromatic (#1687) 2022-05-23 20:42:05 +00:00
Katie Horne 98c89f80b0 chore: add instructions for installation w/ Docker Compose (#1599)
Co-authored-by: Ben Potter <ben@coder.com>
2022-05-23 19:42:45 +00:00
ketang ba66052181 fix incorrect retention field on artifacts in coder.yaml 2022-05-23 14:13:05 -05:00
Abhineet Jain fc46818e31 chore: move contributor list to contributors.md (#1496) 2022-05-23 19:09:45 +00:00
ketang 7de4cd6231 replace .deb artifact with Windows .zip 2022-05-23 13:54:13 -05:00
Abhineet Jain 4a78bade6d bug: Cleaner error message for non logged-in users (#1670)
* add helper text to unauthorized error messages

* fix lint error, add unit tests

* fix test name

* fix test name

* fix lint errors in test

* add unauthorized test for templates create

* remove unnecessary variable

* remove Error struct, change error message

* change [url] to <url>
2022-05-23 14:51:49 -04:00
ketang c543fca92f add tar.gz to artifacts and a 7 day retention period to .deb 2022-05-23 13:35:25 -05:00
Katie Horne b0298a3157 chore: fix in-product copy casing (#1671) 2022-05-23 13:30:38 -05:00
Presley Pizzo 7ac3cbe772 Make workspace machine ephemeral to limit polling (#1674) 2022-05-23 13:25:46 -04:00
Steven Masley 873ae90f39 feat: cli configs should not be space sensitive (#1668) 2022-05-23 12:19:33 -05:00
Mathias Fredriksson c8ed213347 fix: Guard against CLI cmd running after test exit (#1658)
* fix: Guard against CLI cmd running after test exit

* fix: cli: avoid calling t.FailNow in non-test-main goroutine

* fix: cli: server_test: avoid calling t.FailNow outside main goroutine

* fix: cli: clitest_test: avoid calling t.FailNow outside main goroutine

* fix: cli: list_test: avoid calling t.FailNow outside main goroutine

* fix: TestGitSSH use-of-t-after-exit

* fix: TestGitSSH "too many authentication failures"

Due to local SSH keys being given

* chore: clitest: fix TestCli

* chore: Simplify TestTemplateInit

Co-authored-by: Cian Johnston <cian@coder.com>
2022-05-23 20:09:58 +03:00
Kira Pilot fa957d6d65 fix: omit url params on login (#1666)
resolves #1282
2022-05-23 11:01:32 -04:00
Bruno Quaresma 9f3a6d631c refactor: Move schedule info to the sidebar (#1665) 2022-05-23 09:41:04 -05:00
Bruno Quaresma 1f03277f1c refactor: Increase navbar height (#1662) 2022-05-23 10:22:48 -04:00
Cian Johnston a8a8f9dbf3 chore: skip some flaky tests (#1643)
* chore: skip some flaky tests

* Update peer/conn_test.go

* add makefile targets, reduce parallelism in go test
2022-05-21 00:39:51 +01:00
G r e y 4f75291446 feat: form for editing ws schedule (#1634)
* feat: ui for editing ws schedule

Summary:

This presents a form component and storybook. The UI will be a routed
page and added into the dashboard in a separate PR. It is likely a
XService will be used at the page level to supply errors and actions to
this form.

Impact of Change:

Further progress on #1455

Squashed Commits:

* refactor: add className prop to Stack

combine classes with internal classes and an optional external className
to better control the Stack.

* fix: getFormHelpers helperText

the helperText logic was incorrect, the helperText would only show if not touched.
2022-05-20 20:26:43 +00:00
Bruno Quaresma b29a2dfdde refactor: Minor design adjustments (#1637) 2022-05-20 19:37:03 +00:00
Joe Previte 3653fcf256 fix: remove outdated doc paths in goreleaser (#1633)
It appears we were manually moving the `README.md`. This should have been updated in https://github.com/coder/coder/pull/1630 but slipped through CI
2022-05-20 19:02:38 +00:00
Presley Pizzo e40c68399d feat: resources card (#1627)
* Set up table

* Format

* Hook up api and test - bug assigning resources

* Remove debugging code

* Format

* Remove unnecessary cards

* Fix test

* Fix assignment

* Fix tests

* Lint
2022-05-20 18:29:42 +00:00
Steven Masley c189fc52c1 fix: using a trailing slash on login url (#1622) 2022-05-20 12:42:01 -05:00
Bruno Quaresma ce7bf0b847 feat: Redesign the workspace page (#1620) 2022-05-20 17:05:00 +00:00
Joe Previte 0622603220 docs: move README to root (#1630)
We noticed that when you download the repo as a ZIP from GitHub, it
places the `README.md` in the root, which causes the relative links to
break.

By moving it to the root, this will fix that issue.
2022-05-20 09:56:50 -07:00
Steven Masley ad946c3902 feat: Add confirm prompts to some cli actions (#1591)
* feat: Add confirm prompts to some cli actions
- Add optional -y skip. Standardize -y flag across commands
2022-05-20 15:59:04 +00:00
G r e y 4f70f84635 feat: WorkspaceSection action, styles (#1623)
This PR is a squash of refactors and improvements in our Workspace and
WorkspaceSection components. An action prop is added to WorkspaceSection
and along the way, I refactored things that were not meeting conventions
or were hard to read. With this addition, I am further unblocked in
making auto-start/off editable in the UI, as I intend to use the Action
prop to trigger a modal (or routed page view) with the form.

Squashed commits:

* refactor: spaces for readability
It's hard to read HTMl markup without spaces on adjacent nodes

* refactor: props
Our components had unused props and arbitrary ordering.
2022-05-20 11:55:39 -04:00
Garrett Delfosse 0effb71f43 feat: add tracing for sql (#1610) 2022-05-20 10:51:06 -05:00
Abhineet Jain 7c3e1a5d97 feat: Read params from file for template/workspace creation (#1541)
* Read params from file for template/workspace creation

* Use os.ReadFile

* Refactor reading params into a separate module

* Add comments and unit tests

* Rename variable

* Uncomment and fix unit test

* Fix comment

* Refactor tests

* Fix unit tests for windows

* Fix unit tests for Windows

* Add comments for the hotfix
2022-05-20 11:29:10 -04:00
Kira Pilot d0fd0d7040 feat: added error boundary (#1602)
* added error boundary and error ui components

* add body txt and standardize btn size

* added story

* feat: added error boundary

closes #1013

* committing lockfile

* added email body to help link
2022-05-20 10:48:39 -04:00
Cian Johnston 52230fab56 feat: make default autobuild poll intervals configurable (#1618)
* feat: make default poll intervals for autobuild and ssh ttl polling configurable
2022-05-20 10:57:02 +00:00
Mathias Fredriksson 992b58389b fix: Use the cobra CommandPath for usage to avoid duplication (#1617) 2022-05-20 12:42:56 +03:00
Dean Sheather adb7d20c16 feat: skip terraform destroy if there is no state when deleting (#1594) 2022-05-20 14:07:23 +10:00
Spike Curtis a03615a01f feature: disable provisionerd listen endpoint (#1614)
* feature: disable provisionerd listen endpoint

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

* Regenerate ts types

Signed-off-by: Spike Curtis <spike@coder.com>
2022-05-19 23:52:17 +00:00
Spike Curtis d1817310a1 fix build and lint (#1613)
Signed-off-by: Spike Curtis <spike@coder.com>
2022-05-19 23:28:29 +00:00
Spike Curtis 1871b09697 feat: in-process provisionerd connection (#1568)
* in-process provisionerd connection

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

* disable lint for server.go/newProvisionerDaemon

Signed-off-by: Spike Curtis <spike@coder.com>
2022-05-19 17:47:45 -05:00
Garrett Delfosse 376c6819e0 feat: Move from datadog to generic otel (#1567) 2022-05-19 17:43:07 -05:00
Colin Adler 2a85d3d083 chore: unconditionally run all make cmds in CI (#1608) 2022-05-19 17:42:49 -05:00
Garrett Delfosse 077f16ce2c feat: add coder logout command (#1609) 2022-05-19 22:42:32 +00:00
David Wahler 0c4a65b113 fix: manually fix coderd/database/dump.sql and make style/gen check run more reliably (#1607) 2022-05-19 22:37:22 +00:00
Joe Previte 6dae48a1a8 fix: show --help message for CLI errors, add tests for delete (#1403)
* feat(cli): add test for delete

This adds a new test for the `delete` command to ensure it works as
expected when provided the correct args.

* fix(cli): use ExecuteC() to match Cobra

This modifies the `cli.Root().Execute()` to `cli.Root).ExecuteC()` to
match the default behavior of Cobra. We do this so errors will always
print the "run --help" line.

* feat(cli): add WithoutParameters test for delete

This adds a new test to the `delete_test.go` suite to ensure the correct
behavior occurs when `delete` is called without an argument.

* fixup! feat(cli): add WithoutParameters test for delete

* refactor(cli): show --help error message on main

This adds an error message which shows when there is an error with any
commands called to improve the UX.

* fixup! refactor(cli): show --help error message on main

* refactor(cli): handle err with FormatCobraError

This adds a new helper function called `FormatCobraError` to `root.go`
so that we can colorize and add "--help" message to cobra command errors
like calling `delete`.

* refactor(cli): add root_test.go, move delete test
2022-05-19 22:35:59 +00:00
G r e y a64ab6538e chore: update CODEOWNERS (#1600)
Resolves: #1559
2022-05-19 16:26:39 -05:00
Bruno Quaresma 0ffcc47f32 fix: Fix log order in the workspace build page (#1604) 2022-05-19 21:19:28 +00:00
Kyle Carberry 3be356095f feat: Add create workspace page (#1589) 2022-05-19 20:51:10 +00:00
Ben Potter 4afc66faf5 chore: remove docker host from docker-compose (#1596) 2022-05-19 20:38:07 +00:00
Bruno Quaresma 0b1a35f7b8 feat: Add workspace build logs page (#1598) 2022-05-19 15:34:42 -05:00
Cian Johnston d72c45e483 refactor: workspace autostop_schedule -> ttl (#1578)
Co-authored-by: G r e y <grey@coder.com>
2022-05-19 15:09:27 -04:00
Steven Masley 6c1117094d chore: Force codersdk to not import anything from database (#1576)
* chore: Force codersdk to not import anything from database (linter rule)
* chore: Move all database types in codersdk out
2022-05-19 13:04:44 -05:00
G r e y a0834404f7 chore: rm dead code; add check:all (#1595) 2022-05-19 12:40:40 -05:00
Ben Potter c47b6f0381 chore: use docker host in docker-compose (#1592) 2022-05-19 11:49:22 -05:00
G r e y 67333b6186 feat: getWorkspaces filter site api (#1564) 2022-05-19 12:08:55 -04:00
LG 0438430c7c fix: missing spacing added; typo fix (#1586)
Co-authored-by: Ben <ben@coder.com>
2022-05-19 15:51:49 +00:00
G r e y e0165c5d89 fix: static data in mocks (#1574) 2022-05-19 11:36:14 -04:00
Bruno Quaresma 3f770e1111 fix: User permissions on UI (#1570) 2022-05-19 15:10:18 +00:00
Dean Sheather 4eb0bb6afd feat: don't return 200 for deleted workspaces (#1556) 2022-05-20 00:29:10 +10:00
Ben Potter eb8f371f34 chore: add container image to footer of releases (#1579)
* chore: add docker pull to footer
2022-05-19 13:38:05 +00:00
Kyle Carberry 38ee519f42 feat: Expose the values contained in an HCL validation string to the API (#1587)
* feat: Expose the values contained in an HCL validation string to the API

This allows the frontend to render inputs displaying these values!

* Update codersdk/parameters.go

Co-authored-by: Cian Johnston <cian@coder.com>

* Call a spade a space

* Fix linting errors with type conversion

Co-authored-by: Cian Johnston <cian@coder.com>
2022-05-19 13:29:36 +00:00
Mathias Fredriksson ad9bdb7bd1 fix: More robust provisionersdk agent init scripts (#1551)
Related #1544

Co-authored-by: Dean Sheather <dean@deansheather.com>
2022-05-19 13:02:42 +00:00
Katie Horne 6f969214d3 chore: validate docs (#1485) 2022-05-19 08:01:19 -05:00
Dean Sheather cabc164f74 feat: use and display default template values when creating wkspc. (#1584) 2022-05-19 22:49:40 +10:00
Cian Johnston 8814cb0722 Revert "fix: Use Terraform address to index resource + agent association (#1577)" (#1585)
This reverts commit f3fe2a08ce.
2022-05-19 12:18:40 +01:00
Steven Masley c034e8389e feat: Add RBAC to /workspace endpoints (#1566)
* feat: Add RBAC to /workspace endpoints
2022-05-18 18:15:19 -05:00
Kyle Carberry f3fe2a08ce fix: Use Terraform address to index resource + agent association (#1577)
This fixes resources created from Terraform modules not
properly being associated with an agent.

By not using the address, and resource identifiers prefixed
with `module.<name>` would be missed!
2022-05-18 16:26:08 -05:00
Garrett Delfosse 0706c60445 chore: Add watch workspace endpoint (#1493) 2022-05-18 16:16:26 -05:00
Ben Potter b8ee939e52 chore: change Slack to Discord link (#1573) 2022-05-18 21:14:31 +00:00
Ben Potter 37cf3bb491 example: add docker-image-builds + docker docs (#1526)
Co-authored-by: Katie Horne <katie@23spoons.com>
2022-05-18 16:03:20 -05:00
Kyle Carberry 97699e9704 fix: Rename NewMemoryCoderd to NewWithServer (#1571)
This name felt invalid, because `New` was also in memory.
2022-05-18 15:49:46 -05:00
Steven Masley 2638c274cb fix: User's should be able to read what roles available (#1575) 2022-05-18 20:47:43 +00:00
Steven Masley 8bd1abee33 fix: Use sdk type in coderd api response (#1569)
Was using the database type
2022-05-18 15:34:00 -05:00
Garrett Delfosse e2ed581708 Add stages to all proto.Logs (#1563) 2022-05-18 17:33:29 +00:00
David Wahler a50a6e8638 fix: Make TestAgent and TestWorkspaceAgentPTY less flaky (#1562) 2022-05-18 17:06:17 +00:00
Spike Curtis 9f402fa27f Spike/222 workspace build order (#1534)
* chore: refactor before_id/after_id to build_number

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

* pagination of workspace_builds

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

* Disable parallel on postgres tests

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

* Fix lint

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

* Fix workspace build postgres query

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

* Fix JS tests

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

* Fix workspace builds postgres query

Signed-off-by: Spike Curtis <spike@coder.com>
2022-05-18 16:33:33 +00:00
Cian Johnston 13571b0393 examples/docker-local: add explanatory comment (#1545) 2022-05-18 17:10:23 +01:00
Garrett Delfosse 89fb59aa9a chore: remove make build dep from make dev (#1557) 2022-05-18 16:00:20 +00:00
Asher e4e7e10690 feat: add terminal link component (#1538)
* Fix not being able to specify agent when connecting to terminal

The `workspace.agent` syntax was only used when fetching the agent and
not the workspace so it would try to fetch a workspace called
`workspace.agent` instead of just `workspace`.

* Add terminal link component

Currently it does not show anywhere but we can drop it into the
resources card later.
2022-05-18 10:53:59 -05:00
David Wahler 5f21a145d1 bug: Don't try to handle SIGINT when prompting for passwords (#1498) 2022-05-18 15:26:38 +00:00
Steven Masley a3556b12da feat: Single query for all workspaces with optional filter (#1537)
* feat: Add single query for all workspaces using a filter
2022-05-18 10:09:07 -05:00
dependabot[bot] 894646cb7c chore: bump @testing-library/user-event from 14.1.1 to 14.2.0 in /site (#1521)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 14.1.1 to 14.2.0.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.1.1...v14.2)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  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>
2022-05-18 11:01:28 -04:00
Kira Pilot 85a932bfaf bug: fixed menu height diff (#1546)
resolves #1229
2022-05-18 10:34:56 -04:00
Dean Sheather 9141be3656 feat: add port-forward subcommand (#1350) 2022-05-19 00:10:40 +10:00
Kyle Carberry 76fc59aa79 feat: Add templates page (#1510)
* feat: Add template page

* Create xService

* Update column names

* Show create template conditionally

* Add template description

* Route to templates

* Add empty states

* Add tests

* Add loading indicator

* Requested changes
2022-05-18 09:05:18 -05:00
Bruno Quaresma b7481489b1 feat: Add timeline in the workspace page (#1533) 2022-05-18 13:54:06 +00:00
Ben Potter 6bed620d6c example: ec2: document "minimal" policy (#1536)
* example: ec2: document "minimal" policy

* move DescribeInstances

* move ModifyInstanceCreditSpecification
2022-05-18 08:17:05 -05:00
Steven Masley 4e28b2d9c5 test: Using local time in unit test fails in certain time zones (#1540)
* test: Using local time in unit test fails in certain time zones

This test was failing when running in CST (GMT-5) timezone.
My local timezone pushed the next to the upcoming monday

* fix: schedule: assert expected result of String() separately from input spec

Co-authored-by: Cian Johnston <cian@coder.com>
2022-05-18 13:09:36 +00:00
5017 changed files with 99609 additions and 26664 deletions
+83
View File
@@ -0,0 +1,83 @@
FROM ubuntu
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV EDITOR=vim
RUN apt-get update && apt-get upgrade
RUN apt-get install --yes \
ca-certificates \
bash-completion \
build-essential \
curl \
cmake \
direnv \
emacs-nox \
gnupg \
htop \
jq \
less \
lsb-release \
lsof \
man-db \
nano \
neovim \
ssl-cert \
sudo \
unzip \
xz-utils \
zip
# configure locales to UTF8
RUN apt-get install locales && locale-gen en_US.UTF-8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
# configure direnv
RUN direnv hook bash >> $HOME/.bashrc
# install nix
RUN sh <(curl -L https://nixos.org/nix/install) --daemon
RUN mkdir -p $HOME/.config/nix $HOME/.config/nixpkgs \
&& echo 'sandbox = false' >> $HOME/.config/nix/nix.conf \
&& echo '{ allowUnfree = true; }' >> $HOME/.config/nixpkgs/config.nix \
&& echo '. $HOME/.nix-profile/etc/profile.d/nix.sh' >> $HOME/.bashrc
# install docker and configure daemon to use vfs as GitHub codespaces requires vfs
# https://github.com/moby/moby/issues/13742#issuecomment-725197223
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt-get install --yes docker-ce docker-ce-cli containerd.io docker-compose-plugin \
&& mkdir -p /etc/docker \
&& echo '{"cgroup-parent":"/actions_job","storage-driver":"vfs"}' >> /etc/docker/daemon.json
# install golang and language tooling
ENV GO_VERSION=1.19
ENV GOPATH=$HOME/go-packages
ENV GOROOT=$HOME/go
ENV PATH=$GOROOT/bin:$GOPATH/bin:$PATH
RUN curl -fsSL https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz | tar xzs
RUN echo 'export PATH=$GOPATH/bin:$PATH' >> $HOME/.bashrc
RUN bash -c ". $HOME/.bashrc \
go install -v golang.org/x/tools/gopls@latest \
&& go install -v mvdan.cc/sh/v3/cmd/shfmt@latest \
"
# install nodejs
RUN bash -c "$(curl -fsSL https://deb.nodesource.com/setup_14.x)" \
&& apt-get install -y nodejs
# install zstd
RUN bash -c "$(curl -fsSL https://raw.githubusercontent.com/horta/zstd.install/main/install)"
# install nfpm
RUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list \
&& apt update \
&& apt install nfpm
+18
View File
@@ -0,0 +1,18 @@
// For format details, see https://aka.ms/devcontainer.json
{
"name": "Development environments on your infrastructure",
// Sets the run context to one level up instead of the .devcontainer folder.
"context": ".",
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerFile": "Dockerfile",
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
"postStartCommand": "dockerd",
// privileged is required by GitHub codespaces - https://github.com/microsoft/vscode-dev-containers/issues/727
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined", "--privileged", "--init" ]
}
+16
View File
@@ -0,0 +1,16 @@
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
[*.{md,json,yaml,yml,tf,tfvars}]
indent_style = space
indent_size = 2
[coderd/database/dump.sql]
indent_style = space
indent_size = 4
+4
View File
@@ -3,3 +3,7 @@ coderd/database/dump.sql linguist-generated=true
peerbroker/proto/*.go linguist-generated=true
provisionerd/proto/*.go linguist-generated=true
provisionersdk/proto/*.go linguist-generated=true
*.tfplan.json linguist-generated=true
*.tfstate.json linguist-generated=true
*.tfstate.dot linguist-generated=true
*.tfplan.dot linguist-generated=true
+3 -1
View File
@@ -1,2 +1,4 @@
site/ @coder/frontend
site/src/xServices @presleyp
docs/ @coder/docs
README.md @coder/docs
ADOPTERS.md @coder/docs
-37
View File
@@ -1,37 +0,0 @@
---
name: Bug report
about: Report a bug
title: "Bug: "
labels: ["bug :bug:", "needs grooming :razor:"]
---
## OS Information
- OS:
- Browser (if applicable):
- Architecture:
- `coder --version`:
## Steps to Reproduce
<!-- 1. -->
<!-- 2. -->
<!-- 3. -->
## Expected
<!-- What should happen? -->
## Actual
<!-- What actually happens? -->
## Logs
## Screenshot
<!-- Ideally provide a screenshot, gif, video or screen recording. -->
## Notes
<!-- Anything else you want to share -->
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Question?
url: https://github.com/coder/coder/discussions/new?category=q-a
about: Ask for help on our GitHub Discussions board
-12
View File
@@ -1,12 +0,0 @@
---
name: Documentation improvement
about: Suggest a documentation improvement
title: "Docs: "
labels: ["documentation :memo:", "needs grooming :razor:"]
---
## What is your suggestion?
## How will this improve the docs?
## Are you interested in submitting a PR for this?
@@ -0,0 +1,9 @@
<!--- Provide a general summary of the issue in the Title above -->
## Expected Behavior
<!--- Tell us what should happen -->
## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
-14
View File
@@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea to improve coder
title: "Feat: "
labels: ["new feature :sparkles:", "needs grooming :razor:"]
---
## What is your suggestion?
## Why do you want this feature?
## Are there any workarounds to get this functionality today?
## Are you interested in submitting a PR for this?
+13 -4
View File
@@ -1,5 +1,7 @@
codecov:
require_ci_to_pass: false
notify:
after_n_builds: 5
comment: false
@@ -7,19 +9,22 @@ github_checks:
annotations: false
coverage:
range: 50..75
round: down
precision: 2
status:
patch:
default:
informational: yes
project:
default:
target: 70%
informational: yes
target: 65%
informational: true
ignore:
# This is generated code.
- coderd/database/models.go
- coderd/database/query.sql.go
- coderd/database/queries.sql.go
- coderd/database/databasefake
# These are generated or don't require tests.
- cmd
@@ -29,6 +34,10 @@ ignore:
- peerbroker/proto
- provisionerd/proto
- provisionersdk/proto
- scripts/datadog-cireport
- scripts
- site/.storybook
- rules.go
# Packages used for writing tests.
- cli/clitest
- coderd/coderdtest
- pty/ptytest
+18 -14
View File
@@ -3,9 +3,10 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
labels: []
commit-message:
prefix: "chore"
ignore:
@@ -27,27 +28,32 @@ updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
commit-message:
prefix: "chore"
labels:
- "dependencies"
- "go"
labels: []
ignore:
# Ignore patch updates for all dependencies
- dependency-name: "*"
update-types:
- version-update:semver-patch
- package-ecosystem: "npm"
directory: "/site/"
schedule:
interval: "weekly"
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
commit-message:
prefix: "chore"
labels:
- "dependencies"
- "typescript/js"
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"
@@ -55,16 +61,14 @@ updates:
- version-update:semver-major
- package-ecosystem: "terraform"
directory: "/examples"
directory: "/examples/templates"
schedule:
interval: "weekly"
interval: "monthly"
time: "06:00"
timezone: "America/Chicago"
commit-message:
prefix: "chore"
labels:
- "dependencies"
- "terraform"
labels: []
ignore:
# We likely want to update this ourselves.
- dependency-name: "coder/coder"
+2 -12
View File
@@ -1,13 +1,3 @@
<!-- Help reviewers by listing the subtasks in this PR
Here's an example:
This PR adds a new feature to the CLI.
## Subtasks
- [x] added a test for feature
Fixes #345
<!--
Check if your change requires documentation edits before merging: https://coder.com/docs/coder. Make edits in `docs/`.
-->
-14
View File
@@ -1,14 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 14
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 5
# Only apply the stale logic to pulls, since we are using issues to manage work
only: pulls
# Label to apply when stale.
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no activity occurs in the next 5 days.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
-60
View File
@@ -1,60 +0,0 @@
# Note: Chromatic is a separate workflow for coder.yaml as suggested by the
# chromatic docs. Explicitly, Chromatic works best on 'push' instead of other
# event types (like pull request), keep in mind that it works build-over-build
# by storing snapshots.
#
# SEE: https://www.chromatic.com/docs/ci
name: chromatic
on:
push:
branches:
- main
tags:
- "*"
pull_request:
jobs:
deploy:
# REMARK: this is only used to build storybook and deploy it to Chromatic.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# Required by Chromatic for build-over-build history, otherwise we
# only get 1 commit on shallow checkout.
fetch-depth: 0
- name: Install dependencies
run: cd site && yarn
# This step is not meant for mainline because any detected changes to
# storybook snapshots will require manual approval/review in order for
# the check to pass. This is desired in PRs, but not in mainline.
- name: Publish to Chromatic (non-mainline)
if: github.ref != 'refs/heads/main'
uses: chromaui/action@v1
with:
buildScriptName: "storybook:build"
exitOnceUploaded: true
# Chromatic states its fine to make this token public. See:
# https://www.chromatic.com/docs/github-actions#forked-repositories
projectToken: 695c25b6cb65
workingDir: "./site"
# This is a separate step for mainline only that auto accepts and changes
# instead of holding CI up. Since we squash/merge, this is defensive to
# avoid the same changeset from requiring review once squashed into
# main. Chromatic is supposed to be able to detect that we use squash
# commits, but it's good to be defensive in case, otherwise CI remains
# infinitely "in progress" in mainline unless we re-review each build.
- name: Publish to Chromatic (mainline)
if: github.ref == 'refs/heads/main'
uses: chromaui/action@v1
with:
autoAcceptChanges: true
buildScriptName: "storybook:build"
projectToken: 695c25b6cb65
workingDir: "./site"
+299 -132
View File
@@ -30,6 +30,64 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
typos:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: typos-action
uses: crate-ci/typos@master
with:
config: .github/workflows/typos.toml
- name: Fix Helper
if: ${{ failure() }}
run: |
echo "::notice:: you can automatically fix typos from your CLI:
cargo install typos-cli
typos -c .github/workflows/typos.toml -w"
changes:
runs-on: ubuntu-latest
outputs:
docs-only: ${{ steps.filter.outputs.docs_count == steps.filter.outputs.all_count }}
sh: ${{ steps.filter.outputs.sh }}
ts: ${{ steps.filter.outputs.ts }}
k8s: ${{ steps.filter.outputs.k8s }}
steps:
- uses: actions/checkout@v3
# For pull requests it's not necessary to checkout the code
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
all:
- '**'
docs:
- 'docs/**'
# For testing:
# - '.github/**'
sh:
- "**.sh"
ts:
- 'site/**'
k8s:
- 'helm/**'
- Dockerfile
- scripts/helm.sh
- id: debug
run: |
echo "${{ toJSON(steps.filter )}}"
# Debug step
debug-inputs:
needs:
- changes
runs-on: ubuntu-latest
steps:
- id: log
run: |
echo "${{ toJSON(needs) }}"
style-lint-golangci:
name: style/lint/golangci
timeout-minutes: 5
@@ -38,11 +96,33 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
go-version: "~1.19"
- name: golangci-lint
uses: golangci/golangci-lint-action@v3.2.0
with:
version: v1.46.0
version: v1.48.0
check-enterprise-imports:
name: check/enterprise-imports
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check imports of enterprise code
run: ./scripts/check_enterprise_imports.sh
style-lint-shellcheck:
name: style/lint/shellcheck
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@1.1.0
env:
SHELLCHECK_OPTS: --external-sources
with:
ignore: node_modules
style-lint-typescript:
name: "style/lint/typescript"
@@ -70,10 +150,32 @@ jobs:
run: yarn lint
working-directory: site
style-lint-k8s:
name: "style/lint/k8s"
timeout-minutes: 5
needs: changes
if: needs.changes.outputs.k8s == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install helm
uses: azure/setup-helm@v3
with:
version: v3.9.2
- name: cd helm && make lint
run: |
cd helm
make lint
gen:
name: "style/gen"
timeout-minutes: 5
timeout-minutes: 8
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.docs-only == 'false'
steps:
- uses: actions/checkout@v3
@@ -91,22 +193,55 @@ jobs:
- name: Install node_modules
run: ./scripts/yarn_install.sh
- name: Install Protoc
uses: arduino/setup-protoc@v1
with:
version: "3.20.0"
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
- run: curl -sSL
https://github.com/kyleconroy/sqlc/releases/download/v1.13.0/sqlc_1.13.0_linux_amd64.tar.gz
| sudo tar -C /usr/bin -xz sqlc
go-version: "~1.19"
- run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
- run: go install storj.io/drpc/cmd/protoc-gen-go-drpc@v0.0.26
- run: go install golang.org/x/tools/cmd/goimports@latest
- run: "make --output-sync -j gen"
- run: ./scripts/check_unstaged.sh
- name: Echo Go Cache Paths
id: go-cache-paths
run: |
echo "::set-output name=go-build::$(go env GOCACHE)"
echo "::set-output name=go-mod::$(go env GOMODCACHE)"
- name: Go Build Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-build }}
key: ${{ github.job }}-go-build-${{ hashFiles('**/go.sum', '**/**.go') }}
- name: Go Mod Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ github.job }}-go-mod-${{ hashFiles('**/go.sum') }}
- name: Install sqlc
run: |
curl -sSL https://github.com/kyleconroy/sqlc/releases/download/v1.13.0/sqlc_1.13.0_linux_amd64.tar.gz | sudo tar -C /usr/bin -xz sqlc
- name: Install protoc-gen-go
run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
- name: Install protoc-gen-go-drpc
run: go install storj.io/drpc/cmd/protoc-gen-go-drpc@v0.0.26
- name: Install goimports
run: go install golang.org/x/tools/cmd/goimports@latest
- name: Install Protoc
run: |
# protoc must be in lockstep with our dogfood Dockerfile
# or the version in the comments will differ.
set -x
cd dogfood
DOCKER_BUILDKIT=1 docker build . --target proto -t protoc
protoc_path=/usr/local/bin/protoc
docker run --rm --entrypoint cat protoc /tmp/bin/protoc > $protoc_path
chmod +x $protoc_path
protoc --version
- name: make gen
run: "make --output-sync -j -B gen"
- name: Check for unstaged files
run: ./scripts/check_unstaged.sh
style-fmt:
name: "style/fmt"
@@ -133,8 +268,13 @@ jobs:
- name: Install node_modules
run: ./scripts/yarn_install.sh
- name: "make fmt"
run: "make --output-sync -j fmt"
- name: Install shfmt
run: go install mvdan.cc/sh/v3/cmd/shfmt@v3.5.0
- name: make fmt
run: |
export PATH=${PATH}:$(go env GOPATH)/bin
make --output-sync -j -B fmt
test-go:
name: "test/go"
@@ -151,7 +291,7 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
go-version: "~1.19"
- name: Echo Go Cache Paths
id: go-cache-paths
@@ -163,7 +303,7 @@ jobs:
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-build }}
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.**', '**.go') }}
- name: Go Mod Cache
uses: actions/cache@v3
@@ -171,8 +311,8 @@ jobs:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
- name: Install goreleaser
uses: jaxxstorm/action-install-gh-release@v1.6.0
- name: Install gotestsum
uses: jaxxstorm/action-install-gh-release@v1.7.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -181,46 +321,55 @@ jobs:
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.1.2
terraform_version: 1.1.9
terraform_wrapper: false
- name: Test with Mock Database
id: test
shell: bash
env:
GOCOUNT: ${{ runner.os == 'Windows' && 1 || 2 }}
GOMAXPROCS: ${{ runner.os == 'Windows' && 1 || 2 }}
run: gotestsum --junitfile="gotests.xml" --packages="./..." --
-covermode=atomic -coverprofile="gotests.coverage"
-coverpkg=./...,github.com/coder/coder/codersdk
-timeout=3m -count=$GOCOUNT -short -failfast
- name: Upload DataDog Trace
if: always() && github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
env:
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
DD_DATABASE: fake
DD_CATEGORY: unit
GIT_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: go run scripts/datadog-cireport/main.go gotests.xml
run: |
# Code coverage is more computationally expensive and also
# prevents test caching, so we disable it on alternate operating
# systems.
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
echo ::set-output name=cover::true
export COVERAGE_FLAGS='-covermode=atomic -coverprofile="gotests.coverage" -coverpkg=./...'
else
echo ::set-output name=cover::false
fi
set -x
test_timeout=5m
if [[ "${{ matrix.os }}" == windows* ]]; then
test_timeout=10m
fi
gotestsum --junitfile="gotests.xml" --packages="./..." -- -parallel=8 -timeout=$test_timeout -short -failfast $COVERAGE_FLAGS
- uses: codecov/codecov-action@v3
if: github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
# This action has a tendency to error out unexpectedly, it has
# the `fail_ci_if_error` option that defaults to `false`, but
# that is no guarantee, see:
# https://github.com/codecov/codecov-action/issues/788
continue-on-error: true
if: steps.test.outputs.cover && github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./gotests.coverage
flags: unittest-go-${{ matrix.os }}
fail_ci_if_error: true
test-go-postgres:
name: "test/go/postgres"
runs-on: ubuntu-latest
timeout-minutes: 20
# 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:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
go-version: "~1.19"
- name: Echo Go Cache Paths
id: go-cache-paths
@@ -232,7 +381,7 @@ jobs:
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-build }}
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum', '**/**.go') }}
- name: Go Mod Cache
uses: actions/cache@v3
@@ -240,8 +389,8 @@ jobs:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
- name: Install goreleaser
uses: jaxxstorm/action-install-gh-release@v1.6.0
- name: Install gotestsum
uses: jaxxstorm/action-install-gh-release@v1.7.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -250,63 +399,39 @@ jobs:
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.1.2
terraform_version: 1.1.9
terraform_wrapper: false
- name: Start PostgreSQL Database
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres
PGDATA: /tmp
run: |
docker run \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=postgres \
-e PGDATA=/tmp \
-p 5432:5432 \
-d postgres:11 \
-c shared_buffers=1GB \
-c max_connections=1000
while ! pg_isready -h 127.0.0.1
do
echo "$(date) - waiting for database to start"
sleep 0.5
done
- name: Test with PostgreSQL Database
run: DB=ci gotestsum --junitfile="gotests.xml" --packages="./..." --
-covermode=atomic -coverprofile="gotests.coverage" -timeout=3m
-coverpkg=./...,github.com/coder/coder/codersdk
-count=1 -parallel=2 -race -failfast
- name: Upload DataDog Trace
if: always() && github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
env:
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
DD_DATABASE: postgresql
GIT_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: go run scripts/datadog-cireport/main.go gotests.xml
run: make test-postgres
- uses: codecov/codecov-action@v3
# This action has a tendency to error out unexpectedly, it has
# the `fail_ci_if_error` option that defaults to `false`, but
# that is no guarantee, see:
# https://github.com/codecov/codecov-action/issues/788
continue-on-error: true
if: github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./gotests.coverage
flags: unittest-go-postgres-${{ matrix.os }}
fail_ci_if_error: true
deploy:
name: "deploy"
runs-on: ubuntu-latest
timeout-minutes: 20
if: github.ref == 'refs/heads/main' && !github.event.pull_request.head.repo.fork
timeout-minutes: 30
needs: changes
if: |
github.ref == 'refs/heads/main' && !github.event.pull_request.head.repo.fork
&& needs.changes.outputs.docs-only == 'false'
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v0
@@ -319,7 +444,7 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
go-version: "~1.19"
- name: Echo Go Cache Paths
id: go-cache-paths
@@ -339,10 +464,6 @@ jobs:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ runner.os }}-release-go-mod-${{ hashFiles('**/go.sum') }}
- uses: goreleaser/goreleaser-action@v2
with:
install-only: true
- name: Cache Node
id: cache-node
uses: actions/cache@v3
@@ -350,35 +471,48 @@ jobs:
path: |
**/node_modules
.eslintcache
key: js-${{ runner.os }}-test-${{ hashFiles('**/yarn.lock') }}
key: js-${{ runner.os }}-release-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
js-${{ runner.os }}-
- name: Build site
run: make site/out/index.html
- name: Install goimports
run: go install golang.org/x/tools/cmd/goimports@latest
- name: Install nfpm
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.16.0
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Build Release
uses: goreleaser/goreleaser-action@v2.9.1
with:
version: latest
args: release --snapshot --rm-dist --skip-sign
run: |
set -euo pipefail
go mod download
- uses: actions/upload-artifact@v3
with:
name: coder_linux_amd64.deb
path: ./dist/coder_*_linux_amd64.deb
version="$(./scripts/version.sh)"
make -j \
build/coder_"$version"_windows_amd64.zip \
build/coder_"$version"_linux_amd64.{tar.gz,deb}
- name: Install Release
run: |
gcloud config set project coder-dogfood
gcloud config set compute/zone us-central1-a
gcloud compute scp ./dist/coder_*_linux_amd64.deb coder:/tmp/coder.deb
gcloud compute scp ./build/coder_*_linux_amd64.deb coder:/tmp/coder.deb
gcloud compute ssh coder -- sudo dpkg -i --force-confdef /tmp/coder.deb
gcloud compute ssh coder -- sudo systemctl daemon-reload
- name: Start
run: gcloud compute ssh coder -- sudo service coder restart
- uses: actions/upload-artifact@v3
with:
name: coder
path: |
./build/*.zip
./build/*.tar.gz
./build/*.deb
retention-days: 7
test-js:
name: "test/js"
runs-on: ubuntu-latest
@@ -397,11 +531,6 @@ jobs:
restore-keys: |
js-${{ runner.os }}-
# Go is required for uploading the test results to datadog
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
- uses: actions/setup-node@v3
with:
node-version: "14"
@@ -413,23 +542,22 @@ jobs:
working-directory: site
- uses: codecov/codecov-action@v3
# This action has a tendency to error out unexpectedly, it has
# the `fail_ci_if_error` option that defaults to `false`, but
# that is no guarantee, see:
# https://github.com/codecov/codecov-action/issues/788
continue-on-error: true
if: github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./site/coverage/lcov.info
flags: unittest-js
fail_ci_if_error: true
- name: Upload DataDog Trace
if: always() && github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork
env:
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
DD_CATEGORY: unit
GIT_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: go run scripts/datadog-cireport/main.go site/test-results/junit.xml
test-e2e:
name: "test/e2e/${{ matrix.os }}"
needs:
- changes
if: needs.changes.outputs.docs-only == 'false'
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
@@ -446,28 +574,21 @@ jobs:
path: |
**/node_modules
.eslintcache
key: js-${{ runner.os }}-test-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
js-${{ runner.os }}-
key: js-${{ runner.os }}-e2e-${{ hashFiles('**/yarn.lock') }}
# Go is required for uploading the test results to datadog
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
go-version: "~1.19"
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.1.2
terraform_version: 1.1.9
terraform_wrapper: false
- uses: actions/setup-node@v3
with:
node-version: "14"
- uses: goreleaser/goreleaser-action@v2
with:
install-only: true
- name: Echo Go Cache Paths
id: go-cache-paths
run: |
@@ -488,7 +609,8 @@ jobs:
- name: Build
run: |
make site/out/index.html
sudo npm install -g prettier
make -B site/out/index.html
- run: yarn playwright:install
working-directory: site
@@ -501,10 +623,55 @@ jobs:
DEBUG: pw:api
working-directory: site
- name: Upload DataDog Trace
- name: Upload Playwright Failed Tests
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
env:
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
DD_CATEGORY: e2e
GIT_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: go run scripts/datadog-cireport/main.go site/test-results/junit.xml
uses: actions/upload-artifact@v3
with:
name: failed-test-videos
path: ./site/test-results/**/*.webm
retention:days: 7
chromatic:
# REMARK: this is only used to build storybook and deploy it to Chromatic.
runs-on: ubuntu-latest
needs:
- changes
if: needs.changes.outputs.ts == 'true'
steps:
- uses: actions/checkout@v3
with:
# Required by Chromatic for build-over-build history, otherwise we
# only get 1 commit on shallow checkout.
fetch-depth: 0
- name: Install dependencies
run: cd site && yarn
# This step is not meant for mainline because any detected changes to
# storybook snapshots will require manual approval/review in order for
# the check to pass. This is desired in PRs, but not in mainline.
- name: Publish to Chromatic (non-mainline)
if: github.ref != 'refs/heads/main' && github.repository_owner == 'coder'
uses: chromaui/action@v1
with:
buildScriptName: "storybook:build"
exitOnceUploaded: true
# Chromatic states its fine to make this token public. See:
# https://www.chromatic.com/docs/github-actions#forked-repositories
projectToken: 695c25b6cb65
workingDir: "./site"
# This is a separate step for mainline only that auto accepts and changes
# instead of holding CI up. Since we squash/merge, this is defensive to
# avoid the same changeset from requiring review once squashed into
# main. Chromatic is supposed to be able to detect that we use squash
# commits, but it's good to be defensive in case, otherwise CI remains
# infinitely "in progress" in mainline unless we re-review each build.
- name: Publish to Chromatic (mainline)
if: github.ref == 'refs/heads/main' && github.repository_owner == 'coder'
uses: chromaui/action@v1
with:
autoAcceptChanges: true
buildScriptName: "storybook:build"
projectToken: 695c25b6cb65
workingDir: "./site"
+13
View File
@@ -0,0 +1,13 @@
# Dependabot is annoying, but this makes it a bit less so.
name: Auto Approve Dependabot
on: pull_request_target
jobs:
auto-approve:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: hmarr/auto-approve-action@v2
if: github.actor == 'dependabot[bot]'
+51
View File
@@ -0,0 +1,51 @@
name: dogfood
on:
push:
branches:
- main
tags:
- "*"
paths:
- "dogfood/**"
pull_request:
paths:
- "dogfood/**"
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Get branch name
id: branch-name
uses: tj-actions/branch-names@v5.4
- name: "Branch name to Docker tag name"
id: docker-tag-name
run: |
tag=${{ steps.branch-name.outputs.current_branch }}
# Replace / with --, e.g. user/feature => user--feature.
tag=${tag//\//--}
echo "::set-output name=tag::${tag}"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: "{{defaultContext}}:dogfood"
push: true
tags: "codercom/oss-dogfood:${{ steps.docker-tag-name.outputs.tag }},codercom/oss-dogfood:latest"
cache-from: type=registry,ref=codercom/oss-dogfood:latest
cache-to: type=inline
+138 -49
View File
@@ -1,66 +1,58 @@
# GitHub release workflow.
name: release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
snapshot:
description: Force a dev version to be generated, implies dry_run.
type: boolean
required: true
dry_run:
description: Perform a dry-run release.
type: boolean
required: true
permissions:
# Required to publish a release
contents: write
# Necessary to push docker images to ghcr.io.
packages: write
env:
CODER_RELEASE: ${{ github.event.inputs.snapshot && 'false' || 'true' }}
jobs:
goreleaser:
runs-on: macos-latest
release:
runs-on: ubuntu-latest
env:
# Necessary for Docker manifest
DOCKER_CLI_EXPERIMENTAL: "enabled"
steps:
# Docker is not included on macos-latest
- uses: docker-practice/actions-setup-docker@1.0.10
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# If the event that triggered the build was an annotated tag (which our
# tags are supposed to be), actions/checkout has a bug where the tag in
# question is only a lightweight tag and not a full annotated tag. This
# command seems to fix it.
# https://github.com/actions/checkout/issues/290
- name: Fetch git tags
run: git fetch --tags --force
- name: Docker Login
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-go@v3
with:
go-version: "~1.18"
- name: Install Gon
run: |
brew tap mitchellh/gon
brew install mitchellh/gon/gon
- name: Import Signing Certificates
uses: Apple-Actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.AC_CERTIFICATE_P12_BASE64 }}
p12-password: ${{ secrets.AC_CERTIFICATE_PASSWORD }}
- name: Echo Go Cache Paths
id: go-cache-paths
run: |
echo "::set-output name=go-build::$(go env GOCACHE)"
echo "::set-output name=go-mod::$(go env GOMODCACHE)"
- name: Go Build Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-build }}
key: ${{ runner.os }}-release-go-build-${{ hashFiles('**/go.sum') }}
- name: Go Mod Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ runner.os }}-release-go-mod-${{ hashFiles('**/go.sum') }}
go-version: "~1.19"
- name: Cache Node
id: cache-node
@@ -73,18 +65,115 @@ jobs:
restore-keys: |
js-${{ runner.os }}-
- name: Install make
run: brew install make
- name: Install nfpm
run: |
set -euo pipefail
wget -O /tmp/nfpm.deb https://github.com/goreleaser/nfpm/releases/download/v2.18.1/nfpm_amd64.deb
sudo dpkg -i /tmp/nfpm.deb
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Build Site
run: make site/out/index.html
- name: Install rcodesign
run: |
set -euo pipefail
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2.9.1
with:
version: latest
args: release --rm-dist
# Install a prebuilt binary of rcodesign for linux amd64. Once the
# following PR is merged and released upstream, we can download
# directly from GitHub releases instead:
# https://github.com/indygreg/PyOxidizer/pull/635
wget -O /tmp/rcodesign https://cdn.discordapp.com/attachments/283356472258199552/1016767245717872700/rcodesign
sudo install --mode 755 /tmp/rcodesign /usr/local/bin/rcodesign
- name: Setup Apple Developer certificate and API key
run: |
set -euo pipefail
touch /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8}
chmod 600 /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8}
echo "$AC_CERTIFICATE_P12_BASE64" | base64 -d > /tmp/apple_cert.p12
echo "$AC_CERTIFICATE_PASSWORD" > /tmp/apple_cert_password.txt
echo "$AC_APIKEY_P8_BASE64" | base64 -d > /tmp/apple_apikey.p8
env:
AC_CERTIFICATE_P12_BASE64: ${{ secrets.AC_CERTIFICATE_P12_BASE64 }}
AC_CERTIFICATE_PASSWORD: ${{ secrets.AC_CERTIFICATE_PASSWORD }}
AC_APIKEY_P8_BASE64: ${{ secrets.AC_APIKEY_P8_BASE64 }}
- name: Build binaries
run: |
set -euo pipefail
go mod download
version="$(./scripts/version.sh)"
make gen/mark-fresh
make -j \
build/coder_"$version"_linux_{amd64,armv7,arm64}.{tar.gz,apk,deb,rpm} \
build/coder_"$version"_{darwin,windows}_{amd64,arm64}.zip \
build/coder_helm_"$version".tgz
env:
CODER_SIGN_DARWIN: "1"
AC_CERTIFICATE_FILE: /tmp/apple_cert.p12
AC_CERTIFICATE_PASSWORD_FILE: /tmp/apple_cert_password.txt
AC_APIKEY_ISSUER_ID: ${{ secrets.AC_APIKEY_ISSUER_ID }}
AC_APIKEY_ID: ${{ secrets.AC_APIKEY_ID }}
AC_APIKEY_FILE: /tmp/apple_apikey.p8
- name: Delete Apple Developer certificate and API key
run: rm -f /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8}
- name: Build Linux Docker images
run: |
set -euxo pipefail
# build Docker images for each architecture
version="$(./scripts/version.sh)"
make -j 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
if [[ "$CODER_RELEASE" != *t* ]]; then
echo Skipping multi-arch docker builds due to dry-run.
exit 0
fi
# 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
# 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
# push it
if [[ "$(git tag | grep '^v' | grep -vE '(rc|dev|-|\+|\/)' | sort -r --version-sort | head -n1)" == "v$(./scripts/version.sh)" ]]; then
./scripts/build_docker_multiarch.sh \
--push \
--target "$(./scripts/image_tag.sh --version latest)" \
$(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag)
fi
- name: ls build
run: ls -lh build
- name: Publish release
run: |
./scripts/publish_release.sh \
${{ (github.event.inputs.dry_run || github.event.inputs.snapshot) && '--dry-run' }} \
./build/*.zip \
./build/*.tar.gz \
./build/*.tgz \
./build/*.apk \
./build/*.deb \
./build/*.rpm
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AC_USERNAME: ${{ secrets.AC_USERNAME }}
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
- name: Upload artifacts to actions (if dry-run or snapshot)
if: ${{ github.event.inputs.dry_run || github.event.inputs.snapshot }}
uses: actions/upload-artifact@v2
with:
name: release-artifacts
path: |
./build/*.zip
./build/*.tar.gz
./build/*.tgz
./build/*.apk
./build/*.deb
./build/*.rpm
retention-days: 7
+35
View File
@@ -0,0 +1,35 @@
name: Stale Issue Cron
on:
schedule:
# Every day at midnight
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
# v5.1.0 has a weird bug that makes stalebot add then remove its own label
# https://github.com/actions/stale/pull/775
- uses: actions/stale@v5.0.0
with:
stale-issue-label: stale
stale-pr-label: stale
# Pull Requests become stale more quickly due to merge conflicts.
# Also, we promote minimizing WIP.
days-before-pr-stale: 7
days-before-pr-close: 3
stale-pr-message: >
This Pull Request is becoming stale. In order to minimize WIP,
prevent merge conflicts and keep the tracker readable, I'm going
close to this PR in 3 days if there isn't more activity.
stale-issue-message: >
This issue is becoming stale. In order to keep the tracker readable
and actionable, I'm going close to this issue in 7 days if there
isn't more activity.
# Upped from 30 since we have a big tracker and was hitting the limit.
operations-per-run: 60
# Start with the oldest issues, always.
ascending: true
+19
View File
@@ -0,0 +1,19 @@
[default.extend-identifiers]
alog = "alog"
Jetbrains = "JetBrains"
IST = "IST"
MacOS = "macOS"
[default.extend-words]
[files]
extend-exclude = [
"**.svg",
"**.png",
"**.lock",
"go.sum",
"go.mod",
# These files contain base64 strings that confuse the detector
"**XService**.ts",
"**identity.go",
]
+18
View File
@@ -0,0 +1,18 @@
name: Welcome
on:
pull_request:
types: [opened]
jobs:
test:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: wow-actions/welcome@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FIRST_PR_REACTIONS: '+1, hooray, rocket, heart'
FIRST_PR_COMMENT: |
👋 Welcome @{{ author }} to Coder! Yo @coder/docs this is @{{ author }}'s first pull-request here!
FIRST_PR_MERGED: |
🎉 Thanks for the contribution @{{ author }}! Yo @coder/docs @{{ author }}'s first contribution has been merged! 👀👀👀
+8
View File
@@ -13,7 +13,10 @@ node_modules
vendor
.eslintcache
yarn-error.log
gotests.coverage
.idea
.gitpod.yml
.DS_Store
# Front-end ignore
.next/
@@ -28,13 +31,18 @@ site/**/*.typegen.ts
site/build-storybook.log
# Build
build/
dist/
site/out/
*.tfstate
*.tfstate.backup
*.tfplan
*.lock.hcl
.terraform/
.vscode/*.log
.vscode/launch.json
**/*.swp
.coderv2/*
**/__debug_bin
+3 -1
View File
@@ -103,7 +103,7 @@ linters-settings:
settings:
ruleguard:
failOn: all
rules: rules.go
rules: '${configDir}/scripts/rules.go'
staticcheck:
# https://staticcheck.io/docs/options#checks
@@ -201,6 +201,8 @@ run:
concurrency: 4
skip-dirs:
- node_modules
skip-files:
- scripts/rules.go
timeout: 5m
# Over time, add more and more linters from
-165
View File
@@ -1,165 +0,0 @@
archives:
- id: coder-linux
builds: [coder-linux]
format: tar.gz
files:
- src: docs/README.md
dst: README.md
- id: coder-darwin
builds: [coder-darwin]
format: zip
files:
- src: docs/README.md
dst: README.md
- id: coder-windows
builds: [coder-windows]
format: zip
files:
- src: docs/README.md
dst: README.md
before:
hooks:
- go mod tidy
- rm -f site/out/bin/coder*
builds:
- id: coder-slim
dir: cmd/coder
ldflags: ["-s -w -X github.com/coder/coder/buildinfo.tag={{ .Version }}"]
env: [CGO_ENABLED=0]
goos: [darwin, linux, windows]
goarch: [amd64, arm, arm64]
goarm: ["7"]
# Only build arm 7 for Linux
ignore:
- goos: windows
goarm: "7"
- goos: darwin
goarm: "7"
hooks:
# The "trimprefix" appends ".exe" on Windows.
post: |
cp {{.Path}} site/out/bin/coder-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ trimprefix .Name "coder" }}
- id: coder-linux
dir: cmd/coder
flags: [-tags=embed]
ldflags: ["-s -w -X github.com/coder/coder/buildinfo.tag={{ .Version }}"]
env: [CGO_ENABLED=0]
goos: [linux]
goarch: [amd64, arm, arm64]
goarm: ["7"]
- id: coder-windows
dir: cmd/coder
flags: [-tags=embed]
ldflags: ["-s -w -X github.com/coder/coder/buildinfo.tag={{ .Version }}"]
env: [CGO_ENABLED=0]
goos: [windows]
goarch: [amd64, arm64]
- id: coder-darwin
dir: cmd/coder
flags: [-tags=embed]
ldflags: ["-s -w -X github.com/coder/coder/buildinfo.tag={{ .Version }}"]
env: [CGO_ENABLED=0]
goos: [darwin]
goarch: [amd64, arm64]
hooks:
# This signs the binary that will be located inside the zip.
# MacOS requires the binary to be signed for notarization.
#
# If it doesn't successfully sign, the zip sign step will error.
post: |
sh -c 'codesign -s {{.Env.AC_APPLICATION_IDENTITY}} -f -v --timestamp --options runtime {{.Path}} || true'
env:
# Apple identity for signing!
- AC_APPLICATION_IDENTITY=BDB050EB749EDD6A80C6F119BF1382ECA119CCCC
nfpms:
- id: packages
vendor: Coder
homepage: https://coder.com
maintainer: Coder <support@coder.com>
description: |
Provision development environments with infrastructure with code
formats:
- apk
- deb
- rpm
suggests:
- postgresql
builds:
- coder-linux
bindir: /usr/bin
contents:
- src: coder.env
dst: /etc/coder.d/coder.env
type: "config|noreplace"
- src: coder.service
dst: /usr/lib/systemd/system/coder.service
dockers:
- image_templates: ["ghcr.io/coder/coder:{{ .Tag }}-amd64"]
id: coder-linux
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- --platform=linux/amd64
- --label=org.opencontainers.image.title=Coder
- --label=org.opencontainers.image.description=A tool for provisioning self-hosted development environments with Terraform.
- --label=org.opencontainers.image.url=https://github.com/coder/coder
- --label=org.opencontainers.image.source=https://github.com/coder/coder
- --label=org.opencontainers.image.version={{ .Version }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.licenses=AGPL-3.0
- image_templates: ["ghcr.io/coder/coder:{{ .Tag }}-arm64"]
goarch: arm64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- --platform=linux/arm64/v8
- --label=org.opencontainers.image.title=coder
- --label=org.opencontainers.image.description=A tool for provisioning self-hosted development environments with Terraform.
- --label=org.opencontainers.image.url=https://github.com/coder/coder
- --label=org.opencontainers.image.source=https://github.com/coder/coder
- --label=org.opencontainers.image.version={{ .Tag }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.licenses=AGPL-3.0
- image_templates: ["ghcr.io/coder/coder:{{ .Tag }}-armv7"]
goarch: arm
goarm: "7"
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- --platform=linux/arm/v7
- --label=org.opencontainers.image.title=Coder
- --label=org.opencontainers.image.description=A tool for provisioning self-hosted development environments with Terraform.
- --label=org.opencontainers.image.url=https://github.com/coder/coder
- --label=org.opencontainers.image.source=https://github.com/coder/coder
- --label=org.opencontainers.image.version={{ .Tag }}
- --label=org.opencontainers.image.revision={{ .FullCommit }}
- --label=org.opencontainers.image.licenses=AGPL-3.0
docker_manifests:
- name_template: ghcr.io/coder/coder:{{ .Tag }}
image_templates:
- ghcr.io/coder/coder:{{ .Tag }}-amd64
- ghcr.io/coder/coder:{{ .Tag }}-arm64
- ghcr.io/coder/coder:{{ .Tag }}-armv7
release:
ids: [coder-linux, coder-darwin, coder-windows, packages]
signs:
- ids: [coder-darwin]
artifacts: archive
cmd: ./scripts/sign_macos.sh
args: ["${artifact}"]
output: true
snapshot:
name_template: "{{ .Version }}-devel+{{ .ShortCommit }}"
+2 -1
View File
@@ -8,6 +8,7 @@
"zxh404.vscode-proto3",
"redhat.vscode-yaml",
"streetsidesoftware.code-spell-checker",
"dbaeumer.vscode-eslint"
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
]
}
+91 -9
View File
@@ -1,11 +1,23 @@
{
"cSpell.words": [
"apps",
"awsidentity",
"bodyclose",
"buildinfo",
"buildname",
"circbuf",
"cliflag",
"cliui",
"codecov",
"Codespaces",
"coderd",
"coderdtest",
"codersdk",
"cronstrue",
"databasefake",
"DERP",
"derphttp",
"derpmap",
"devel",
"drpc",
"drpcconn",
@@ -13,61 +25,130 @@
"drpcserver",
"Dsts",
"fatih",
"Formik",
"gitsshkey",
"goarch",
"gographviz",
"goleak",
"gonet",
"gossh",
"gsyslog",
"GTTY",
"hashicorp",
"hclsyntax",
"httpapi",
"httpmw",
"idtoken",
"Iflag",
"incpatch",
"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",
"promptui",
"protobuf",
"provisionerd",
"provisionersdk",
"ptty",
"ptys",
"ptytest",
"quickstart",
"reconfig",
"retrier",
"rpty",
"sdkproto",
"sdktrace",
"Signup",
"slogtest",
"sourcemapped",
"Srcs",
"stretchr",
"STTY",
"stuntest",
"tailbroker",
"tailcfg",
"tailexchange",
"tailnet",
"tailnettest",
"Tailscale",
"TCGETS",
"tcpip",
"TCSETS",
"templateversions",
"testdata",
"testid",
"testutil",
"tfexec",
"tfjson",
"tfplan",
"tfstate",
"tios",
"tparallel",
"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",
"wsconncache",
"wsjson",
"xerrors",
"xstate",
"yamux"
],
"cSpell.ignorePaths": [
"site/package.json",
".vscode/settings.json"
],
"emeraldwalk.runonsave": {
"commands": [
{
@@ -76,7 +157,7 @@
},
{
"match": "provisionerd/proto/provisionerd.proto",
"cmd": "make provisionerd/proto/provisionerd.pb.go",
"cmd": "make provisionerd/proto/provisionerd.pb.go"
}
]
},
@@ -90,19 +171,20 @@
"go.lintFlags": ["--fast"],
"go.lintOnSave": "package",
"go.coverOnSave": true,
// The codersdk is used by coderd another other packages extensively.
// To reduce redundancy in tests, it's covered by other packages.
"go.testFlags": ["-short", "-coverpkg=./.,github.com/coder/coder/codersdk"],
"go.coverageDecorator": {
"type": "gutter",
"coveredHighlightColor": "rgba(64,128,128,0.5)",
"uncoveredHighlightColor": "rgba(128,64,64,0.25)",
"coveredBorderColor": "rgba(64,128,128,0.5)",
"uncoveredBorderColor": "rgba(128,64,64,0.25)",
"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",
"typescript.tsdk": "./site/node_modules/typescript/lib"
}
+12
View File
@@ -0,0 +1,12 @@
# Adopters
[!["Join us on
Discord"](https://img.shields.io/badge/join-us%20on%20Discord-gray.svg?longCache=true&logo=discord&colorB=green)](https://coder.com/chat?utm_source=github.com/coder/coder&utm_medium=github&utm_campaign=adopters.md) [![Twitter
Follow](https://img.shields.io/twitter/follow/coderhq?label=%40coderhq&style=social)](https://twitter.com/coderhq)
🦩 _If you're using Coder in your organization, please try to add your company name to this list. It really helps the project to gain momentum and credibility. It's a small contribution back to the project with a big impact. You can do this by by editing this file and contributing your changes via a pull-request on GitHub._
> 👋 _If you are considering using Coder in your organization please introduce yourself via https://coder.com/demo_ 🙇🏻‍♂️
| Organization | Contact | Description of Use |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Coder](https://www.coder.com) | [@coderhq](https://twitter.com/coderhq) | Coder builds coder with Coder. |
+28 -3
View File
@@ -1,6 +1,31 @@
FROM alpine
# This is the multi-arch Dockerfile used for Coder. Since it's multi-arch and
# cross-compiled, it cannot have ANY "RUN" commands. All binaries are built
# using the go toolchain on the host and then copied into the build context by
# scripts/build_docker.sh.
FROM alpine:latest
# Generated by goreleaser on `goreleaser release`
ADD coder /opt/coder
# LABEL doesn't add any real layers so it's fine (and easier) to do it here than
# in the build script.
ARG CODER_VERSION
LABEL \
org.opencontainers.image.title="Coder" \
org.opencontainers.image.description="A tool for provisioning self-hosted development environments with Terraform." \
org.opencontainers.image.url="https://github.com/coder/coder" \
org.opencontainers.image.source="https://github.com/coder/coder" \
org.opencontainers.image.version="$CODER_VERSION" \
org.opencontainers.image.licenses="AGPL-3.0"
# The coder binary is injected by scripts/build_docker.sh.
COPY --chown=coder:coder --chmod=755 coder /opt/coder
# Create coder group and user. We cannot use `addgroup` and `adduser` because
# they won't work if we're building the image for a different architecture.
COPY --chown=root:root --chmod=644 group passwd /etc/
COPY --chown=coder:coder --chmod=700 empty-dir /home/coder
USER coder:coder
ENV HOME=/home/coder
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt
WORKDIR /home/coder
ENTRYPOINT [ "/opt/coder", "server" ]
+31
View File
@@ -0,0 +1,31 @@
## Acceptance
By using any software and associated documentation files under Coder
Technologies Inc.s ("Coder") directory named "enterprise" ("Enterprise
Software"), you agree to all of the terms and conditions below.
## Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide,
non-sublicensable, non-transferable license to use, copy, distribute, make
available, modify and prepare derivative works of the Enterprise Software, in
each case subject to the limitations and conditions below.
## Limitations
You may not move, change, disable, or circumvent the license key functionality
in the software, and you may not remove or obscure any functionality in the
software that is protected by the license key.
You may not alter, remove, or obscure any licensing, copyright, or other notices
of the licensor in the software.
You agree that Coder and/or its licensors (as applicable) retain all right,
title and interest in and to all such modifications and/or patches.
## Additional Terms
This Enterprise Software may only be used in production, if you (and any entity
that you represent) have agreed to, and are in compliance with, the Coders
Terms of Service, available at https://coder.com/legal/terms-of-service, or
other agreement governing the use of the Software, as agreed by you and Coder.
+443 -58
View File
@@ -1,39 +1,356 @@
.DEFAULT_GOAL := build
# This is the Coder Makefile. The build directory for most tasks is `build/`.
#
# These are the targets you're probably looking for:
# - clean
# - build-fat: builds all "fat" binaries for all architectures
# - build-slim: builds all "slim" binaries (no frontend or slim binaries
# embedded) for all architectures
# - release: simulate a release (mostly, does not push images)
# - build/coder(-slim)?_${os}_${arch}(.exe)?: build a single fat binary
# - build/coder_${os}_${arch}.(zip|tar.gz): build a release archive
# - build/coder_linux_${arch}.(apk|deb|rpm): build a release Linux package
# - build/coder_${version}_linux_${arch}.tag: build a release Linux Docker image
# - build/coder_helm.tgz: build a release Helm chart
INSTALL_DIR=$(shell go env GOPATH)/bin
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)
.DEFAULT_GOAL := build-fat
bin: $(shell find . -not -path './vendor/*' -type f -name '*.go') go.mod go.sum
@echo "== This builds binaries for command-line usage."
@echo "== Use \"make build\" to embed the site."
goreleaser build --snapshot --rm-dist --single-target
# Use a single bash shell for each job, and immediately exit on failure
SHELL := bash
.SHELLFLAGS := -ceu
.ONESHELL:
build: dist/artifacts.json
.PHONY: build
# This doesn't work on directories.
# See https://stackoverflow.com/questions/25752543/make-delete-on-error-for-directory-targets
.DELETE_ON_ERROR:
# Runs migrations to output a dump of the database.
coderd/database/dump.sql: $(wildcard coderd/database/migrations/*.sql)
go run coderd/database/dump/main.go
# Don't print the commands in the file unless you specify VERBOSE. This is
# essentially the same as putting "@" at the start of each line.
ifndef VERBOSE
.SILENT:
endif
# Generates Go code for querying the database.
coderd/database/querier.go: coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql)
coderd/database/generate.sh
# Create the output directories if they do not exist.
$(shell mkdir -p build site/out/bin)
dev: build
./scripts/develop.sh
.PHONY: dev
GOOS := $(shell go env GOOS)
GOARCH := $(shell go env GOARCH)
GOOS_BIN_EXT := $(if $(filter windows, $(GOOS)),.exe,)
VERSION := $(shell ./scripts/version.sh)
dist/artifacts.json: site/out/index.html $(shell find . -not -path './vendor/*' -type f -name '*.go') go.mod go.sum
goreleaser release --snapshot --rm-dist --skip-sign
# Use the highest ZSTD compression level in CI.
ifdef CI
ZSTDFLAGS := -22 --ultra
else
ZSTDFLAGS := -6
endif
# All ${OS}_${ARCH} combos we build for. Windows binaries have the .exe suffix.
OS_ARCHES := \
linux_amd64 linux_arm64 linux_armv7 \
darwin_amd64 darwin_arm64 \
windows_amd64.exe windows_arm64.exe
# Archive formats and their corresponding ${OS}_${ARCH} combos.
ARCHIVE_TAR_GZ := linux_amd64 linux_arm64 linux_armv7
ARCHIVE_ZIP := \
darwin_amd64 darwin_arm64 \
windows_amd64 windows_arm64
# All package formats we build and the ${OS}_${ARCH} combos we build them for.
PACKAGE_FORMATS := apk deb rpm
PACKAGE_OS_ARCHES := linux_amd64 linux_armv7 linux_arm64
# All architectures we build Docker images for (Linux only).
DOCKER_ARCHES := amd64 arm64 armv7
# Computed variables based on the above.
CODER_SLIM_BINARIES := $(addprefix build/coder-slim_$(VERSION)_,$(OS_ARCHES))
CODER_FAT_BINARIES := $(addprefix build/coder_$(VERSION)_,$(OS_ARCHES))
CODER_ALL_BINARIES := $(CODER_SLIM_BINARIES) $(CODER_FAT_BINARIES)
CODER_TAR_GZ_ARCHIVES := $(foreach os_arch, $(ARCHIVE_TAR_GZ), build/coder_$(VERSION)_$(os_arch).tar.gz)
CODER_ZIP_ARCHIVES := $(foreach os_arch, $(ARCHIVE_ZIP), build/coder_$(VERSION)_$(os_arch).zip)
CODER_ALL_ARCHIVES := $(CODER_TAR_GZ_ARCHIVES) $(CODER_ZIP_ARCHIVES)
CODER_ALL_PACKAGES := $(foreach os_arch, $(PACKAGE_OS_ARCHES), $(addprefix build/coder_$(VERSION)_$(os_arch).,$(PACKAGE_FORMATS)))
CODER_ARCH_IMAGES := $(foreach arch, $(DOCKER_ARCHES), build/coder_$(VERSION)_linux_$(arch).tag)
CODER_ARCH_IMAGES_PUSHED := $(addprefix push/, $(CODER_ARCH_IMAGES))
CODER_MAIN_IMAGE := build/coder_$(VERSION)_linux.tag
CODER_SLIM_NOVERSION_BINARIES := $(addprefix build/coder-slim_,$(OS_ARCHES))
CODER_FAT_NOVERSION_BINARIES := $(addprefix build/coder_,$(OS_ARCHES))
CODER_ALL_NOVERSION_IMAGES := $(foreach arch, $(DOCKER_ARCHES), build/coder_linux_$(arch).tag) build/coder_linux.tag
CODER_ALL_NOVERSION_IMAGES_PUSHED := $(addprefix push/, $(CODER_ALL_NOVERSION_IMAGES))
clean:
rm -rf build site/out
mkdir -p build site/out/bin
git restore site/out
.PHONY: clean
build-slim: $(CODER_SLIM_BINARIES)
.PHONY: build-slim
build-fat build-full build: $(CODER_FAT_BINARIES)
.PHONY: build-fat build-full build
release: $(CODER_FAT_BINARIES) $(CODER_ALL_ARCHIVES) $(CODER_ALL_PACKAGES) $(CODER_ARCH_IMAGES) build/coder_helm_$(VERSION).tgz
.PHONY: release
build/coder-slim_$(VERSION)_checksums.sha1 site/out/bin/coder.sha1: $(CODER_SLIM_BINARIES)
pushd ./site/out/bin
openssl dgst -r -sha1 coder-* | tee coder.sha1
popd
cp "site/out/bin/coder.sha1" "build/coder-slim_$(VERSION)_checksums.sha1"
build/coder-slim_$(VERSION).tar: build/coder-slim_$(VERSION)_checksums.sha1 $(CODER_SLIM_BINARIES)
pushd ./site/out/bin
tar cf "../../../build/$(@F)" coder-*
popd
build/coder-slim_$(VERSION).tar.zst site/out/bin/coder.tar.zst: build/coder-slim_$(VERSION).tar
zstd $(ZSTDFLAGS) \
--force \
--long \
--no-progress \
-o "build/coder-slim_$(VERSION).tar.zst" \
"build/coder-slim_$(VERSION).tar"
cp "build/coder-slim_$(VERSION).tar.zst" "site/out/bin/coder.tar.zst"
# delete the uncompressed binaries from the embedded dir
rm site/out/bin/coder-*
# Redirect from version-less targets to the versioned ones. There is a similar
# target for slim binaries below.
#
# Called like this:
# make build/coder_linux_amd64
# make build/coder_windows_amd64.exe
$(CODER_FAT_NOVERSION_BINARIES): build/coder_%: build/coder_$(VERSION)_%
rm -f "$@"
ln "$<" "$@"
# Same as above, but for slim binaries.
#
# Called like this:
# make build/coder-slim_linux_amd64
# make build/coder-slim_windows_amd64.exe
$(CODER_SLIM_NOVERSION_BINARIES): build/coder-slim_%: build/coder-slim_$(VERSION)_%
rm -f "$@"
ln "$<" "$@"
# "fat" binaries always depend on the site and the compressed slim binaries.
$(CODER_FAT_BINARIES): \
site/out/index.html \
site/out/bin/coder.sha1 \
site/out/bin/coder.tar.zst
# This is a handy block that parses the target to determine whether it's "slim"
# or "fat", which OS was specified and which architecture was specified.
#
# It populates the following variables: mode, os, arch_ext, arch, ext (without
# dot).
define get-mode-os-arch-ext =
mode="$$([[ "$@" = build/coder-slim* ]] && echo "slim" || echo "fat")"
os="$$(echo $@ | cut -d_ -f3)"
arch_ext="$$(echo $@ | cut -d_ -f4)"
if [[ "$$arch_ext" == *.* ]]; then
arch="$$(echo $$arch_ext | cut -d. -f1)"
ext="$${arch_ext#*.}"
else
arch="$$arch_ext"
ext=""
fi
endef
# This task handles all builds, for both "fat" and "slim" binaries. It parses
# the target name to get the metadata for the build, so it must be specified in
# this format:
# build/coder(-slim)?_${version}_${os}_${arch}(.exe)?
#
# You should probably use the non-version targets above instead if you're
# calling this manually.
$(CODER_ALL_BINARIES): go.mod go.sum \
$(shell find . -not -path './vendor/*' -type f -name '*.go') \
$(shell find ./examples/templates)
$(get-mode-os-arch-ext)
if [[ "$$os" != "windows" ]] && [[ "$$ext" != "" ]]; then
echo "ERROR: Invalid build binary extension" 1>&2
exit 1
fi
if [[ "$$os" == "windows" ]] && [[ "$$ext" != exe ]]; then
echo "ERROR: Windows binaries must have an .exe extension." 1>&2
exit 1
fi
build_args=( \
--os "$$os" \
--arch "$$arch" \
--version "$(VERSION)" \
--output "$@" \
)
if [ "$$mode" == "slim" ]; then
build_args+=(--slim)
fi
./scripts/build_go.sh "$${build_args[@]}"
if [[ "$$mode" == "slim" ]]; then
dot_ext=""
if [[ "$$ext" != "" ]]; then
dot_ext=".$$ext"
fi
cp "$@" "./site/out/bin/coder-$$os-$$arch$$dot_ext"
fi
# This task builds all archives. It parses the target name to get the metadata
# for the build, so it must be specified in this format:
# build/coder_${version}_${os}_${arch}.${format}
#
# The following OS/arch/format combinations are supported:
# .tar.gz: linux_amd64, linux_arm64, linux_armv7
# .zip: darwin_amd64, darwin_arm64, windows_amd64, windows_arm64
#
# This depends on all fat binaries because it's difficult to do dynamic
# dependencies due to the .exe requirement on Windows. These targets are
# typically only used during release anyways.
$(CODER_ALL_ARCHIVES): $(CODER_FAT_BINARIES)
$(get-mode-os-arch-ext)
bin_ext=""
if [[ "$$os" == "windows" ]]; then
bin_ext=".exe"
fi
./scripts/archive.sh \
--format "$$ext" \
--os "$$os" \
--output "$@" \
"build/coder_$(VERSION)_$${os}_$${arch}$${bin_ext}"
# This task builds all packages. It parses the target name to get the metadata
# for the build, so it must be specified in this format:
# build/coder_${version}_linux_${arch}.${format}
#
# Supports apk, deb, rpm for all linux targets.
#
# This depends on all Linux fat binaries and archives because it's difficult to
# do dynamic dependencies due to the extensions in the filenames. These targets
# are typically only used during release anyways.
#
# Packages need to run after the archives are built, otherwise they cause tar
# errors like "file changed as we read it".
CODER_PACKAGE_DEPS := $(foreach os_arch, $(PACKAGE_OS_ARCHES), build/coder_$(VERSION)_$(os_arch) build/coder_$(VERSION)_$(os_arch).tar.gz)
$(CODER_ALL_PACKAGES): $(CODER_PACKAGE_DEPS)
$(get-mode-os-arch-ext)
./scripts/package.sh \
--arch "$$arch" \
--format "$$ext" \
--version "$(VERSION)" \
--output "$@" \
"build/coder_$(VERSION)_$${os}_$${arch}"
# Redirect from version-less Docker image targets to the versioned ones.
#
# Called like this:
# make build/coder_linux_amd64.tag
$(CODER_ALL_NOVERSION_IMAGES): build/coder_%: build/coder_$(VERSION)_%
.PHONY: $(CODER_ALL_NOVERSION_IMAGES)
# Redirect from version-less push Docker image targets to the versioned ones.
#
# Called like this:
# make push/build/coder_linux_amd64.tag
$(CODER_ALL_NOVERSION_IMAGES_PUSHED): push/build/coder_%: push/build/coder_$(VERSION)_%
.PHONY: $(CODER_ALL_NOVERSION_IMAGES_PUSHED)
# This task builds all Docker images. It parses the target name to get the
# metadata for the build, so it must be specified in this format:
# build/coder_${version}_${os}_${arch}.tag
#
# Supports linux_amd64, linux_arm64, linux_armv7.
#
# Images need to run after the archives and packages are built, otherwise they
# cause errors like "file changed as we read it".
$(CODER_ARCH_IMAGES): build/coder_$(VERSION)_%.tag: \
build/coder_$(VERSION)_% \
build/coder_$(VERSION)_%.apk \
build/coder_$(VERSION)_%.deb \
build/coder_$(VERSION)_%.rpm \
build/coder_$(VERSION)_%.tar.gz
$(get-mode-os-arch-ext)
image_tag="$$(./scripts/image_tag.sh --arch "$$arch" --version "$(VERSION)")"
./scripts/build_docker.sh \
--arch "$$arch" \
--target "$$image_tag" \
--version "$(VERSION)" \
"build/coder_$(VERSION)_$${os}_$${arch}"
echo "$$image_tag" > "$@"
# Multi-arch Docker image. This requires all architecture-specific images to be
# built AND pushed.
$(CODER_MAIN_IMAGE): $(CODER_ARCH_IMAGES_PUSHED)
image_tag="$$(./scripts/image_tag.sh --version "$(VERSION)")"
./scripts/build_docker_multiarch.sh \
--target "$$image_tag" \
--version "$(VERSION)" \
$(foreach img, $^, "$$(cat "$(img:push/%=%)")")
echo "$$image_tag" > "$@"
# Push a Docker image.
$(CODER_ARCH_IMAGES_PUSHED): push/%: %
image_tag="$$(cat "$<")"
docker push "$$image_tag"
.PHONY: $(CODER_ARCH_IMAGES_PUSHED)
# Push the multi-arch Docker manifest.
push/$(CODER_MAIN_IMAGE): $(CODER_MAIN_IMAGE)
image_tag="$$(cat "$<")"
docker manifest push "$$image_tag"
.PHONY: push/$(CODER_MAIN_IMAGE)
# Shortcut for Helm chart package.
build/coder_helm.tgz: build/coder_helm_$(VERSION).tgz
rm -f "$@"
ln "$<" "$@"
# Helm chart package.
build/coder_helm_$(VERSION).tgz:
./scripts/helm.sh \
--version "$(VERSION)" \
--output "$@"
site/out/index.html: $(shell find ./site -not -path './site/node_modules/*' -type f -name '*.tsx') $(shell find ./site -not -path './site/node_modules/*' -type f -name '*.ts') site/package.json
./scripts/yarn_install.sh
cd site
yarn typegen
yarn build
install: build/coder_$(VERSION)_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT)
install_dir="$$(go env GOPATH)/bin"
output_file="$${install_dir}/coder$(GOOS_BIN_EXT)"
mkdir -p "$$install_dir"
cp "$<" "$$output_file"
.PHONY: install
fmt: fmt/prettier fmt/terraform fmt/shfmt
.PHONY: fmt
fmt/prettier:
@echo "--- prettier"
echo "--- prettier"
cd site
# Avoid writing files in CI to reduce file write activity
ifdef CI
cd site && yarn run format:check
yarn run format:check
else
cd site && yarn run format:write
yarn run format:write
endif
.PHONY: fmt/prettier
@@ -41,36 +358,63 @@ fmt/terraform: $(wildcard *.tf)
terraform fmt -recursive
.PHONY: fmt/terraform
fmt: fmt/prettier fmt/terraform
.PHONY: fmt
fmt/shfmt: $(shell shfmt -f .)
echo "--- shfmt"
# Only do diff check in CI, errors on diff.
ifdef CI
shfmt -d $(shell shfmt -f .)
else
shfmt -w $(shell shfmt -f .)
endif
.PHONY: fmt/shfmt
gen: coderd/database/querier.go peerbroker/proto/peerbroker.pb.go provisionersdk/proto/provisioner.pb.go provisionerd/proto/provisionerd.pb.go site/src/api/typesGenerated.ts
install: build
@echo "--- Copying from bin to $(INSTALL_DIR)"
cp -r ./dist/coder-$(GOOS)_$(GOOS)_$(GOARCH)*/* $(INSTALL_DIR)
@echo "-- CLI available at $(shell ls $(INSTALL_DIR)/coder*)"
.PHONY: install
lint:
golangci-lint run
lint: lint/shellcheck lint/go
.PHONY: lint
peerbroker/proto/peerbroker.pb.go: peerbroker/proto/peerbroker.proto
protoc \
--go_out=. \
--go_opt=paths=source_relative \
--go-drpc_out=. \
--go-drpc_opt=paths=source_relative \
./peerbroker/proto/peerbroker.proto
lint/go:
./scripts/check_enterprise_imports.sh
golangci-lint run
.PHONY: lint/go
provisionerd/proto/provisionerd.pb.go: provisionerd/proto/provisionerd.proto
protoc \
--go_out=. \
--go_opt=paths=source_relative \
--go-drpc_out=. \
--go-drpc_opt=paths=source_relative \
./provisionerd/proto/provisionerd.proto
# Use shfmt to determine the shell files, takes editorconfig into consideration.
lint/shellcheck: $(shell shfmt -f .)
echo "--- shellcheck"
shellcheck --external-sources $(shell shfmt -f .)
.PHONY: lint/shellcheck
# all gen targets should be added here and to gen/mark-fresh
gen: \
coderd/database/dump.sql \
coderd/database/querier.go \
provisionersdk/proto/provisioner.pb.go \
provisionerd/proto/provisionerd.pb.go \
site/src/api/typesGenerated.ts
.PHONY: gen
# Mark all generated files as fresh so make thinks they're up-to-date. This is
# used during releases so we don't run generation scripts.
gen/mark-fresh:
files="coderd/database/dump.sql coderd/database/querier.go provisionersdk/proto/provisioner.pb.go provisionerd/proto/provisionerd.pb.go site/src/api/typesGenerated.ts"
for file in $$files; do
echo "$$file"
if [ ! -f "$$file" ]; then
echo "File '$$file' does not exist"
exit 1
fi
# touch sets the mtime of the file to the current time
touch $$file
done
.PHONY: gen/mark-fresh
# Runs migrations to output a dump of the database schema after migrations are
# applied.
coderd/database/dump.sql: coderd/database/gen/dump/main.go $(wildcard coderd/database/migrations/*.sql)
go run ./coderd/database/gen/dump/main.go
# Generates Go code for querying the database.
coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql) coderd/database/gen/enum/main.go
./coderd/database/generate.sh
provisionersdk/proto/provisioner.pb.go: provisionersdk/proto/provisioner.proto
protoc \
@@ -80,16 +424,57 @@ provisionersdk/proto/provisioner.pb.go: provisionersdk/proto/provisioner.proto
--go-drpc_opt=paths=source_relative \
./provisionersdk/proto/provisioner.proto
site/out/index.html: $(shell find ./site -not -path './site/node_modules/*' -type f -name '*.tsx') $(shell find ./site -not -path './site/node_modules/*' -type f -name '*.ts') site/package.json
./scripts/yarn_install.sh
cd site && yarn typegen
cd site && yarn build
# Restores GITKEEP files!
git checkout HEAD site/out
provisionerd/proto/provisionerd.pb.go: provisionerd/proto/provisionerd.proto
protoc \
--go_out=. \
--go_opt=paths=source_relative \
--go-drpc_out=. \
--go-drpc_opt=paths=source_relative \
./provisionerd/proto/provisionerd.proto
site/src/api/typesGenerated.ts: scripts/apitypings/main.go $(shell find codersdk -type f -name '*.go')
go run scripts/apitypings/main.go > site/src/api/typesGenerated.ts
cd site && yarn run format:types
cd site
yarn run format:types
test:
test: test-clean
gotestsum -- -v -short ./...
.PHONY: test
# When updating -timeout for this test, keep in sync with
# test-go-postgres (.github/workflows/coder.yaml).
test-postgres: test-clean test-postgres-docker
DB=ci DB_FROM=$(shell go run scripts/migrate-ci/main.go) gotestsum --junitfile="gotests.xml" --packages="./..." -- \
-covermode=atomic -coverprofile="gotests.coverage" -timeout=20m \
-coverpkg=./... \
-count=1 -race -failfast
.PHONY: test-postgres
test-postgres-docker:
docker rm -f test-postgres-docker || true
docker run \
--env POSTGRES_PASSWORD=postgres \
--env POSTGRES_USER=postgres \
--env POSTGRES_DB=postgres \
--env PGDATA=/tmp \
--tmpfs /tmp \
--publish 5432:5432 \
--name test-postgres-docker \
--restart no \
--detach \
postgres:13 \
-c shared_buffers=1GB \
-c max_connections=1000 \
-c fsync=off \
-c synchronous_commit=off \
-c full_page_writes=off
while ! pg_isready -h 127.0.0.1
do
echo "$(date) - waiting for database to start"
sleep 0.5
done
.PHONY: test-postgres-docker
test-clean:
go clean -testcache
.PHONY: test-clean
+102
View File
@@ -0,0 +1,102 @@
# Coder
[!["Join us on
Discord"](https://img.shields.io/badge/join-us%20on%20Discord-gray.svg?longCache=true&logo=discord&colorB=green)](https://coder.com/chat?utm_source=github.com/coder/coder&utm_medium=github&utm_campaign=readme.md)
[![codecov](https://codecov.io/gh/coder/coder/branch/main/graph/badge.svg?token=TNLW3OAP6G)](https://codecov.io/gh/coder/coder)
[![Go Reference](https://pkg.go.dev/badge/github.com/coder/coder.svg)](https://pkg.go.dev/github.com/coder/coder)
[![Twitter
Follow](https://img.shields.io/twitter/follow/coderhq?label=%40coderhq&style=social)](https://twitter.com/coderhq)
Coder creates remote development machines so your team can develop from anywhere.
<p align="center">
<img src="./docs/images/hero-image.png">
</p>
**Manage less**
- Ensure your entire team is using the same tools and resources
- Rollout critical updates to your developers with one command
- Automatically shut down expensive cloud resources
- Keep your source code and data behind your firewall
**Code more**
- Build and test faster
- Leveraging cloud CPUs, RAM, network speeds, etc.
- Access your environment from any place on any client (even an iPad)
- Onboard instantly then stay up to date continuously
## Getting Started
> **Note**:
> Coder is in a beta state. [Report issues here](https://github.com/coder/coder/issues/new).
The easiest way to install Coder is to use our [install script](https://github.com/coder/coder/blob/main/install.sh) for Linux and macOS.
To install, run:
```bash
curl -L https://coder.com/install.sh | sh
```
You can preview what occurs during the install process:
```bash
curl -L https://coder.com/install.sh | sh -s -- --dry-run
```
You can modify the installation process by including flags. Run the help command for reference:
```bash
curl -L https://coder.com/install.sh | sh -s -- --help
```
> See [install](docs/install) for additional methods.
Once installed, you can start a production deployment<sup>1</sup> with a single command:
```sh
# Automatically sets up an external access URL on *.try.coder.app
coder server --tunnel
# Requires a PostgreSQL instance and external access URL
coder server --postgres-url <url> --access-url <url>
```
> <sup>1</sup> The embedded database is great for trying out Coder with small deployments, but do consider using an external database for increased assurance and control.
Use `coder --help` to get a complete list of flags and environment variables. Use our [quickstart guide](https://coder.com/docs/coder-oss/latest/quickstart) for a full walkthrough.
## Documentation
Visit our docs [here](https://coder.com/docs/coder-oss).
## Comparison
Please file [an issue](https://github.com/coder/coder/issues/new) if any information is out of date. Also refer to: [What Coder is not](https://coder.com/docs/coder-oss/latest/index#what-coder-is-not).
| Tool | Type | Delivery Model | Cost | Environments |
| :---------------------------------------------------------- | :------- | :----------------- | :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Coder](https://github.com/coder/coder) | Platform | OSS + Self-Managed | Pay your cloud | All [Terraform](https://www.terraform.io/registry/providers) resources, all clouds, multi-architecture: Linux, Mac, Windows, containers, VMs, amd64, arm64 |
| [code-server](https://github.com/cdr/code-server) | Web IDE | OSS + Self-Managed | Pay your cloud | Linux, Mac, Windows, containers, VMs, amd64, arm64 |
| [Coder (Classic)](https://coder.com/docs) | Platform | Self-Managed | Pay your cloud + license fees | Kubernetes Linux Containers |
| [GitHub Codespaces](https://github.com/features/codespaces) | Platform | SaaS | 2x Azure Compute | Linux containers |
---
_Last updated: 5/27/22_
## Community and Support
Join our community on [Discord](https://coder.com/chat?utm_source=github.com/coder/coder&utm_medium=github&utm_campaign=readme.md) and [Twitter](https://twitter.com/coderhq)!
[Suggest improvements and report problems](https://github.com/coder/coder/issues/new/choose)
## Contributing
If you're using Coder in your organization, please try to add your company name to the [ADOPTERS.md](./ADOPTERS.md). It really helps the project to gain momentum and credibility. It's a small contribution back to the project with a big impact.
Read the [contributing docs](https://coder.com/docs/coder-oss/latest/CONTRIBUTING).
Find our list of contributors [here](https://github.com/coder/coder/graphs/contributors).
+412 -182
View File
@@ -4,11 +4,13 @@ import (
"context"
"crypto/rand"
"crypto/rsa"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/netip"
"os"
"os/exec"
"os/user"
@@ -20,62 +22,75 @@ import (
"time"
"github.com/armon/circbuf"
"github.com/gliderlabs/ssh"
"github.com/google/uuid"
"github.com/pkg/sftp"
"go.uber.org/atomic"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/xerrors"
"tailscale.com/net/speedtest"
"tailscale.com/tailcfg"
"cdr.dev/slog"
"github.com/coder/coder/agent/usershell"
"github.com/coder/coder/peer"
"github.com/coder/coder/peerbroker"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/pty"
"github.com/coder/coder/tailnet"
"github.com/coder/retry"
)
"github.com/pkg/sftp"
const (
ProtocolReconnectingPTY = "reconnecting-pty"
ProtocolSSH = "ssh"
ProtocolDial = "dial"
"github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/xerrors"
// MagicSessionErrorCode indicates that something went wrong with the session, rather than the
// command just returning a nonzero exit code, and is chosen as an arbitrary, high number
// unlikely to shadow other exit codes, which are typically 1, 2, 3, etc.
MagicSessionErrorCode = 229
)
type Options struct {
ReconnectingPTYTimeout time.Duration
EnvironmentVariables map[string]string
Logger slog.Logger
CoordinatorDialer CoordinatorDialer
FetchMetadata FetchMetadata
StatsReporter StatsReporter
WorkspaceAgentApps WorkspaceAgentApps
PostWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth
ReconnectingPTYTimeout time.Duration
EnvironmentVariables map[string]string
Logger slog.Logger
}
type Metadata struct {
OwnerEmail string `json:"owner_email"`
OwnerUsername string `json:"owner_username"`
EnvironmentVariables map[string]string `json:"environment_variables"`
StartupScript string `json:"startup_script"`
Directory string `json:"directory"`
}
// CoordinatorDialer is a function that constructs a new broker.
// A dialer must be passed in to allow for reconnects.
type CoordinatorDialer func(context.Context) (net.Conn, error)
type Dialer func(ctx context.Context, logger slog.Logger) (Metadata, *peerbroker.Listener, error)
// FetchMetadata is a function to obtain metadata for the agent.
type FetchMetadata func(context.Context) (codersdk.WorkspaceAgentMetadata, error)
func New(dialer Dialer, options *Options) io.Closer {
if options == nil {
options = &Options{}
}
func New(options Options) io.Closer {
if options.ReconnectingPTYTimeout == 0 {
options.ReconnectingPTYTimeout = 5 * time.Minute
}
ctx, cancelFunc := context.WithCancel(context.Background())
server := &agent{
dialer: dialer,
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
logger: options.Logger,
closeCancel: cancelFunc,
closed: make(chan struct{}),
envVars: options.EnvironmentVariables,
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
logger: options.Logger,
closeCancel: cancelFunc,
closed: make(chan struct{}),
envVars: options.EnvironmentVariables,
coordinatorDialer: options.CoordinatorDialer,
fetchMetadata: options.FetchMetadata,
stats: &Stats{},
statsReporter: options.StatsReporter,
workspaceAgentApps: options.WorkspaceAgentApps,
postWorkspaceAgentAppHealth: options.PostWorkspaceAgentAppHealth,
}
server.init(ctx)
return server
}
type agent struct {
dialer Dialer
logger slog.Logger
reconnectingPTYs sync.Map
@@ -89,18 +104,25 @@ type agent struct {
envVars map[string]string
// metadata is atomic because values can change after reconnection.
metadata atomic.Value
startupScript atomic.Bool
fetchMetadata FetchMetadata
sshServer *ssh.Server
network *tailnet.Conn
coordinatorDialer CoordinatorDialer
stats *Stats
statsReporter StatsReporter
workspaceAgentApps WorkspaceAgentApps
postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth
}
func (a *agent) run(ctx context.Context) {
var metadata Metadata
var peerListener *peerbroker.Listener
var metadata codersdk.WorkspaceAgentMetadata
var err error
// An exponential back-off occurs when the connection is failing to dial.
// This is to prevent server spam in case of a coderd outage.
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
metadata, peerListener, err = a.dialer(ctx, a.logger)
a.logger.Info(ctx, "connecting")
metadata, err = a.fetchMetadata(ctx)
if err != nil {
if errors.Is(err, context.Canceled) {
return
@@ -111,7 +133,7 @@ func (a *agent) run(ctx context.Context) {
a.logger.Warn(context.Background(), "failed to dial", slog.Error(err))
continue
}
a.logger.Info(context.Background(), "connected")
a.logger.Info(context.Background(), "fetched metadata")
break
}
select {
@@ -121,106 +143,214 @@ func (a *agent) run(ctx context.Context) {
}
a.metadata.Store(metadata)
if a.startupScript.CAS(false, true) {
// The startup script has not ran yet!
go func() {
err := a.runStartupScript(ctx, metadata.StartupScript)
if errors.Is(err, context.Canceled) {
return
}
if err != nil {
a.logger.Warn(ctx, "agent script failed", slog.Error(err))
}
}()
}
for {
conn, err := peerListener.Accept()
if err != nil {
if a.isClosed() {
return
}
a.logger.Debug(ctx, "peer listener accept exited; restarting connection", slog.Error(err))
a.run(ctx)
// The startup script has not ran yet!
go func() {
err := a.runStartupScript(ctx, metadata.StartupScript)
if errors.Is(err, context.Canceled) {
return
}
a.closeMutex.Lock()
a.connCloseWait.Add(1)
a.closeMutex.Unlock()
go a.handlePeerConn(ctx, conn)
if err != nil {
a.logger.Warn(ctx, "agent script failed", slog.Error(err))
}
}()
if metadata.DERPMap != nil {
go a.runTailnet(ctx, metadata.DERPMap)
}
if a.workspaceAgentApps != nil && a.postWorkspaceAgentAppHealth != nil {
go NewWorkspaceAppHealthReporter(a.logger, a.workspaceAgentApps, a.postWorkspaceAgentAppHealth)(ctx)
}
}
func (*agent) runStartupScript(ctx context.Context, script string) error {
func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
a.closeMutex.Lock()
defer a.closeMutex.Unlock()
if a.isClosed() {
return
}
if a.network != nil {
a.network.SetDERPMap(derpMap)
return
}
var err error
a.network, err = tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(codersdk.TailnetIP, 128)},
DERPMap: derpMap,
Logger: a.logger.Named("tailnet"),
})
if err != nil {
a.logger.Critical(ctx, "create tailnet", slog.Error(err))
return
}
a.network.SetForwardTCPCallback(func(conn net.Conn, listenerExists bool) net.Conn {
if listenerExists {
// If a listener already exists, we would double-wrap the conn.
return conn
}
return a.stats.wrapConn(conn)
})
go a.runCoordinator(ctx)
sshListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSSHPort))
if err != nil {
a.logger.Critical(ctx, "listen for ssh", slog.Error(err))
return
}
go func() {
for {
conn, err := sshListener.Accept()
if err != nil {
return
}
go a.sshServer.HandleConn(a.stats.wrapConn(conn))
}
}()
reconnectingPTYListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetReconnectingPTYPort))
if err != nil {
a.logger.Critical(ctx, "listen for reconnecting pty", slog.Error(err))
return
}
go func() {
for {
conn, err := reconnectingPTYListener.Accept()
if err != nil {
a.logger.Debug(ctx, "accept pty failed", slog.Error(err))
return
}
conn = a.stats.wrapConn(conn)
// This cannot use a JSON decoder, since that can
// buffer additional data that is required for the PTY.
rawLen := make([]byte, 2)
_, err = conn.Read(rawLen)
if err != nil {
continue
}
length := binary.LittleEndian.Uint16(rawLen)
data := make([]byte, length)
_, err = conn.Read(data)
if err != nil {
continue
}
var msg codersdk.ReconnectingPTYInit
err = json.Unmarshal(data, &msg)
if err != nil {
continue
}
go a.handleReconnectingPTY(ctx, msg, conn)
}
}()
speedtestListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSpeedtestPort))
if err != nil {
a.logger.Critical(ctx, "listen for speedtest", slog.Error(err))
return
}
go func() {
for {
conn, err := speedtestListener.Accept()
if err != nil {
a.logger.Debug(ctx, "speedtest listener failed", slog.Error(err))
return
}
a.closeMutex.Lock()
a.connCloseWait.Add(1)
a.closeMutex.Unlock()
go func() {
defer a.connCloseWait.Done()
_ = speedtest.ServeConn(conn)
}()
}
}()
}
// runCoordinator listens for nodes and updates the self-node as it changes.
func (a *agent) runCoordinator(ctx context.Context) {
for {
reconnect := a.runCoordinatorWithRetry(ctx)
if !reconnect {
return
}
}
}
func (a *agent) runCoordinatorWithRetry(ctx context.Context) (reconnect bool) {
var coordinator net.Conn
var err error
// An exponential back-off occurs when the connection is failing to dial.
// This is to prevent server spam in case of a coderd outage.
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
coordinator, err = a.coordinatorDialer(ctx)
if err != nil {
if errors.Is(err, context.Canceled) {
return false
}
if a.isClosed() {
return false
}
a.logger.Warn(context.Background(), "failed to dial", slog.Error(err))
continue
}
//nolint:revive // Defer is ok because we're exiting this loop.
defer coordinator.Close()
a.logger.Info(context.Background(), "connected to coordination server")
break
}
select {
case <-ctx.Done():
return false
default:
}
sendNodes, errChan := tailnet.ServeCoordinator(coordinator, a.network.UpdateNodes)
a.network.SetNodeCallback(sendNodes)
select {
case <-ctx.Done():
return false
case err := <-errChan:
if a.isClosed() {
return false
}
if errors.Is(err, context.Canceled) {
return false
}
a.logger.Debug(ctx, "node broker accept exited; restarting connection", slog.Error(err))
return true
}
}
func (a *agent) runStartupScript(ctx context.Context, script string) error {
if script == "" {
return nil
}
currentUser, err := user.Current()
if err != nil {
return xerrors.Errorf("get current user: %w", err)
}
username := currentUser.Username
shell, err := usershell.Get(username)
if err != nil {
return xerrors.Errorf("get user shell: %w", err)
}
writer, err := os.OpenFile(filepath.Join(os.TempDir(), "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0600)
writer, err := os.OpenFile(filepath.Join(os.TempDir(), "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return xerrors.Errorf("open startup script log file: %w", err)
}
defer func() {
_ = writer.Close()
}()
caller := "-c"
if runtime.GOOS == "windows" {
caller = "/c"
cmd, err := a.createCommand(ctx, script, nil)
if err != nil {
return xerrors.Errorf("create command: %w", err)
}
cmd := exec.CommandContext(ctx, shell, caller, script)
cmd.Stdout = writer
cmd.Stderr = writer
err = cmd.Run()
if err != nil {
// cmd.Run does not return a context canceled error, it returns "signal: killed".
if ctx.Err() != nil {
return ctx.Err()
}
return xerrors.Errorf("run: %w", err)
}
return nil
}
func (a *agent) handlePeerConn(ctx context.Context, conn *peer.Conn) {
go func() {
select {
case <-a.closed:
case <-conn.Closed():
}
_ = conn.Close()
a.connCloseWait.Done()
}()
for {
channel, err := conn.Accept(ctx)
if err != nil {
if errors.Is(err, peer.ErrClosed) || a.isClosed() {
return
}
a.logger.Debug(ctx, "accept channel from peer connection", slog.Error(err))
return
}
switch channel.Protocol() {
case "ssh":
go a.sshServer.HandleConn(channel.NetConn())
case "reconnecting-pty":
go a.handleReconnectingPTY(ctx, channel.Label(), channel.NetConn())
default:
a.logger.Warn(ctx, "unhandled protocol from channel",
slog.F("protocol", channel.Protocol()),
slog.F("label", channel.Label()),
)
}
}
}
func (a *agent) init(ctx context.Context) {
a.logger.Info(ctx, "generating host key")
// Clients' should ignore the host key when connecting.
// The agent needs to authenticate with coderd to SSH,
// so SSH authentication doesn't improve security.
@@ -244,9 +374,17 @@ func (a *agent) init(ctx context.Context) {
},
Handler: func(session ssh.Session) {
err := a.handleSSHSession(session)
var exitError *exec.ExitError
if xerrors.As(err, &exitError) {
a.logger.Debug(ctx, "ssh session returned", slog.Error(exitError))
_ = session.Exit(exitError.ExitCode())
return
}
if err != nil {
a.logger.Warn(ctx, "ssh session failed", slog.Error(err))
_ = session.Exit(1)
// This exit code is designed to be unlikely to be confused for a legit exit code
// from the process.
_ = session.Exit(MagicSessionErrorCode)
return
}
},
@@ -279,6 +417,8 @@ func (a *agent) init(ctx context.Context) {
},
SubsystemHandlers: map[string]ssh.SubsystemHandler{
"sftp": func(session ssh.Session) {
session.DisablePTYEmulation()
server, err := sftp.NewServer(session)
if err != nil {
a.logger.Debug(session.Context(), "initialize sftp server", slog.Error(err))
@@ -295,6 +435,21 @@ func (a *agent) init(ctx context.Context) {
}
go a.run(ctx)
if a.statsReporter != nil {
cl, err := a.statsReporter(ctx, a.logger, func() *codersdk.AgentStats {
return a.stats.Copy()
})
if err != nil {
a.logger.Error(ctx, "report stats", slog.Error(err))
return
}
a.connCloseWait.Add(1)
go func() {
defer a.connCloseWait.Done()
<-a.closed
cl.Close()
}()
}
}
// createCommand processes raw command input with OpenSSH-like behavior.
@@ -316,7 +471,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
if rawMetadata == nil {
return nil, xerrors.Errorf("no metadata was provided: %w", err)
}
metadata, valid := rawMetadata.(Metadata)
metadata, valid := rawMetadata.(codersdk.WorkspaceAgentMetadata)
if !valid {
return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata)
}
@@ -326,6 +481,11 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
command := rawCommand
if len(command) == 0 {
command = shell
if runtime.GOOS != "windows" {
// On Linux and macOS, we should start a login
// shell to consume juicy environment variables!
command += " -l"
}
}
// OpenSSH executes all commands with the users current shell.
@@ -345,54 +505,90 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
if err != nil {
return nil, xerrors.Errorf("getting os executable: %w", err)
}
// Set environment variables reliable detection of being inside a
// Coder workspace.
cmd.Env = append(cmd.Env, "CODER=true")
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
// Git on Windows resolves with UNIX-style paths.
// If using backslashes, it's unable to find the executable.
executablePath = strings.ReplaceAll(executablePath, "\\", "/")
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, executablePath))
// These prevent the user from having to specify _anything_ to successfully commit.
// Both author and committer must be set!
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_AUTHOR_EMAIL=%s`, metadata.OwnerEmail))
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_EMAIL=%s`, metadata.OwnerEmail))
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_AUTHOR_NAME=%s`, metadata.OwnerUsername))
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_NAME=%s`, metadata.OwnerUsername))
unixExecutablePath := strings.ReplaceAll(executablePath, "\\", "/")
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, unixExecutablePath))
// Set SSH connection environment variables (these are also set by OpenSSH
// and thus expected to be present by SSH clients). Since the agent does
// networking in-memory, trying to provide accurate values here would be
// nonsensical. For now, we hard code these values so that they're present.
srcAddr, srcPort := "0.0.0.0", "0"
dstAddr, dstPort := "0.0.0.0", "0"
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CLIENT=%s %s %s", srcAddr, srcPort, dstPort))
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CONNECTION=%s %s %s %s", srcAddr, srcPort, dstAddr, dstPort))
// Load environment variables passed via the agent.
// These should override all variables we manually specify.
for key, value := range metadata.EnvironmentVariables {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
for envKey, value := range metadata.EnvironmentVariables {
// Expanding environment variables allows for customization
// of the $PATH, among other variables. Customers can prepend
// or append to the $PATH, so allowing expand is required!
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envKey, os.ExpandEnv(value)))
}
// Agent-level environment variables should take over all!
// This is used for setting agent-specific variables like "CODER_AGENT_TOKEN".
for key, value := range a.envVars {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
for envKey, value := range a.envVars {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envKey, value))
}
return cmd, nil
}
func (a *agent) handleSSHSession(session ssh.Session) error {
cmd, err := a.createCommand(session.Context(), session.RawCommand(), session.Environ())
func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
ctx := session.Context()
cmd, err := a.createCommand(ctx, session.RawCommand(), session.Environ())
if err != nil {
return err
}
if ssh.AgentRequested(session) {
l, err := ssh.NewAgentListener()
if err != nil {
return xerrors.Errorf("new agent listener: %w", err)
}
defer l.Close()
go ssh.ForwardAgentConnections(l, session)
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", "SSH_AUTH_SOCK", l.Addr().String()))
}
sshPty, windowSize, isPty := session.Pty()
if isPty {
// Disable minimal PTY emulation set by gliderlabs/ssh (NL-to-CRNL).
// See https://github.com/coder/coder/issues/3371.
session.DisablePTYEmulation()
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term))
ptty, process, err := pty.Start(cmd)
// The pty package sets `SSH_TTY` on supported platforms.
ptty, process, err := pty.Start(cmd, pty.WithPTYOption(
pty.WithSSHRequest(sshPty),
pty.WithLogger(slog.Stdlib(ctx, a.logger, slog.LevelInfo)),
))
if err != nil {
return xerrors.Errorf("start command: %w", err)
}
err = ptty.Resize(uint16(sshPty.Window.Height), uint16(sshPty.Window.Width))
if err != nil {
return xerrors.Errorf("resize ptty: %w", err)
}
defer func() {
closeErr := ptty.Close()
if closeErr != nil {
a.logger.Warn(ctx, "failed to close tty", slog.Error(closeErr))
if retErr == nil {
retErr = closeErr
}
}
}()
go func() {
for win := range windowSize {
err = ptty.Resize(uint16(win.Height), uint16(win.Width))
if err != nil {
a.logger.Warn(context.Background(), "failed to resize tty", slog.Error(err))
resizeErr := ptty.Resize(uint16(win.Height), uint16(win.Width))
if resizeErr != nil {
a.logger.Warn(ctx, "failed to resize tty", slog.Error(resizeErr))
}
}
}()
@@ -402,9 +598,14 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
go func() {
_, _ = io.Copy(session, ptty.Output())
}()
_, _ = process.Wait()
_ = ptty.Close()
return nil
err = process.Wait()
var exitErr *exec.ExitError
// ExitErrors just mean the command we run returned a non-zero exit code, which is normal
// and not something to be concerned about. But, if it's something else, we should log it.
if err != nil && !xerrors.As(err, &exitErr) {
a.logger.Warn(ctx, "wait error", slog.Error(err))
}
return err
}
cmd.Stdout = session
@@ -417,6 +618,7 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
}
go func() {
_, _ = io.Copy(stdinPipe, session)
_ = stdinPipe.Close()
}()
err = cmd.Start()
if err != nil {
@@ -425,60 +627,36 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
return cmd.Wait()
}
func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn net.Conn) {
func (a *agent) handleReconnectingPTY(ctx context.Context, msg codersdk.ReconnectingPTYInit, conn net.Conn) {
defer conn.Close()
// The ID format is referenced in conn.go.
// <uuid>:<height>:<width>
idParts := strings.Split(rawID, ":")
if len(idParts) != 3 {
a.logger.Warn(ctx, "client sent invalid id format", slog.F("raw-id", rawID))
return
}
id := idParts[0]
// Enforce a consistent format for IDs.
_, err := uuid.Parse(id)
if err != nil {
a.logger.Warn(ctx, "client sent reconnection token that isn't a uuid", slog.F("id", id), slog.Error(err))
return
}
// Parse the initial terminal dimensions.
height, err := strconv.Atoi(idParts[1])
if err != nil {
a.logger.Warn(ctx, "client sent invalid height", slog.F("id", id), slog.F("height", idParts[1]))
return
}
width, err := strconv.Atoi(idParts[2])
if err != nil {
a.logger.Warn(ctx, "client sent invalid width", slog.F("id", id), slog.F("width", idParts[2]))
return
}
var rpty *reconnectingPTY
rawRPTY, ok := a.reconnectingPTYs.Load(id)
rawRPTY, ok := a.reconnectingPTYs.Load(msg.ID)
if ok {
rpty, ok = rawRPTY.(*reconnectingPTY)
if !ok {
a.logger.Warn(ctx, "found invalid type in reconnecting pty map", slog.F("id", id))
a.logger.Error(ctx, "found invalid type in reconnecting pty map", slog.F("id", msg.ID))
return
}
} else {
// Empty command will default to the users shell!
cmd, err := a.createCommand(ctx, "", nil)
cmd, err := a.createCommand(ctx, msg.Command, nil)
if err != nil {
a.logger.Warn(ctx, "create reconnecting pty command", slog.Error(err))
a.logger.Error(ctx, "create reconnecting pty command", slog.Error(err))
return
}
cmd.Env = append(cmd.Env, "TERM=xterm-256color")
ptty, process, err := pty.Start(cmd)
// Default to buffer 64KiB.
circularBuffer, err := circbuf.NewBuffer(64 << 10)
if err != nil {
a.logger.Warn(ctx, "start reconnecting pty command", slog.F("id", id))
a.logger.Error(ctx, "create circular buffer", slog.Error(err))
return
}
// Default to buffer 64KB.
circularBuffer, err := circbuf.NewBuffer(64 * 1024)
ptty, process, err := pty.Start(cmd)
if err != nil {
a.logger.Warn(ctx, "create circular buffer", slog.Error(err))
a.logger.Error(ctx, "start reconnecting pty command", slog.F("id", msg.ID))
return
}
@@ -493,7 +671,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
timeout: time.AfterFunc(a.reconnectingPTYTimeout, cancelFunc),
circularBuffer: circularBuffer,
}
a.reconnectingPTYs.Store(id, rpty)
a.reconnectingPTYs.Store(msg.ID, rpty)
go func() {
// CommandContext isn't respected for Windows PTYs right now,
// so we need to manually track the lifecycle.
@@ -506,7 +684,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
go func() {
// If the process dies randomly, we should
// close the pty.
_, _ = process.Wait()
_ = process.Wait()
rpty.Close()
}()
go func() {
@@ -522,7 +700,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
_, err = rpty.circularBuffer.Write(part)
rpty.circularBufferMutex.Unlock()
if err != nil {
a.logger.Error(ctx, "reconnecting pty write buffer", slog.Error(err), slog.F("id", id))
a.logger.Error(ctx, "reconnecting pty write buffer", slog.Error(err), slog.F("id", msg.ID))
break
}
rpty.activeConnsMutex.Lock()
@@ -536,22 +714,22 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
// ID from memory.
_ = process.Kill()
rpty.Close()
a.reconnectingPTYs.Delete(id)
a.reconnectingPTYs.Delete(msg.ID)
a.connCloseWait.Done()
}()
}
// Resize the PTY to initial height + width.
err = rpty.ptty.Resize(uint16(height), uint16(width))
err := rpty.ptty.Resize(msg.Height, msg.Width)
if err != nil {
// We can continue after this, it's not fatal!
a.logger.Error(ctx, "resize reconnecting pty", slog.F("id", id), slog.Error(err))
a.logger.Error(ctx, "resize reconnecting pty", slog.F("id", msg.ID), slog.Error(err))
}
// Write any previously stored data for the TTY.
rpty.circularBufferMutex.RLock()
_, err = conn.Write(rpty.circularBuffer.Bytes())
rpty.circularBufferMutex.RUnlock()
if err != nil {
a.logger.Warn(ctx, "write reconnecting pty buffer", slog.F("id", id), slog.Error(err))
a.logger.Warn(ctx, "write reconnecting pty buffer", slog.F("id", msg.ID), slog.Error(err))
return
}
connectionID := uuid.NewString()
@@ -590,19 +768,19 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
rpty.activeConnsMutex.Unlock()
}()
decoder := json.NewDecoder(conn)
var req ReconnectingPTYRequest
var req codersdk.ReconnectingPTYRequest
for {
err = decoder.Decode(&req)
if xerrors.Is(err, io.EOF) {
return
}
if err != nil {
a.logger.Warn(ctx, "reconnecting pty buffer read error", slog.F("id", id), slog.Error(err))
a.logger.Warn(ctx, "reconnecting pty buffer read error", slog.F("id", msg.ID), slog.Error(err))
return
}
_, err = rpty.ptty.Input().Write([]byte(req.Data))
if err != nil {
a.logger.Warn(ctx, "write to reconnecting pty", slog.F("id", id), slog.Error(err))
a.logger.Warn(ctx, "write to reconnecting pty", slog.F("id", msg.ID), slog.Error(err))
return
}
// Check if a resize needs to happen!
@@ -612,7 +790,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, rawID string, conn ne
err = rpty.ptty.Resize(req.Height, req.Width)
if err != nil {
// We can continue after this, it's not fatal!
a.logger.Error(ctx, "resize reconnecting pty", slog.F("id", id), slog.Error(err))
a.logger.Error(ctx, "resize reconnecting pty", slog.F("id", msg.ID), slog.Error(err))
}
}
}
@@ -635,6 +813,9 @@ func (a *agent) Close() error {
}
close(a.closed)
a.closeCancel()
if a.network != nil {
_ = a.network.Close()
}
_ = a.sshServer.Close()
a.connCloseWait.Wait()
return nil
@@ -659,6 +840,55 @@ func (r *reconnectingPTY) Close() {
_ = conn.Close()
}
_ = r.ptty.Close()
r.circularBufferMutex.Lock()
r.circularBuffer.Reset()
r.circularBufferMutex.Unlock()
r.timeout.Stop()
}
// Bicopy copies all of the data between the two connections and will close them
// after one or both of them are done writing. If the context is canceled, both
// of the connections will be closed.
func Bicopy(ctx context.Context, c1, c2 io.ReadWriteCloser) {
defer c1.Close()
defer c2.Close()
var wg sync.WaitGroup
copyFunc := func(dst io.WriteCloser, src io.Reader) {
defer wg.Done()
_, _ = io.Copy(dst, src)
}
wg.Add(2)
go copyFunc(c1, c2)
go copyFunc(c2, c1)
// Convert waitgroup to a channel so we can also wait on the context.
done := make(chan struct{})
go func() {
defer close(done)
wg.Wait()
}()
select {
case <-ctx.Done():
case <-done:
}
}
// ExpandRelativeHomePath expands the tilde at the beginning of a path to the
// current user's home directory and returns a full absolute path.
func ExpandRelativeHomePath(in string) (string, error) {
usr, err := user.Current()
if err != nil {
return "", xerrors.Errorf("get current user details: %w", err)
}
if in == "~" {
in = usr.HomeDir
} else if strings.HasPrefix(in, "~/") {
in = filepath.Join(usr.HomeDir, in[2:])
}
return filepath.Abs(in)
}
+423 -55
View File
@@ -1,23 +1,31 @@
package agent_test
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/netip"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
"golang.org/x/xerrors"
"tailscale.com/net/speedtest"
scp "github.com/bramvdbogaerde/go-scp"
"github.com/google/uuid"
"github.com/pion/webrtc/v3"
"github.com/pion/udp"
"github.com/pkg/sftp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"golang.org/x/crypto/ssh"
@@ -27,11 +35,11 @@ import (
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/agent"
"github.com/coder/coder/peer"
"github.com/coder/coder/peerbroker"
"github.com/coder/coder/peerbroker/proto"
"github.com/coder/coder/provisionersdk"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/tailnet"
"github.com/coder/coder/tailnet/tailnettest"
"github.com/coder/coder/testutil"
)
func TestMain(m *testing.M) {
@@ -40,9 +48,55 @@ func TestMain(m *testing.M) {
func TestAgent(t *testing.T) {
t.Parallel()
t.Run("Stats", func(t *testing.T) {
t.Parallel()
t.Run("SSH", func(t *testing.T) {
t.Parallel()
conn, stats := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
sshClient, err := conn.SSHClient()
require.NoError(t, err)
defer sshClient.Close()
session, err := sshClient.NewSession()
require.NoError(t, err)
defer session.Close()
assert.EqualValues(t, 1, (<-stats).NumConns)
assert.Greater(t, (<-stats).RxBytes, int64(0))
assert.Greater(t, (<-stats).TxBytes, int64(0))
})
t.Run("ReconnectingPTY", func(t *testing.T) {
t.Parallel()
conn, stats := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
ptyConn, err := conn.ReconnectingPTY(uuid.NewString(), 128, 128, "/bin/bash")
require.NoError(t, err)
defer ptyConn.Close()
data, err := json.Marshal(codersdk.ReconnectingPTYRequest{
Data: "echo test\r\n",
})
require.NoError(t, err)
_, err = ptyConn.Write(data)
require.NoError(t, err)
var s *codersdk.AgentStats
require.Eventuallyf(t, func() bool {
var ok bool
s, ok = (<-stats)
return ok && s.NumConns > 0 && s.RxBytes > 0 && s.TxBytes > 0
}, testutil.WaitLong, testutil.IntervalFast,
"never saw stats: %+v", s,
)
})
})
t.Run("SessionExec", func(t *testing.T) {
t.Parallel()
session := setupSSHSession(t, agent.Metadata{})
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
command := "echo test"
if runtime.GOOS == "windows" {
@@ -55,7 +109,7 @@ func TestAgent(t *testing.T) {
t.Run("GitSSH", func(t *testing.T) {
t.Parallel()
session := setupSSHSession(t, agent.Metadata{})
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
command := "sh -c 'echo $GIT_SSH_COMMAND'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %GIT_SSH_COMMAND%"
@@ -65,7 +119,7 @@ func TestAgent(t *testing.T) {
require.True(t, strings.HasSuffix(strings.TrimSpace(string(output)), "gitssh --"))
})
t.Run("SessionTTY", func(t *testing.T) {
t.Run("SessionTTYShell", func(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
// This might be our implementation, or ConPTY itself.
@@ -73,7 +127,7 @@ func TestAgent(t *testing.T) {
// it seems like it could be either.
t.Skip("ConPTY appears to be inconsistent on Windows.")
}
session := setupSSHSession(t, agent.Metadata{})
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
command := "bash"
if runtime.GOOS == "windows" {
command = "cmd.exe"
@@ -99,6 +153,29 @@ func TestAgent(t *testing.T) {
require.NoError(t, err)
})
t.Run("SessionTTYExitCode", func(t *testing.T) {
t.Parallel()
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
command := "areallynotrealcommand"
err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
require.NoError(t, err)
ptty := ptytest.New(t)
require.NoError(t, err)
session.Stdout = ptty.Output()
session.Stderr = ptty.Output()
session.Stdin = ptty.Input()
err = session.Start(command)
require.NoError(t, err)
err = session.Wait()
exitErr := &ssh.ExitError{}
require.True(t, xerrors.As(err, &exitErr))
if runtime.GOOS == "windows" {
assert.Equal(t, 1, exitErr.ExitStatus())
} else {
assert.Equal(t, 127, exitErr.ExitStatus())
}
})
t.Run("LocalForwarding", func(t *testing.T) {
t.Parallel()
random, err := net.Listen("tcp", "127.0.0.1:0")
@@ -116,10 +193,12 @@ func TestAgent(t *testing.T) {
localPort := tcpAddr.Port
done := make(chan struct{})
go func() {
defer close(done)
conn, err := local.Accept()
require.NoError(t, err)
if !assert.NoError(t, err) {
return
}
_ = conn.Close()
close(done)
}()
err = setupSSHCommand(t, []string{"-L", fmt.Sprintf("%d:127.0.0.1:%d", randomPort, localPort)}, []string{"echo", "test"}).Start()
@@ -133,8 +212,10 @@ func TestAgent(t *testing.T) {
t.Run("SFTP", func(t *testing.T) {
t.Parallel()
sshClient, err := setupAgent(t, agent.Metadata{}, 0).SSHClient()
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
sshClient, err := conn.SSHClient()
require.NoError(t, err)
defer sshClient.Close()
client, err := sftp.NewClient(sshClient)
require.NoError(t, err)
tempFile := filepath.Join(t.TempDir(), "sftp")
@@ -146,11 +227,28 @@ func TestAgent(t *testing.T) {
require.NoError(t, err)
})
t.Run("SCP", func(t *testing.T) {
t.Parallel()
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
sshClient, err := conn.SSHClient()
require.NoError(t, err)
defer sshClient.Close()
scpClient, err := scp.NewClientBySSH(sshClient)
require.NoError(t, err)
tempFile := filepath.Join(t.TempDir(), "scp")
content := "hello world"
err = scpClient.CopyFile(context.Background(), strings.NewReader(content), tempFile, "0755")
require.NoError(t, err)
_, err = os.Stat(tempFile)
require.NoError(t, err)
})
t.Run("EnvironmentVariables", func(t *testing.T) {
t.Parallel()
key := "EXAMPLE"
value := "value"
session := setupSSHSession(t, agent.Metadata{
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{
EnvironmentVariables: map[string]string{
key: value,
},
@@ -164,13 +262,79 @@ func TestAgent(t *testing.T) {
require.Equal(t, value, strings.TrimSpace(string(output)))
})
t.Run("EnvironmentVariableExpansion", func(t *testing.T) {
t.Parallel()
key := "EXAMPLE"
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{
EnvironmentVariables: map[string]string{
key: "$SOMETHINGNOTSET",
},
})
command := "sh -c 'echo $" + key + "'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %" + key + "%"
}
output, err := session.Output(command)
require.NoError(t, err)
expect := ""
if runtime.GOOS == "windows" {
expect = "%EXAMPLE%"
}
// Output should be empty, because the variable is not set!
require.Equal(t, expect, strings.TrimSpace(string(output)))
})
t.Run("Coder env vars", func(t *testing.T) {
t.Parallel()
for _, key := range []string{"CODER"} {
key := key
t.Run(key, func(t *testing.T) {
t.Parallel()
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
command := "sh -c 'echo $" + key + "'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %" + key + "%"
}
output, err := session.Output(command)
require.NoError(t, err)
require.NotEmpty(t, strings.TrimSpace(string(output)))
})
}
})
t.Run("SSH connection env vars", func(t *testing.T) {
t.Parallel()
// Note: the SSH_TTY environment variable should only be set for TTYs.
// For some reason this test produces a TTY locally and a non-TTY in CI
// so we don't test for the absence of SSH_TTY.
for _, key := range []string{"SSH_CONNECTION", "SSH_CLIENT"} {
key := key
t.Run(key, func(t *testing.T) {
t.Parallel()
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
command := "sh -c 'echo $" + key + "'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %" + key + "%"
}
output, err := session.Output(command)
require.NoError(t, err)
require.NotEmpty(t, strings.TrimSpace(string(output)))
})
}
})
t.Run("StartupScript", func(t *testing.T) {
t.Parallel()
tempPath := filepath.Join(os.TempDir(), "content.txt")
tempPath := filepath.Join(t.TempDir(), "content.txt")
content := "somethingnice"
setupAgent(t, agent.Metadata{
StartupScript: "echo " + content + " > " + tempPath,
setupAgent(t, codersdk.WorkspaceAgentMetadata{
StartupScript: fmt.Sprintf("echo %s > %s", content, tempPath),
}, 0)
var gotContent string
require.Eventually(t, func() bool {
content, err := os.ReadFile(tempPath)
@@ -183,11 +347,13 @@ func TestAgent(t *testing.T) {
if runtime.GOOS == "windows" {
// Windows uses UTF16! 🪟🪟🪟
content, _, err = transform.Bytes(unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder(), content)
require.NoError(t, err)
if !assert.NoError(t, err) {
return false
}
}
gotContent = string(content)
return true
}, 15*time.Second, 100*time.Millisecond)
}, testutil.WaitMedium, testutil.IntervalMedium)
require.Equal(t, content, strings.TrimSpace(gotContent))
})
@@ -199,61 +365,165 @@ func TestAgent(t *testing.T) {
// it seems like it could be either.
t.Skip("ConPTY appears to be inconsistent on Windows.")
}
conn := setupAgent(t, agent.Metadata{}, 0)
id := uuid.NewString()
netConn, err := conn.ReconnectingPTY(id, 100, 100)
require.NoError(t, err)
data, err := json.Marshal(agent.ReconnectingPTYRequest{
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
id := uuid.NewString()
netConn, err := conn.ReconnectingPTY(id, 100, 100, "/bin/bash")
require.NoError(t, err)
bufRead := bufio.NewReader(netConn)
// Brief pause to reduce the likelihood that we send keystrokes while
// the shell is simultaneously sending a prompt.
time.Sleep(100 * time.Millisecond)
data, err := json.Marshal(codersdk.ReconnectingPTYRequest{
Data: "echo test\r\n",
})
require.NoError(t, err)
_, err = netConn.Write(data)
require.NoError(t, err)
findEcho := func() {
expectLine := func(matcher func(string) bool) {
for {
read, err := netConn.Read(data)
line, err := bufRead.ReadString('\n')
require.NoError(t, err)
if strings.Contains(string(data[:read]), "test") {
if matcher(line) {
break
}
}
}
matchEchoCommand := func(line string) bool {
return strings.Contains(line, "echo test")
}
matchEchoOutput := func(line string) bool {
return strings.Contains(line, "test") && !strings.Contains(line, "echo")
}
// Once for typing the command...
findEcho()
expectLine(matchEchoCommand)
// And another time for the actual output.
findEcho()
expectLine(matchEchoOutput)
_ = netConn.Close()
netConn, err = conn.ReconnectingPTY(id, 100, 100)
netConn, err = conn.ReconnectingPTY(id, 100, 100, "/bin/bash")
require.NoError(t, err)
bufRead = bufio.NewReader(netConn)
// Same output again!
findEcho()
findEcho()
expectLine(matchEchoCommand)
expectLine(matchEchoOutput)
})
t.Run("Dial", func(t *testing.T) {
t.Parallel()
cases := []struct {
name string
setup func(t *testing.T) net.Listener
}{
{
name: "TCP",
setup: func(t *testing.T) net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err, "create TCP listener")
return l
},
},
{
name: "UDP",
setup: func(t *testing.T) net.Listener {
addr := net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
}
l, err := udp.Listen("udp", &addr)
require.NoError(t, err, "create UDP listener")
return l
},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
// Setup listener
l := c.setup(t)
defer l.Close()
go func() {
for {
c, err := l.Accept()
if err != nil {
return
}
go testAccept(t, c)
}
}()
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
require.Eventually(t, func() bool {
_, err := conn.Ping()
return err == nil
}, testutil.WaitMedium, testutil.IntervalFast)
conn1, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String())
require.NoError(t, err)
defer conn1.Close()
conn2, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String())
require.NoError(t, err)
defer conn2.Close()
testDial(t, conn2)
testDial(t, conn1)
time.Sleep(150 * time.Millisecond)
})
}
})
t.Run("Speedtest", func(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("The minimum duration for a speedtest is hardcoded in Tailscale to 5s!")
}
derpMap := tailnettest.RunDERPAndSTUN(t)
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{
DERPMap: derpMap,
}, 0)
defer conn.Close()
res, err := conn.Speedtest(speedtest.Upload, 250*time.Millisecond)
require.NoError(t, err)
t.Logf("%.2f MBits/s", res[len(res)-1].MBitsPerSecond())
})
}
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd {
agentConn := setupAgent(t, agent.Metadata{}, 0)
agentConn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
waitGroup := sync.WaitGroup{}
go func() {
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
return
}
ssh, err := agentConn.SSH()
require.NoError(t, err)
go io.Copy(conn, ssh)
go io.Copy(ssh, conn)
if err != nil {
_ = conn.Close()
return
}
waitGroup.Add(1)
go func() {
agent.Bicopy(context.Background(), conn, ssh)
waitGroup.Done()
}()
}
}()
t.Cleanup(func() {
_ = listener.Close()
waitGroup.Wait()
})
tcpAddr, valid := listener.Addr().(*net.TCPAddr)
require.True(t, valid)
@@ -265,41 +535,139 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe
return exec.Command("ssh", args...)
}
func setupSSHSession(t *testing.T, options agent.Metadata) *ssh.Session {
sshClient, err := setupAgent(t, options, 0).SSHClient()
func setupSSHSession(t *testing.T, options codersdk.WorkspaceAgentMetadata) *ssh.Session {
conn, _ := setupAgent(t, options, 0)
sshClient, err := conn.SSHClient()
require.NoError(t, err)
t.Cleanup(func() {
_ = sshClient.Close()
})
session, err := sshClient.NewSession()
require.NoError(t, err)
return session
}
func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) *agent.Conn {
client, server := provisionersdk.TransportPipe()
closer := agent.New(func(ctx context.Context, logger slog.Logger) (agent.Metadata, *peerbroker.Listener, error) {
listener, err := peerbroker.Listen(server, nil)
return metadata, listener, err
}, &agent.Options{
type closeFunc func() error
func (c closeFunc) Close() error {
return c()
}
func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeout time.Duration) (
*codersdk.AgentConn,
<-chan *codersdk.AgentStats,
) {
if metadata.DERPMap == nil {
metadata.DERPMap = tailnettest.RunDERPAndSTUN(t)
}
coordinator := tailnet.NewCoordinator()
agentID := uuid.New()
statsCh := make(chan *codersdk.AgentStats)
closer := agent.New(agent.Options{
FetchMetadata: func(ctx context.Context) (codersdk.WorkspaceAgentMetadata, error) {
return metadata, nil
},
CoordinatorDialer: func(ctx context.Context) (net.Conn, error) {
clientConn, serverConn := net.Pipe()
closed := make(chan struct{})
t.Cleanup(func() {
_ = serverConn.Close()
_ = clientConn.Close()
<-closed
})
go func() {
_ = coordinator.ServeAgent(serverConn, agentID)
close(closed)
}()
return clientConn, nil
},
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
ReconnectingPTYTimeout: ptyTimeout,
StatsReporter: func(ctx context.Context, log slog.Logger, statsFn func() *codersdk.AgentStats) (io.Closer, error) {
doneCh := make(chan struct{})
ctx, cancel := context.WithCancel(ctx)
go func() {
defer close(doneCh)
t := time.NewTicker(time.Millisecond * 100)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
select {
case statsCh <- statsFn():
case <-ctx.Done():
return
default:
// We don't want to send old stats.
continue
}
}
}()
return closeFunc(func() error {
cancel()
<-doneCh
close(statsCh)
return nil
}), nil
},
})
t.Cleanup(func() {
_ = client.Close()
_ = server.Close()
_ = closer.Close()
})
api := proto.NewDRPCPeerBrokerClient(provisionersdk.Conn(client))
stream, err := api.NegotiateConnection(context.Background())
require.NoError(t, err)
conn, err := peerbroker.Dial(stream, []webrtc.ICEServer{}, &peer.ConnOptions{
Logger: slogtest.Make(t, nil),
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
DERPMap: metadata.DERPMap,
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
})
require.NoError(t, err)
clientConn, serverConn := net.Pipe()
t.Cleanup(func() {
_ = clientConn.Close()
_ = serverConn.Close()
_ = conn.Close()
})
return &agent.Conn{
Negotiator: api,
Conn: conn,
}
go coordinator.ServeClient(serverConn, uuid.New(), agentID)
sendNode, _ := tailnet.ServeCoordinator(clientConn, func(node []*tailnet.Node) error {
return conn.UpdateNodes(node)
})
conn.SetNodeCallback(sendNode)
return &codersdk.AgentConn{
Conn: conn,
}, statsCh
}
var dialTestPayload = []byte("dean-was-here123")
func testDial(t *testing.T, c net.Conn) {
t.Helper()
assertWritePayload(t, c, dialTestPayload)
assertReadPayload(t, c, dialTestPayload)
}
func testAccept(t *testing.T, c net.Conn) {
t.Helper()
defer c.Close()
assertReadPayload(t, c, dialTestPayload)
assertWritePayload(t, c, dialTestPayload)
}
func assertReadPayload(t *testing.T, r io.Reader, payload []byte) {
b := make([]byte, len(payload)+16)
n, err := r.Read(b)
assert.NoError(t, err, "read payload")
assert.Equal(t, len(payload), n, "read payload length does not match")
assert.Equal(t, payload, b[:n])
}
func assertWritePayload(t *testing.T, w io.Writer, payload []byte) {
n, err := w.Write(payload)
assert.NoError(t, err, "write payload")
assert.Equal(t, len(payload), n, "payload length does not match")
}
+184
View File
@@ -0,0 +1,184 @@
package agent
import (
"context"
"net/http"
"sync"
"time"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/codersdk"
"github.com/coder/retry"
)
// WorkspaceAgentApps fetches the workspace apps.
type WorkspaceAgentApps func(context.Context) ([]codersdk.WorkspaceApp, error)
// PostWorkspaceAgentAppHealth updates the workspace app health.
type PostWorkspaceAgentAppHealth func(context.Context, codersdk.PostWorkspaceAppHealthsRequest) error
// WorkspaceAppHealthReporter is a function that checks and reports the health of the workspace apps until the passed context is canceled.
type WorkspaceAppHealthReporter func(ctx context.Context)
// NewWorkspaceAppHealthReporter creates a WorkspaceAppHealthReporter that reports app health to coderd.
func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps WorkspaceAgentApps, postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth) WorkspaceAppHealthReporter {
runHealthcheckLoop := func(ctx context.Context) error {
apps, err := workspaceAgentApps(ctx)
if err != nil {
if xerrors.Is(err, context.Canceled) {
return nil
}
return xerrors.Errorf("getting workspace apps: %w", err)
}
// no need to run this loop if no apps for this workspace.
if len(apps) == 0 {
return nil
}
hasHealthchecksEnabled := false
health := make(map[string]codersdk.WorkspaceAppHealth, 0)
for _, app := range apps {
health[app.Name] = app.Health
if !hasHealthchecksEnabled && app.Health != codersdk.WorkspaceAppHealthDisabled {
hasHealthchecksEnabled = true
}
}
// no need to run this loop if no health checks are configured.
if !hasHealthchecksEnabled {
return nil
}
// run a ticker for each app health check.
var mu sync.RWMutex
failures := make(map[string]int, 0)
for _, nextApp := range apps {
if !shouldStartTicker(nextApp) {
continue
}
app := nextApp
t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second)
go func() {
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)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
return err
}
// successful healthcheck is a non-5XX status code
res.Body.Close()
if res.StatusCode >= http.StatusInternalServerError {
return xerrors.Errorf("error status code: %d", res.StatusCode)
}
return nil
}()
if err != nil {
mu.Lock()
if failures[app.Name] < int(app.Healthcheck.Threshold) {
// increment the failure count and keep status the same.
// we will change it when we hit the threshold.
failures[app.Name]++
} else {
// set to unhealthy if we hit the failure threshold.
// we stop incrementing at the threshold to prevent the failure value from increasing forever.
health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy
}
mu.Unlock()
} else {
mu.Lock()
// we only need one successful health check to be considered healthy.
health[app.Name] = codersdk.WorkspaceAppHealthHealthy
failures[app.Name] = 0
mu.Unlock()
}
t.Reset(time.Duration(app.Healthcheck.Interval) * time.Second)
}
}()
}
mu.Lock()
lastHealth := copyHealth(health)
mu.Unlock()
reportTicker := time.NewTicker(time.Second)
// 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():
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, codersdk.PostWorkspaceAppHealthsRequest{
Healths: lastHealth,
})
if err != nil {
logger.Error(ctx, "failed to report workspace app stat", slog.Error(err))
}
}
}
}
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
}
logger.Error(ctx, "failed running workspace app reporter", slog.Error(err))
}
}
}
func shouldStartTicker(app codersdk.WorkspaceApp) bool {
return app.Healthcheck.URL != "" && app.Healthcheck.Interval > 0 && app.Healthcheck.Threshold > 0
}
func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]codersdk.WorkspaceAppHealth) bool {
for name, newValue := range new {
oldValue, found := old[name]
if !found {
return true
}
if newValue != oldValue {
return true
}
}
return false
}
func copyHealth(h1 map[string]codersdk.WorkspaceAppHealth) map[string]codersdk.WorkspaceAppHealth {
h2 := make(map[string]codersdk.WorkspaceAppHealth, 0)
for k, v := range h1 {
h2[k] = v
}
return h2
}
+209
View File
@@ -0,0 +1,209 @@
package agent_test
import (
"context"
"net/http"
"net/http/httptest"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/agent"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/testutil"
)
func TestAppHealth(t *testing.T) {
t.Parallel()
t.Run("Healthy", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
Name: "app1",
Healthcheck: codersdk.Healthcheck{},
Health: codersdk.WorkspaceAppHealthDisabled,
},
{
Name: "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{
nil,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(r.Context(), w, http.StatusOK, nil)
}),
}
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
defer closeFn()
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
}
return apps[1].Health == codersdk.WorkspaceAppHealthHealthy
}, testutil.WaitLong, testutil.IntervalSlow)
})
t.Run("500", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
Name: "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) {
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)
})
t.Run("Timeout", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
Name: "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)
})
t.Run("NotSpamming", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
apps := []codersdk.WorkspaceApp{
{
Name: "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,
},
}
var counter = new(int32)
handlers := []http.Handler{
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(counter, 1)
}),
}
_, closeFn := setupAppReporter(ctx, t, apps, handlers)
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, *counter, int32(2))
})
}
func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.WorkspaceApp, handlers []http.Handler) (agent.WorkspaceAgentApps, func()) {
closers := []func(){}
for i, handler := range handlers {
if handler == nil {
continue
}
ts := httptest.NewServer(handler)
app := apps[i]
app.Healthcheck.URL = ts.URL
apps[i] = app
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
}
postWorkspaceAgentAppHealth := func(_ context.Context, req codersdk.PostWorkspaceAppHealthsRequest) error {
mu.Lock()
for name, health := range req.Healths {
for i, app := range apps {
if app.Name != name {
continue
}
app.Health = health
apps[i] = app
}
}
mu.Unlock()
return nil
}
go agent.NewWorkspaceAppHealthReporter(slogtest.Make(t, nil).Leveled(slog.LevelDebug), workspaceAgentApps, postWorkspaceAgentAppHealth)(ctx)
return workspaceAgentApps, func() {
for _, closeFn := range closers {
closeFn()
}
}
}
-77
View File
@@ -1,77 +0,0 @@
package agent
import (
"context"
"fmt"
"net"
"golang.org/x/crypto/ssh"
"golang.org/x/xerrors"
"github.com/coder/coder/peer"
"github.com/coder/coder/peerbroker/proto"
)
// ReconnectingPTYRequest is sent from the client to the server
// to pipe data to a PTY.
type ReconnectingPTYRequest struct {
Data string `json:"data"`
Height uint16 `json:"height"`
Width uint16 `json:"width"`
}
// Conn wraps a peer connection with helper functions to
// communicate with the agent.
type Conn struct {
// Negotiator is responsible for exchanging messages.
Negotiator proto.DRPCPeerBrokerClient
*peer.Conn
}
// ReconnectingPTY returns a connection serving a TTY that can
// be reconnected to via ID.
func (c *Conn) ReconnectingPTY(id string, height, width uint16) (net.Conn, error) {
channel, err := c.Dial(context.Background(), fmt.Sprintf("%s:%d:%d", id, height, width), &peer.ChannelOptions{
Protocol: "reconnecting-pty",
})
if err != nil {
return nil, xerrors.Errorf("pty: %w", err)
}
return channel.NetConn(), nil
}
// SSH dials the built-in SSH server.
func (c *Conn) SSH() (net.Conn, error) {
channel, err := c.Dial(context.Background(), "ssh", &peer.ChannelOptions{
Protocol: "ssh",
})
if err != nil {
return nil, xerrors.Errorf("dial: %w", err)
}
return channel.NetConn(), nil
}
// SSHClient calls SSH to create a client that uses a weak cipher
// for high throughput.
func (c *Conn) SSHClient() (*ssh.Client, error) {
netConn, err := c.SSH()
if err != nil {
return nil, xerrors.Errorf("ssh: %w", err)
}
sshConn, channels, requests, err := ssh.NewClientConn(netConn, "localhost:22", &ssh.ClientConfig{
// SSH host validation isn't helpful, because obtaining a peer
// connection already signifies user-intent to dial a workspace.
// #nosec
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
return nil, xerrors.Errorf("ssh conn: %w", err)
}
return ssh.NewClient(sshConn, channels, requests), nil
}
func (c *Conn) Close() error {
_ = c.Negotiator.DRPCConn().Close()
return c.Conn.Close()
}
+4
View File
@@ -0,0 +1,4 @@
// Package reaper contains logic for reaping subprocesses. It is
// specifically used in the agent to avoid the accumulation of
// zombie processes.
package reaper
+28
View File
@@ -0,0 +1,28 @@
package reaper
import "github.com/hashicorp/go-reap"
type Option func(o *options)
// WithExecArgs specifies the exec arguments for the fork exec call.
// By default the same arguments as the parent are used as dictated by
// os.Args. Since ForkReap calls a fork-exec it is the responsibility of
// the caller to avoid fork-bombing oneself.
func WithExecArgs(args ...string) Option {
return func(o *options) {
o.ExecArgs = args
}
}
// WithPIDCallback sets the channel that reaped child process PIDs are pushed
// onto.
func WithPIDCallback(ch reap.PidCh) Option {
return func(o *options) {
o.PIDs = ch
}
}
type options struct {
ExecArgs []string
PIDs reap.PidCh
}
+12
View File
@@ -0,0 +1,12 @@
//go:build !linux
package reaper
// IsInitProcess returns true if the current process's PID is 1.
func IsInitProcess() bool {
return false
}
func ForkReap(_ ...Option) error {
return nil
}
+66
View File
@@ -0,0 +1,66 @@
//go:build linux
package reaper_test
import (
"os"
"os/exec"
"testing"
"time"
"github.com/hashicorp/go-reap"
"github.com/stretchr/testify/require"
"github.com/coder/coder/agent/reaper"
"github.com/coder/coder/testutil"
)
func TestReap(t *testing.T) {
t.Parallel()
// Don't run the reaper test in CI. It does weird
// things like forkexecing which may have unintended
// consequences in CI.
if _, ok := os.LookupEnv("CI"); ok {
t.Skip("Detected CI, skipping reaper tests")
}
// OK checks that's the reaper is successfully reaping
// exited processes and passing the PIDs through the shared
// channel.
t.Run("OK", func(t *testing.T) {
t.Parallel()
pids := make(reap.PidCh, 1)
err := reaper.ForkReap(
reaper.WithPIDCallback(pids),
// Provide some argument that immediately exits.
reaper.WithExecArgs("/bin/sh", "-c", "exit 0"),
)
require.NoError(t, err)
cmd := exec.Command("tail", "-f", "/dev/null")
err = cmd.Start()
require.NoError(t, err)
cmd2 := exec.Command("tail", "-f", "/dev/null")
err = cmd2.Start()
require.NoError(t, err)
err = cmd.Process.Kill()
require.NoError(t, err)
err = cmd2.Process.Kill()
require.NoError(t, err)
expectedPIDs := []int{cmd.Process.Pid, cmd2.Process.Pid}
for i := 0; i < len(expectedPIDs); i++ {
select {
case <-time.After(testutil.WaitShort):
t.Fatalf("Timed out waiting for process")
case pid := <-pids:
require.Contains(t, expectedPIDs, pid)
}
}
})
}
+63
View File
@@ -0,0 +1,63 @@
//go:build linux
package reaper
import (
"os"
"syscall"
"github.com/hashicorp/go-reap"
"golang.org/x/xerrors"
)
// IsInitProcess returns true if the current process's PID is 1.
func IsInitProcess() bool {
return os.Getpid() == 1
}
// ForkReap spawns a goroutine that reaps children. In order to avoid
// complications with spawning `exec.Commands` in the same process that
// is reaping, we forkexec a child process. This prevents a race between
// the reaper and an exec.Command waiting for its process to complete.
// The provided 'pids' channel may be nil if the caller does not care about the
// reaped children PIDs.
func ForkReap(opt ...Option) error {
opts := &options{
ExecArgs: os.Args,
}
for _, o := range opt {
o(opts)
}
go reap.ReapChildren(opts.PIDs, nil, nil, nil)
pwd, err := os.Getwd()
if err != nil {
return xerrors.Errorf("get wd: %w", err)
}
pattrs := &syscall.ProcAttr{
Dir: pwd,
Env: os.Environ(),
Sys: &syscall.SysProcAttr{
Setsid: true,
},
Files: []uintptr{
uintptr(syscall.Stdin),
uintptr(syscall.Stdout),
uintptr(syscall.Stderr),
},
}
//#nosec G204
pid, _ := syscall.ForkExec(opts.ExecArgs[0], opts.ExecArgs, pattrs)
var wstatus syscall.WaitStatus
_, err = syscall.Wait4(pid, &wstatus, 0, nil)
for xerrors.Is(err, syscall.EINTR) {
_, err = syscall.Wait4(pid, &wstatus, 0, nil)
}
return nil
}
+68
View File
@@ -0,0 +1,68 @@
package agent
import (
"context"
"io"
"net"
"sync/atomic"
"cdr.dev/slog"
"github.com/coder/coder/codersdk"
)
// statsConn wraps a net.Conn with statistics.
type statsConn struct {
*Stats
net.Conn `json:"-"`
}
var _ net.Conn = new(statsConn)
func (c *statsConn) Read(b []byte) (n int, err error) {
n, err = c.Conn.Read(b)
atomic.AddInt64(&c.RxBytes, int64(n))
return n, err
}
func (c *statsConn) Write(b []byte) (n int, err error) {
n, err = c.Conn.Write(b)
atomic.AddInt64(&c.TxBytes, int64(n))
return n, err
}
var _ net.Conn = new(statsConn)
// Stats records the Agent's network connection statistics for use in
// user-facing metrics and debugging.
// Each member value must be written and read with atomic.
type Stats struct {
NumConns int64 `json:"num_comms"`
RxBytes int64 `json:"rx_bytes"`
TxBytes int64 `json:"tx_bytes"`
}
func (s *Stats) Copy() *codersdk.AgentStats {
return &codersdk.AgentStats{
NumConns: atomic.LoadInt64(&s.NumConns),
RxBytes: atomic.LoadInt64(&s.RxBytes),
TxBytes: atomic.LoadInt64(&s.TxBytes),
}
}
// wrapConn returns a new connection that records statistics.
func (s *Stats) wrapConn(conn net.Conn) net.Conn {
atomic.AddInt64(&s.NumConns, 1)
cs := &statsConn{
Stats: s,
Conn: conn,
}
return cs
}
// StatsReporter periodically accept and records agent stats.
type StatsReporter func(
ctx context.Context,
log slog.Logger,
stats func() *codersdk.AgentStats,
) (io.Closer, error)
+1 -1
View File
@@ -3,6 +3,6 @@ package usershell
import "os"
// Get returns the $SHELL environment variable.
func Get(username string) (string, error) {
func Get(_ string) (string, error) {
return os.Getenv("SHELL"), nil
}
+21 -1
View File
@@ -3,6 +3,7 @@ package buildinfo
import (
"fmt"
"runtime/debug"
"strings"
"sync"
"time"
@@ -24,6 +25,11 @@ var (
tag string
)
const (
// develPrefix is prefixed to developer versions of the application.
develPrefix = "v0.0.0-devel"
)
// Version returns the semantic version of the build.
// Use golang.org/x/mod/semver to compare versions.
func Version() string {
@@ -35,7 +41,7 @@ func Version() string {
if tag == "" {
// This occurs when the tag hasn't been injected,
// like when using "go run".
version = "v0.0.0-devel" + revision
version = develPrefix + revision
return
}
version = "v" + tag
@@ -48,6 +54,20 @@ func Version() string {
return version
}
// VersionsMatch compares the two versions. It assumes the versions match if
// the major and the minor versions are equivalent. Patch versions are
// disregarded. If it detects that either version is a developer build it
// returns true.
func VersionsMatch(v1, v2 string) bool {
// Developer versions are disregarded...hopefully they know what they are
// doing.
if strings.HasPrefix(v1, develPrefix) || strings.HasPrefix(v2, develPrefix) {
return true
}
return semver.MajorMinor(v1) == semver.MajorMinor(v2)
}
// ExternalURL returns a URL referencing the current Coder version.
// For production builds, this will link directly to a release.
// For development builds, this will link to a commit.
+67
View File
@@ -1,6 +1,7 @@
package buildinfo_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
@@ -29,4 +30,70 @@ func TestBuildInfo(t *testing.T) {
_, valid := buildinfo.Time()
require.False(t, valid)
})
t.Run("VersionsMatch", func(t *testing.T) {
t.Parallel()
type testcase struct {
name string
v1 string
v2 string
expectMatch bool
}
cases := []testcase{
{
name: "OK",
v1: "v1.2.3",
v2: "v1.2.3",
expectMatch: true,
},
// Test that we return true if a developer version is detected.
// Developers do not need to be warned of mismatched versions.
{
name: "DevelIgnored",
v1: "v0.0.0-devel+123abac",
v2: "v1.2.3",
expectMatch: true,
},
// Our CI instance uses a "-devel" prerelease
// flag. This is not the same as a developer WIP build.
{
name: "DevelPreleaseNotIgnored",
v1: "v1.1.1-devel+123abac",
v2: "v1.2.3",
expectMatch: false,
},
{
name: "MajorMismatch",
v1: "v1.2.3",
v2: "v0.1.2",
expectMatch: false,
},
{
name: "MinorMismatch",
v1: "v1.2.3",
v2: "v1.3.2",
expectMatch: false,
},
// Different patches are ok, breaking changes are not allowed
// in patches.
{
name: "PatchMismatch",
v1: "v1.2.3+hash.whocares",
v2: "v1.2.4+somestuff.hm.ok",
expectMatch: true,
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, c.expectMatch, buildinfo.VersionsMatch(c.v1, c.v2),
fmt.Sprintf("expected match=%v for version %s and %s", c.expectMatch, c.v1, c.v2),
)
})
}
})
}
+78 -6
View File
@@ -2,30 +2,36 @@ package cli
import (
"context"
"fmt"
"net/http"
_ "net/http/pprof" //nolint: gosec
"net/url"
"os"
"path/filepath"
"runtime"
"time"
"cloud.google.com/go/compute/metadata"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"gopkg.in/natefinch/lumberjack.v2"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/agent"
"github.com/coder/coder/agent/reaper"
"github.com/coder/coder/buildinfo"
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/codersdk"
"github.com/coder/retry"
"gopkg.in/natefinch/lumberjack.v2"
)
func workspaceAgent() *cobra.Command {
var (
auth string
auth string
pprofEnabled bool
pprofAddress string
noReap bool
)
cmd := &cobra.Command{
Use: "agent",
@@ -47,8 +53,44 @@ func workspaceAgent() *cobra.Command {
}
defer logWriter.Close()
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()), sloghuman.Sink(logWriter)).Leveled(slog.LevelDebug)
isLinux := runtime.GOOS == "linux"
// Spawn a reaper so that we don't accumulate a ton
// of zombie processes.
if reaper.IsInitProcess() && !noReap && isLinux {
logger.Info(cmd.Context(), "spawning reaper process")
// Do not start a reaper on the child process. It's important
// to do this else we fork bomb ourselves.
args := append(os.Args, "--no-reap")
err := reaper.ForkReap(reaper.WithExecArgs(args...))
if err != nil {
logger.Error(cmd.Context(), "failed to reap", slog.Error(err))
return xerrors.Errorf("fork reap: %w", err)
}
logger.Info(cmd.Context(), "reaper process exiting")
return nil
}
version := buildinfo.Version()
logger.Info(cmd.Context(), "starting agent",
slog.F("url", coderURL),
slog.F("auth", auth),
slog.F("version", version),
)
client := codersdk.New(coderURL)
if pprofEnabled {
srvClose := serveHandler(cmd.Context(), logger, nil, pprofAddress, "pprof")
defer srvClose()
} else {
// If pprof wasn't enabled at startup, allow a
// `kill -USR1 $agent_pid` to start it (on Unix).
srvClose := agentStartPPROFOnUSR1(cmd.Context(), logger, pprofAddress)
defer srvClose()
}
// exchangeToken returns a session token.
// This is abstracted to allow for the same looping condition
// regardless of instance identity auth type.
@@ -102,6 +144,7 @@ func workspaceAgent() *cobra.Command {
}
if exchangeToken != nil {
logger.Info(cmd.Context(), "exchanging identity token")
// Agent's can start before resources are returned from the provisioner
// daemon. If there are many resources being provisioned, this time
// could be significant. This is arbitrarily set at an hour to prevent
@@ -125,13 +168,39 @@ func workspaceAgent() *cobra.Command {
}
}
closer := agent.New(client.ListenWorkspaceAgent, &agent.Options{
Logger: logger,
ctx, cancelFunc := context.WithTimeout(cmd.Context(), time.Hour)
defer cancelFunc()
for retry.New(100*time.Millisecond, 5*time.Second).Wait(ctx) {
err := client.PostWorkspaceAgentVersion(cmd.Context(), version)
if err != nil {
logger.Warn(cmd.Context(), "post agent version: %w", slog.Error(err), slog.F("version", version))
continue
}
logger.Info(ctx, "updated agent version", slog.F("version", version))
break
}
executablePath, err := os.Executable()
if err != nil {
return xerrors.Errorf("getting os executable: %w", err)
}
err = os.Setenv("PATH", fmt.Sprintf("%s%c%s", os.Getenv("PATH"), filepath.ListSeparator, filepath.Dir(executablePath)))
if err != nil {
return xerrors.Errorf("add executable to $PATH: %w", err)
}
closer := agent.New(agent.Options{
FetchMetadata: client.WorkspaceAgentMetadata,
Logger: logger,
EnvironmentVariables: map[string]string{
// Override the "CODER_AGENT_TOKEN" variable in all
// shells so "gitssh" works!
"CODER_AGENT_TOKEN": client.SessionToken,
},
CoordinatorDialer: client.ListenWorkspaceAgentTailnet,
StatsReporter: client.AgentReportStats,
WorkspaceAgentApps: client.WorkspaceAgentApps,
PostWorkspaceAgentAppHealth: client.PostWorkspaceAgentAppHealth,
})
<-cmd.Context().Done()
return closer.Close()
@@ -139,5 +208,8 @@ func workspaceAgent() *cobra.Command {
}
cliflag.StringVarP(cmd.Flags(), &auth, "auth", "", "CODER_AGENT_AUTH", "token", "Specify the authentication type to use for the agent")
cliflag.BoolVarP(cmd.Flags(), &pprofEnabled, "pprof-enable", "", "CODER_AGENT_PPROF_ENABLE", false, "Enable serving pprof metrics on the address defined by --pprof-address.")
cliflag.BoolVarP(cmd.Flags(), &noReap, "no-reap", "", "", false, "Do not start a process reaper.")
cliflag.StringVarP(cmd.Flags(), &pprofAddress, "pprof-address", "", "CODER_AGENT_PPROF_ADDRESS", "127.0.0.1:6060", "The address to serve pprof.")
return cmd
}
+55 -33
View File
@@ -4,12 +4,16 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/testutil"
)
func TestWorkspaceAgent(t *testing.T) {
@@ -19,10 +23,10 @@ func TestWorkspaceAgent(t *testing.T) {
instanceID := "instanceidentifier"
certificates, metadataClient := coderdtest.NewAzureInstanceIdentity(t, instanceID)
client := coderdtest.New(t, &coderdtest.Options{
AzureCertificates: certificates,
AzureCertificates: certificates,
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
@@ -49,23 +53,29 @@ func TestWorkspaceAgent(t *testing.T) {
cmd, _ := clitest.New(t, "agent", "--auth", "azure-instance-identity", "--agent-url", client.URL.String())
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
errC := make(chan error)
go func() {
// A linting error occurs for weakly typing the context value here,
// but it seems reasonable for a one-off test.
// nolint
ctx = context.WithValue(ctx, "azure-client", metadataClient)
err := cmd.ExecuteContext(ctx)
require.NoError(t, err)
// A linting error occurs for weakly typing the context value here.
//nolint // The above seems reasonable for a one-off test.
ctx := context.WithValue(ctx, "azure-client", metadataClient)
errC <- cmd.ExecuteContext(ctx)
}()
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
require.NoError(t, err)
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
assert.NotEmpty(t, resources[0].Agents[0].Version)
}
dialer, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, resources[0].Agents[0].ID)
require.NoError(t, err)
defer dialer.Close()
_, err = dialer.Ping()
require.NoError(t, err)
require.Eventually(t, func() bool {
_, err := dialer.Ping()
return err == nil
}, testutil.WaitMedium, testutil.IntervalFast)
cancelFunc()
err = <-errC
require.NoError(t, err)
})
t.Run("AWS", func(t *testing.T) {
@@ -73,10 +83,10 @@ func TestWorkspaceAgent(t *testing.T) {
instanceID := "instanceidentifier"
certificates, metadataClient := coderdtest.NewAWSInstanceIdentity(t, instanceID)
client := coderdtest.New(t, &coderdtest.Options{
AWSCertificates: certificates,
AWSCertificates: certificates,
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
@@ -103,23 +113,29 @@ func TestWorkspaceAgent(t *testing.T) {
cmd, _ := clitest.New(t, "agent", "--auth", "aws-instance-identity", "--agent-url", client.URL.String())
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
errC := make(chan error)
go func() {
// A linting error occurs for weakly typing the context value here,
// but it seems reasonable for a one-off test.
// nolint
ctx = context.WithValue(ctx, "aws-client", metadataClient)
err := cmd.ExecuteContext(ctx)
require.NoError(t, err)
// A linting error occurs for weakly typing the context value here.
//nolint // The above seems reasonable for a one-off test.
ctx := context.WithValue(ctx, "aws-client", metadataClient)
errC <- cmd.ExecuteContext(ctx)
}()
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
require.NoError(t, err)
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
assert.NotEmpty(t, resources[0].Agents[0].Version)
}
dialer, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, resources[0].Agents[0].ID)
require.NoError(t, err)
defer dialer.Close()
_, err = dialer.Ping()
require.NoError(t, err)
require.Eventually(t, func() bool {
_, err := dialer.Ping()
return err == nil
}, testutil.WaitMedium, testutil.IntervalFast)
cancelFunc()
err = <-errC
require.NoError(t, err)
})
t.Run("GoogleCloud", func(t *testing.T) {
@@ -127,10 +143,10 @@ func TestWorkspaceAgent(t *testing.T) {
instanceID := "instanceidentifier"
validator, metadata := coderdtest.NewGoogleInstanceIdentity(t, instanceID, false)
client := coderdtest.New(t, &coderdtest.Options{
GoogleTokenValidator: validator,
GoogleTokenValidator: validator,
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
@@ -157,22 +173,28 @@ func TestWorkspaceAgent(t *testing.T) {
cmd, _ := clitest.New(t, "agent", "--auth", "google-instance-identity", "--agent-url", client.URL.String())
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
errC := make(chan error)
go func() {
// A linting error occurs for weakly typing the context value here,
// but it seems reasonable for a one-off test.
// nolint
ctx = context.WithValue(ctx, "gcp-client", metadata)
err := cmd.ExecuteContext(ctx)
require.NoError(t, err)
// A linting error occurs for weakly typing the context value here.
//nolint // The above seems reasonable for a one-off test.
ctx := context.WithValue(ctx, "gcp-client", metadata)
errC <- cmd.ExecuteContext(ctx)
}()
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
require.NoError(t, err)
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
assert.NotEmpty(t, resources[0].Agents[0].Version)
}
dialer, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, resources[0].Agents[0].ID)
require.NoError(t, err)
defer dialer.Close()
_, err = dialer.Ping()
require.NoError(t, err)
require.Eventually(t, func() bool {
_, err := dialer.Ping()
return err == nil
}, testutil.WaitMedium, testutil.IntervalFast)
cancelFunc()
err = <-errC
require.NoError(t, err)
})
}
+38
View File
@@ -0,0 +1,38 @@
//go:build !windows
package cli
import (
"context"
"os"
"os/signal"
"syscall"
"cdr.dev/slog"
)
func agentStartPPROFOnUSR1(ctx context.Context, logger slog.Logger, pprofAddress string) (srvClose func()) {
ctx, cancel := context.WithCancel(ctx)
usr1 := make(chan os.Signal, 1)
signal.Notify(usr1, syscall.SIGUSR1)
go func() {
defer close(usr1)
defer signal.Stop(usr1)
select {
case <-usr1:
signal.Stop(usr1)
srvClose := serveHandler(ctx, logger, nil, pprofAddress, "pprof")
defer srvClose()
case <-ctx.Done():
return
}
<-ctx.Done() // Prevent defer close until done.
}()
return func() {
cancel()
<-usr1 // Wait until usr1 is closed, ensures srvClose was run.
}
}
+12
View File
@@ -0,0 +1,12 @@
package cli
import (
"context"
"cdr.dev/slog"
)
// agentStartPPROFOnUSR1 is no-op on Windows (no SIGUSR1 signal).
func agentStartPPROFOnUSR1(ctx context.Context, logger slog.Logger, pprofAddress string) (srvClose func()) {
return func() {}
}
-167
View File
@@ -1,167 +0,0 @@
package cli
import (
"fmt"
"os"
"time"
"github.com/spf13/cobra"
"github.com/coder/coder/coderd/autobuild/schedule"
"github.com/coder/coder/codersdk"
)
const autostartDescriptionLong = `To have your workspace build automatically at a regular time you can enable autostart.
When enabling autostart, provide the minute, hour, and day(s) of week.
The default schedule is at 09:00 in your local timezone (TZ env, UTC by default).
`
func autostart() *cobra.Command {
autostartCmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "autostart enable <workspace>",
Short: "schedule a workspace to automatically start at a regular time",
Long: autostartDescriptionLong,
Example: "coder autostart enable my-workspace --minute 30 --hour 9 --days 1-5 --tz Europe/Dublin",
}
autostartCmd.AddCommand(autostartShow())
autostartCmd.AddCommand(autostartEnable())
autostartCmd.AddCommand(autostartDisable())
return autostartCmd
}
func autostartShow() *cobra.Command {
cmd := &cobra.Command{
Use: "show <workspace_name>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
if workspace.AutostartSchedule == "" {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "not enabled\n")
return nil
}
validSchedule, err := schedule.Weekly(workspace.AutostartSchedule)
if err != nil {
// This should never happen.
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "invalid autostart schedule %q for workspace %s: %s\n", workspace.AutostartSchedule, workspace.Name, err.Error())
return nil
}
next := validSchedule.Next(time.Now())
loc, _ := time.LoadLocation(validSchedule.Timezone())
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
"schedule: %s\ntimezone: %s\nnext: %s\n",
validSchedule.Cron(),
validSchedule.Timezone(),
next.In(loc),
)
return nil
},
}
return cmd
}
func autostartEnable() *cobra.Command {
// yes some of these are technically numbers but the cron library will do that work
var autostartMinute string
var autostartHour string
var autostartDayOfWeek string
var autostartTimezone string
cmd := &cobra.Command{
Use: "enable <workspace_name> <schedule>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
spec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", autostartTimezone, autostartMinute, autostartHour, autostartDayOfWeek)
validSchedule, err := schedule.Weekly(spec)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
err = client.UpdateWorkspaceAutostart(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: validSchedule.String(),
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace will automatically start at %s.\n\n", workspace.Name, validSchedule.Next(time.Now()))
return nil
},
}
cmd.Flags().StringVar(&autostartMinute, "minute", "0", "autostart minute")
cmd.Flags().StringVar(&autostartHour, "hour", "9", "autostart hour")
cmd.Flags().StringVar(&autostartDayOfWeek, "days", "1-5", "autostart day(s) of week")
tzEnv := os.Getenv("TZ")
if tzEnv == "" {
tzEnv = "UTC"
}
cmd.Flags().StringVar(&autostartTimezone, "tz", tzEnv, "autostart timezone")
return cmd
}
func autostartDisable() *cobra.Command {
return &cobra.Command{
Use: "disable <workspace_name>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
err = client.UpdateWorkspaceAutostart(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: "",
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace will no longer automatically start.\n\n", workspace.Name)
return nil
},
}
}
-165
View File
@@ -1,165 +0,0 @@
package cli_test
import (
"bytes"
"context"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
)
func TestAutostart(t *testing.T) {
t.Parallel()
t.Run("ShowOK", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
cmdArgs = []string{"autostart", "show", workspace.Name}
sched = "CRON_TZ=Europe/Dublin 30 17 * * 1-5"
stdoutBuf = &bytes.Buffer{}
)
err := client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: sched,
})
require.NoError(t, err)
cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
err = cmd.Execute()
require.NoError(t, err, "unexpected error")
// CRON_TZ gets stripped
require.Contains(t, stdoutBuf.String(), "schedule: 30 17 * * 1-5")
})
t.Run("EnableDisableOK", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
tz = "Europe/Dublin"
cmdArgs = []string{"autostart", "enable", workspace.Name, "--minute", "30", "--hour", "9", "--days", "1-5", "--tz", tz}
sched = "CRON_TZ=Europe/Dublin 30 9 * * 1-5"
stdoutBuf = &bytes.Buffer{}
)
cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
err := cmd.Execute()
require.NoError(t, err, "unexpected error")
require.Contains(t, stdoutBuf.String(), "will automatically start at", "unexpected output")
// Ensure autostart schedule updated
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Equal(t, sched, updated.AutostartSchedule, "expected autostart schedule to be set")
// Disable schedule
cmd, root = clitest.New(t, "autostart", "disable", workspace.Name)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
err = cmd.Execute()
require.NoError(t, err, "unexpected error")
require.Contains(t, stdoutBuf.String(), "will no longer automatically start", "unexpected output")
// Ensure autostart schedule updated
updated, err = client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Empty(t, updated.AutostartSchedule, "expected autostart schedule to not be set")
})
t.Run("Enable_NotFound", func(t *testing.T) {
t.Parallel()
var (
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
)
cmd, root := clitest.New(t, "autostart", "enable", "doesnotexist")
clitest.SetupConfig(t, client, root)
err := cmd.Execute()
require.ErrorContains(t, err, "status code 403: forbidden", "unexpected error")
})
t.Run("Disable_NotFound", func(t *testing.T) {
t.Parallel()
var (
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
)
cmd, root := clitest.New(t, "autostart", "disable", "doesnotexist")
clitest.SetupConfig(t, client, root)
err := cmd.Execute()
require.ErrorContains(t, err, "status code 403: forbidden", "unexpected error")
})
t.Run("Enable_DefaultSchedule", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
)
// check current TZ env var
currTz := os.Getenv("TZ")
if currTz == "" {
currTz = "UTC"
}
expectedSchedule := fmt.Sprintf("CRON_TZ=%s 0 9 * * 1-5", currTz)
cmd, root := clitest.New(t, "autostart", "enable", workspace.Name)
clitest.SetupConfig(t, client, root)
err := cmd.Execute()
require.NoError(t, err, "unexpected error")
// Ensure nothing happened
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Equal(t, expectedSchedule, updated.AutostartSchedule, "expected default autostart schedule")
})
}
-167
View File
@@ -1,167 +0,0 @@
package cli
import (
"fmt"
"os"
"time"
"github.com/spf13/cobra"
"github.com/coder/coder/coderd/autobuild/schedule"
"github.com/coder/coder/codersdk"
)
const autostopDescriptionLong = `To have your workspace stop automatically at a regular time you can enable autostop.
When enabling autostop, provide the minute, hour, and day(s) of week.
The default autostop schedule is at 18:00 in your local timezone (TZ env, UTC by default).
`
func autostop() *cobra.Command {
autostopCmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "autostop enable <workspace>",
Short: "schedule a workspace to automatically stop at a regular time",
Long: autostopDescriptionLong,
Example: "coder autostop enable my-workspace --minute 0 --hour 18 --days 1-5 -tz Europe/Dublin",
}
autostopCmd.AddCommand(autostopShow())
autostopCmd.AddCommand(autostopEnable())
autostopCmd.AddCommand(autostopDisable())
return autostopCmd
}
func autostopShow() *cobra.Command {
cmd := &cobra.Command{
Use: "show <workspace_name>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
if workspace.AutostopSchedule == "" {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "not enabled\n")
return nil
}
validSchedule, err := schedule.Weekly(workspace.AutostopSchedule)
if err != nil {
// This should never happen.
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "invalid autostop schedule %q for workspace %s: %s\n", workspace.AutostopSchedule, workspace.Name, err.Error())
return nil
}
next := validSchedule.Next(time.Now())
loc, _ := time.LoadLocation(validSchedule.Timezone())
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
"schedule: %s\ntimezone: %s\nnext: %s\n",
validSchedule.Cron(),
validSchedule.Timezone(),
next.In(loc),
)
return nil
},
}
return cmd
}
func autostopEnable() *cobra.Command {
// yes some of these are technically numbers but the cron library will do that work
var autostopMinute string
var autostopHour string
var autostopDayOfWeek string
var autostopTimezone string
cmd := &cobra.Command{
Use: "enable <workspace_name> <schedule>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
spec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", autostopTimezone, autostopMinute, autostopHour, autostopDayOfWeek)
validSchedule, err := schedule.Weekly(spec)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
err = client.UpdateWorkspaceAutostop(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
Schedule: validSchedule.String(),
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace will automatically stop at %s.\n\n", workspace.Name, validSchedule.Next(time.Now()))
return nil
},
}
cmd.Flags().StringVar(&autostopMinute, "minute", "0", "autostop minute")
cmd.Flags().StringVar(&autostopHour, "hour", "18", "autostop hour")
cmd.Flags().StringVar(&autostopDayOfWeek, "days", "1-5", "autostop day(s) of week")
tzEnv := os.Getenv("TZ")
if tzEnv == "" {
tzEnv = "UTC"
}
cmd.Flags().StringVar(&autostopTimezone, "tz", tzEnv, "autostop timezone")
return cmd
}
func autostopDisable() *cobra.Command {
return &cobra.Command{
Use: "disable <workspace_name>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
err = client.UpdateWorkspaceAutostop(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
Schedule: "",
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace will no longer automatically stop.\n\n", workspace.Name)
return nil
},
}
}
-165
View File
@@ -1,165 +0,0 @@
package cli_test
import (
"bytes"
"context"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
)
func TestAutostop(t *testing.T) {
t.Parallel()
t.Run("ShowOK", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
cmdArgs = []string{"autostop", "show", workspace.Name}
sched = "CRON_TZ=Europe/Dublin 30 17 * * 1-5"
stdoutBuf = &bytes.Buffer{}
)
err := client.UpdateWorkspaceAutostop(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
Schedule: sched,
})
require.NoError(t, err)
cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
err = cmd.Execute()
require.NoError(t, err, "unexpected error")
// CRON_TZ gets stripped
require.Contains(t, stdoutBuf.String(), "schedule: 30 17 * * 1-5")
})
t.Run("EnableDisableOK", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
cmdArgs = []string{"autostop", "enable", workspace.Name, "--minute", "30", "--hour", "17", "--days", "1-5", "--tz", "Europe/Dublin"}
sched = "CRON_TZ=Europe/Dublin 30 17 * * 1-5"
stdoutBuf = &bytes.Buffer{}
)
cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
err := cmd.Execute()
require.NoError(t, err, "unexpected error")
require.Contains(t, stdoutBuf.String(), "will automatically stop at", "unexpected output")
// Ensure autostop schedule updated
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Equal(t, sched, updated.AutostopSchedule, "expected autostop schedule to be set")
// Disable schedule
cmd, root = clitest.New(t, "autostop", "disable", workspace.Name)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)
err = cmd.Execute()
require.NoError(t, err, "unexpected error")
require.Contains(t, stdoutBuf.String(), "will no longer automatically stop", "unexpected output")
// Ensure autostop schedule updated
updated, err = client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Empty(t, updated.AutostopSchedule, "expected autostop schedule to not be set")
})
t.Run("Enable_NotFound", func(t *testing.T) {
t.Parallel()
var (
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
)
cmd, root := clitest.New(t, "autostop", "enable", "doesnotexist")
clitest.SetupConfig(t, client, root)
err := cmd.Execute()
require.ErrorContains(t, err, "status code 403: forbidden", "unexpected error")
})
t.Run("Disable_NotFound", func(t *testing.T) {
t.Parallel()
var (
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
)
cmd, root := clitest.New(t, "autostop", "disable", "doesnotexist")
clitest.SetupConfig(t, client, root)
err := cmd.Execute()
require.ErrorContains(t, err, "status code 403: forbidden", "unexpected error")
})
t.Run("Enable_DefaultSchedule", func(t *testing.T) {
t.Parallel()
var (
ctx = context.Background()
client = coderdtest.New(t, nil)
_ = coderdtest.NewProvisionerDaemon(t, client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
)
// check current TZ env var
currTz := os.Getenv("TZ")
if currTz == "" {
currTz = "UTC"
}
expectedSchedule := fmt.Sprintf("CRON_TZ=%s 0 18 * * 1-5", currTz)
cmd, root := clitest.New(t, "autostop", "enable", workspace.Name)
clitest.SetupConfig(t, client, root)
err := cmd.Execute()
require.NoError(t, err, "unexpected error")
// Ensure nothing happened
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.Equal(t, expectedSchedule, updated.AutostopSchedule, "expected default autostop schedule")
})
}
+100 -7
View File
@@ -6,8 +6,7 @@
//
// Will produce the following usage docs:
//
// -a, --address string The address to serve the API and dashboard (uses $CODER_ADDRESS). (default "127.0.0.1:3000")
//
// -a, --address string The address to serve the API and dashboard (uses $CODER_ADDRESS). (default "127.0.0.1:3000")
package cliflag
import (
@@ -15,10 +14,37 @@ import (
"os"
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/coder/coder/cli/cliui"
)
// IsSetBool returns the value of the boolean flag if it is set.
// It returns false if the flag isn't set or if any error occurs attempting
// to parse the value of the flag.
func IsSetBool(cmd *cobra.Command, name string) bool {
val, ok := IsSet(cmd, name)
if !ok {
return false
}
b, err := strconv.ParseBool(val)
return err == nil && b
}
// IsSet returns the string value of the flag and whether it was set.
func IsSet(cmd *cobra.Command, name string) (string, bool) {
flag := cmd.Flag(name)
if flag == nil {
return "", false
}
return flag.Value.String(), flag.Changed
}
// String sets a string flag on the given flag set.
func String(flagset *pflag.FlagSet, name, shorthand, env, def, usage string) {
v, ok := os.LookupEnv(env)
@@ -37,6 +63,18 @@ func StringVarP(flagset *pflag.FlagSet, p *string, name string, shorthand string
flagset.StringVarP(p, name, shorthand, v, fmtUsage(usage, env))
}
func StringArray(flagset *pflag.FlagSet, name, shorthand, env string, def []string, usage string) {
v, ok := os.LookupEnv(env)
if !ok || v == "" {
if v == "" {
def = []string{}
} else {
def = strings.Split(v, ",")
}
}
flagset.StringArrayP(name, shorthand, def, fmtUsage(usage, env))
}
func StringArrayVarP(flagset *pflag.FlagSet, ptr *[]string, name string, shorthand string, env string, def []string, usage string) {
val, ok := os.LookupEnv(env)
if ok {
@@ -46,7 +84,7 @@ func StringArrayVarP(flagset *pflag.FlagSet, ptr *[]string, name string, shortha
def = strings.Split(val, ",")
}
}
flagset.StringArrayVarP(ptr, name, shorthand, def, usage)
flagset.StringArrayVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
}
// Uint8VarP sets a uint8 flag on the given flag set.
@@ -66,6 +104,39 @@ func Uint8VarP(flagset *pflag.FlagSet, ptr *uint8, name string, shorthand string
flagset.Uint8VarP(ptr, name, shorthand, uint8(vi64), fmtUsage(usage, env))
}
// IntVarP sets a uint8 flag on the given flag set.
func IntVarP(flagset *pflag.FlagSet, ptr *int, name string, shorthand string, env string, def int, usage string) {
val, ok := os.LookupEnv(env)
if !ok || val == "" {
flagset.IntVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
return
}
vi64, err := strconv.ParseUint(val, 10, 8)
if err != nil {
flagset.IntVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
return
}
flagset.IntVarP(ptr, name, shorthand, int(vi64), fmtUsage(usage, env))
}
func Bool(flagset *pflag.FlagSet, name, shorthand, env string, def bool, usage string) {
val, ok := os.LookupEnv(env)
if !ok || val == "" {
flagset.BoolP(name, shorthand, def, fmtUsage(usage, env))
return
}
valb, err := strconv.ParseBool(val)
if err != nil {
flagset.BoolP(name, shorthand, def, fmtUsage(usage, env))
return
}
flagset.BoolP(name, shorthand, valb, fmtUsage(usage, env))
}
// BoolVarP sets a bool flag on the given flag set.
func BoolVarP(flagset *pflag.FlagSet, ptr *bool, name string, shorthand string, env string, def bool, usage string) {
val, ok := os.LookupEnv(env)
@@ -83,10 +154,32 @@ func BoolVarP(flagset *pflag.FlagSet, ptr *bool, name string, shorthand string,
flagset.BoolVarP(ptr, name, shorthand, valb, fmtUsage(usage, env))
}
func fmtUsage(u string, env string) string {
if env == "" {
return fmt.Sprintf("%s.", u)
// DurationVarP sets a time.Duration flag on the given flag set.
func DurationVarP(flagset *pflag.FlagSet, ptr *time.Duration, name string, shorthand string, env string, def time.Duration, usage string) {
val, ok := os.LookupEnv(env)
if !ok || val == "" {
flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
return
}
return fmt.Sprintf("%s - consumes $%s.", u, env)
valb, err := time.ParseDuration(val)
if err != nil {
flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
return
}
flagset.DurationVarP(ptr, name, shorthand, valb, fmtUsage(usage, env))
}
func fmtUsage(u string, env string) string {
if env != "" {
// Avoid double dotting.
dot := "."
if strings.HasSuffix(u, ".") {
dot = ""
}
u = fmt.Sprintf("%s%s\n"+cliui.Styles.Placeholder.Render("Consumes $%s"), u, dot, env)
}
return u
}
+88 -8
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"testing"
"time"
"github.com/spf13/pflag"
"github.com/stretchr/testify/require"
@@ -13,6 +14,7 @@ import (
)
// Testcliflag cannot run in parallel because it uses t.Setenv.
//
//nolint:paralleltest
func TestCliflag(t *testing.T) {
t.Run("StringDefault", func(t *testing.T) {
@@ -23,7 +25,7 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err)
require.Equal(t, def, got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env))
})
t.Run("StringEnvVar", func(t *testing.T) {
@@ -47,7 +49,7 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err)
require.Equal(t, def, got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env))
})
t.Run("StringVarPEnvVar", func(t *testing.T) {
@@ -73,7 +75,7 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err)
require.Equal(t, def, got)
require.Contains(t, flagset.FlagUsages(), usage)
require.NotContains(t, flagset.FlagUsages(), " - consumes")
require.NotContains(t, flagset.FlagUsages(), "Consumes")
})
t.Run("StringArrayDefault", func(t *testing.T) {
@@ -106,7 +108,7 @@ func TestCliflag(t *testing.T) {
require.Equal(t, []string{}, got)
})
t.Run("IntDefault", func(t *testing.T) {
t.Run("UInt8Default", func(t *testing.T) {
var ptr uint8
flagset, name, shorthand, env, usage := randomFlag()
def, _ := cryptorand.Int63n(10)
@@ -116,10 +118,10 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err)
require.Equal(t, uint8(def), got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env))
})
t.Run("IntEnvVar", func(t *testing.T) {
t.Run("UInt8EnvVar", func(t *testing.T) {
var ptr uint8
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.Int63n(10)
@@ -132,7 +134,7 @@ func TestCliflag(t *testing.T) {
require.Equal(t, uint8(envValue), got)
})
t.Run("IntFailParse", func(t *testing.T) {
t.Run("UInt8FailParse", func(t *testing.T) {
var ptr uint8
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.String(10)
@@ -145,6 +147,45 @@ func TestCliflag(t *testing.T) {
require.Equal(t, uint8(def), got)
})
t.Run("IntDefault", func(t *testing.T) {
var ptr int
flagset, name, shorthand, env, usage := randomFlag()
def, _ := cryptorand.Int63n(10)
cliflag.IntVarP(flagset, &ptr, name, shorthand, env, int(def), usage)
got, err := flagset.GetInt(name)
require.NoError(t, err)
require.Equal(t, int(def), got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env))
})
t.Run("IntEnvVar", func(t *testing.T) {
var ptr int
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.Int63n(10)
t.Setenv(env, strconv.FormatUint(uint64(envValue), 10))
def, _ := cryptorand.Int()
cliflag.IntVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetInt(name)
require.NoError(t, err)
require.Equal(t, int(envValue), got)
})
t.Run("IntFailParse", func(t *testing.T) {
var ptr int
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.String(10)
t.Setenv(env, envValue)
def, _ := cryptorand.Int63n(10)
cliflag.IntVarP(flagset, &ptr, name, shorthand, env, int(def), usage)
got, err := flagset.GetInt(name)
require.NoError(t, err)
require.Equal(t, int(def), got)
})
t.Run("BoolDefault", func(t *testing.T) {
var ptr bool
flagset, name, shorthand, env, usage := randomFlag()
@@ -155,7 +196,7 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err)
require.Equal(t, def, got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env))
})
t.Run("BoolEnvVar", func(t *testing.T) {
@@ -183,6 +224,45 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err)
require.Equal(t, def, got)
})
t.Run("DurationDefault", func(t *testing.T) {
var ptr time.Duration
flagset, name, shorthand, env, usage := randomFlag()
def, _ := cryptorand.Duration()
cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetDuration(name)
require.NoError(t, err)
require.Equal(t, def, got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env))
})
t.Run("DurationEnvVar", func(t *testing.T) {
var ptr time.Duration
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.Duration()
t.Setenv(env, envValue.String())
def, _ := cryptorand.Duration()
cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetDuration(name)
require.NoError(t, err)
require.Equal(t, envValue, got)
})
t.Run("DurationFailParse", func(t *testing.T) {
var ptr time.Duration
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.String(10)
t.Setenv(env, envValue)
def, _ := cryptorand.Duration()
cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetDuration(name)
require.NoError(t, err)
require.Equal(t, def, got)
})
}
func randomFlag() (*pflag.FlagSet, string, string, string, string) {
+17 -1
View File
@@ -5,6 +5,7 @@ import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -21,10 +22,22 @@ import (
// New creates a CLI instance with a configuration pointed to a
// temporary testing directory.
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
cmd := cli.Root()
return NewWithSubcommands(t, cli.AGPL(), args...)
}
func NewWithSubcommands(
t *testing.T, subcommands []*cobra.Command, args ...string,
) (*cobra.Command, config.Root) {
cmd := cli.Root(subcommands)
dir := t.TempDir()
root := config.Root(dir)
cmd.SetArgs(append([]string{"--global-config", dir}, args...))
// We could consider using writers
// that log via t.Log here instead.
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
return cmd, root
}
@@ -40,6 +53,9 @@ func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) {
// new temporary testing directory.
func CreateTemplateVersionSource(t *testing.T, responses *echo.Responses) string {
directory := t.TempDir()
f, err := ioutil.TempFile(directory, "*.tf")
require.NoError(t, err)
f.Close()
data, err := echo.Tar(responses)
require.NoError(t, err)
extractTar(t, data, directory)
+1 -3
View File
@@ -3,7 +3,6 @@ package clitest_test
import (
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"github.com/coder/coder/cli/clitest"
@@ -25,8 +24,7 @@ func TestCli(t *testing.T) {
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
err := cmd.Execute()
require.NoError(t, err)
_ = cmd.Execute()
}()
pty.ExpectMatch("coder")
}
+1 -1
View File
@@ -79,7 +79,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
defer resourceMutex.Unlock()
message := "Don't panic, your workspace is booting up!"
if agent.Status == codersdk.WorkspaceAgentDisconnected {
message = "The workspace agent lost connection! Wait for it to reconnect or run: " + Styles.Code.Render("coder rebuild "+opts.WorkspaceName)
message = "The workspace agent lost connection! Wait for it to reconnect or restart your workspace."
}
// This saves the cursor position, then defers clearing from the cursor
// position to the end of the screen.
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
"github.com/coder/coder/cli/cliui"
@@ -43,7 +43,7 @@ func TestAgent(t *testing.T) {
go func() {
defer close(done)
err := cmd.Execute()
require.NoError(t, err)
assert.NoError(t, err)
}()
ptty.ExpectMatch("lost connection")
disconnected.Store(true)
+6 -4
View File
@@ -26,6 +26,7 @@ var Styles = struct {
Checkmark,
Code,
Crossmark,
DateTimeStamp,
Error,
Field,
Keyword,
@@ -33,7 +34,7 @@ var Styles = struct {
Placeholder,
Prompt,
FocusedPrompt,
Fuschia,
Fuchsia,
Logo,
Warn,
Wrap lipgloss.Style
@@ -42,15 +43,16 @@ var Styles = struct {
Checkmark: defaultStyles.Checkmark,
Code: defaultStyles.Code,
Crossmark: defaultStyles.Error.Copy().SetString("✘"),
DateTimeStamp: defaultStyles.LabelDim,
Error: defaultStyles.Error,
Field: defaultStyles.Code.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}),
Keyword: defaultStyles.Keyword,
Paragraph: defaultStyles.Paragraph,
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#585858", Dark: "#4d46b3"}),
Prompt: defaultStyles.Prompt.Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"}),
FocusedPrompt: defaultStyles.FocusedPrompt.Foreground(lipgloss.Color("#651fff")),
Fuschia: defaultStyles.SelectedMenuItem.Copy(),
Fuchsia: defaultStyles.SelectedMenuItem.Copy(),
Logo: defaultStyles.Logo.SetString("Coder"),
Warn: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}),
Wrap: defaultStyles.Wrap,
Wrap: lipgloss.NewStyle().Width(80),
}
+20 -3
View File
@@ -10,7 +10,7 @@ import (
"github.com/coder/coder/codersdk"
)
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.TemplateVersionParameterSchema) (string, error) {
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchema) (string, error) {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render("var."+parameterSchema.Name))
if parameterSchema.Description != "" {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n")
@@ -30,6 +30,7 @@ func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.TemplateVersio
_, _ = fmt.Fprint(cmd.OutOrStdout(), "\033[1A")
value, err = Select(cmd, SelectOptions{
Options: options,
Default: parameterSchema.DefaultSourceValue,
HideSearch: true,
})
if err == nil {
@@ -37,9 +38,25 @@ func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.TemplateVersio
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+Styles.Prompt.String()+Styles.Field.Render(value))
}
} else {
text := "Enter a value"
if parameterSchema.DefaultSourceValue != "" {
text += fmt.Sprintf(" (default: %q)", parameterSchema.DefaultSourceValue)
}
text += ":"
value, err = Prompt(cmd, PromptOptions{
Text: Styles.Bold.Render("Enter a value:"),
Text: Styles.Bold.Render(text),
})
value = strings.TrimSpace(value)
}
return value, err
if err != nil {
return "", err
}
// If they didn't specify anything, use the default value if set.
if len(options) == 0 && value == "" {
value = parameterSchema.DefaultSourceValue
}
return value, nil
}
+35 -4
View File
@@ -24,18 +24,45 @@ type PromptOptions struct {
Validate func(string) error
}
const skipPromptFlag = "yes"
func AllowSkipPrompt(cmd *cobra.Command) {
cmd.Flags().BoolP(skipPromptFlag, "y", false, "Bypass prompts")
}
const (
ConfirmYes = "yes"
ConfirmNo = "no"
)
// Prompt asks the user for input.
func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
// If the cmd has a "yes" flag for skipping confirm prompts, honor it.
// If it's not a "Confirm" prompt, then don't skip. As the default value of
// "yes" makes no sense.
if opts.IsConfirm && cmd.Flags().Lookup(skipPromptFlag) != nil {
if skip, _ := cmd.Flags().GetBool(skipPromptFlag); skip {
return ConfirmYes, nil
}
}
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.FocusedPrompt.String()+opts.Text+" ")
if opts.IsConfirm {
opts.Default = "yes"
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.Placeholder.Render("("+Styles.Bold.Render("yes")+Styles.Placeholder.Render("/no) ")))
if len(opts.Default) == 0 {
opts.Default = ConfirmYes
}
renderedYes := Styles.Placeholder.Render(ConfirmYes)
renderedNo := Styles.Placeholder.Render(ConfirmNo)
if opts.Default == ConfirmYes {
renderedYes = Styles.Bold.Render(ConfirmYes)
} else {
renderedNo = Styles.Bold.Render(ConfirmNo)
}
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.Placeholder.Render("("+renderedYes+Styles.Placeholder.Render("/"+renderedNo+Styles.Placeholder.Render(") "))))
} else if opts.Default != "" {
_, _ = fmt.Fprint(cmd.OutOrStdout(), Styles.Placeholder.Render("("+opts.Default+") "))
}
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
defer signal.Stop(interrupt)
errCh := make(chan error, 1)
lineCh := make(chan string)
@@ -45,8 +72,12 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
inFile, isInputFile := cmd.InOrStdin().(*os.File)
if opts.Secret && isInputFile && isatty.IsTerminal(inFile.Fd()) {
// we don't install a signal handler here because speakeasy has its own
line, err = speakeasy.Ask("")
} else {
signal.Notify(interrupt, os.Interrupt)
defer signal.Stop(interrupt)
reader := bufio.NewReader(cmd.InOrStdin())
line, err = reader.ReadString('\n')
+120 -12
View File
@@ -1,14 +1,21 @@
package cliui_test
import (
"bytes"
"context"
"io"
"os"
"os/exec"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/pty"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)
func TestPrompt(t *testing.T) {
@@ -20,8 +27,8 @@ func TestPrompt(t *testing.T) {
go func() {
resp, err := newPrompt(ptty, cliui.PromptOptions{
Text: "Example",
})
require.NoError(t, err)
}, nil)
assert.NoError(t, err)
msgChan <- resp
}()
ptty.ExpectMatch("Example")
@@ -37,8 +44,8 @@ func TestPrompt(t *testing.T) {
resp, err := newPrompt(ptty, cliui.PromptOptions{
Text: "Example",
IsConfirm: true,
})
require.NoError(t, err)
}, nil)
assert.NoError(t, err)
doneChan <- resp
}()
ptty.ExpectMatch("Example")
@@ -46,6 +53,47 @@ func TestPrompt(t *testing.T) {
require.Equal(t, "yes", <-doneChan)
})
t.Run("Skip", func(t *testing.T) {
t.Parallel()
ptty := ptytest.New(t)
var buf bytes.Buffer
// Copy all data written out to a buffer. When we close the ptty, we can
// no longer read from the ptty.Output(), but we can read what was
// written to the buffer.
dataRead, doneReading := context.WithTimeout(context.Background(), testutil.WaitShort)
go func() {
// This will throw an error sometimes. The underlying ptty
// has its own cleanup routines in t.Cleanup. Instead of
// trying to control the close perfectly, just let the ptty
// double close. This error isn't important, we just
// want to know the ptty is done sending output.
_, _ = io.Copy(&buf, ptty.Output())
doneReading()
}()
doneChan := make(chan string)
go func() {
resp, err := newPrompt(ptty, cliui.PromptOptions{
Text: "ShouldNotSeeThis",
IsConfirm: true,
}, func(cmd *cobra.Command) {
cliui.AllowSkipPrompt(cmd)
cmd.SetArgs([]string{"-y"})
})
assert.NoError(t, err)
doneChan <- resp
}()
require.Equal(t, "yes", <-doneChan)
// Close the reader to end the io.Copy
require.NoError(t, ptty.Close(), "close eof reader")
// Wait for the IO copy to finish
<-dataRead.Done()
// Timeout error means the output was hanging
require.ErrorIs(t, dataRead.Err(), context.Canceled, "should be canceled")
require.Len(t, buf.Bytes(), 0, "expect no output")
})
t.Run("JSON", func(t *testing.T) {
t.Parallel()
ptty := ptytest.New(t)
@@ -53,8 +101,8 @@ func TestPrompt(t *testing.T) {
go func() {
resp, err := newPrompt(ptty, cliui.PromptOptions{
Text: "Example",
})
require.NoError(t, err)
}, nil)
assert.NoError(t, err)
doneChan <- resp
}()
ptty.ExpectMatch("Example")
@@ -69,8 +117,8 @@ func TestPrompt(t *testing.T) {
go func() {
resp, err := newPrompt(ptty, cliui.PromptOptions{
Text: "Example",
})
require.NoError(t, err)
}, nil)
assert.NoError(t, err)
doneChan <- resp
}()
ptty.ExpectMatch("Example")
@@ -85,8 +133,8 @@ func TestPrompt(t *testing.T) {
go func() {
resp, err := newPrompt(ptty, cliui.PromptOptions{
Text: "Example",
})
require.NoError(t, err)
}, nil)
assert.NoError(t, err)
doneChan <- resp
}()
ptty.ExpectMatch("Example")
@@ -97,7 +145,7 @@ func TestPrompt(t *testing.T) {
})
}
func newPrompt(ptty *ptytest.PTY, opts cliui.PromptOptions) (string, error) {
func newPrompt(ptty *ptytest.PTY, opts cliui.PromptOptions, cmdOpt func(cmd *cobra.Command)) (string, error) {
value := ""
cmd := &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
@@ -106,7 +154,67 @@ func newPrompt(ptty *ptytest.PTY, opts cliui.PromptOptions) (string, error) {
return err
},
}
cmd.SetOutput(ptty.Output())
// Optionally modify the cmd
if cmdOpt != nil {
cmdOpt(cmd)
}
cmd.SetOut(ptty.Output())
cmd.SetErr(ptty.Output())
cmd.SetIn(ptty.Input())
return value, cmd.ExecuteContext(context.Background())
}
func TestPasswordTerminalState(t *testing.T) {
if os.Getenv("TEST_SUBPROCESS") == "1" {
passwordHelper()
return
}
t.Parallel()
ptty := ptytest.New(t)
ptyWithFlags, ok := ptty.PTY.(pty.WithFlags)
if !ok {
t.Skip("unable to check PTY local echo on this platform")
}
cmd := exec.Command(os.Args[0], "-test.run=TestPasswordTerminalState") //nolint:gosec
cmd.Env = append(os.Environ(), "TEST_SUBPROCESS=1")
// connect the child process's stdio to the PTY directly, not via a pipe
cmd.Stdin = ptty.Input().Reader
cmd.Stdout = ptty.Output().Writer
cmd.Stderr = ptty.Output().Writer
err := cmd.Start()
require.NoError(t, err)
process := cmd.Process
defer process.Kill()
ptty.ExpectMatch("Password: ")
require.Eventually(t, func() bool {
echo, err := ptyWithFlags.EchoEnabled()
return err == nil && !echo
}, testutil.WaitShort, testutil.IntervalMedium, "echo is on while reading password")
err = process.Signal(os.Interrupt)
require.NoError(t, err)
_, err = process.Wait()
require.NoError(t, err)
require.Eventually(t, func() bool {
echo, err := ptyWithFlags.EchoEnabled()
return err == nil && echo
}, testutil.WaitShort, testutil.IntervalMedium, "echo is off after reading password")
}
// nolint:unused
func passwordHelper() {
cmd := &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Password:",
Secret: true,
})
},
}
cmd.ExecuteContext(context.Background())
}
+37 -10
View File
@@ -1,6 +1,7 @@
package cliui
import (
"bytes"
"context"
"fmt"
"io"
@@ -12,7 +13,6 @@ import (
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
)
@@ -22,7 +22,7 @@ func WorkspaceBuild(ctx context.Context, writer io.Writer, client *codersdk.Clie
build, err := client.WorkspaceBuild(ctx, build)
return build.Job, err
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
return client.WorkspaceBuildLogsAfter(ctx, build, before)
},
})
@@ -31,11 +31,14 @@ func WorkspaceBuild(ctx context.Context, writer io.Writer, client *codersdk.Clie
type ProvisionerJobOptions struct {
Fetch func() (codersdk.ProvisionerJob, error)
Cancel func() error
Logs func() (<-chan codersdk.ProvisionerJobLog, error)
Logs func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error)
FetchInterval time.Duration
// Verbose determines whether debug and trace logs will be shown.
Verbose bool
// Silent determines whether log output will be shown unless there is an
// error.
Silent bool
}
// ProvisionerJob renders a provisioner job with interactive cancellation.
@@ -129,17 +132,36 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
// The initial stage needs to print after the signal handler has been registered.
printStage()
logs, err := opts.Logs()
logs, closer, err := opts.Logs()
if err != nil {
return xerrors.Errorf("logs: %w", err)
}
defer closer.Close()
var (
// logOutput is where log output is written
logOutput = writer
// logBuffer is where logs are buffered if opts.Silent is true
logBuffer = &bytes.Buffer{}
)
if opts.Silent {
logOutput = logBuffer
}
flushLogBuffer := func() {
if opts.Silent {
_, _ = io.Copy(writer, logBuffer)
}
}
ticker := time.NewTicker(opts.FetchInterval)
defer ticker.Stop()
for {
select {
case err = <-errChan:
flushLogBuffer()
return err
case <-ctx.Done():
flushLogBuffer()
return ctx.Err()
case <-ticker.C:
updateJob()
@@ -161,30 +183,35 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
}
err = xerrors.New(job.Error)
jobMutex.Unlock()
flushLogBuffer()
return err
}
output := ""
switch log.Level {
case database.LogLevelTrace, database.LogLevelDebug:
case codersdk.LogLevelTrace, codersdk.LogLevelDebug:
if !opts.Verbose {
continue
}
output = Styles.Placeholder.Render(log.Output)
case database.LogLevelError:
case codersdk.LogLevelError:
output = defaultStyles.Error.Render(log.Output)
case database.LogLevelWarn:
case codersdk.LogLevelWarn:
output = Styles.Warn.Render(log.Output)
case database.LogLevelInfo:
case codersdk.LogLevelInfo:
output = log.Output
}
jobMutex.Lock()
if log.Stage != currentStage && log.Stage != "" {
updateStage(log.Stage, log.CreatedAt)
jobMutex.Unlock()
continue
}
_, _ = fmt.Fprintf(writer, "%s %s\n", Styles.Placeholder.Render(" "), output)
didLogBetweenStage = true
_, _ = fmt.Fprintf(logOutput, "%s %s\n", Styles.Placeholder.Render(" "), output)
if !opts.Silent {
didLogBetweenStage = true
}
jobMutex.Unlock()
}
}
+15 -6
View File
@@ -2,6 +2,7 @@ package cliui_test
import (
"context"
"io"
"os"
"runtime"
"sync"
@@ -9,7 +10,7 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
@@ -90,9 +91,9 @@ func TestProvisionerJob(t *testing.T) {
go func() {
<-test.Next
currentProcess, err := os.FindProcess(os.Getpid())
require.NoError(t, err)
assert.NoError(t, err)
err = currentProcess.Signal(os.Interrupt)
require.NoError(t, err)
assert.NoError(t, err)
<-test.Next
test.JobMutex.Lock()
test.Job.Status = codersdk.ProvisionerJobCanceled
@@ -136,8 +137,10 @@ func newProvisionerJob(t *testing.T) provisionerJobTest {
Cancel: func() error {
return nil
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
return logs, nil
Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
return logs, closeFunc(func() error {
return nil
}), nil
},
})
},
@@ -150,7 +153,7 @@ func newProvisionerJob(t *testing.T) provisionerJobTest {
defer close(done)
err := cmd.ExecuteContext(context.Background())
if err != nil {
require.ErrorIs(t, err, cliui.Canceled)
assert.ErrorIs(t, err, cliui.Canceled)
}
}()
t.Cleanup(func() {
@@ -164,3 +167,9 @@ func newProvisionerJob(t *testing.T) provisionerJobTest {
PTY: ptty,
}
}
type closeFunc func() error
func (c closeFunc) Close() error {
return c()
}
+58 -50
View File
@@ -7,8 +7,10 @@ import (
"strconv"
"github.com/jedib0t/go-pretty/v6/table"
"golang.org/x/mod/semver"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
)
@@ -17,18 +19,19 @@ type WorkspaceResourcesOptions struct {
HideAgentState bool
HideAccess bool
Title string
ServerVersion string
}
// WorkspaceResources displays the connection status and tree-view of provided resources.
// ┌────────────────────────────────────────────────────────────────────────────┐
// │ RESOURCE STATUS ACCESS │
// ├────────────────────────────────────────────────────────────────────────────┤
// │ google_compute_disk.root persistent
// │ google_compute_disk.root
// ├────────────────────────────────────────────────────────────────────────────┤
// │ google_compute_instance.dev ephemeral
// │ google_compute_instance.dev
// │ └─ dev (linux, amd64) ⦾ connecting [10s] coder ssh dev.dev │
// ├────────────────────────────────────────────────────────────────────────────┤
// │ kubernetes_pod.dev ephemeral
// │ kubernetes_pod.dev
// │ ├─ go (linux, amd64) ⦿ connected coder ssh dev.go │
// │ └─ postgres (linux, amd64) ⦾ disconnected [4s] coder ssh dev.postgres │
// └────────────────────────────────────────────────────────────────────────────┘
@@ -38,26 +41,17 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
return resources[i].Type < resources[j].Type
})
// Address on stop indexes whether a resource still exists when in the stopped transition.
addressOnStop := map[string]codersdk.WorkspaceResource{}
for _, resource := range resources {
if resource.Transition != database.WorkspaceTransitionStop {
continue
}
addressOnStop[resource.Type+"."+resource.Name] = resource
}
// Displayed stores whether a resource has already been shown.
// Resources can be stored with numerous states, which we
// process prior to display.
displayed := map[string]struct{}{}
tableWriter := table.NewWriter()
if options.Title != "" {
tableWriter.SetTitle(options.Title)
}
tableWriter.SetStyle(table.StyleLight)
tableWriter.Style().Options.SeparateColumns = false
row := table.Row{"Resource", "Status"}
row := table.Row{"Resource"}
if !options.HideAgentState {
row = append(row, "Status")
row = append(row, "Version")
}
if !options.HideAccess {
row = append(row, "Access")
}
@@ -76,50 +70,20 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
continue
}
resourceAddress := resource.Type + "." + resource.Name
if _, shown := displayed[resourceAddress]; shown {
// The same resource can have multiple transitions.
continue
}
displayed[resourceAddress] = struct{}{}
// Sort agents by name for consistent output.
sort.Slice(resource.Agents, func(i, j int) bool {
return resource.Agents[i].Name < resource.Agents[j].Name
})
_, existsOnStop := addressOnStop[resourceAddress]
resourceState := "ephemeral"
if existsOnStop {
resourceState = "persistent"
}
// Display a line for the resource.
tableWriter.AppendRow(table.Row{
Styles.Bold.Render(resourceAddress),
Styles.Placeholder.Render(resourceState),
"",
"",
})
// Display all agents associated with the resource.
for index, agent := range resource.Agents {
sshCommand := "coder ssh " + options.WorkspaceName
if totalAgents > 1 {
sshCommand += "." + agent.Name
}
sshCommand = Styles.Code.Render(sshCommand)
var agentStatus string
if !options.HideAgentState {
switch agent.Status {
case codersdk.WorkspaceAgentConnecting:
since := database.Now().Sub(agent.CreatedAt)
agentStatus = Styles.Warn.Render("⦾ connecting") + " " +
Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
case codersdk.WorkspaceAgentDisconnected:
since := database.Now().Sub(*agent.DisconnectedAt)
agentStatus = Styles.Error.Render("⦾ disconnected") + " " +
Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
case codersdk.WorkspaceAgentConnected:
agentStatus = Styles.Keyword.Render("⦿ connected")
}
}
pipe := "├"
if index == len(resource.Agents)-1 {
pipe = "└"
@@ -127,9 +91,22 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
row := table.Row{
// These tree from a resource!
fmt.Sprintf("%s─ %s (%s, %s)", pipe, agent.Name, agent.OperatingSystem, agent.Architecture),
agentStatus,
}
if !options.HideAgentState {
var agentStatus string
var agentVersion string
if !options.HideAgentState {
agentStatus = renderAgentStatus(agent)
agentVersion = renderAgentVersion(agent.Version, options.ServerVersion)
}
row = append(row, agentStatus, agentVersion)
}
if !options.HideAccess {
sshCommand := "coder ssh " + options.WorkspaceName
if totalAgents > 1 {
sshCommand += "." + agent.Name
}
sshCommand = Styles.Code.Render(sshCommand)
row = append(row, sshCommand)
}
tableWriter.AppendRow(row)
@@ -139,3 +116,34 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
_, err := fmt.Fprintln(writer, tableWriter.Render())
return err
}
func renderAgentStatus(agent codersdk.WorkspaceAgent) string {
switch agent.Status {
case codersdk.WorkspaceAgentConnecting:
since := database.Now().Sub(agent.CreatedAt)
return Styles.Warn.Render("⦾ connecting") + " " +
Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
case codersdk.WorkspaceAgentDisconnected:
since := database.Now().Sub(*agent.DisconnectedAt)
return Styles.Error.Render("⦾ disconnected") + " " +
Styles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
case codersdk.WorkspaceAgentConnected:
return Styles.Keyword.Render("⦿ connected")
default:
return Styles.Warn.Render("○ unknown")
}
}
func renderAgentVersion(agentVersion, serverVersion string) string {
if agentVersion == "" {
agentVersion = "(unknown)"
}
if !semver.IsValid(serverVersion) || !semver.IsValid(agentVersion) {
return Styles.Placeholder.Render(agentVersion)
}
outdated := semver.Compare(agentVersion, serverVersion) < 0
if outdated {
return Styles.Warn.Render(agentVersion + " (outdated)")
}
return Styles.Keyword.Render(agentVersion)
}
+50
View File
@@ -0,0 +1,50 @@
package cliui
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderAgentVersion(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
agentVersion string
serverVersion string
expected string
}{
{
name: "OK",
agentVersion: "v1.2.3",
serverVersion: "v1.2.3",
expected: "v1.2.3",
},
{
name: "Outdated",
agentVersion: "v1.2.3",
serverVersion: "v1.2.4",
expected: "v1.2.3 (outdated)",
},
{
name: "AgentUnknown",
agentVersion: "",
serverVersion: "v1.2.4",
expected: "(unknown)",
},
{
name: "ServerUnknown",
agentVersion: "v1.2.3",
serverVersion: "",
expected: "v1.2.3",
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
actual := renderAgentVersion(testCase.agentVersion, testCase.serverVersion)
assert.Equal(t, testCase.expected, actual)
})
}
}
+8 -8
View File
@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
@@ -22,7 +22,7 @@ func TestWorkspaceResources(t *testing.T) {
err := cliui.WorkspaceResources(ptty.Output(), []codersdk.WorkspaceResource{{
Type: "google_compute_instance",
Name: "dev",
Transition: database.WorkspaceTransitionStart,
Transition: codersdk.WorkspaceTransitionStart,
Agents: []codersdk.WorkspaceAgent{{
Name: "dev",
Status: codersdk.WorkspaceAgentConnected,
@@ -32,7 +32,7 @@ func TestWorkspaceResources(t *testing.T) {
}}, cliui.WorkspaceResourcesOptions{
WorkspaceName: "example",
})
require.NoError(t, err)
assert.NoError(t, err)
close(done)
}()
ptty.ExpectMatch("coder ssh example")
@@ -46,15 +46,15 @@ func TestWorkspaceResources(t *testing.T) {
done := make(chan struct{})
go func() {
err := cliui.WorkspaceResources(ptty.Output(), []codersdk.WorkspaceResource{{
Transition: database.WorkspaceTransitionStart,
Transition: codersdk.WorkspaceTransitionStart,
Type: "google_compute_disk",
Name: "root",
}, {
Transition: database.WorkspaceTransitionStop,
Transition: codersdk.WorkspaceTransitionStop,
Type: "google_compute_disk",
Name: "root",
}, {
Transition: database.WorkspaceTransitionStart,
Transition: codersdk.WorkspaceTransitionStart,
Type: "google_compute_instance",
Name: "dev",
Agents: []codersdk.WorkspaceAgent{{
@@ -65,7 +65,7 @@ func TestWorkspaceResources(t *testing.T) {
Architecture: "amd64",
}},
}, {
Transition: database.WorkspaceTransitionStart,
Transition: codersdk.WorkspaceTransitionStart,
Type: "kubernetes_pod",
Name: "dev",
Agents: []codersdk.WorkspaceAgent{{
@@ -85,7 +85,7 @@ func TestWorkspaceResources(t *testing.T) {
HideAgentState: false,
HideAccess: false,
})
require.NoError(t, err)
assert.NoError(t, err)
close(done)
}()
ptty.ExpectMatch("google_compute_disk.root")
+10 -1
View File
@@ -35,7 +35,9 @@ func init() {
}
type SelectOptions struct {
Options []string
Options []string
// Default will be highlighted first if it's a valid option.
Default string
Size int
HideSearch bool
}
@@ -50,9 +52,16 @@ func Select(cmd *cobra.Command, opts SelectOptions) (string, error) {
if flag.Lookup("test.v") != nil {
return opts.Options[0], nil
}
var defaultOption interface{}
if opts.Default != "" {
defaultOption = opts.Default
}
var value string
err := survey.AskOne(&survey.Select{
Options: opts.Options,
Default: defaultOption,
PageSize: opts.Size,
}, &value, survey.WithIcons(func(is *survey.IconSet) {
is.Help.Text = "Type to search"
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/cliui"
@@ -21,7 +22,7 @@ func TestSelect(t *testing.T) {
resp, err := newSelect(ptty, cliui.SelectOptions{
Options: []string{"First", "Second"},
})
require.NoError(t, err)
assert.NoError(t, err)
msgChan <- resp
}()
require.Equal(t, "First", <-msgChan)
+264
View File
@@ -1,9 +1,14 @@
package cliui
import (
"fmt"
"reflect"
"strings"
"time"
"github.com/fatih/structtag"
"github.com/jedib0t/go-pretty/v6/table"
"golang.org/x/xerrors"
)
// Table creates a new table with standardized styles.
@@ -41,3 +46,262 @@ 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
// containing the name of the column in the outputted table.
//
// Nested structs are processed if the field has the `table:"$NAME,recursive"`
// tag and their fields will be named as `$PARENT_NAME $NAME`. If the tag is
// malformed or a field is marked as recursive but does not contain a struct or
// a pointer to a struct, this function will return an error (even with an empty
// input slice).
//
// If sort is empty, the input order will be used. If filterColumns is empty or
// nil, all available columns are included.
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")
}
// Get the list of table column headers.
headersRaw, err := typeToTableHeaders(v.Type().Elem())
if err != nil {
return "", xerrors.Errorf("get table headers recursively for type %q: %w", v.Type().Elem().String(), err)
}
if len(headersRaw) == 0 {
return "", xerrors.New(`no table headers found on the input type, make sure there is at least one "table" struct tag`)
}
headers := make(table.Row, len(headersRaw))
for i, header := range headersRaw {
headers[i] = header
}
// Verify that the given sort column and filter columns are valid.
if sort != "" || len(filterColumns) != 0 {
headersMap := make(map[string]string, len(headersRaw))
for _, header := range headersRaw {
headersMap[strings.ToLower(header)] = header
}
if sort != "" {
sort = strings.ToLower(strings.ReplaceAll(sort, "_", " "))
h, ok := headersMap[sort]
if !ok {
return "", xerrors.Errorf(`specified sort column %q not found in table headers, available columns are "%v"`, sort, strings.Join(headersRaw, `", "`))
}
// Autocorrect
sort = h
}
for i, column := range filterColumns {
column := strings.ToLower(strings.ReplaceAll(column, "_", " "))
h, ok := headersMap[column]
if !ok {
return "", xerrors.Errorf(`specified filter column %q not found in table headers, available columns are "%v"`, sort, strings.Join(headersRaw, `", "`))
}
// Autocorrect
filterColumns[i] = h
}
}
// Verify that the given sort column is valid.
if sort != "" {
sort = strings.ReplaceAll(sort, "_", " ")
found := false
for _, header := range headersRaw {
if strings.EqualFold(sort, header) {
found = true
sort = header
break
}
}
if !found {
return "", xerrors.Errorf("specified sort column %q not found in table headers, available columns are %q", sort, strings.Join(headersRaw, `", "`))
}
}
// Setup the table formatter.
tw := Table()
tw.AppendHeader(headers)
tw.SetColumnConfigs(FilterTableColumns(headers, filterColumns))
if sort != "" {
tw.SortBy([]table.SortBy{{
Name: sort,
}})
}
// Write each struct to the table.
for i := 0; i < v.Len(); i++ {
// Format the row as a slice.
rowMap, err := valueToTableMap(v.Index(i))
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]
if !ok {
v = nil
}
// Special type formatting.
switch val := v.(type) {
case time.Time:
v = val.Format(time.Stamp)
case *time.Time:
if val != nil {
v = val.Format(time.Stamp)
}
case fmt.Stringer:
if val != nil {
v = val.String()
}
}
rowSlice[i] = v
}
tw.AppendRow(table.Row(rowSlice))
}
return tw.Render(), nil
}
// parseTableStructTag returns the name of the field according to the `table`
// struct tag. If the table tag does not exist or is "-", an empty string is
// 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, recurse bool, err error) {
tags, err := structtag.Parse(string(field.Tag))
if err != nil {
return "", 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, nil
}
recursive := false
for _, opt := range tag.Options {
if opt == "recursive" {
recursive = true
continue
}
return "", false, xerrors.Errorf("unknown option %q in struct field tag", opt)
}
return strings.ReplaceAll(tag.Name, "_", " "), recursive, nil
}
func isStructOrStructPointer(t reflect.Type) bool {
return t.Kind() == reflect.Struct || (t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct)
}
// typeToTableHeaders converts a type to a slice of column names. If the given
// type is invalid (not a struct or a pointer to a struct, has invalid table
// tags, etc.), an error is returned.
func typeToTableHeaders(t reflect.Type) ([]string, error) {
if !isStructOrStructPointer(t) {
return nil, xerrors.Errorf("typeToTableHeaders called with a non-struct or a non-pointer-to-a-struct type")
}
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
headers := []string{}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
name, recursive, 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 name == "" {
continue
}
fieldType := field.Type
if recursive {
if !isStructOrStructPointer(fieldType) {
return nil, xerrors.Errorf("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct", field.Name, t.String())
}
childNames, err := typeToTableHeaders(fieldType)
if err != nil {
return nil, xerrors.Errorf("get child field header names for field %q in type %q: %w", field.Name, fieldType.String(), err)
}
for _, childName := range childNames {
headers = append(headers, fmt.Sprintf("%s %s", name, childName))
}
continue
}
headers = append(headers, name)
}
return headers, nil
}
// valueToTableMap converts a struct to a map of column name to value. If the
// given type is invalid (not a struct or a pointer to a struct, has invalid
// table tags, etc.), an error is returned.
func valueToTableMap(val reflect.Value) (map[string]any, error) {
if !isStructOrStructPointer(val.Type()) {
return nil, xerrors.Errorf("valueToTableMap called with a non-struct or a non-pointer-to-a-struct type")
}
if val.Kind() == reflect.Pointer {
if val.IsNil() {
// No data for this struct, so return an empty map. All values will
// be rendered as nil in the resulting table.
return map[string]any{}, nil
}
val = val.Elem()
}
row := map[string]any{}
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)
fieldVal := val.Field(i)
name, recursive, err := parseTableStructTag(field)
if err != nil {
return nil, xerrors.Errorf("parse struct tags for field %q in type %T: %w", field.Name, val, err)
}
if name == "" {
continue
}
// Recurse if it's a struct.
fieldType := field.Type
if recursive {
if !isStructOrStructPointer(fieldType) {
return nil, xerrors.Errorf("field %q in type %q is marked as recursive but does not contain a struct or a pointer to a struct", field.Name, fieldType.String())
}
// valueToTableMap does nothing on pointers so we don't need to
// filter here.
childMap, err := valueToTableMap(fieldVal)
if err != nil {
return nil, xerrors.Errorf("get child field values for field %q in type %q: %w", field.Name, fieldType.String(), err)
}
for childName, childValue := range childMap {
row[fmt.Sprintf("%s %s", name, childName)] = childValue
}
continue
}
// Otherwise, we just use the field value.
row[name] = val.Field(i).Interface()
}
return row, nil
}
+352
View File
@@ -0,0 +1,352 @@
package cliui_test
import (
"fmt"
"log"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/cliui"
)
type stringWrapper struct {
str string
}
var _ fmt.Stringer = stringWrapper{}
func (s stringWrapper) String() string {
return s.str
}
type tableTest1 struct {
Name string `table:"name"`
NotIncluded string // no table tag
Age int `table:"age"`
Roles []string `table:"roles"`
Sub1 tableTest2 `table:"sub_1,recursive"`
Sub2 *tableTest2 `table:"sub_2,recursive"`
Sub3 tableTest3 `table:"sub 3,recursive"`
Sub4 tableTest2 `table:"sub 4"` // not recursive
// Types with special formatting.
Time time.Time `table:"time"`
TimePtr *time.Time `table:"time_ptr"`
}
type tableTest2 struct {
Name stringWrapper `table:"name"`
Age int `table:"age"`
NotIncluded string `table:"-"`
}
type tableTest3 struct {
NotIncluded string // no table tag
Sub tableTest2 `table:"inner,recursive"`
}
func Test_DisplayTable(t *testing.T) {
t.Parallel()
someTime := time.Date(2022, 8, 2, 15, 49, 10, 0, time.Local)
in := []tableTest1{
{
Name: "foo",
Age: 10,
Roles: []string{"a", "b", "c"},
Sub1: tableTest2{
Name: stringWrapper{str: "foo1"},
Age: 11,
},
Sub2: &tableTest2{
Name: stringWrapper{str: "foo2"},
Age: 12,
},
Sub3: tableTest3{
Sub: tableTest2{
Name: stringWrapper{str: "foo3"},
Age: 13,
},
},
Sub4: tableTest2{
Name: stringWrapper{str: "foo4"},
Age: 14,
},
Time: someTime,
TimePtr: &someTime,
},
{
Name: "bar",
Age: 20,
Roles: []string{"a"},
Sub1: tableTest2{
Name: stringWrapper{str: "bar1"},
Age: 21,
},
Sub2: nil,
Sub3: tableTest3{
Sub: tableTest2{
Name: stringWrapper{str: "bar3"},
Age: 23,
},
},
Sub4: tableTest2{
Name: stringWrapper{str: "bar4"},
Age: 24,
},
Time: someTime,
TimePtr: nil,
},
{
Name: "baz",
Age: 30,
Roles: nil,
Sub1: tableTest2{
Name: stringWrapper{str: "baz1"},
Age: 31,
},
Sub2: nil,
Sub3: tableTest3{
Sub: tableTest2{
Name: stringWrapper{str: "baz3"},
Age: 33,
},
},
Sub4: tableTest2{
Name: stringWrapper{str: "baz4"},
Age: 34,
},
Time: someTime,
TimePtr: nil,
},
}
// This test tests skipping fields without table tags, recursion, pointer
// dereferencing, and nil pointer skipping.
t.Run("OK", 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
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } Aug 2 15:49:10 Aug 2 15:49:10
bar 20 [a] bar1 21 <nil> <nil> bar3 23 {bar4 24 } Aug 2 15:49:10 <nil>
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } Aug 2 15:49:10 <nil>
`
// Test with non-pointer values.
out, err := cliui.DisplayTable(in, "", nil)
log.Println("rendered table:\n" + out)
require.NoError(t, err)
compareTables(t, expected, out)
// Test with pointer values.
inPtr := make([]*tableTest1, len(in))
for i, v := range in {
v := v
inPtr[i] = &v
}
out, err = cliui.DisplayTable(inPtr, "", nil)
require.NoError(t, err)
compareTables(t, expected, out)
})
t.Run("Sort", 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 } Aug 2 15:49:10 <nil>
baz 30 [] baz1 31 <nil> <nil> baz3 33 {baz4 34 } Aug 2 15:49:10 <nil>
foo 10 [a b c] foo1 11 foo2 12 foo3 13 {foo4 14 } Aug 2 15:49:10 Aug 2 15:49:10
`
out, err := cliui.DisplayTable(in, "name", nil)
log.Println("rendered table:\n" + out)
require.NoError(t, err)
compareTables(t, expected, out)
})
t.Run("Filter", func(t *testing.T) {
t.Parallel()
expected := `
NAME SUB 1 NAME SUB 3 INNER NAME TIME
foo foo1 foo3 Aug 2 15:49:10
bar bar1 bar3 Aug 2 15:49:10
baz baz1 baz3 Aug 2 15:49:10
`
out, err := cliui.DisplayTable(in, "", []string{"name", "sub_1_name", "sub_3 inner name", "time"})
log.Println("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) {
t.Parallel()
t.Run("NotSlice", func(t *testing.T) {
t.Parallel()
var in string
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
t.Run("BadSortColumn", func(t *testing.T) {
t.Parallel()
_, err := cliui.DisplayTable(in, "bad_column_does_not_exist", nil)
require.Error(t, err)
})
t.Run("BadFilterColumns", func(t *testing.T) {
t.Parallel()
_, err := cliui.DisplayTable(in, "", []string{"name", "bad_column_does_not_exist"})
require.Error(t, err)
})
t.Run("Interfaces", func(t *testing.T) {
t.Parallel()
t.Run("WithoutData", func(t *testing.T) {
t.Parallel()
var in []any
_, 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) {
t.Parallel()
t.Run("WithoutData", func(t *testing.T) {
t.Parallel()
var in []string
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
t.Run("WithData", func(t *testing.T) {
t.Parallel()
in := []string{"foo", "bar", "baz"}
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
})
t.Run("NoTableTags", func(t *testing.T) {
t.Parallel()
type noTableTagsTest struct {
Field string `json:"field"`
}
t.Run("WithoutData", func(t *testing.T) {
t.Parallel()
var in []noTableTagsTest
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
t.Run("WithData", func(t *testing.T) {
t.Parallel()
in := []noTableTagsTest{{Field: "hi"}}
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
})
t.Run("InvalidTag/NoName", func(t *testing.T) {
t.Parallel()
type noNameTest struct {
Field string `table:""`
}
t.Run("WithoutData", func(t *testing.T) {
t.Parallel()
var in []noNameTest
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
t.Run("WithData", func(t *testing.T) {
t.Parallel()
in := []noNameTest{{Field: "test"}}
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
})
t.Run("InvalidTag/BadSyntax", func(t *testing.T) {
t.Parallel()
type invalidSyntaxTest struct {
Field string `table:"asda,asdjada"`
}
t.Run("WithoutData", func(t *testing.T) {
t.Parallel()
var in []invalidSyntaxTest
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
t.Run("WithData", func(t *testing.T) {
t.Parallel()
in := []invalidSyntaxTest{{Field: "test"}}
_, err := cliui.DisplayTable(in, "", nil)
require.Error(t, err)
})
})
})
}
// compareTables normalizes the incoming table lines
func compareTables(t *testing.T, expected, out string) {
t.Helper()
expectedLines := strings.Split(strings.TrimSpace(expected), "\n")
gotLines := strings.Split(strings.TrimSpace(out), "\n")
assert.Equal(t, len(expectedLines), len(gotLines), "expected line count does not match generated line count")
// Map the expected and got lines to normalize them.
expectedNormalized := make([]string, len(expectedLines))
gotNormalized := make([]string, len(gotLines))
normalizeLine := func(s string) string {
return strings.Join(strings.Fields(strings.TrimSpace(s)), " ")
}
for i, s := range expectedLines {
expectedNormalized[i] = normalizeLine(s)
}
for i, s := range gotLines {
gotNormalized[i] = normalizeLine(s)
}
require.Equal(t, expectedNormalized, gotNormalized, "expected lines to match generated lines")
}
+16
View File
@@ -21,6 +21,22 @@ func (r Root) Organization() File {
return File(filepath.Join(string(r), "organization"))
}
func (r Root) DotfilesURL() File {
return File(filepath.Join(string(r), "dotfilesurl"))
}
func (r Root) PostgresPath() string {
return filepath.Join(string(r), "postgres")
}
func (r Root) PostgresPassword() File {
return File(filepath.Join(r.PostgresPath(), "password"))
}
func (r Root) PostgresPort() File {
return File(filepath.Join(r.PostgresPath(), "port"))
}
// File provides convenience methods for interacting with *os.File.
type File string
+519 -101
View File
@@ -1,157 +1,548 @@
package cli
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"github.com/cli/safeexec"
"github.com/pkg/diff"
"github.com/pkg/diff/write"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
)
const sshStartToken = "# ------------START-CODER-----------"
const sshStartMessage = `# This was generated by "coder config-ssh".
const (
sshDefaultConfigFileName = "~/.ssh/config"
sshStartToken = "# ------------START-CODER-----------"
sshEndToken = "# ------------END-CODER------------"
sshConfigSectionHeader = "# This section is managed by coder. DO NOT EDIT."
sshConfigDocsHeader = `
#
# To remove this blob, run:
#
# coder config-ssh --remove
#
# You should not hand-edit this section, unless you are deleting it.`
const sshEndToken = "# ------------END-CODER------------"
# You should not hand-edit this section unless you are removing it, all
# changes will be lost when running "coder config-ssh".
`
sshConfigOptionsHeader = `#
# Last config-ssh options:
`
)
// sshConfigOptions represents options that can be stored and read
// from the coder config in ~/.ssh/coder.
type sshConfigOptions struct {
sshOptions []string
}
func (o sshConfigOptions) equal(other sshConfigOptions) bool {
// Compare without side-effects or regard to order.
opt1 := slices.Clone(o.sshOptions)
sort.Strings(opt1)
opt2 := slices.Clone(other.sshOptions)
sort.Strings(opt2)
return slices.Equal(opt1, opt2)
}
func (o sshConfigOptions) asList() (list []string) {
for _, opt := range o.sshOptions {
list = append(list, fmt.Sprintf("ssh-option: %s", opt))
}
return list
}
type sshWorkspaceConfig struct {
Name string
Hosts []string
}
func sshFetchWorkspaceConfigs(ctx context.Context, client *codersdk.Client) ([]sshWorkspaceConfig, error) {
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
Owner: codersdk.Me,
})
if err != nil {
return nil, err
}
var errGroup errgroup.Group
workspaceConfigs := make([]sshWorkspaceConfig, len(workspaces))
for i, workspace := range workspaces {
i := i
workspace := workspace
errGroup.Go(func() error {
resources, err := client.TemplateVersionResources(ctx, workspace.LatestBuild.TemplateVersionID)
if err != nil {
return err
}
wc := sshWorkspaceConfig{Name: workspace.Name}
var agents []codersdk.WorkspaceAgent
for _, resource := range resources {
if resource.Transition != codersdk.WorkspaceTransitionStart {
continue
}
agents = append(agents, resource.Agents...)
}
// handle both WORKSPACE and WORKSPACE.AGENT syntax
if len(agents) == 1 {
wc.Hosts = append(wc.Hosts, workspace.Name)
}
for _, agent := range agents {
hostname := workspace.Name + "." + agent.Name
wc.Hosts = append(wc.Hosts, hostname)
}
workspaceConfigs[i] = wc
return nil
})
}
err = errGroup.Wait()
if err != nil {
return nil, err
}
return workspaceConfigs, nil
}
func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (receive func() ([]sshWorkspaceConfig, error)) {
wcC := make(chan []sshWorkspaceConfig, 1)
errC := make(chan error, 1)
go func() {
wc, err := sshFetchWorkspaceConfigs(ctx, client)
wcC <- wc
errC <- err
}()
return func() ([]sshWorkspaceConfig, error) {
return <-wcC, <-errC
}
}
func configSSH() *cobra.Command {
var (
sshConfigFile string
sshOptions []string
sshConfigOpts sshConfigOptions
usePreviousOpts bool
dryRun bool
skipProxyCommand bool
)
cmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "config-ssh",
Short: "Populate your SSH config with Host entries for all of your workspaces",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
Short: "Add an SSH Host entry for your workspaces \"ssh coder.workspace\"",
Example: 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{
Description: "You can use --dry-run (or -n) to see the changes that would be made",
Command: "coder config-ssh --dry-run",
},
),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, _ []string) error {
client, err := CreateClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
if strings.HasPrefix(sshConfigFile, "~/") {
dirname, _ := os.UserHomeDir()
sshConfigFile = filepath.Join(dirname, sshConfigFile[2:])
}
// Doesn't matter if this fails, because we write the file anyways.
sshConfigContentRaw, _ := os.ReadFile(sshConfigFile)
sshConfigContent := string(sshConfigContentRaw)
startIndex := strings.Index(sshConfigContent, sshStartToken)
endIndex := strings.Index(sshConfigContent, sshEndToken)
if startIndex != -1 && endIndex != -1 {
sshConfigContent = sshConfigContent[:startIndex-1] + sshConfigContent[endIndex+len(sshEndToken):]
}
workspaces, err := client.WorkspacesByOwner(cmd.Context(), organization.ID, codersdk.Me)
if err != nil {
return err
}
if len(workspaces) == 0 {
return xerrors.New("You don't have any workspaces!")
}
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs(cmd.Context(), client)
binaryFile, err := currentBinPath(cmd)
out := cmd.OutOrStdout()
if dryRun {
// Print everything except diff to stderr so
// that it's possible to capture the diff.
out = cmd.OutOrStderr()
}
coderBinary, err := currentBinPath(out)
if err != nil {
return err
}
escapedCoderBinary, err := sshConfigExecEscape(coderBinary)
if err != nil {
return xerrors.Errorf("escape coder binary for ssh failed: %w", err)
}
root := createConfig(cmd)
sshConfigContent += "\n" + sshStartToken + "\n" + sshStartMessage + "\n\n"
sshConfigContentMutex := sync.Mutex{}
var errGroup errgroup.Group
for _, workspace := range workspaces {
workspace := workspace
errGroup.Go(func() error {
resources, err := client.TemplateVersionResources(cmd.Context(), workspace.LatestBuild.TemplateVersionID)
if err != nil {
return err
}
for _, resource := range resources {
if resource.Transition != database.WorkspaceTransitionStart {
continue
}
for _, agent := range resource.Agents {
sshConfigContentMutex.Lock()
hostname := workspace.Name
if len(resource.Agents) > 1 {
hostname += "." + agent.Name
}
configOptions := []string{
"Host coder." + hostname,
}
for _, option := range sshOptions {
configOptions = append(configOptions, "\t"+option)
}
configOptions = append(configOptions,
"\tHostName coder."+hostname,
"\tConnectTimeout=0",
"\tStrictHostKeyChecking=no",
// Without this, the "REMOTE HOST IDENTITY CHANGED"
// message will appear.
"\tUserKnownHostsFile=/dev/null",
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
// message from appearing on every SSH. This happens because we ignore the known hosts.
"\tLogLevel ERROR",
)
if !skipProxyCommand {
configOptions = append(configOptions, fmt.Sprintf("\tProxyCommand %q --global-config %q ssh --stdio %s", binaryFile, root, hostname))
}
sshConfigContent += strings.Join(configOptions, "\n") + "\n"
sshConfigContentMutex.Unlock()
}
}
return nil
escapedGlobalConfig, err := sshConfigExecEscape(string(root))
if err != nil {
return xerrors.Errorf("escape global config for ssh failed: %w", err)
}
homedir, err := os.UserHomeDir()
if err != nil {
return xerrors.Errorf("user home dir failed: %w", err)
}
if strings.HasPrefix(sshConfigFile, "~/") {
sshConfigFile = filepath.Join(homedir, sshConfigFile[2:])
}
// Only allow not-exist errors to avoid trashing
// the users SSH config.
configRaw, err := os.ReadFile(sshConfigFile)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return xerrors.Errorf("read ssh config failed: %w", err)
}
// Keep track of changes we are making.
var changes []string
// Parse the previous configuration only if config-ssh
// has been run previously.
var lastConfig *sshConfigOptions
if section, ok := sshConfigGetCoderSection(configRaw); ok {
c := sshConfigParseLastOptions(bytes.NewReader(section))
lastConfig = &c
}
// Avoid prompting in diff mode (unexpected behavior)
// or when a previous config does not exist.
if usePreviousOpts && lastConfig != nil {
sshConfigOpts = *lastConfig
} else if lastConfig != nil && !sshConfigOpts.equal(*lastConfig) {
newOpts := sshConfigOpts.asList()
newOptsMsg := "\n\n New options: none"
if len(newOpts) > 0 {
newOptsMsg = fmt.Sprintf("\n\n New options:\n * %s", strings.Join(newOpts, "\n * "))
}
oldOpts := lastConfig.asList()
oldOptsMsg := "\n\n Previous options: none"
if len(oldOpts) > 0 {
oldOptsMsg = fmt.Sprintf("\n\n Previous options:\n * %s", strings.Join(oldOpts, "\n * "))
}
line, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: fmt.Sprintf("New options differ from previous options:%s%s\n\n Use new options?", newOptsMsg, oldOptsMsg),
IsConfirm: true,
})
if err != nil {
if line == "" && xerrors.Is(err, cliui.Canceled) {
return nil
}
// Selecting "no" will use the last config.
sshConfigOpts = *lastConfig
} else {
changes = append(changes, "Use new SSH options")
}
// Only print when prompts are shown.
if yes, _ := cmd.Flags().GetBool("yes"); !yes {
_, _ = fmt.Fprint(out, "\n")
}
}
err = errGroup.Wait()
configModified := configRaw
buf := &bytes.Buffer{}
before, after := sshConfigSplitOnCoderSection(configModified)
// Write the first half of the users config file to buf.
_, _ = buf.Write(before)
// Write comment and store the provided options as part
// of the config for future (re)use.
newline := len(before) > 0
sshConfigWriteSectionHeader(buf, newline, sshConfigOpts)
workspaceConfigs, err := recvWorkspaceConfigs()
if err != nil {
return err
return xerrors.Errorf("fetch workspace configs failed: %w", err)
}
sshConfigContent += "\n" + sshEndToken
err = os.MkdirAll(filepath.Dir(sshConfigFile), os.ModePerm)
if err != nil {
return err
// Ensure stable sorting of output.
slices.SortFunc(workspaceConfigs, func(a, b sshWorkspaceConfig) bool {
return a.Name < b.Name
})
for _, wc := range workspaceConfigs {
sort.Strings(wc.Hosts)
// Write agent configuration.
for _, hostname := range wc.Hosts {
configOptions := []string{
"Host coder." + hostname,
}
for _, option := range sshConfigOpts.sshOptions {
configOptions = append(configOptions, "\t"+option)
}
configOptions = append(configOptions,
"\tHostName coder."+hostname,
"\tConnectTimeout=0",
"\tStrictHostKeyChecking=no",
// Without this, the "REMOTE HOST IDENTITY CHANGED"
// message will appear.
"\tUserKnownHostsFile=/dev/null",
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
// message from appearing on every SSH. This happens because we ignore the known hosts.
"\tLogLevel ERROR",
)
if !skipProxyCommand {
configOptions = append(
configOptions,
fmt.Sprintf(
"\tProxyCommand %s --global-config %s ssh --stdio %s",
escapedCoderBinary, escapedGlobalConfig, hostname,
),
)
}
_, _ = buf.WriteString(strings.Join(configOptions, "\n"))
_ = buf.WriteByte('\n')
}
}
err = os.WriteFile(sshConfigFile, []byte(sshConfigContent), os.ModePerm)
if err != nil {
return err
sshConfigWriteSectionEnd(buf)
// Write the remainder of the users config file to buf.
_, _ = buf.Write(after)
if !bytes.Equal(configModified, buf.Bytes()) {
changes = append(changes, fmt.Sprintf("Update the coder section in %s", sshConfigFile))
configModified = buf.Bytes()
}
if len(changes) == 0 {
_, _ = fmt.Fprintf(out, "No changes to make.\n")
return nil
}
if dryRun {
_, _ = fmt.Fprintf(out, "Dry run, the following changes would be made to your SSH configuration:\n\n * %s\n\n", strings.Join(changes, "\n * "))
color := isTTYOut(cmd)
diff, err := diffBytes(sshConfigFile, configRaw, configModified, color)
if err != nil {
return xerrors.Errorf("diff failed: %w", err)
}
if len(diff) > 0 {
// Write diff to stdout.
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s", diff)
}
return nil
}
if len(changes) > 0 {
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: fmt.Sprintf("The following changes will be made to your SSH configuration:\n\n * %s\n\n Continue?", strings.Join(changes, "\n * ")),
IsConfirm: true,
})
if err != nil {
return nil
}
// Only print when prompts are shown.
if yes, _ := cmd.Flags().GetBool("yes"); !yes {
_, _ = fmt.Fprint(out, "\n")
}
}
if !bytes.Equal(configRaw, configModified) {
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configModified))
if err != nil {
return xerrors.Errorf("write ssh config failed: %w", err)
}
}
if len(workspaceConfigs) > 0 {
_, _ = fmt.Fprintln(out, "You should now be able to ssh into your workspace.")
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh coder.%s\n", workspaceConfigs[0].Name)
} else {
_, _ = fmt.Fprint(out, "You don't have any workspaces yet, try creating one with:\n\n\t$ coder create <workspace>\n")
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "An auto-generated ssh config was written to %q\n", sshConfigFile)
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "You should now be able to ssh into your workspace")
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "For example, try running\n\n\t$ ssh coder.%s\n\n", workspaces[0].Name)
return nil
},
}
cliflag.StringVarP(cmd.Flags(), &sshConfigFile, "ssh-config-file", "", "CODER_SSH_CONFIG_FILE", "~/.ssh/config", "Specifies the path to an SSH config.")
cmd.Flags().StringArrayVarP(&sshOptions, "ssh-option", "o", []string{}, "Specifies additional SSH options to embed in each host stanza.")
cliflag.StringVarP(cmd.Flags(), &sshConfigFile, "ssh-config-file", "", "CODER_SSH_CONFIG_FILE", sshDefaultConfigFileName, "Specifies the path to an SSH config.")
cmd.Flags().StringArrayVarP(&sshConfigOpts.sshOptions, "ssh-option", "o", []string{}, "Specifies additional SSH options to embed in each host stanza.")
cmd.Flags().BoolVarP(&dryRun, "dry-run", "n", false, "Perform a trial run with no changes made, showing a diff at the end.")
cmd.Flags().BoolVarP(&skipProxyCommand, "skip-proxy-command", "", false, "Specifies whether the ProxyCommand option should be skipped. Useful for testing.")
_ = cmd.Flags().MarkHidden("skip-proxy-command")
cliflag.BoolVarP(cmd.Flags(), &usePreviousOpts, "use-previous-options", "", "CODER_SSH_USE_PREVIOUS_OPTIONS", false, "Specifies whether or not to keep options from previous run of config-ssh.")
cliui.AllowSkipPrompt(cmd)
return cmd
}
//nolint:revive
func sshConfigWriteSectionHeader(w io.Writer, addNewline bool, o sshConfigOptions) {
nl := "\n"
if !addNewline {
nl = ""
}
_, _ = fmt.Fprint(w, nl+sshStartToken+"\n")
_, _ = fmt.Fprint(w, sshConfigSectionHeader)
_, _ = fmt.Fprint(w, sshConfigDocsHeader)
if len(o.sshOptions) > 0 {
_, _ = fmt.Fprint(w, sshConfigOptionsHeader)
for _, opt := range o.sshOptions {
_, _ = fmt.Fprintf(w, "# :%s=%s\n", "ssh-option", opt)
}
}
_, _ = fmt.Fprint(w, "#\n")
}
func sshConfigWriteSectionEnd(w io.Writer) {
_, _ = fmt.Fprint(w, sshEndToken+"\n")
}
func sshConfigParseLastOptions(r io.Reader) (o sshConfigOptions) {
s := bufio.NewScanner(r)
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "# :") {
line = strings.TrimPrefix(line, "# :")
parts := strings.SplitN(line, "=", 2)
switch parts[0] {
case "ssh-option":
o.sshOptions = append(o.sshOptions, parts[1])
default:
// Unknown option, ignore.
}
}
}
if err := s.Err(); err != nil {
panic(err)
}
return o
}
func sshConfigGetCoderSection(data []byte) (section []byte, ok bool) {
startIndex := bytes.Index(data, []byte(sshStartToken))
endIndex := bytes.Index(data, []byte(sshEndToken))
if startIndex != -1 && endIndex != -1 {
return data[startIndex : endIndex+len(sshEndToken)], true
}
return nil, false
}
// sshConfigSplitOnCoderSection splits the SSH config into two sections,
// before contains the lines before sshStartToken and after contains the
// lines after sshEndToken.
func sshConfigSplitOnCoderSection(data []byte) (before, after []byte) {
startIndex := bytes.Index(data, []byte(sshStartToken))
endIndex := bytes.Index(data, []byte(sshEndToken))
if startIndex != -1 && endIndex != -1 {
// We use -1 and +1 here to also include the preceding
// and trailing newline, where applicable.
start := startIndex
if start > 0 {
start--
}
end := endIndex + len(sshEndToken)
if end < len(data) {
end++
}
return data[:start], data[end:]
}
return data, 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
// cannot simply be covered by `fmt.Sprintf("%q", path)`.
//
// Always escaping the path with `fmt.Sprintf("%q", path)` usually works
// on most platforms, but double quotes sometimes break on Windows 10
// (see #2853). This function takes a best-effort approach to improving
// compatibility and covering edge cases.
//
// Given the following ProxyCommand:
//
// ProxyCommand "/path/with space/coder" ssh --stdio work
//
// This is ~what OpenSSH would execute:
//
// /bin/bash -c '"/path/with space/to/coder" ssh --stdio workspace'
//
// However, since it's actually an arg in C, the contents inside the
// single quotes are interpreted as is, e.g. if there was a '\t', it
// would be the literal string '\t', not a tab.
//
// See:
// - https://github.com/coder/coder/issues/2853
// - https://github.com/openssh/openssh-portable/blob/V_9_0_P1/sshconnect.c#L158-L167
// - https://github.com/PowerShell/openssh-portable/blob/v8.1.0.0/sshconnect.c#L231-L293
// - https://github.com/PowerShell/openssh-portable/blob/v8.1.0.0/contrib/win32/win32compat/w32fd.c#L1075-L1100
func sshConfigExecEscape(path string) (string, error) {
// This is unlikely to ever happen, but newlines are allowed on
// certain filesystems, but cannot be used inside ssh config.
if strings.ContainsAny(path, "\n") {
return "", xerrors.Errorf("invalid path: %s", path)
}
// In the unlikely even that a path contains quotes, they must be
// escaped so that they are not interpreted as shell quotes.
if strings.Contains(path, "\"") {
path = strings.ReplaceAll(path, "\"", "\\\"")
}
// A space or a tab requires quoting, but tabs must not be escaped
// (\t) since OpenSSH interprets it as a literal \t, not a tab.
if strings.ContainsAny(path, " \t") {
path = fmt.Sprintf("\"%s\"", path) //nolint:gocritic // We don't want %q here.
}
return path, nil
}
// currentBinPath returns the path to the coder binary suitable for use in ssh
// ProxyCommand.
func currentBinPath(cmd *cobra.Command) (string, error) {
func currentBinPath(w io.Writer) (string, error) {
exePath, err := os.Executable()
if err != nil {
return "", xerrors.Errorf("get executable path: %w", err)
@@ -167,11 +558,12 @@ func currentBinPath(cmd *cobra.Command) (string, error) {
// correctly. Check if the current executable is in $PATH, and warn the user
// if it isn't.
if err != nil && runtime.GOOS == "windows" {
cliui.Warn(cmd.OutOrStdout(),
cliui.Warn(w,
"The current executable is not in $PATH.",
"This may lead to problems connecting to your workspace via SSH.",
fmt.Sprintf("Please move %q to a location in your $PATH (such as System32) and run `%s config-ssh` again.", binName, binName),
)
_, _ = fmt.Fprint(w, "\n")
// Return the exePath so SSH at least works outside of Msys2.
return exePath, nil
}
@@ -179,13 +571,39 @@ func currentBinPath(cmd *cobra.Command) (string, error) {
// Warn the user if the current executable is not the same as the one in
// $PATH.
if filepath.Clean(pathPath) != filepath.Clean(exePath) {
cliui.Warn(cmd.OutOrStdout(),
cliui.Warn(w,
"The current executable path does not match the executable path found in $PATH.",
"This may cause issues connecting to your workspace via SSH.",
fmt.Sprintf("\tCurrent executable path: %q", exePath),
fmt.Sprintf("\tExecutable path in $PATH: %q", pathPath),
)
_, _ = fmt.Fprint(w, "\n")
}
return binName, nil
return exePath, nil
}
// diffBytes takes two byte slices and diffs them as if they were in a
// file named name.
// nolint: revive // Color is an option, not a control coupling.
func diffBytes(name string, b1, b2 []byte, color bool) ([]byte, error) {
var buf bytes.Buffer
var opts []write.Option
if color {
opts = append(opts, write.TerminalColor())
}
err := diff.Text(name, name, b1, b2, &buf, opts...)
if err != nil {
return nil, err
}
b := buf.Bytes()
// Check if diff only output two lines, if yes, there's no diff.
//
// Example:
// --- /home/user/.ssh/config
// +++ /home/user/.ssh/config
if bytes.Count(b, []byte{'\n'}) == 2 {
b = nil
}
return b, nil
}
+62
View File
@@ -0,0 +1,62 @@
package cli
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
// This test tries to mimic the behavior of OpenSSH
// when executing e.g. a ProxyCommand.
func Test_sshConfigExecEscape(t *testing.T) {
t.Parallel()
tests := []struct {
name string
path string
wantErr bool
windows bool
}{
{"no spaces", "simple", false, true},
{"spaces", "path with spaces", false, true},
{"quotes", "path with \"quotes\"", false, false},
{"backslashes", "path with \\backslashes", false, false},
{"tabs", "path with \ttabs", false, false},
{"newline fails", "path with \nnewline", true, false},
}
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.")
}
dir := filepath.Join(t.TempDir(), tt.path)
err := os.MkdirAll(dir, 0o755)
require.NoError(t, err)
bin := filepath.Join(dir, "coder")
contents := []byte("#!/bin/sh\necho yay\n")
err = os.WriteFile(bin, contents, 0o755) //nolint:gosec
require.NoError(t, err)
escaped, err := sshConfigExecEscape(bin)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
b, err := exec.Command("/bin/sh", "-c", escaped).CombinedOutput() //nolint:gosec
require.NoError(t, err)
got := strings.TrimSpace(string(b))
require.Equal(t, "yay", got)
})
}
}
+624 -24
View File
@@ -1,18 +1,25 @@
package cli_test
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/agent"
@@ -24,12 +31,40 @@ import (
"github.com/coder/coder/pty/ptytest"
)
func sshConfigFileName(t *testing.T) (sshConfig string) {
t.Helper()
tmpdir := t.TempDir()
dotssh := filepath.Join(tmpdir, ".ssh")
err := os.Mkdir(dotssh, 0o700)
require.NoError(t, err)
n := filepath.Join(dotssh, "config")
return n
}
func sshConfigFileCreate(t *testing.T, name string, data io.Reader) {
t.Helper()
t.Logf("Writing %s", name)
f, err := os.Create(name)
require.NoError(t, err)
n, err := io.Copy(f, data)
t.Logf("Wrote %d", n)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
}
func sshConfigFileRead(t *testing.T, name string) string {
t.Helper()
b, err := os.ReadFile(name)
require.NoError(t, err)
return string(b)
}
func TestConfigSSH(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
@@ -71,47 +106,56 @@ func TestConfigSSH(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
agentClient := codersdk.New(client.URL)
agentClient.SessionToken = authToken
agentCloser := agent.New(agentClient.ListenWorkspaceAgent, &agent.Options{
Logger: slogtest.Make(t, nil),
agentCloser := agent.New(agent.Options{
FetchMetadata: agentClient.WorkspaceAgentMetadata,
CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet,
Logger: slogtest.Make(t, nil).Named("agent"),
})
t.Cleanup(func() {
defer func() {
_ = agentCloser.Close()
})
tempFile, err := os.CreateTemp(t.TempDir(), "")
require.NoError(t, err)
_ = tempFile.Close()
}()
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
agentConn, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil)
agentConn, err := client.DialWorkspaceAgentTailnet(context.Background(), slog.Logger{}, resources[0].Agents[0].ID)
require.NoError(t, err)
defer agentConn.Close()
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() {
defer func() {
_ = listener.Close()
})
}()
copyDone := make(chan struct{})
go func() {
defer close(copyDone)
var wg sync.WaitGroup
for {
conn, err := listener.Accept()
if err != nil {
return
break
}
ssh, err := agentConn.SSH()
require.NoError(t, err)
go io.Copy(conn, ssh)
go io.Copy(ssh, conn)
assert.NoError(t, err)
wg.Add(2)
go func() {
defer wg.Done()
_, _ = io.Copy(conn, ssh)
}()
go func() {
defer wg.Done()
_, _ = io.Copy(ssh, conn)
}()
}
wg.Wait()
}()
t.Cleanup(func() {
_ = listener.Close()
})
sshConfigFile := sshConfigFileName(t)
tcpAddr, valid := listener.Addr().(*net.TCPAddr)
require.True(t, valid)
cmd, root := clitest.New(t, "config-ssh",
"--ssh-option", "HostName "+tcpAddr.IP.String(),
"--ssh-option", "Port "+strconv.Itoa(tcpAddr.Port),
"--ssh-config-file", tempFile.Name(),
"--ssh-config-file", sshConfigFile,
"--skip-proxy-command")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
@@ -121,15 +165,571 @@ func TestConfigSSH(t *testing.T) {
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
assert.NoError(t, err)
}()
matches := []struct {
match, write string
}{
{match: "Continue?", write: "yes"},
}
for _, m := range matches {
pty.ExpectMatch(m.match)
pty.WriteLine(m.write)
}
<-doneChan
t.Log(tempFile.Name())
home := filepath.Dir(filepath.Dir(sshConfigFile))
// #nosec
sshCmd := exec.Command("ssh", "-F", tempFile.Name(), "coder."+workspace.Name, "echo", "test")
sshCmd.Stderr = os.Stderr
sshCmd := exec.Command("ssh", "-F", sshConfigFile, "coder."+workspace.Name, "echo", "test")
pty = ptytest.New(t)
// Set HOME because coder config is included from ~/.ssh/coder.
sshCmd.Env = append(sshCmd.Env, fmt.Sprintf("HOME=%s", home))
sshCmd.Stderr = pty.Output()
data, err := sshCmd.Output()
require.NoError(t, err)
require.Equal(t, "test", strings.TrimSpace(string(data)))
_ = listener.Close()
<-copyDone
}
func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
t.Parallel()
headerStart := strings.Join([]string{
"# ------------START-CODER-----------",
"# This section is managed by coder. DO NOT EDIT.",
"#",
"# You should not hand-edit this section unless you are removing it, all",
"# changes will be lost when running \"coder config-ssh\".",
"#",
}, "\n")
headerEnd := "# ------------END-CODER------------"
baseHeader := strings.Join([]string{
headerStart,
headerEnd,
}, "\n")
type writeConfig struct {
ssh string
}
type wantConfig struct {
ssh string
}
type match struct {
match, write string
}
tests := []struct {
name string
args []string
matches []match
writeConfig writeConfig
wantConfig wantConfig
wantErr bool
}{
{
name: "Config file is created",
matches: []match{
{match: "Continue?", write: "yes"},
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
baseHeader,
"",
}, "\n"),
},
},
{
name: "Section is written after user content",
writeConfig: writeConfig{
ssh: strings.Join([]string{
"Host myhost",
" HostName myhost",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
"Host myhost",
" HostName myhost",
baseHeader,
"",
}, "\n"),
},
matches: []match{
{match: "Continue?", write: "yes"},
},
},
{
name: "Section is not moved on re-run",
writeConfig: writeConfig{
ssh: strings.Join([]string{
"Host myhost",
" HostName myhost",
"",
baseHeader,
"",
"Host otherhost",
" HostName otherhost",
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
"Host myhost",
" HostName myhost",
"",
baseHeader,
"",
"Host otherhost",
" HostName otherhost",
"",
}, "\n"),
},
},
{
name: "Section is not moved on re-run with new options",
writeConfig: writeConfig{
ssh: strings.Join([]string{
"Host myhost",
" HostName myhost",
"",
baseHeader,
"",
"Host otherhost",
" HostName otherhost",
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
"Host myhost",
" HostName myhost",
"",
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
"Host otherhost",
" HostName otherhost",
"",
}, "\n"),
},
args: []string{
"--ssh-option", "ForwardAgent=yes",
},
matches: []match{
{match: "Use new options?", write: "yes"},
{match: "Continue?", write: "yes"},
},
},
{
name: "Adds newline at EOF",
writeConfig: writeConfig{
ssh: strings.Join([]string{
baseHeader,
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
baseHeader,
"",
}, "\n"),
},
matches: []match{
{match: "Continue?", write: "yes"},
},
},
{
name: "Do not prompt for new options on first run",
writeConfig: writeConfig{
ssh: "",
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
args: []string{"--ssh-option", "ForwardAgent=yes"},
matches: []match{
{match: "Continue?", write: "yes"},
},
},
{
name: "Prompt for new options when there are no previous options",
writeConfig: writeConfig{
ssh: strings.Join([]string{
baseHeader,
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
args: []string{"--ssh-option", "ForwardAgent=yes"},
matches: []match{
{match: "Use new options?", write: "yes"},
{match: "Continue?", write: "yes"},
},
},
{
name: "Prompt for new options when there are previous options",
writeConfig: writeConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
baseHeader,
"",
}, "\n"),
},
matches: []match{
{match: "Use new options?", write: "yes"},
{match: "Continue?", write: "yes"},
},
},
{
name: "No prompt on no changes",
writeConfig: writeConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
args: []string{"--ssh-option", "ForwardAgent=yes"},
},
{
name: "No changes when continue = no",
writeConfig: writeConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
args: []string{"--ssh-option", "ForwardAgent=no"},
matches: []match{
{match: "Use new options?", write: "yes"},
{match: "Continue?", write: "no"},
},
},
{
name: "Do not prompt when using --yes",
writeConfig: writeConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
// Last options overwritten.
baseHeader,
"",
}, "\n"),
},
args: []string{"--yes"},
},
{
name: "Do not prompt for new options when prev opts flag is set",
writeConfig: writeConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
headerStart,
"# Last config-ssh options:",
"# :ssh-option=ForwardAgent=yes",
"#",
headerEnd,
"",
}, "\n"),
},
args: []string{
"--use-previous-options",
"--yes",
},
},
{
name: "Do not overwrite config when using --dry-run",
writeConfig: writeConfig{
ssh: strings.Join([]string{
baseHeader,
"",
}, "\n"),
},
wantConfig: wantConfig{
ssh: strings.Join([]string{
baseHeader,
"",
}, "\n"),
},
args: []string{
"--ssh-option", "ForwardAgent=yes",
"--dry-run",
"--yes",
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var (
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
)
// Prepare ssh config files.
sshConfigName := sshConfigFileName(t)
if tt.writeConfig.ssh != "" {
sshConfigFileCreate(t, sshConfigName, strings.NewReader(tt.writeConfig.ssh))
}
args := []string{
"config-ssh",
"--ssh-config-file", sshConfigName,
}
args = append(args, tt.args...)
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
done := tGo(t, func() {
err := cmd.Execute()
if !tt.wantErr {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
for _, m := range tt.matches {
pty.ExpectMatch(m.match)
pty.WriteLine(m.write)
}
<-done
if tt.wantConfig.ssh != "" {
got := sshConfigFileRead(t, sshConfigName)
assert.Equal(t, tt.wantConfig.ssh, got)
}
})
}
}
func TestConfigSSH_Hostnames(t *testing.T) {
t.Parallel()
type resourceSpec struct {
name string
agents []string
}
tests := []struct {
name string
resources []resourceSpec
expected []string
}{
{
name: "one resource with one agent",
resources: []resourceSpec{
{name: "foo", agents: []string{"agent1"}},
},
expected: []string{"coder.@", "coder.@.agent1"},
},
{
name: "one resource with two agents",
resources: []resourceSpec{
{name: "foo", agents: []string{"agent1", "agent2"}},
},
expected: []string{"coder.@.agent1", "coder.@.agent2"},
},
{
name: "two resources with one agent",
resources: []resourceSpec{
{name: "foo", agents: []string{"agent1"}},
{name: "bar"},
},
expected: []string{"coder.@", "coder.@.agent1"},
},
{
name: "two resources with two agents",
resources: []resourceSpec{
{name: "foo", agents: []string{"agent1"}},
{name: "bar", agents: []string{"agent2"}},
},
expected: []string{"coder.@.agent1", "coder.@.agent2"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var resources []*proto.Resource
for _, resourceSpec := range tt.resources {
resource := &proto.Resource{
Name: resourceSpec.name,
Type: "aws_instance",
}
for _, agentName := range resourceSpec.agents {
resource.Agents = append(resource.Agents, &proto.Agent{
Id: uuid.NewString(),
Name: agentName,
})
}
resources = append(resources, resource)
}
provisionResponse := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: resources,
},
},
}}
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
// authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: provisionResponse,
Provision: provisionResponse,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
sshConfigFile := sshConfigFileName(t)
cmd, root := clitest.New(t, "config-ssh", "--ssh-config-file", sshConfigFile)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
matches := []struct {
match, write string
}{
{match: "Continue?", write: "yes"},
}
for _, m := range matches {
pty.ExpectMatch(m.match)
pty.WriteLine(m.write)
}
<-doneChan
var expectedHosts []string
for _, hostnamePattern := range tt.expected {
hostname := strings.ReplaceAll(hostnamePattern, "@", workspace.Name)
expectedHosts = append(expectedHosts, hostname)
}
hosts := sshConfigFileParseHosts(t, sshConfigFile)
require.ElementsMatch(t, expectedHosts, hosts)
})
}
}
// sshConfigFileParseHosts reads a file in the format of .ssh/config and extracts
// the hostnames that are listed in "Host" directives.
func sshConfigFileParseHosts(t *testing.T, name string) []string {
t.Helper()
b, err := os.ReadFile(name)
require.NoError(t, err)
var result []string
lineScanner := bufio.NewScanner(bytes.NewBuffer(b))
for lineScanner.Scan() {
line := lineScanner.Text()
line = strings.TrimSpace(line)
tokenScanner := bufio.NewScanner(bytes.NewBufferString(line))
tokenScanner.Split(bufio.ScanWords)
ok := tokenScanner.Scan()
if ok && tokenScanner.Text() == "Host" {
for tokenScanner.Scan() {
result = append(result, tokenScanner.Text())
}
}
}
return result
}
+6
View File
@@ -0,0 +1,6 @@
package cli
const (
timeFormat = "3:04PM MST"
dateFormat = "Jan 2, 2006"
)
+155 -69
View File
@@ -2,6 +2,7 @@ package cli
import (
"fmt"
"io"
"time"
"github.com/spf13/cobra"
@@ -10,21 +11,24 @@ import (
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
)
func create() *cobra.Command {
var (
workspaceName string
parameterFile string
templateName string
startAt string
stopAfter time.Duration
workspaceName string
)
cmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "create [name]",
Short: "Create a workspace from a template",
Short: "Create a workspace",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
client, err := CreateClient(cmd)
if err != nil {
return err
}
@@ -42,7 +46,7 @@ func create() *cobra.Command {
workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Specify a name for your workspace:",
Validate: func(workspaceName string) error {
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, workspaceName)
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
if err == nil {
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
}
@@ -54,7 +58,7 @@ func create() *cobra.Command {
}
}
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, workspaceName)
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
if err == nil {
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
}
@@ -69,7 +73,7 @@ func create() *cobra.Command {
}
slices.SortFunc(templates, func(a, b codersdk.Template) bool {
return a.WorkspaceOwnerCount > b.WorkspaceOwnerCount
return a.ActiveUserCount > b.ActiveUserCount
})
templateNames := make([]string, 0, len(templates))
@@ -78,13 +82,13 @@ func create() *cobra.Command {
for _, template := range templates {
templateName := template.Name
if template.WorkspaceOwnerCount > 0 {
developerText := "developer"
if template.WorkspaceOwnerCount != 1 {
developerText = "developers"
}
templateName += cliui.Styles.Placeholder.Render(fmt.Sprintf(" (used by %d %s)", template.WorkspaceOwnerCount, developerText))
if template.ActiveUserCount > 0 {
templateName += cliui.Styles.Placeholder.Render(
fmt.Sprintf(
" (used by %s)",
formatActiveDevelopers(template.ActiveUserCount),
),
)
}
templateNames = append(templateNames, templateName)
@@ -108,47 +112,20 @@ func create() *cobra.Command {
}
}
templateVersion, err := client.TemplateVersion(cmd.Context(), template.ActiveVersionID)
if err != nil {
return err
}
parameterSchemas, err := client.TemplateVersionSchema(cmd.Context(), templateVersion.ID)
if err != nil {
return err
}
printed := false
parameters := make([]codersdk.CreateParameterRequest, 0)
for _, parameterSchema := range parameterSchemas {
if !parameterSchema.AllowOverrideSource {
continue
}
if !printed {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has customizable parameters. Values can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
printed = true
}
value, err := cliui.ParameterSchema(cmd, parameterSchema)
var schedSpec *string
if startAt != "" {
sched, err := parseCLISchedule(startAt)
if err != nil {
return err
}
parameters = append(parameters, codersdk.CreateParameterRequest{
Name: parameterSchema.Name,
SourceValue: value,
SourceScheme: database.ParameterSourceSchemeData,
DestinationScheme: parameterSchema.DefaultDestinationScheme,
})
schedSpec = ptr.Ref(sched.String())
}
_, _ = fmt.Fprintln(cmd.OutOrStdout())
resources, err := client.TemplateVersionResources(cmd.Context(), templateVersion.ID)
if err != nil {
return err
}
err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{
WorkspaceName: workspaceName,
// Since agent's haven't connected yet, hiding this makes more sense.
HideAgentState: true,
Title: "Workspace Preview",
parameters, err := prepWorkspaceBuild(cmd, client, prepWorkspaceBuildArgs{
Template: template,
ExistingParams: []codersdk.Parameter{},
ParameterFile: parameterFile,
NewWorkspaceName: workspaceName,
})
if err != nil {
return err
@@ -162,38 +139,147 @@ func create() *cobra.Command {
return err
}
before := time.Now()
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: workspaceName,
ParameterValues: parameters,
after := time.Now()
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.Me, codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: workspaceName,
AutostartSchedule: schedSpec,
TTLMillis: ptr.Ref(stopAfter.Milliseconds()),
ParameterValues: parameters,
})
if err != nil {
return err
}
err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID, before)
err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID, after)
if err != nil {
return err
}
resources, err = client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID)
if err != nil {
return err
}
err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{
WorkspaceName: workspaceName,
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "The %s workspace has been created!\n", cliui.Styles.Keyword.Render(workspace.Name))
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace has been created at %s!\n", cliui.Styles.Keyword.Render(workspace.Name), cliui.Styles.DateTimeStamp.Render(time.Now().Format(time.Stamp)))
return nil
},
}
cliui.AllowSkipPrompt(cmd)
cliflag.StringVarP(cmd.Flags(), &templateName, "template", "t", "CODER_TEMPLATE_NAME", "", "Specify a template name.")
cliflag.StringVarP(cmd.Flags(), &parameterFile, "parameter-file", "", "CODER_PARAMETER_FILE", "", "Specify a file path with parameter values.")
cliflag.StringVarP(cmd.Flags(), &startAt, "start-at", "", "CODER_WORKSPACE_START_AT", "", "Specify the workspace autostart schedule. Check `coder schedule start --help` for the syntax.")
cliflag.DurationVarP(cmd.Flags(), &stopAfter, "stop-after", "", "CODER_WORKSPACE_STOP_AFTER", 8*time.Hour, "Specify a duration after which the workspace should shut down (e.g. 8h).")
return cmd
}
type prepWorkspaceBuildArgs struct {
Template codersdk.Template
ExistingParams []codersdk.Parameter
ParameterFile string
NewWorkspaceName string
}
// prepWorkspaceBuild will ensure a workspace build will succeed on the latest template version.
// Any missing params will be prompted to the user.
func prepWorkspaceBuild(cmd *cobra.Command, client *codersdk.Client, args prepWorkspaceBuildArgs) ([]codersdk.CreateParameterRequest, error) {
ctx := cmd.Context()
templateVersion, err := client.TemplateVersion(ctx, args.Template.ActiveVersionID)
if err != nil {
return nil, err
}
parameterSchemas, err := client.TemplateVersionSchema(ctx, templateVersion.ID)
if err != nil {
return nil, err
}
// parameterMapFromFile can be nil if parameter file is not specified
var parameterMapFromFile map[string]string
useParamFile := false
if args.ParameterFile != "" {
useParamFile = true
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Attempting to read the variables from the parameter file.")+"\r\n")
parameterMapFromFile, err = createParameterMapFromFile(args.ParameterFile)
if err != nil {
return nil, err
}
}
disclaimerPrinted := false
parameters := make([]codersdk.CreateParameterRequest, 0)
PromptParamLoop:
for _, parameterSchema := range parameterSchemas {
if !parameterSchema.AllowOverrideSource {
continue
}
if !disclaimerPrinted {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has customizable parameters. Values can be changed after create, but may have unintended side effects (like data loss).")+"\r\n")
disclaimerPrinted = true
}
// Param file is all or nothing
if !useParamFile {
for _, e := range args.ExistingParams {
if e.Name == parameterSchema.Name {
// If the param already exists, we do not need to prompt it again.
// The workspace scope will reuse params for each build.
continue PromptParamLoop
}
}
}
parameterValue, err := getParameterValueFromMapOrInput(cmd, parameterMapFromFile, parameterSchema)
if err != nil {
return nil, err
}
parameters = append(parameters, codersdk.CreateParameterRequest{
Name: parameterSchema.Name,
SourceValue: parameterValue,
SourceScheme: codersdk.ParameterSourceSchemeData,
DestinationScheme: parameterSchema.DefaultDestinationScheme,
})
}
_, _ = fmt.Fprintln(cmd.OutOrStdout())
// Run a dry-run with the given parameters to check correctness
after := time.Now()
dryRun, err := client.CreateTemplateVersionDryRun(cmd.Context(), templateVersion.ID, codersdk.CreateTemplateVersionDryRunRequest{
WorkspaceName: args.NewWorkspaceName,
ParameterValues: parameters,
})
if err != nil {
return nil, xerrors.Errorf("begin workspace dry-run: %w", err)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Planning workspace...")
err = cliui.ProvisionerJob(cmd.Context(), cmd.OutOrStdout(), cliui.ProvisionerJobOptions{
Fetch: func() (codersdk.ProvisionerJob, error) {
return client.TemplateVersionDryRun(cmd.Context(), templateVersion.ID, dryRun.ID)
},
Cancel: func() error {
return client.CancelTemplateVersionDryRun(cmd.Context(), templateVersion.ID, dryRun.ID)
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
return client.TemplateVersionDryRunLogsAfter(cmd.Context(), templateVersion.ID, dryRun.ID, after)
},
// Don't show log output for the dry-run unless there's an error.
Silent: true,
})
if err != nil {
// TODO (Dean): reprompt for parameter values if we deem it to
// be a validation error
return nil, xerrors.Errorf("dry-run workspace: %w", err)
}
resources, err := client.TemplateVersionDryRunResources(cmd.Context(), templateVersion.ID, dryRun.ID)
if err != nil {
return nil, xerrors.Errorf("get workspace dry-run resources: %w", err)
}
err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{
WorkspaceName: args.NewWorkspaceName,
// Since agents haven't connected yet, hiding this makes more sense.
HideAgentState: true,
Title: "Workspace Preview",
})
if err != nil {
return nil, err
}
return parameters, nil
}
+270 -97
View File
@@ -1,87 +1,112 @@
package cli_test
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)
func TestCreate(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
Provision: provisionCompleteWithAgent,
ProvisionDryRun: provisionCompleteWithAgent,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
args := []string{
"create",
"my-workspace",
"--template", template.Name,
"--start-at", "9:30AM Mon-Fri US/Central",
"--stop-after", "8h",
}
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
matches := []struct {
match string
write string
}{
{match: "compute.main"},
{match: "smith (linux, i386)"},
{match: "Confirm create", write: "yes"},
}
for _, m := range matches {
pty.ExpectMatch(m.match)
if len(m.write) > 0 {
pty.WriteLine(m.write)
}
}
<-doneChan
ws, err := client.WorkspaceByOwnerAndName(context.Background(), "testuser", "my-workspace", codersdk.WorkspaceOptions{})
if assert.NoError(t, err, "expected workspace to be created") {
assert.Equal(t, ws.TemplateName, template.Name)
if assert.NotNil(t, ws.AutostartSchedule) {
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
}
if assert.NotNil(t, ws.TTLMillis) {
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
}
}
})
t.Run("CreateFromListWithSkip", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "my-workspace", "-y")
member := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
clitest.SetupConfig(t, member, root)
cmdCtx, done := context.WithTimeout(context.Background(), testutil.WaitLong)
go func() {
defer done()
err := cmd.ExecuteContext(cmdCtx)
assert.NoError(t, err)
}()
// No pty interaction needed since we use the -y skip prompt flag
<-cmdCtx.Done()
require.ErrorIs(t, cmdCtx.Err(), context.Canceled)
})
t.Run("FromNothing", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "my-workspace", "--template", template.Name)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
}()
matches := []string{
"Confirm create", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
<-doneChan
})
t.Run("CreateFromList", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "my-workspace")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
}()
matches := []string{
"Confirm create", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
<-doneChan
})
t.Run("FromNothing", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
@@ -91,7 +116,7 @@ func TestCreate(t *testing.T) {
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
assert.NoError(t, err)
}()
matches := []string{
"Specify a name", "my-workspace",
@@ -104,37 +129,116 @@ func TestCreate(t *testing.T) {
pty.WriteLine(value)
}
<-doneChan
ws, err := client.WorkspaceByOwnerAndName(cmd.Context(), "testuser", "my-workspace", codersdk.WorkspaceOptions{})
if assert.NoError(t, err, "expected workspace to be created") {
assert.Equal(t, ws.TemplateName, template.Name)
assert.Nil(t, ws.AutostartSchedule, "expected workspace autostart schedule to be nil")
}
})
t.Run("WithParameter", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
defaultValue := "something"
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: []*proto.ParameterSchema{{
AllowOverrideSource: true,
Name: "region",
Description: "description",
DefaultSource: &proto.ParameterSource{
Scheme: proto.ParameterSource_DATA,
Value: "something",
},
DefaultDestination: &proto.ParameterDestination{
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
},
}},
},
},
}},
Parse: createTestParseResponseWithDefault(defaultValue),
Provision: echo.ProvisionComplete,
ProvisionDryRun: echo.ProvisionComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
matches := []string{
"Specify a name", "my-workspace",
fmt.Sprintf("Enter a value (default: %q):", defaultValue), "bingo",
"Enter a value:", "boingo",
"Confirm create?", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
<-doneChan
})
t.Run("WithParameterFileContainingTheValue", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
defaultValue := "something"
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: createTestParseResponseWithDefault(defaultValue),
Provision: echo.ProvisionComplete,
ProvisionDryRun: echo.ProvisionComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
tempDir := t.TempDir()
removeTmpDirUntilSuccessAfterTest(t, tempDir)
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
_, _ = parameterFile.WriteString("region: \"bingo\"\nusername: \"boingo\"")
cmd, root := clitest.New(t, "create", "", "--parameter-file", parameterFile.Name())
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.NoError(t, err)
}()
matches := []string{
"Specify a name", "my-workspace",
"Confirm create?", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
<-doneChan
})
t.Run("WithParameterFileNotContainingTheValue", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
defaultValue := "something"
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: createTestParseResponseWithDefault(defaultValue),
Provision: echo.ProvisionComplete,
ProvisionDryRun: echo.ProvisionComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "")
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
tempDir := t.TempDir()
removeTmpDirUntilSuccessAfterTest(t, tempDir)
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
_, _ = parameterFile.WriteString("zone: \"bananas\"")
cmd, root := clitest.New(t, "create", "my-workspace", "--template", template.Name, "--parameter-file", parameterFile.Name())
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
@@ -143,19 +247,88 @@ func TestCreate(t *testing.T) {
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
assert.EqualError(t, err, "Parameter value absent in parameter file for \"region\"!")
}()
matches := []string{
"Specify a name", "my-workspace",
"Enter a value", "bananas",
"Confirm create?", "yes",
}
for i := 0; i < len(matches); i += 2 {
match := matches[i]
value := matches[i+1]
pty.ExpectMatch(match)
pty.WriteLine(value)
}
<-doneChan
})
t.Run("FailedDryRun", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: echo.ParameterSuccess,
},
},
}},
ProvisionDryRun: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
},
},
})
tempDir := t.TempDir()
parameterFile, err := os.CreateTemp(tempDir, "testParameterFile*.yaml")
require.NoError(t, err)
defer parameterFile.Close()
_, _ = parameterFile.WriteString(fmt.Sprintf("%s: %q", echo.ParameterExecKey, echo.ParameterError("fail")))
// The template import job should end up failed, but we need it to be
// succeeded so the dry-run can begin.
version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
require.Equal(t, codersdk.ProvisionerJobSucceeded, version.Job.Status, "job is not failed")
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
cmd, root := clitest.New(t, "create", "test", "--parameter-file", parameterFile.Name())
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
err = cmd.Execute()
require.Error(t, err)
require.ErrorContains(t, err, "dry-run workspace")
})
}
func createTestParseResponseWithDefault(defaultValue string) []*proto.Parse_Response {
return []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: []*proto.ParameterSchema{
{
AllowOverrideSource: true,
Name: "region",
Description: "description 1",
DefaultSource: &proto.ParameterSource{
Scheme: proto.ParameterSource_DATA,
Value: defaultValue,
},
DefaultDestination: &proto.ParameterDestination{
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
},
},
{
AllowOverrideSource: true,
Name: "username",
Description: "description 2",
DefaultSource: &proto.ParameterSource{
Scheme: proto.ParameterSource_DATA,
// No default value
Value: "",
},
DefaultDestination: &proto.ParameterDestination{
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
},
},
},
},
},
}}
}
+50 -19
View File
@@ -1,44 +1,75 @@
package cli
import (
"fmt"
"time"
"github.com/spf13/cobra"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
)
// nolint
func delete() *cobra.Command {
return &cobra.Command{
func deleteWorkspace() *cobra.Command {
var orphan bool
cmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "delete <workspace>",
Short: "Delete a workspace",
Aliases: []string{"rm"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
workspace, err := client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, args[0])
if err != nil {
return err
}
before := time.Now()
build, err := client.CreateWorkspaceBuild(cmd.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: database.WorkspaceTransitionDelete,
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Confirm delete workspace?",
IsConfirm: true,
Default: cliui.ConfirmNo,
})
if err != nil {
return err
}
return cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, build.ID, before)
client, err := CreateClient(cmd)
if err != nil {
return err
}
workspace, err := namedWorkspace(cmd, client, args[0])
if err != nil {
return err
}
var state []byte
if orphan {
cliui.Warn(
cmd.ErrOrStderr(),
"Orphaning workspace requires template edit permission",
)
}
before := time.Now()
build, err := client.CreateWorkspaceBuild(cmd.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionDelete,
ProvisionerState: state,
Orphan: orphan,
})
if err != nil {
return err
}
err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, build.ID, before)
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace has been deleted at %s!\n", cliui.Styles.Keyword.Render(workspace.Name), cliui.Styles.DateTimeStamp.Render(time.Now().Format(time.Stamp)))
return nil
},
}
cmd.Flags().BoolVar(&orphan, "orphan", false,
`Delete a workspace without deleting its resources. This can delete a
workspace in a broken state, but may also lead to unaccounted cloud resources.`,
)
cliui.AllowSkipPrompt(cmd)
return cmd
}
+125
View File
@@ -0,0 +1,125 @@
package cli_test
import (
"context"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/pty/ptytest"
)
func TestDelete(t *testing.T) {
t.Parallel()
t.Run("WithParameter", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, root := clitest.New(t, "delete", workspace.Name, "-y")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
// When running with the race detector on, we sometimes get an EOF.
if err != nil {
assert.ErrorIs(t, err, io.EOF)
}
}()
pty.ExpectMatch("Cleaning Up")
<-doneChan
})
t.Run("Orphan", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, root := clitest.New(t, "delete", workspace.Name, "-y", "--orphan")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
// When running with the race detector on, we sometimes get an EOF.
if err != nil {
assert.ErrorIs(t, err, io.EOF)
}
}()
pty.ExpectMatch("Cleaning Up")
<-doneChan
})
t.Run("DifferentUser", func(t *testing.T) {
t.Parallel()
adminClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
adminUser := coderdtest.CreateFirstUser(t, adminClient)
orgID := adminUser.OrganizationID
client := coderdtest.CreateAnotherUser(t, adminClient, orgID)
user, err := client.User(context.Background(), codersdk.Me)
require.NoError(t, err)
version := coderdtest.CreateTemplateVersion(t, adminClient, orgID, nil)
coderdtest.AwaitTemplateVersionJob(t, adminClient, version.ID)
template := coderdtest.CreateTemplate(t, adminClient, orgID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, orgID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, root := clitest.New(t, "delete", user.Username+"/"+workspace.Name, "-y")
clitest.SetupConfig(t, adminClient, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
// When running with the race detector on, we sometimes get an EOF.
if err != nil {
assert.ErrorIs(t, err, io.EOF)
}
}()
pty.ExpectMatch("Cleaning Up")
<-doneChan
workspace, err = client.Workspace(context.Background(), workspace.ID)
require.ErrorContains(t, err, "was deleted")
})
t.Run("InvalidWorkspaceIdentifier", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
cmd, root := clitest.New(t, "delete", "a/b/c", "-y")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.ErrorContains(t, err, "invalid workspace name: \"a/b/c\"")
}()
<-doneChan
})
}
+282
View File
@@ -0,0 +1,282 @@
package cli
import (
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/cli/cliui"
)
func dotfiles() *cobra.Command {
var symlinkDir string
cmd := &cobra.Command{
Use: "dotfiles [git_repo_url]",
Args: cobra.ExactArgs(1),
Short: "Checkout and install a dotfiles repository from a Git URL",
Example: formatExamples(
example{
Description: "Check out and install a dotfiles repository without prompts",
Command: "coder dotfiles --yes git@github.com:example/dotfiles.git",
},
),
RunE: func(cmd *cobra.Command, args []string) error {
var (
dotfilesRepoDir = "dotfiles"
gitRepo = args[0]
cfg = createConfig(cmd)
cfgDir = string(cfg)
dotfilesDir = filepath.Join(cfgDir, dotfilesRepoDir)
// This follows the same pattern outlined by others in the market:
// https://github.com/coder/coder/pull/1696#issue-1245742312
installScriptSet = []string{
"install.sh",
"install",
"bootstrap.sh",
"bootstrap",
"script/bootstrap",
"setup.sh",
"setup",
"script/setup",
}
)
_, _ = fmt.Fprint(cmd.OutOrStdout(), "Checking if dotfiles repository already exists...\n")
dotfilesExists, err := dirExists(dotfilesDir)
if err != nil {
return xerrors.Errorf("checking dir %s: %w", dotfilesDir, err)
}
moved := false
if dotfilesExists {
du, err := cfg.DotfilesURL().Read()
if err != nil && !errors.Is(err, os.ErrNotExist) {
return xerrors.Errorf("reading dotfiles url config: %w", err)
}
// if the git url has changed we create a backup and clone fresh
if gitRepo != du {
backupDir := fmt.Sprintf("%s_backup_%s", dotfilesDir, time.Now().Format(time.RFC3339))
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: fmt.Sprintf("The dotfiles URL has changed from %q to %q.\n Coder will backup the existing repo to %s.\n\n Continue?", du, gitRepo, backupDir),
IsConfirm: true,
})
if err != nil {
return err
}
err = os.Rename(dotfilesDir, backupDir)
if err != nil {
return xerrors.Errorf("renaming dir %s: %w", dotfilesDir, err)
}
_, _ = fmt.Fprint(cmd.OutOrStdout(), "Done backup up dotfiles.\n")
dotfilesExists = false
moved = true
}
}
var (
gitCmdDir string
subcommands []string
promptText string
)
if dotfilesExists {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Found dotfiles repository at %s\n", dotfilesDir)
gitCmdDir = dotfilesDir
subcommands = []string{"pull", "--ff-only"}
promptText = fmt.Sprintf("Pulling latest from %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
} else {
if !moved {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Did not find dotfiles repository at %s\n", dotfilesDir)
}
gitCmdDir = cfgDir
subcommands = []string{"clone", args[0], dotfilesRepoDir}
promptText = fmt.Sprintf("Cloning %s into directory %s.\n\n Continue?", gitRepo, dotfilesDir)
}
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: promptText,
IsConfirm: true,
})
if err != nil {
return err
}
// ensure command dir exists
err = os.MkdirAll(gitCmdDir, 0750)
if err != nil {
return xerrors.Errorf("ensuring dir at %s: %w", gitCmdDir, err)
}
// check if git ssh command already exists so we can just wrap it
gitsshCmd := os.Getenv("GIT_SSH_COMMAND")
if gitsshCmd == "" {
gitsshCmd = "ssh"
}
// clone or pull repo
c := exec.CommandContext(cmd.Context(), "git", subcommands...)
c.Dir = gitCmdDir
c.Env = append(os.Environ(), fmt.Sprintf(`GIT_SSH_COMMAND=%s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no`, gitsshCmd))
c.Stdout = cmd.OutOrStdout()
c.Stderr = cmd.ErrOrStderr()
err = c.Run()
if err != nil {
if !dotfilesExists {
return err
}
// if the repo exists we soft fail the update operation and try to continue
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render("Failed to update repo, continuing..."))
}
// save git repo url so we can detect changes next time
err = cfg.DotfilesURL().Write(gitRepo)
if err != nil {
return xerrors.Errorf("writing dotfiles url config: %w", err)
}
files, err := os.ReadDir(dotfilesDir)
if err != nil {
return xerrors.Errorf("reading files in dir %s: %w", dotfilesDir, err)
}
var dotfiles []string
for _, f := range files {
// make sure we do not copy `.git*` files
if strings.HasPrefix(f.Name(), ".") && !strings.HasPrefix(f.Name(), ".git") {
dotfiles = append(dotfiles, f.Name())
}
}
script := findScript(installScriptSet, files)
if script != "" {
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: fmt.Sprintf("Running install script %s.\n\n Continue?", script),
IsConfirm: true,
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Running %s...\n", script)
// it is safe to use a variable command here because it's from
// a filtered list of pre-approved install scripts
// nolint:gosec
scriptCmd := exec.CommandContext(cmd.Context(), filepath.Join(dotfilesDir, script))
scriptCmd.Dir = dotfilesDir
scriptCmd.Stdout = cmd.OutOrStdout()
scriptCmd.Stderr = cmd.ErrOrStderr()
err = scriptCmd.Run()
if err != nil {
return xerrors.Errorf("running %s: %w", script, err)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Dotfiles installation complete.")
return nil
}
if len(dotfiles) == 0 {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "No install scripts or dotfiles found, nothing to do.")
return nil
}
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "No install scripts found, symlinking dotfiles to home directory.\n\n Continue?",
IsConfirm: true,
})
if err != nil {
return err
}
if symlinkDir == "" {
symlinkDir, err = os.UserHomeDir()
if err != nil {
return xerrors.Errorf("getting user home: %w", err)
}
}
for _, df := range dotfiles {
from := filepath.Join(dotfilesDir, df)
to := filepath.Join(symlinkDir, df)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Symlinking %s to %s...\n", from, to)
isRegular, err := isRegular(to)
if err != nil {
return xerrors.Errorf("checking symlink for %s: %w", to, err)
}
// move conflicting non-symlink files to file.ext.bak
if isRegular {
backup := fmt.Sprintf("%s.bak", to)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Moving %s to %s...\n", to, backup)
err = os.Rename(to, backup)
if err != nil {
return xerrors.Errorf("renaming dir %s: %w", to, err)
}
}
err = os.Symlink(from, to)
if err != nil {
return xerrors.Errorf("symlinking %s to %s: %w", from, to, err)
}
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Dotfiles installation complete.")
return nil
},
}
cliui.AllowSkipPrompt(cmd)
cliflag.StringVarP(cmd.Flags(), &symlinkDir, "symlink-dir", "", "CODER_SYMLINK_DIR", "", "Specifies the directory for the dotfiles symlink destinations. If empty will use $HOME.")
return cmd
}
// dirExists checks if the path exists and is a directory.
func dirExists(name string) (bool, error) {
fi, err := os.Stat(name)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, xerrors.Errorf("stat dir: %w", err)
}
if !fi.IsDir() {
return false, xerrors.New("exists but not a directory")
}
return true, nil
}
// findScript will find the first file that matches the script set.
func findScript(scriptSet []string, files []fs.DirEntry) string {
for _, i := range scriptSet {
for _, f := range files {
if f.Name() == i {
return f.Name()
}
}
}
return ""
}
// isRegular detects if the file exists and is not a symlink.
func isRegular(to string) (bool, error) {
fi, err := os.Lstat(to)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
return false, xerrors.Errorf("lstat %s: %w", to, err)
}
return fi.Mode().IsRegular(), nil
}
+141
View File
@@ -0,0 +1,141 @@
package cli_test
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/cli/config"
"github.com/coder/coder/cryptorand"
)
// nolint:paralleltest
func TestDotfiles(t *testing.T) {
t.Run("MissingArg", func(t *testing.T) {
cmd, _ := clitest.New(t, "dotfiles")
err := cmd.Execute()
require.Error(t, err)
})
t.Run("NoInstallScript", func(t *testing.T) {
_, root := clitest.New(t)
testRepo := testGitRepo(t, root)
// nolint:gosec
err := os.WriteFile(filepath.Join(testRepo, ".bashrc"), []byte("wow"), 0750)
require.NoError(t, err)
c := exec.Command("git", "add", ".bashrc")
c.Dir = testRepo
err = c.Run()
require.NoError(t, err)
c = exec.Command("git", "commit", "-m", `"add .bashrc"`)
c.Dir = testRepo
out, err := c.CombinedOutput()
require.NoError(t, err, string(out))
cmd, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
err = cmd.Execute()
require.NoError(t, err)
b, err := os.ReadFile(filepath.Join(string(root), ".bashrc"))
require.NoError(t, err)
require.Equal(t, string(b), "wow")
})
t.Run("InstallScript", func(t *testing.T) {
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)
// nolint:gosec
err := os.WriteFile(filepath.Join(testRepo, "install.sh"), []byte("#!/bin/bash\necho wow > "+filepath.Join(string(root), ".bashrc")), 0750)
require.NoError(t, err)
c := exec.Command("git", "add", "install.sh")
c.Dir = testRepo
err = c.Run()
require.NoError(t, err)
c = exec.Command("git", "commit", "-m", `"add install.sh"`)
c.Dir = testRepo
err = c.Run()
require.NoError(t, err)
cmd, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
err = cmd.Execute()
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("SymlinkBackup", func(t *testing.T) {
_, root := clitest.New(t)
testRepo := testGitRepo(t, root)
// nolint:gosec
err := os.WriteFile(filepath.Join(testRepo, ".bashrc"), []byte("wow"), 0750)
require.NoError(t, err)
// add a conflicting file at destination
// nolint:gosec
err = os.WriteFile(filepath.Join(string(root), ".bashrc"), []byte("backup"), 0750)
require.NoError(t, err)
c := exec.Command("git", "add", ".bashrc")
c.Dir = testRepo
err = c.Run()
require.NoError(t, err)
c = exec.Command("git", "commit", "-m", `"add .bashrc"`)
c.Dir = testRepo
out, err := c.CombinedOutput()
require.NoError(t, err, string(out))
cmd, _ := clitest.New(t, "dotfiles", "--global-config", string(root), "--symlink-dir", string(root), "-y", testRepo)
err = cmd.Execute()
require.NoError(t, err)
b, err := os.ReadFile(filepath.Join(string(root), ".bashrc"))
require.NoError(t, err)
require.Equal(t, string(b), "wow")
// check for backup file
b, err = os.ReadFile(filepath.Join(string(root), ".bashrc.bak"))
require.NoError(t, err)
require.Equal(t, string(b), "backup")
})
}
func testGitRepo(t *testing.T, root config.Root) string {
r, err := cryptorand.String(8)
require.NoError(t, err)
dir := filepath.Join(string(root), fmt.Sprintf("test-repo-%s", r))
err = os.MkdirAll(dir, 0750)
require.NoError(t, err)
c := exec.Command("git", "init")
c.Dir = dir
err = c.Run()
require.NoError(t, err)
c = exec.Command("git", "config", "user.email", "ci@coder.com")
c.Dir = dir
err = c.Run()
require.NoError(t, err)
c = exec.Command("git", "config", "user.name", "C I")
c.Dir = dir
err = c.Run()
require.NoError(t, err)
return dir
}
+121 -4
View File
@@ -1,9 +1,15 @@
package cli
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"github.com/spf13/cobra"
@@ -13,16 +19,30 @@ import (
)
func gitssh() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "gitssh",
Hidden: true,
Short: `Wraps the "ssh" command and uses the coder gitssh key for authentication`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
env := os.Environ()
// Catch interrupt signals to ensure the temporary private
// key file is cleaned up on most cases.
ctx, stop := signal.NotifyContext(ctx, interruptSignals...)
defer stop()
// Early check so errors are reported immediately.
identityFiles, err := parseIdentityFilesForHost(ctx, args, env)
if err != nil {
return err
}
client, err := createAgentClient(cmd)
if err != nil {
return xerrors.Errorf("create agent client: %w", err)
}
key, err := client.AgentGitSSHKey(cmd.Context())
key, err := client.AgentGitSSHKey(ctx)
if err != nil {
return xerrors.Errorf("get agent git ssh token: %w", err)
}
@@ -44,8 +64,23 @@ func gitssh() *cobra.Command {
return xerrors.Errorf("close temp gitsshkey file: %w", err)
}
args = append([]string{"-i", privateKeyFile.Name()}, args...)
c := exec.CommandContext(cmd.Context(), "ssh", args...)
// Append our key, giving precedence to user keys. Note that
// OpenSSH server are typically configured with MaxAuthTries
// set to the default value of 6. This means that only the 6
// first keys can be tried. However, we will assume that if
// a user has configured 6+ keys for a host, they know what
// they're doing. This behavior is critical if a server has
// been configured with MaxAuthTries set to 1.
identityFiles = append(identityFiles, privateKeyFile.Name())
var identityArgs []string
for _, id := range identityFiles {
identityArgs = append(identityArgs, "-i", id)
}
args = append(identityArgs, args...)
c := exec.CommandContext(ctx, "ssh", args...)
c.Env = append(c.Env, env...)
c.Stderr = cmd.ErrOrStderr()
c.Stdout = cmd.OutOrStdout()
c.Stdin = cmd.InOrStdin()
@@ -69,4 +104,86 @@ func gitssh() *cobra.Command {
return nil
},
}
return cmd
}
// fallbackIdentityFiles is the list of identity files SSH tries when
// none have been defined for a host.
var fallbackIdentityFiles = strings.Join([]string{
"identityfile ~/.ssh/id_rsa",
"identityfile ~/.ssh/id_dsa",
"identityfile ~/.ssh/id_ecdsa",
"identityfile ~/.ssh/id_ecdsa_sk",
"identityfile ~/.ssh/id_ed25519",
"identityfile ~/.ssh/id_ed25519_sk",
"identityfile ~/.ssh/id_xmss",
}, "\n")
// parseIdentityFilesForHost uses ssh -G to discern what SSH keys have
// been enabled for the host (via the users SSH config) and returns a
// list of existing identity files.
//
// We do this because when no keys are defined for a host, SSH uses
// fallback keys (see above). However, by passing `-i` to attach our
// private key, we're effectively disabling the fallback keys.
//
// Example invocation:
//
// ssh -G -o SendEnv=GIT_PROTOCOL git@github.com git-upload-pack 'coder/coder'
//
// The extra arguments work without issue and lets us run the command
// as-is without stripping out the excess (git-upload-pack 'coder/coder').
func parseIdentityFilesForHost(ctx context.Context, args, env []string) (identityFiles []string, error error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, xerrors.Errorf("get user home dir failed: %w", err)
}
var outBuf bytes.Buffer
var r io.Reader = &outBuf
args = append([]string{"-G"}, args...)
cmd := exec.CommandContext(ctx, "ssh", args...)
cmd.Env = append(cmd.Env, env...)
cmd.Stdout = &outBuf
cmd.Stderr = io.Discard
err = cmd.Run()
if err != nil {
// If ssh -G failed, the SSH version is likely too old, fallback
// to using the default identity files.
r = strings.NewReader(fallbackIdentityFiles)
}
s := bufio.NewScanner(r)
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "identityfile ") {
id := strings.TrimPrefix(line, "identityfile ")
if strings.HasPrefix(id, "~/") {
id = home + id[1:]
}
// OpenSSH on Windows is weird, it supports using (and does
// use) mixed \ and / in paths.
//
// Example: C:\Users\ZeroCool/.ssh/known_hosts
//
// To check the file existence in Go, though, we want to use
// proper Windows paths.
// OpenSSH is amazing, this will work on Windows too:
// C:\Users\ZeroCool/.ssh/id_rsa
id = filepath.FromSlash(id)
// Only include the identity file if it exists.
if _, err := os.Stat(id); err == nil {
identityFiles = append(identityFiles, id)
}
}
}
if err := s.Err(); err != nil {
// This should never happen, the check is for completeness.
return nil, xerrors.Errorf("scan ssh output: %w", err)
}
return identityFiles, nil
}
+227 -75
View File
@@ -2,8 +2,16 @@ package cli_test
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
@@ -17,92 +25,236 @@ import (
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)
func prepareTestGitSSH(ctx context.Context, t *testing.T) (*codersdk.Client, string, gossh.PublicKey) {
t.Helper()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
ctx, cancel := context.WithCancel(ctx)
defer t.Cleanup(cancel) // Defer so that cancel is the first cleanup.
// get user public key
keypair, err := client.GitSSHKey(ctx, codersdk.Me)
require.NoError(t, err)
//nolint:dogsled
pubkey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(keypair.PublicKey))
require.NoError(t, err)
// setup template
agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "somename",
Type: "someinstance",
Agents: []*proto.Agent{{
Auth: &proto.Agent_Token{
Token: agentToken,
},
}},
}},
},
},
}},
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
// start workspace agent
cmd, root := clitest.New(t, "agent", "--agent-token", agentToken, "--agent-url", client.URL.String())
agentClient := client
clitest.SetupConfig(t, agentClient, root)
errC := make(chan error, 1)
go func() {
errC <- cmd.ExecuteContext(ctx)
}()
t.Cleanup(func() { require.NoError(t, <-errC) })
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
return agentClient, agentToken, pubkey
}
func serveSSHForGitSSH(t *testing.T, handler func(ssh.Session), pubkeys ...gossh.PublicKey) *net.TCPAddr {
t.Helper()
// start ssh server
l, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
t.Cleanup(func() { _ = l.Close() })
serveOpts := []ssh.Option{
ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
for _, pubkey := range pubkeys {
if ssh.KeysEqual(pubkey, key) {
return true
}
}
return false
}),
}
errC := make(chan error, 1)
go func() {
// as long as we get a successful session we don't care if the server errors
errC <- ssh.Serve(l, handler, serveOpts...)
}()
t.Cleanup(func() {
_ = l.Close() // Ensure server shutdown.
<-errC
})
// start ssh session
addr, ok := l.Addr().(*net.TCPAddr)
require.True(t, ok)
return addr
}
func writePrivateKeyToFile(t *testing.T, name string, key *ecdsa.PrivateKey) {
t.Helper()
b, err := x509.MarshalPKCS8PrivateKey(key)
require.NoError(t, err)
b = pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: b,
})
err = os.WriteFile(name, b, 0o600)
require.NoError(t, err)
}
func TestGitSSH(t *testing.T) {
t.Parallel()
t.Run("Dial", func(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
t.Parallel()
// get user public key
keypair, err := client.GitSSHKey(context.Background(), codersdk.Me)
require.NoError(t, err)
publicKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(keypair.PublicKey))
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
// setup provisioner
agentToken := uuid.NewString()
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "somename",
Type: "someinstance",
Agents: []*proto.Agent{{
Auth: &proto.Agent_Token{
Token: agentToken,
},
}},
}},
},
},
}},
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
// start workspace agent
cmd, root := clitest.New(t, "agent", "--agent-token", agentToken, "--agent-url", client.URL.String())
agentClient := client
clitest.SetupConfig(t, agentClient, root)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
go func() {
err := cmd.ExecuteContext(ctx)
require.NoError(t, err)
}()
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
require.NoError(t, err)
dialer, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil)
require.NoError(t, err)
defer dialer.Close()
_, err = dialer.Ping()
require.NoError(t, err)
// start ssh server
l, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
defer l.Close()
publicKeyOption := ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
return ssh.KeysEqual(publicKey, key)
})
client, token, pubkey := prepareTestGitSSH(ctx, t)
var inc int64
go func() {
// as long as we get a successful session we don't care if the server errors
_ = ssh.Serve(l, func(s ssh.Session) {
atomic.AddInt64(&inc, 1)
t.Log("got authenticated session")
err := s.Exit(0)
require.NoError(t, err)
}, publicKeyOption)
}()
errC := make(chan error, 1)
addr := serveSSHForGitSSH(t, func(s ssh.Session) {
atomic.AddInt64(&inc, 1)
t.Log("got authenticated session")
select {
case errC <- s.Exit(0):
default:
t.Error("error channel is full")
}
}, pubkey)
// start ssh session
addr, ok := l.Addr().(*net.TCPAddr)
require.True(t, ok)
// set to agent config dir
cmd, _ = clitest.New(t, "gitssh", "--agent-url", agentClient.URL.String(), "--agent-token", agentToken, "--", fmt.Sprintf("-p%d", addr.Port), "-o", "StrictHostKeyChecking=no", "127.0.0.1")
err = cmd.ExecuteContext(context.Background())
cmd, _ := clitest.New(t,
"gitssh",
"--agent-url", client.URL.String(),
"--agent-token", token,
"--",
fmt.Sprintf("-p%d", addr.Port),
"-o", "StrictHostKeyChecking=no",
"-o", "IdentitiesOnly=yes",
"127.0.0.1",
)
err := cmd.ExecuteContext(ctx)
require.NoError(t, err)
require.EqualValues(t, 1, inc)
err = <-errC
require.NoError(t, err, "error in agent execute")
})
t.Run("Local SSH Keys", func(t *testing.T) {
t.Parallel()
home := t.TempDir()
sshdir := filepath.Join(home, ".ssh")
err := os.MkdirAll(sshdir, 0o700)
require.NoError(t, err)
idFile := filepath.Join(sshdir, "id_ed25519")
privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
localPubkey, err := gossh.NewPublicKey(&privkey.PublicKey)
require.NoError(t, err)
writePrivateKeyToFile(t, idFile, privkey)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
client, token, coderPubkey := prepareTestGitSSH(ctx, t)
authkey := make(chan gossh.PublicKey, 1)
addr := serveSSHForGitSSH(t, func(s ssh.Session) {
t.Logf("authenticated with: %s", gossh.MarshalAuthorizedKey(s.PublicKey()))
select {
case authkey <- s.PublicKey():
default:
t.Error("authkey channel is full")
}
}, localPubkey, coderPubkey)
// Create a new config which sets an identity file.
config := filepath.Join(sshdir, "config")
knownHosts := filepath.Join(sshdir, "known_hosts")
err = os.WriteFile(config, []byte(strings.Join([]string{
"Host mytest",
" HostName 127.0.0.1",
fmt.Sprintf(" Port %d", addr.Port),
" StrictHostKeyChecking no",
" UserKnownHostsFile=" + knownHosts,
" IdentitiesOnly yes",
" IdentityFile=" + idFile,
}, "\n")), 0o600)
require.NoError(t, err)
pty := ptytest.New(t)
cmdArgs := []string{
"gitssh",
"--agent-url", client.URL.String(),
"--agent-token", token,
"--",
"-F", config,
"mytest",
}
// Test authentication via local private key.
cmd, _ := clitest.New(t, cmdArgs...)
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
err = cmd.ExecuteContext(ctx)
require.NoError(t, err)
select {
case key := <-authkey:
require.Equal(t, localPubkey, key)
case <-ctx.Done():
t.Fatal("timeout waiting for auth")
}
// Delete the local private key.
err = os.Remove(idFile)
require.NoError(t, err)
// With the local file deleted, the coder key should be used.
cmd, _ = clitest.New(t, cmdArgs...)
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
err = cmd.ExecuteContext(ctx)
require.NoError(t, err)
select {
case key := <-authkey:
require.Equal(t, coderPubkey, key)
case <-ctx.Done():
t.Fatal("timeout waiting for auth")
}
})
}
+76 -96
View File
@@ -2,42 +2,93 @@ package cli
import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/autobuild/schedule"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
)
type workspaceListRow struct {
Workspace string `table:"workspace"`
Template string `table:"template"`
Status string `table:"status"`
LastBuilt string `table:"last built"`
Outdated bool `table:"outdated"`
StartsAt string `table:"starts at"`
StopsAfter string `table:"stops after"`
}
func workspaceListRowFromWorkspace(now time.Time, usersByID map[uuid.UUID]codersdk.User, workspace codersdk.Workspace) workspaceListRow {
status := codersdk.WorkspaceDisplayStatus(workspace.LatestBuild.Job.Status, workspace.LatestBuild.Transition)
lastBuilt := now.UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
autostartDisplay := "-"
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
if sched, err := schedule.Weekly(*workspace.AutostartSchedule); err == nil {
autostartDisplay = fmt.Sprintf("%s %s (%s)", sched.Time(), sched.DaysOfWeek(), sched.Location())
}
}
autostopDisplay := "-"
if !ptr.NilOrZero(workspace.TTLMillis) {
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
autostopDisplay = durationDisplay(dur)
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.Time.After(now) && status == "Running" {
remaining := time.Until(workspace.LatestBuild.Deadline.Time)
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
}
}
user := usersByID[workspace.OwnerID]
return workspaceListRow{
Workspace: user.Username + "/" + workspace.Name,
Template: workspace.TemplateName,
Status: status,
LastBuilt: durationDisplay(lastBuilt),
Outdated: workspace.Outdated,
StartsAt: autostartDisplay,
StopsAfter: autostopDisplay,
}
}
func list() *cobra.Command {
var (
columns []string
all bool
columns []string
defaultQuery = "owner:me"
searchQuery string
)
cmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "list",
Short: "List all workspaces",
Short: "List workspaces",
Aliases: []string{"ls"},
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
client, err := CreateClient(cmd)
if err != nil {
return err
}
workspaces, err := client.WorkspacesByUser(cmd.Context(), codersdk.Me)
filter := codersdk.WorkspaceFilter{
FilterQuery: searchQuery,
}
if all && searchQuery == defaultQuery {
filter.FilterQuery = ""
}
workspaces, err := client.Workspaces(cmd.Context(), filter)
if err != nil {
return err
}
if len(workspaces) == 0 {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"No workspaces found! Create one:")
_, _ = fmt.Fprintln(cmd.OutOrStdout())
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder create <name>"))
_, _ = fmt.Fprintln(cmd.OutOrStdout())
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Prompt.String()+"No workspaces found! Create one:")
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), " "+cliui.Styles.Code.Render("coder create <name>"))
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
return nil
}
users, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
@@ -49,96 +100,25 @@ func list() *cobra.Command {
usersByID[user.ID] = user
}
tableWriter := cliui.Table()
header := table.Row{"workspace", "template", "status", "last built", "outdated", "autostart", "autostop"}
tableWriter.AppendHeader(header)
tableWriter.SortBy([]table.SortBy{{
Name: "workspace",
}})
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, columns))
for _, workspace := range workspaces {
status := ""
inProgress := false
if workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobRunning ||
workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobCanceling {
inProgress = true
}
switch workspace.LatestBuild.Transition {
case database.WorkspaceTransitionStart:
status = "Running"
if inProgress {
status = "Starting"
}
case database.WorkspaceTransitionStop:
status = "Stopped"
if inProgress {
status = "Stopping"
}
case database.WorkspaceTransitionDelete:
status = "Deleted"
if inProgress {
status = "Deleting"
}
}
if workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobFailed {
status = "Failed"
}
duration := time.Now().UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
if duration > time.Hour {
duration = duration.Truncate(time.Hour)
}
if duration > time.Minute {
duration = duration.Truncate(time.Minute)
}
days := 0
for duration.Hours() > 24 {
days++
duration -= 24 * time.Hour
}
durationDisplay := duration.String()
if days > 0 {
durationDisplay = fmt.Sprintf("%dd%s", days, durationDisplay)
}
if strings.HasSuffix(durationDisplay, "m0s") {
durationDisplay = durationDisplay[:len(durationDisplay)-2]
}
if strings.HasSuffix(durationDisplay, "h0m") {
durationDisplay = durationDisplay[:len(durationDisplay)-2]
}
autostartDisplay := "-"
if workspace.AutostartSchedule != "" {
if sched, err := schedule.Weekly(workspace.AutostartSchedule); err == nil {
autostartDisplay = sched.Cron()
}
}
autostopDisplay := "-"
if workspace.AutostopSchedule != "" {
if sched, err := schedule.Weekly(workspace.AutostopSchedule); err == nil {
autostopDisplay = sched.Cron()
}
}
user := usersByID[workspace.OwnerID]
tableWriter.AppendRow(table.Row{
user.Username + "/" + workspace.Name,
workspace.TemplateName,
status,
durationDisplay,
workspace.Outdated,
autostartDisplay,
autostopDisplay,
})
now := time.Now()
displayWorkspaces := make([]workspaceListRow, len(workspaces))
for i, workspace := range workspaces {
displayWorkspaces[i] = workspaceListRowFromWorkspace(now, usersByID, workspace)
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), tableWriter.Render())
out, err := cliui.DisplayTable(displayWorkspaces, "workspace", columns)
if err != nil {
return err
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
return err
},
}
cmd.Flags().BoolVarP(&all, "all", "a", false,
"Specifies whether all workspaces will be listed or not.")
cmd.Flags().StringArrayVarP(&columns, "column", "c", nil,
"Specify a column to filter in the table.")
cmd.Flags().StringVar(&searchQuery, "search", defaultQuery, "Search for a workspace with a query.")
return cmd
}
+14 -9
View File
@@ -1,22 +1,23 @@
package cli_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)
func TestList(t *testing.T) {
t.Parallel()
t.Run("Single", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@@ -24,17 +25,21 @@ func TestList(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, root := clitest.New(t, "ls")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancelFunc()
done := make(chan any)
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
errC := cmd.ExecuteContext(ctx)
assert.NoError(t, errC)
close(done)
}()
pty.ExpectMatch(workspace.Name)
pty.ExpectMatch("Running")
<-doneChan
pty.ExpectMatch("Started")
cancelFunc()
<-done
})
}
+104 -68
View File
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"os/user"
"path"
"runtime"
"strings"
@@ -16,6 +17,7 @@ import (
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)
@@ -36,9 +38,14 @@ func init() {
}
func login() *cobra.Command {
return &cobra.Command{
var (
email string
username string
password string
)
cmd := &cobra.Command{
Use: "login <url>",
Short: "Authenticate with a Coder deployment",
Short: "Authenticate with Coder deployment",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
rawURL := args[0]
@@ -59,77 +66,100 @@ func login() *cobra.Command {
serverURL.Scheme = "https"
}
client := codersdk.New(serverURL)
client, err := createUnauthenticatedClient(cmd, serverURL)
if err != nil {
return err
}
// Try to check the version of the server prior to logging in.
// It may be useful to warn the user if they are trying to login
// on a very old client.
err = checkVersions(cmd, client)
if err != nil {
// Checking versions isn't a fatal error so we print a warning
// and proceed.
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Warn.Render(err.Error()))
}
hasInitialUser, err := client.HasFirstUser(cmd.Context())
if err != nil {
return xerrors.Errorf("has initial user: %w", err)
return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err)
}
if !hasInitialUser {
if !isTTY(cmd) {
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"Your Coder deployment hasn't been set up!\n")
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Would you like to create the first user?",
Default: "yes",
IsConfirm: true,
})
if errors.Is(err, cliui.Canceled) {
return nil
}
if err != nil {
return err
}
currentUser, err := user.Current()
if err != nil {
return xerrors.Errorf("get current user: %w", err)
}
username, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "What " + cliui.Styles.Field.Render("username") + " would you like?",
Default: currentUser.Username,
})
if errors.Is(err, cliui.Canceled) {
return nil
}
if err != nil {
return xerrors.Errorf("pick username prompt: %w", err)
}
email, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "What's your " + cliui.Styles.Field.Render("email") + "?",
Validate: func(s string) error {
err := validator.New().Var(s, "email")
if err != nil {
return xerrors.New("That's not a valid email address!")
}
return err
},
})
if err != nil {
return xerrors.Errorf("specify email prompt: %w", err)
}
password, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Enter a " + cliui.Styles.Field.Render("password") + ":",
Secret: true,
Validate: cliui.ValidateNotEmpty,
})
if err != nil {
return xerrors.Errorf("specify password prompt: %w", err)
}
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Confirm " + cliui.Styles.Field.Render("password") + ":",
Secret: true,
Validate: func(s string) error {
if s != password {
return xerrors.Errorf("Passwords do not match")
}
if username == "" {
if !isTTY(cmd) {
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
}
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Would you like to create the first user?",
Default: cliui.ConfirmYes,
IsConfirm: true,
})
if errors.Is(err, cliui.Canceled) {
return nil
},
})
if err != nil {
return xerrors.Errorf("confirm password prompt: %w", err)
}
if err != nil {
return err
}
currentUser, err := user.Current()
if err != nil {
return xerrors.Errorf("get current user: %w", err)
}
username, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "What " + cliui.Styles.Field.Render("username") + " would you like?",
Default: currentUser.Username,
})
if errors.Is(err, cliui.Canceled) {
return nil
}
if err != nil {
return xerrors.Errorf("pick username prompt: %w", err)
}
}
if email == "" {
email, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "What's your " + cliui.Styles.Field.Render("email") + "?",
Validate: func(s string) error {
err := validator.New().Var(s, "email")
if err != nil {
return xerrors.New("That's not a valid email address!")
}
return err
},
})
if err != nil {
return xerrors.Errorf("specify email prompt: %w", err)
}
}
if password == "" {
var matching bool
for !matching {
password, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Enter a " + cliui.Styles.Field.Render("password") + ":",
Secret: true,
Validate: cliui.ValidateNotEmpty,
})
if err != nil {
return xerrors.Errorf("specify password prompt: %w", err)
}
confirm, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Confirm " + cliui.Styles.Field.Render("password") + ":",
Secret: true,
})
if err != nil {
return xerrors.Errorf("confirm password prompt: %w", err)
}
matching = confirm == password
if !matching {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render("Passwords do not match"))
}
}
}
_, err = client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{
@@ -164,14 +194,16 @@ func login() *cobra.Command {
cliui.Styles.Paragraph.Render(fmt.Sprintf("Welcome to Coder, %s! You're authenticated.", cliui.Styles.Keyword.Render(username)))+"\n")
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
cliui.Styles.Paragraph.Render("Get started by creating a template: "+cliui.Styles.Code.Render("coder templates create"))+"\n")
cliui.Styles.Paragraph.Render("Get started by creating a template: "+cliui.Styles.Code.Render("coder templates init"))+"\n")
return nil
}
sessionToken, _ := cmd.Flags().GetString(varToken)
if sessionToken == "" {
authURL := *serverURL
authURL.Path = serverURL.Path + "/cli-auth"
// Don't use filepath.Join, we don't want to use the os separator
// for a url.
authURL.Path = path.Join(serverURL.Path, "/cli-auth")
if err := openURL(cmd, authURL.String()); err != nil {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Open the following in your browser:\n\n\t%s\n\n", authURL.String())
} else {
@@ -216,6 +248,10 @@ func login() *cobra.Command {
return nil
},
}
cliflag.StringVarP(cmd.Flags(), &email, "email", "e", "CODER_EMAIL", "", "Specifies an email address to authenticate with.")
cliflag.StringVarP(cmd.Flags(), &username, "username", "u", "CODER_USERNAME", "", "Specifies a username to authenticate with.")
cliflag.StringVarP(cmd.Flags(), &password, "password", "p", "CODER_PASSWORD", "", "Specifies a password to authenticate with.")
return cmd
}
// isWSL determines if coder-cli is running within Windows Subsystem for Linux
+44 -6
View File
@@ -2,11 +2,14 @@ package cli_test
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/pty/ptytest"
)
@@ -21,6 +24,15 @@ func TestLogin(t *testing.T) {
require.Error(t, err)
})
t.Run("InitialUserBadLoginURL", func(t *testing.T) {
t.Parallel()
badLoginURL := "https://fcca2077f06e68aaf9"
root, _ := clitest.New(t, "login", badLoginURL)
err := root.Execute()
errMsg := fmt.Sprintf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser?", badLoginURL)
require.ErrorContains(t, err, errMsg)
})
t.Run("InitialUserTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
@@ -35,7 +47,7 @@ func TestLogin(t *testing.T) {
go func() {
defer close(doneChan)
err := root.Execute()
require.NoError(t, err)
assert.NoError(t, err)
}()
matches := []string{
@@ -55,6 +67,26 @@ func TestLogin(t *testing.T) {
<-doneChan
})
t.Run("InitialUserFlags", 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", client.URL.String(), "--username", "testuser", "--email", "user@coder.com", "--password", "password")
pty := ptytest.New(t)
root.SetIn(pty.Input())
root.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := root.Execute()
assert.NoError(t, err)
}()
pty.ExpectMatch("Welcome to Coder")
<-doneChan
})
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
@@ -71,7 +103,7 @@ func TestLogin(t *testing.T) {
go func() {
defer close(doneChan)
err := root.ExecuteContext(ctx)
require.ErrorIs(t, err, context.Canceled)
assert.NoError(t, err)
}()
matches := []string{
@@ -87,9 +119,15 @@ func TestLogin(t *testing.T) {
pty.ExpectMatch(match)
pty.WriteLine(value)
}
// Validate that we reprompt for matching passwords.
pty.ExpectMatch("Passwords do not match")
pty.ExpectMatch("password") // Re-prompt password.
cancel()
pty.ExpectMatch("Enter a " + cliui.Styles.Field.Render("password"))
pty.WriteLine("pass")
pty.ExpectMatch("Confirm")
pty.WriteLine("pass")
pty.ExpectMatch("Welcome to Coder")
<-doneChan
})
@@ -106,7 +144,7 @@ func TestLogin(t *testing.T) {
go func() {
defer close(doneChan)
err := root.Execute()
require.NoError(t, err)
assert.NoError(t, err)
}()
pty.ExpectMatch("Paste your token here:")
@@ -131,7 +169,7 @@ func TestLogin(t *testing.T) {
defer close(doneChan)
err := root.ExecuteContext(ctx)
// An error is expected in this case, since the login wasn't successful:
require.Error(t, err)
assert.Error(t, err)
}()
pty.ExpectMatch("Paste your token here:")
+77
View File
@@ -0,0 +1,77 @@
package cli
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliui"
)
func logout() *cobra.Command {
cmd := &cobra.Command{
Use: "logout",
Short: "Unauthenticate your local session",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := CreateClient(cmd)
if err != nil {
return err
}
var errors []error
config := createConfig(cmd)
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Are you sure you want to log out?",
IsConfirm: true,
Default: cliui.ConfirmYes,
})
if err != nil {
return err
}
err = client.Logout(cmd.Context())
if err != nil {
errors = append(errors, xerrors.Errorf("logout api: %w", err))
}
err = config.URL().Delete()
// Only throw error if the URL configuration file is present,
// otherwise the user is already logged out, and we proceed
if err != nil && !os.IsNotExist(err) {
errors = append(errors, xerrors.Errorf("remove URL file: %w", err))
}
err = config.Session().Delete()
// Only throw error if the session configuration file is present,
// otherwise the user is already logged out, and we proceed
if err != nil && !os.IsNotExist(err) {
errors = append(errors, xerrors.Errorf("remove session file: %w", err))
}
err = config.Organization().Delete()
// If the organization configuration file is absent, we still proceed
if err != nil && !os.IsNotExist(err) {
errors = append(errors, xerrors.Errorf("remove organization file: %w", err))
}
if len(errors) > 0 {
var errorStringBuilder strings.Builder
for _, err := range errors {
_, _ = fmt.Fprint(&errorStringBuilder, "\t"+err.Error()+"\n")
}
errorString := strings.TrimRight(errorStringBuilder.String(), "\n")
return xerrors.New("Failed to log out.\n" + errorString)
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"You are no longer logged in. You can log in using 'coder login <url>'.\n")
return nil
},
}
cliui.AllowSkipPrompt(cmd)
return cmd
}
+217
View File
@@ -0,0 +1,217 @@
package cli_test
import (
"fmt"
"os"
"regexp"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/cli/config"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/pty/ptytest"
)
func TestLogout(t *testing.T) {
t.Parallel()
t.Run("Logout", func(t *testing.T) {
t.Parallel()
pty := ptytest.New(t)
config := login(t, pty)
// Ensure session files exist.
require.FileExists(t, string(config.URL()))
require.FileExists(t, string(config.Session()))
logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))
logout.SetIn(pty.Input())
logout.SetOut(pty.Output())
go func() {
defer close(logoutChan)
err := logout.Execute()
assert.NoError(t, err)
assert.NoFileExists(t, string(config.URL()))
assert.NoFileExists(t, string(config.Session()))
}()
pty.ExpectMatch("Are you sure you want to log out?")
pty.WriteLine("yes")
pty.ExpectMatch("You are no longer logged in. You can log in using 'coder login <url>'.")
<-logoutChan
})
t.Run("SkipPrompt", func(t *testing.T) {
t.Parallel()
pty := ptytest.New(t)
config := login(t, pty)
// Ensure session files exist.
require.FileExists(t, string(config.URL()))
require.FileExists(t, string(config.Session()))
logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config), "-y")
logout.SetIn(pty.Input())
logout.SetOut(pty.Output())
go func() {
defer close(logoutChan)
err := logout.Execute()
assert.NoError(t, err)
assert.NoFileExists(t, string(config.URL()))
assert.NoFileExists(t, string(config.Session()))
}()
pty.ExpectMatch("You are no longer logged in. You can log in using 'coder login <url>'.")
<-logoutChan
})
t.Run("NoURLFile", func(t *testing.T) {
t.Parallel()
pty := ptytest.New(t)
config := login(t, pty)
// Ensure session files exist.
require.FileExists(t, string(config.URL()))
require.FileExists(t, string(config.Session()))
err := os.Remove(string(config.URL()))
require.NoError(t, err)
logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))
logout.SetIn(pty.Input())
logout.SetOut(pty.Output())
go func() {
defer close(logoutChan)
err := logout.Execute()
assert.EqualError(t, err, "You are not logged in. Try logging in using 'coder login <url>'.")
}()
<-logoutChan
})
t.Run("NoSessionFile", func(t *testing.T) {
t.Parallel()
pty := ptytest.New(t)
config := login(t, pty)
// Ensure session files exist.
require.FileExists(t, string(config.URL()))
require.FileExists(t, string(config.Session()))
err := os.Remove(string(config.Session()))
require.NoError(t, err)
logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))
logout.SetIn(pty.Input())
logout.SetOut(pty.Output())
go func() {
defer close(logoutChan)
err = logout.Execute()
assert.EqualError(t, err, "You are not logged in. Try logging in using 'coder login <url>'.")
}()
<-logoutChan
})
t.Run("CannotDeleteFiles", func(t *testing.T) {
t.Parallel()
pty := ptytest.New(t)
config := login(t, pty)
// Ensure session files exist.
require.FileExists(t, string(config.URL()))
require.FileExists(t, string(config.Session()))
var (
err error
urlFile *os.File
sessionFile *os.File
)
if runtime.GOOS == "windows" {
// Opening the files so Windows does not allow deleting them.
urlFile, err = os.Open(string(config.URL()))
require.NoError(t, err)
sessionFile, err = os.Open(string(config.Session()))
require.NoError(t, err)
} else {
// Changing the permissions to throw error during deletion.
err = os.Chmod(string(config), 0500)
require.NoError(t, err)
}
defer func() {
if runtime.GOOS == "windows" {
// Closing the opened files for cleanup.
err = urlFile.Close()
assert.NoError(t, err)
err = sessionFile.Close()
assert.NoError(t, err)
} else {
// Setting the permissions back for cleanup.
err = os.Chmod(string(config), 0o700)
assert.NoError(t, err)
}
}()
logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))
logout.SetIn(pty.Input())
logout.SetOut(pty.Output())
go func() {
defer close(logoutChan)
err := logout.Execute()
assert.NotNil(t, err)
var errorMessage string
if runtime.GOOS == "windows" {
errorMessage = "The process cannot access the file because it is being used by another process."
} else {
errorMessage = "permission denied"
}
errRegex := regexp.MustCompile(fmt.Sprintf("Failed to log out.\n\tremove URL file: .+: %s\n\tremove session file: .+: %s", errorMessage, errorMessage))
assert.Regexp(t, errRegex, err.Error())
}()
pty.ExpectMatch("Are you sure you want to log out?")
pty.WriteLine("yes")
<-logoutChan
})
}
func login(t *testing.T, pty *ptytest.PTY) config.Root {
t.Helper()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
doneChan := make(chan struct{})
root, cfg := clitest.New(t, "login", "--force-tty", client.URL.String(), "--no-open")
root.SetIn(pty.Input())
root.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := root.Execute()
assert.NoError(t, err)
}()
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken)
pty.ExpectMatch("Welcome to Coder")
<-doneChan
return cfg
}
+57
View File
@@ -0,0 +1,57 @@
package cli
import (
"os"
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
"github.com/spf13/cobra"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)
// Reads a YAML file and populates a string -> string map.
// Throws an error if the file name is empty.
func createParameterMapFromFile(parameterFile string) (map[string]string, error) {
if parameterFile != "" {
parameterMap := make(map[string]string)
parameterFileContents, err := os.ReadFile(parameterFile)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(parameterFileContents, &parameterMap)
if err != nil {
return nil, err
}
return parameterMap, nil
}
return nil, xerrors.Errorf("Parameter file name is not specified")
}
// Returns a parameter value from a given map, if the map exists, else takes input from the user.
// Throws an error if the map exists but does not include a value for the parameter.
func getParameterValueFromMapOrInput(cmd *cobra.Command, parameterMap map[string]string, parameterSchema codersdk.ParameterSchema) (string, error) {
var parameterValue string
if parameterMap != nil {
var ok bool
parameterValue, ok = parameterMap[parameterSchema.Name]
if !ok {
return "", xerrors.Errorf("Parameter value absent in parameter file for %q!", parameterSchema.Name)
}
} else {
var err error
parameterValue, err = cliui.ParameterSchema(cmd, parameterSchema)
if err != nil {
return "", err
}
}
return parameterValue, nil
}
+79
View File
@@ -0,0 +1,79 @@
package cli
import (
"os"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateParameterMapFromFile(t *testing.T) {
t.Parallel()
t.Run("CreateParameterMapFromFile", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
_, _ = parameterFile.WriteString("region: \"bananas\"\ndisk: \"20\"\n")
parameterMapFromFile, err := createParameterMapFromFile(parameterFile.Name())
expectedMap := map[string]string{
"region": "bananas",
"disk": "20",
}
assert.Equal(t, expectedMap, parameterMapFromFile)
assert.Nil(t, err)
removeTmpDirUntilSuccess(t, tempDir)
})
t.Run("WithEmptyFilename", func(t *testing.T) {
t.Parallel()
parameterMapFromFile, err := createParameterMapFromFile("")
assert.Nil(t, parameterMapFromFile)
assert.EqualError(t, err, "Parameter file name is not specified")
})
t.Run("WithInvalidFilename", func(t *testing.T) {
t.Parallel()
parameterMapFromFile, err := createParameterMapFromFile("invalidFile.yaml")
assert.Nil(t, parameterMapFromFile)
// On Unix based systems, it is: `open invalidFile.yaml: no such file or directory`
// On Windows, it is `open invalidFile.yaml: The system cannot find the file specified.`
if runtime.GOOS == "windows" {
assert.EqualError(t, err, "open invalidFile.yaml: The system cannot find the file specified.")
} else {
assert.EqualError(t, err, "open invalidFile.yaml: no such file or directory")
}
})
t.Run("WithInvalidYAML", func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml")
_, _ = parameterFile.WriteString("region = \"bananas\"\ndisk = \"20\"\n")
parameterMapFromFile, err := createParameterMapFromFile(parameterFile.Name())
assert.Nil(t, parameterMapFromFile)
assert.EqualError(t, err, "yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `region ...` into map[string]string")
removeTmpDirUntilSuccess(t, tempDir)
})
}
// Need this for Windows because of a known issue with Go:
// https://github.com/golang/go/issues/52986
func removeTmpDirUntilSuccess(t *testing.T, tempDir string) {
t.Helper()
t.Cleanup(func() {
err := os.RemoveAll(tempDir)
for err != nil {
err = os.RemoveAll(tempDir)
}
})
}
+28
View File
@@ -0,0 +1,28 @@
package cli
import (
"github.com/spf13/cobra"
)
func parameters() *cobra.Command {
cmd := &cobra.Command{
Short: "List parameters for a given scope",
Example: formatExamples(
example{
Command: "coder parameters list workspace my-workspace",
},
),
Use: "parameters",
// Currently hidden as this shows parameter values, not parameter
// schemes. Until we have a good way to distinguish the two, it's better
// not to add confusion or lock ourselves into a certain api.
// This cmd is still valuable debugging tool for devs to avoid
// constructing curl requests.
Hidden: true,
Aliases: []string{"params"},
}
cmd.AddCommand(
parameterList(),
)
return cmd
}
+86
View File
@@ -0,0 +1,86 @@
package cli
import (
"fmt"
"github.com/google/uuid"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)
func parameterList() *cobra.Command {
var (
columns []string
)
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
scope, name := args[0], args[1]
client, err := CreateClient(cmd)
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return xerrors.Errorf("get current organization: %w", err)
}
var scopeID uuid.UUID
switch codersdk.ParameterScope(scope) {
case codersdk.ParameterWorkspace:
workspace, err := namedWorkspace(cmd, client, name)
if err != nil {
return err
}
scopeID = workspace.ID
case codersdk.ParameterTemplate:
template, err := client.TemplateByName(cmd.Context(), organization.ID, name)
if err != nil {
return xerrors.Errorf("get workspace template: %w", err)
}
scopeID = template.ID
case codersdk.ParameterImportJob, "template_version":
scope = string(codersdk.ParameterImportJob)
scopeID, err = uuid.Parse(name)
if err != nil {
return xerrors.Errorf("%q must be a uuid for this scope type", name)
}
// Could be a template_version id or a job id. Check for the
// version id.
tv, err := client.TemplateVersion(cmd.Context(), scopeID)
if err == nil {
scopeID = tv.Job.ID
}
default:
return xerrors.Errorf("%q is an unsupported scope, use %v", scope, []codersdk.ParameterScope{
codersdk.ParameterWorkspace, codersdk.ParameterTemplate, codersdk.ParameterImportJob,
})
}
params, err := client.Parameters(cmd.Context(), codersdk.ParameterScope(scope), scopeID)
if err != nil {
return xerrors.Errorf("fetch params: %w", err)
}
out, err := cliui.DisplayTable(params, "name", columns)
if err != nil {
return xerrors.Errorf("render table: %w", err)
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
return err
},
}
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"name", "scope", "destination scheme"},
"Specify a column to filter in the table.")
return cmd
}
+318
View File
@@ -0,0 +1,318 @@
package cli
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/pion/udp"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/agent"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)
func portForward() *cobra.Command {
var (
tcpForwards []string // <port>:<port>
udpForwards []string // <port>:<port>
)
cmd := &cobra.Command{
Use: "port-forward <workspace>",
Short: "Forward ports from machine to a workspace",
Aliases: []string{"tunnel"},
Args: cobra.ExactArgs(1),
Example: 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{
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{
Description: "Port forward multiple TCP ports and a UDP port",
Command: "coder port-forward <workspace> --tcp 8080:8080 --tcp 9000:3000 --udp 5353:53",
},
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
specs, err := parsePortForwards(tcpForwards, udpForwards)
if err != nil {
return xerrors.Errorf("parse port-forward specs: %w", err)
}
if len(specs) == 0 {
err = cmd.Help()
if err != nil {
return xerrors.Errorf("generate help output: %w", err)
}
return xerrors.New("no port-forwards requested")
}
client, err := CreateClient(cmd)
if err != nil {
return err
}
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, cmd, client, codersdk.Me, args[0], false)
if err != nil {
return err
}
if workspace.LatestBuild.Transition != codersdk.WorkspaceTransitionStart {
return xerrors.New("workspace must be in start transition to port-forward")
}
if workspace.LatestBuild.Job.CompletedAt == nil {
err = cliui.WorkspaceBuild(ctx, cmd.ErrOrStderr(), client, workspace.LatestBuild.ID, workspace.CreatedAt)
if err != nil {
return err
}
}
err = cliui.Agent(ctx, cmd.ErrOrStderr(), cliui.AgentOptions{
WorkspaceName: workspace.Name,
Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) {
return client.WorkspaceAgent(ctx, workspaceAgent.ID)
},
})
if err != nil {
return xerrors.Errorf("await agent: %w", err)
}
conn, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, workspaceAgent.ID)
if err != nil {
return err
}
defer conn.Close()
// Start all listeners.
var (
wg = new(sync.WaitGroup)
listeners = make([]net.Listener, len(specs))
closeAllListeners = func() {
for _, l := range listeners {
if l == nil {
continue
}
_ = l.Close()
}
}
)
defer closeAllListeners()
for i, spec := range specs {
l, err := listenAndPortForward(ctx, cmd, conn, wg, spec)
if err != nil {
return err
}
listeners[i] = l
}
// Wait for the context to be canceled or for a signal and close
// all listeners.
var closeErr error
wg.Add(1)
go func() {
defer wg.Done()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
select {
case <-ctx.Done():
closeErr = ctx.Err()
case <-sigs:
_, _ = fmt.Fprintln(cmd.OutOrStderr(), "Received signal, closing all listeners and active connections")
closeErr = xerrors.New("signal received")
}
cancel()
closeAllListeners()
}()
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
_, err = conn.Ping()
if err != nil {
continue
}
break
}
ticker.Stop()
_, _ = fmt.Fprintln(cmd.OutOrStderr(), "Ready!")
wg.Wait()
return closeErr
},
}
cmd.Flags().StringArrayVarP(&tcpForwards, "tcp", "p", []string{}, "Forward a TCP port from the workspace to the local machine")
cmd.Flags().StringArrayVar(&udpForwards, "udp", []string{}, "Forward a UDP port from the workspace to the local machine. The UDP connection has TCP-like semantics to support stateful UDP protocols")
return cmd
}
func listenAndPortForward(ctx context.Context, cmd *cobra.Command, conn *codersdk.AgentConn, wg *sync.WaitGroup, spec portForwardSpec) (net.Listener, error) {
_, _ = fmt.Fprintf(cmd.OutOrStderr(), "Forwarding '%v://%v' locally to '%v://%v' in the workspace\n", spec.listenNetwork, spec.listenAddress, spec.dialNetwork, spec.dialAddress)
var (
l net.Listener
err error
)
switch spec.listenNetwork {
case "tcp":
l, err = net.Listen(spec.listenNetwork, spec.listenAddress)
case "udp":
var host, port string
host, port, err = net.SplitHostPort(spec.listenAddress)
if err != nil {
return nil, xerrors.Errorf("split %q: %w", spec.listenAddress, err)
}
var portInt int
portInt, err = strconv.Atoi(port)
if err != nil {
return nil, xerrors.Errorf("parse port %v from %q as int: %w", port, spec.listenAddress, err)
}
l, err = udp.Listen(spec.listenNetwork, &net.UDPAddr{
IP: net.ParseIP(host),
Port: portInt,
})
default:
return nil, xerrors.Errorf("unknown listen network %q", spec.listenNetwork)
}
if err != nil {
return nil, xerrors.Errorf("listen '%v://%v': %w", spec.listenNetwork, spec.listenAddress, err)
}
wg.Add(1)
go func(spec portForwardSpec) {
defer wg.Done()
for {
netConn, err := l.Accept()
if err != nil {
_, _ = fmt.Fprintf(cmd.OutOrStderr(), "Error accepting connection from '%v://%v': %+v\n", spec.listenNetwork, spec.listenAddress, err)
_, _ = fmt.Fprintln(cmd.OutOrStderr(), "Killing listener")
return
}
go func(netConn net.Conn) {
defer netConn.Close()
remoteConn, err := conn.DialContext(ctx, spec.dialNetwork, spec.dialAddress)
if err != nil {
_, _ = fmt.Fprintf(cmd.OutOrStderr(), "Failed to dial '%v://%v' in workspace: %s\n", spec.dialNetwork, spec.dialAddress, err)
return
}
defer remoteConn.Close()
agent.Bicopy(ctx, netConn, remoteConn)
}(netConn)
}
}(spec)
return l, nil
}
type portForwardSpec struct {
listenNetwork string // tcp, udp
listenAddress string // <ip>:<port> or path
dialNetwork string // tcp, udp
dialAddress string // <ip>:<port> or path
}
func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) {
specs := []portForwardSpec{}
for _, spec := range tcpSpecs {
local, remote, err := parsePortPort(spec)
if err != nil {
return nil, xerrors.Errorf("failed to parse TCP port-forward specification %q: %w", spec, err)
}
specs = append(specs, portForwardSpec{
listenNetwork: "tcp",
listenAddress: fmt.Sprintf("127.0.0.1:%v", local),
dialNetwork: "tcp",
dialAddress: fmt.Sprintf("127.0.0.1:%v", remote),
})
}
for _, spec := range udpSpecs {
local, remote, err := parsePortPort(spec)
if err != nil {
return nil, xerrors.Errorf("failed to parse UDP port-forward specification %q: %w", spec, err)
}
specs = append(specs, portForwardSpec{
listenNetwork: "udp",
listenAddress: fmt.Sprintf("127.0.0.1:%v", local),
dialNetwork: "udp",
dialAddress: fmt.Sprintf("127.0.0.1:%v", remote),
})
}
// Check for duplicate entries.
locals := map[string]struct{}{}
for _, spec := range specs {
localStr := fmt.Sprintf("%v:%v", spec.listenNetwork, spec.listenAddress)
if _, ok := locals[localStr]; ok {
return nil, xerrors.Errorf("local %v %v is specified twice", spec.listenNetwork, spec.listenAddress)
}
locals[localStr] = struct{}{}
}
return specs, nil
}
func parsePort(in string) (uint16, error) {
port, err := strconv.ParseUint(strings.TrimSpace(in), 10, 16)
if err != nil {
return 0, xerrors.Errorf("parse port %q: %w", in, err)
}
if port == 0 {
return 0, xerrors.New("port cannot be 0")
}
return uint16(port), nil
}
func parsePortPort(in string) (local uint16, remote uint16, err error) {
parts := strings.Split(in, ":")
if len(parts) > 2 {
return 0, 0, xerrors.Errorf("invalid port specification %q", in)
}
if len(parts) == 1 {
// Duplicate the single part
parts = append(parts, parts[0])
}
local, err = parsePort(parts[0])
if err != nil {
return 0, 0, xerrors.Errorf("parse local port from %q: %w", in, err)
}
remote, err = parsePort(parts[1])
if err != nil {
return 0, 0, xerrors.Errorf("parse remote port from %q: %w", in, err)
}
return local, remote, nil
}
+422
View File
@@ -0,0 +1,422 @@
package cli_test
import (
"context"
"fmt"
"io"
"net"
"sync"
"testing"
"github.com/google/uuid"
"github.com/pion/udp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)
func TestPortForward(t *testing.T) {
t.Parallel()
t.Skip("These tests flake... a lot. It seems related to the Tailscale change, but all other tests pass...")
t.Run("None", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)
cmd, root := clitest.New(t, "port-forward", "blah")
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
err := cmd.Execute()
require.Error(t, err)
require.ErrorContains(t, err, "no port-forwards")
// Check that the help was printed.
pty.ExpectMatch("port-forward <workspace>")
})
cases := []struct {
name string
network string
// The flag to pass to `coder port-forward X` to port-forward this type
// of connection. Has two format args (both strings), the first is the
// local address and the second is the remote address.
flag string
// setupRemote creates a "remote" listener to emulate a service in the
// workspace.
setupRemote func(t *testing.T) net.Listener
// setupLocal returns an available port that the
// port-forward command will listen on "locally". Returns the address
// you pass to net.Dial, and the port/path you pass to `coder
// port-forward`.
setupLocal func(t *testing.T) (string, string)
}{
{
name: "TCP",
network: "tcp",
flag: "--tcp=%v:%v",
setupRemote: func(t *testing.T) net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err, "create TCP listener")
return l
},
setupLocal: func(t *testing.T) (string, string) {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err, "create TCP listener to generate random port")
defer l.Close()
_, port, err := net.SplitHostPort(l.Addr().String())
require.NoErrorf(t, err, "split TCP address %q", l.Addr().String())
return l.Addr().String(), port
},
},
{
name: "UDP",
network: "udp",
flag: "--udp=%v:%v",
setupRemote: func(t *testing.T) net.Listener {
addr := net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
}
l, err := udp.Listen("udp", &addr)
require.NoError(t, err, "create UDP listener")
return l
},
setupLocal: func(t *testing.T) (string, string) {
addr := net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
}
l, err := udp.Listen("udp", &addr)
require.NoError(t, err, "create UDP listener to generate random port")
defer l.Close()
_, port, err := net.SplitHostPort(l.Addr().String())
require.NoErrorf(t, err, "split UDP address %q", l.Addr().String())
return l.Addr().String(), port
},
},
}
// Setup agent once to be shared between test-cases (avoid expensive
// non-parallel setup).
var (
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user = coderdtest.CreateFirstUser(t, client)
_, workspace = runAgent(t, client, user.UserID)
)
for _, c := range cases { //nolint:paralleltest // the `c := c` confuses the linter
c := c
// Delay parallel tests here because setupLocal reserves
// a free open port which is not guaranteed to be free
// between the listener closing and port-forward ready.
t.Run(c.name, func(t *testing.T) {
t.Run("OnePort", func(t *testing.T) {
p1 := setupTestListener(t, c.setupRemote(t))
// Create a flag that forwards from local to listener 1.
localAddress, localFlag := c.setupLocal(t)
flag := fmt.Sprintf(c.flag, localFlag, p1)
// Launch port-forward in a goroutine so we can start dialing
// the "local" listener.
cmd, root := clitest.New(t, "-v", "port-forward", workspace.Name, flag)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errC := make(chan error)
go func() {
errC <- cmd.ExecuteContext(ctx)
}()
pty.ExpectMatch("Ready!")
t.Parallel() // Port is reserved, enable parallel execution.
// Open two connections simultaneously and test them out of
// sync.
d := net.Dialer{Timeout: testutil.WaitShort}
c1, err := d.DialContext(ctx, c.network, localAddress)
require.NoError(t, err, "open connection 1 to 'local' listener")
defer c1.Close()
c2, err := d.DialContext(ctx, c.network, localAddress)
require.NoError(t, err, "open connection 2 to 'local' listener")
defer c2.Close()
testDial(t, c2)
testDial(t, c1)
cancel()
err = <-errC
require.ErrorIs(t, err, context.Canceled)
})
//nolint:paralleltest
t.Run("TwoPorts", func(t *testing.T) {
var (
p1 = setupTestListener(t, c.setupRemote(t))
p2 = setupTestListener(t, c.setupRemote(t))
)
// Create a flags for listener 1 and listener 2.
localAddress1, localFlag1 := c.setupLocal(t)
localAddress2, localFlag2 := c.setupLocal(t)
flag1 := fmt.Sprintf(c.flag, localFlag1, p1)
flag2 := fmt.Sprintf(c.flag, localFlag2, p2)
// Launch port-forward in a goroutine so we can start dialing
// the "local" listeners.
cmd, root := clitest.New(t, "-v", "port-forward", workspace.Name, flag1, flag2)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errC := make(chan error)
go func() {
errC <- cmd.ExecuteContext(ctx)
}()
pty.ExpectMatch("Ready!")
t.Parallel() // Port is reserved, enable parallel execution.
// Open a connection to both listener 1 and 2 simultaneously and
// then test them out of order.
d := net.Dialer{Timeout: testutil.WaitShort}
c1, err := d.DialContext(ctx, c.network, localAddress1)
require.NoError(t, err, "open connection 1 to 'local' listener 1")
defer c1.Close()
c2, err := d.DialContext(ctx, c.network, localAddress2)
require.NoError(t, err, "open connection 2 to 'local' listener 2")
defer c2.Close()
testDial(t, c2)
testDial(t, c1)
cancel()
err = <-errC
require.ErrorIs(t, err, context.Canceled)
})
})
}
// Test doing TCP and UDP at the same time.
//nolint:paralleltest
t.Run("All", func(t *testing.T) {
var (
dials = []addr{}
flags = []string{}
)
// Start listeners and populate arrays with the cases.
for _, c := range cases {
p := setupTestListener(t, c.setupRemote(t))
localAddress, localFlag := c.setupLocal(t)
dials = append(dials, addr{
network: c.network,
addr: localAddress,
})
flags = append(flags, fmt.Sprintf(c.flag, localFlag, p))
}
// Launch port-forward in a goroutine so we can start dialing
// the "local" listeners.
cmd, root := clitest.New(t, append([]string{"-v", "port-forward", workspace.Name}, flags...)...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errC := make(chan error)
go func() {
errC <- cmd.ExecuteContext(ctx)
}()
pty.ExpectMatch("Ready!")
t.Parallel() // Port is reserved, enable parallel execution.
// Open connections to all items in the "dial" array.
var (
d = net.Dialer{Timeout: testutil.WaitShort}
conns = make([]net.Conn, len(dials))
)
for i, a := range dials {
c, err := d.DialContext(ctx, a.network, a.addr)
require.NoErrorf(t, err, "open connection %v to 'local' listener %v", i+1, i+1)
t.Cleanup(func() {
_ = c.Close()
})
conns[i] = c
}
// Test each connection in reverse order.
for i := len(conns) - 1; i >= 0; i-- {
testDial(t, conns[i])
}
cancel()
err := <-errC
require.ErrorIs(t, err, context.Canceled)
})
}
// runAgent creates a fake workspace and starts an agent locally for that
// workspace. The agent will be cleaned up on test completion.
// nolint:unused
func runAgent(t *testing.T, client *codersdk.Client, userID uuid.UUID) ([]codersdk.WorkspaceResource, codersdk.Workspace) {
ctx := context.Background()
user, err := client.User(ctx, userID.String())
require.NoError(t, err, "specified user does not exist")
require.Greater(t, len(user.OrganizationIDs), 0, "user has no organizations")
orgID := user.OrganizationIDs[0]
// Setup template
agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionDryRun: echo.ProvisionComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "somename",
Type: "someinstance",
Agents: []*proto.Agent{{
Auth: &proto.Agent_Token{
Token: agentToken,
},
}},
}},
},
},
}},
})
// Create template and workspace
template := coderdtest.CreateTemplate(t, client, orgID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, orgID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
// Start workspace agent in a goroutine
cmd, root := clitest.New(t, "agent", "--agent-token", agentToken, "--agent-url", client.URL.String())
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
cmd.SetErr(pty.Output())
errC := make(chan error)
agentCtx, agentCancel := context.WithCancel(ctx)
t.Cleanup(func() {
agentCancel()
err := <-errC
require.NoError(t, err)
})
go func() {
errC <- cmd.ExecuteContext(agentCtx)
}()
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
require.NoError(t, err)
return resources, workspace
}
// setupTestListener starts accepting connections and echoing a single packet.
// Returns the listener and the listen port.
func setupTestListener(t *testing.T, l net.Listener) string {
t.Helper()
// Wait for listener to completely exit before releasing.
done := make(chan struct{})
t.Cleanup(func() {
_ = l.Close()
<-done
})
go func() {
defer close(done)
// Guard against testAccept running require after test completion.
var wg sync.WaitGroup
defer wg.Wait()
for {
c, err := l.Accept()
if err != nil {
_ = l.Close()
return
}
wg.Add(1)
go func() {
testAccept(t, c)
wg.Done()
}()
}
}()
addr := l.Addr().String()
_, port, err := net.SplitHostPort(addr)
require.NoErrorf(t, err, "split non-Unix listen path %q", addr)
addr = port
return addr
}
var dialTestPayload = []byte("dean-was-here123")
func testDial(t *testing.T, c net.Conn) {
t.Helper()
assertWritePayload(t, c, dialTestPayload)
assertReadPayload(t, c, dialTestPayload)
}
func testAccept(t *testing.T, c net.Conn) {
t.Helper()
defer c.Close()
assertReadPayload(t, c, dialTestPayload)
assertWritePayload(t, c, dialTestPayload)
}
func assertReadPayload(t *testing.T, r io.Reader, payload []byte) {
t.Helper()
b := make([]byte, len(payload)+16)
n, err := r.Read(b)
assert.NoError(t, err, "read payload")
assert.Equal(t, len(payload), n, "read payload length does not match")
assert.Equal(t, payload, b[:n])
}
func assertWritePayload(t *testing.T, w io.Writer, payload []byte) {
t.Helper()
n, err := w.Write(payload)
assert.NoError(t, err, "write payload")
assert.Equal(t, len(payload), n, "payload length does not match")
}
type addr struct {
network string
addr string
}
+30 -3
View File
@@ -11,16 +11,39 @@ import (
)
func publickey() *cobra.Command {
return &cobra.Command{
var (
reset bool
)
cmd := &cobra.Command{
Use: "publickey",
Aliases: []string{"pubkey"},
Short: "Output your public key for Git operations",
Short: "Output your Coder public key used for Git operations",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
client, err := CreateClient(cmd)
if err != nil {
return xerrors.Errorf("create codersdk client: %w", err)
}
if reset {
// Confirm prompt if using --reset. We don't want to accidentally
// reset our public key.
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Confirm regenerate a new sshkey for your workspaces? This will require updating the key " +
"on any services it is registered with. This action cannot be reverted.",
IsConfirm: true,
})
if err != nil {
return err
}
// Reset the public key, let the retrieve re-read it.
_, err = client.RegenerateGitSSHKey(cmd.Context(), codersdk.Me)
if err != nil {
return err
}
}
key, err := client.GitSSHKey(cmd.Context(), codersdk.Me)
if err != nil {
return xerrors.Errorf("create codersdk client: %w", err)
@@ -40,4 +63,8 @@ func publickey() *cobra.Command {
return nil
},
}
cmd.Flags().BoolVar(&reset, "reset", false, "Regenerate your public key. This will require updating the key on any services it's registered with.")
cliui.AllowSkipPrompt(cmd)
return cmd
}
+1
View File
@@ -13,6 +13,7 @@ import (
func TestPublicKey(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)
cmd, root := clitest.New(t, "publickey")

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