Skip to content

Commit 1cfedaf

Browse files
committed
Merge branch 'main' into template-schedule-flake
2 parents 62fe36c + b742661 commit 1cfedaf

File tree

11 files changed

+296
-94
lines changed

11 files changed

+296
-94
lines changed

cli/login.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,7 @@ func (r *RootCmd) login() *clibase.Cmd {
278278
}
279279

280280
sessionToken, err = cliui.Prompt(inv, cliui.PromptOptions{
281-
Text: "Paste your token here:",
282-
Secret: true,
281+
Text: "Paste your token here:",
283282
Validate: func(token string) error {
284283
client.SetSessionToken(token)
285284
_, err := client.User(ctx, codersdk.Me)

cli/login_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cli_test
33
import (
44
"context"
55
"fmt"
6+
"runtime"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
@@ -170,6 +171,10 @@ func TestLogin(t *testing.T) {
170171

171172
pty.ExpectMatch("Paste your token here:")
172173
pty.WriteLine(client.SessionToken())
174+
if runtime.GOOS != "windows" {
175+
// For some reason, the match does not show up on Windows.
176+
pty.ExpectMatch(client.SessionToken())
177+
}
173178
pty.ExpectMatch("Welcome to Coder")
174179
<-doneChan
175180
})
@@ -193,6 +198,10 @@ func TestLogin(t *testing.T) {
193198

194199
pty.ExpectMatch("Paste your token here:")
195200
pty.WriteLine("an-invalid-token")
201+
if runtime.GOOS != "windows" {
202+
// For some reason, the match does not show up on Windows.
203+
pty.ExpectMatch("an-invalid-token")
204+
}
196205
pty.ExpectMatch("That's not a valid token!")
197206
cancelFunc()
198207
<-doneChan

codersdk/deployment.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func (n FeatureName) AlwaysEnable() bool {
9292
FeatureMultipleGitAuth: true,
9393
FeatureExternalProvisionerDaemons: true,
9494
FeatureAppearance: true,
95+
FeatureWorkspaceBatchActions: true,
9596
}[n]
9697
}
9798

docs/admin/encryption.md

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,26 @@ Additional database fields may be encrypted in the future.
4242
4343
## Enabling encryption
4444

45-
1. Ensure you have a valid backup of your database. **Do not skip this step.**
46-
If you are using the built-in PostgreSQL database, you can run
47-
[`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md)
48-
to get the connection URL.
45+
- Ensure you have a valid backup of your database. **Do not skip this step.** If
46+
you are using the built-in PostgreSQL database, you can run
47+
[`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md)
48+
to get the connection URL.
4949

50-
1. Generate a 32-byte random key and base64-encode it. For example:
50+
- Generate a 32-byte random key and base64-encode it. For example:
5151

5252
```shell
5353
dd if=/dev/urandom bs=32 count=1 | base64
5454
```
5555

56-
1. Store this key in a secure location (for example, a Kubernetes secret):
56+
- Store this key in a secure location (for example, a Kubernetes secret):
5757

5858
```shell
5959
kubectl create secret generic coder-external-token-encryption-keys --from-literal=keys=<key>
6060
```
6161

62-
1. In your Coder configuration set `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS` to a
63-
comma-separated list of base64-encoded keys. For example, in your Helm
64-
`values.yaml`:
62+
- In your Coder configuration set `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS` to a
63+
comma-separated list of base64-encoded keys. For example, in your Helm
64+
`values.yaml`:
6565

6666
```yaml
6767
coder:
@@ -74,22 +74,22 @@ coder:
7474
key: keys
7575
```
7676
77-
1. Restart the Coder server. The server will now encrypt all new data with the
78-
provided key.
77+
- Restart the Coder server. The server will now encrypt all new data with the
78+
provided key.
7979
8080
## Rotating keys
8181
8282
We recommend only having one active encryption key at a time normally. However,
8383
if you need to rotate keys, you can perform the following procedure:
8484
85-
1. Ensure you have a valid backup of your database. **Do not skip this step.**
85+
- Ensure you have a valid backup of your database. **Do not skip this step.**
8686
87-
1. Generate a new encryption key following the same procedure as above.
87+
- Generate a new encryption key following the same procedure as above.
8888
89-
1. Add the above key to the list of
90-
[external token encryption keys](../cli/server.md#--external-token-encryption-keys).
91-
**The new key must appear first in the list**. For example, in the Kubernetes
92-
secret created above:
89+
- Add the above key to the list of
90+
[external token encryption keys](../cli/server.md#--external-token-encryption-keys).
91+
**The new key must appear first in the list**. For example, in the Kubernetes
92+
secret created above:
9393
9494
```yaml
9595
apiVersion: v1
@@ -102,70 +102,70 @@ data:
102102
keys: <new-key>,<old-key1>,<old-key2>,...
103103
```
104104
105-
1. After updating the configuration, restart the Coder server. The server will
106-
now encrypt all new data with the new key, but will be able to decrypt tokens
107-
encrypted with the old key(s).
105+
- After updating the configuration, restart the Coder server. The server will
106+
now encrypt all new data with the new key, but will be able to decrypt tokens
107+
encrypted with the old key(s).
108108
109-
1. To re-encrypt all encrypted database fields with the new key, run
110-
[`coder server dbcrypt rotate`](../cli/server_dbcrypt_rotate.md). This
111-
command will re-encrypt all tokens with the specified new encryption key. We
112-
recommend performing this action during a maintenance window.
109+
- To re-encrypt all encrypted database fields with the new key, run
110+
[`coder server dbcrypt rotate`](../cli/server_dbcrypt_rotate.md). This command
111+
will re-encrypt all tokens with the specified new encryption key. We recommend
112+
performing this action during a maintenance window.
113113

114-
> Note: this command requires direct access to the database. If you are using
115-
> the built-in PostgreSQL database, you can run
116-
> [`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md)
117-
> to get the connection URL.
114+
> Note: this command requires direct access to the database. If you are using
115+
> the built-in PostgreSQL database, you can run
116+
> [`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md)
117+
> to get the connection URL.
118118

119-
1. Once the above command completes successfully, remove the old encryption key
120-
from Coder's configuration and restart Coder once more. You can now safely
121-
delete the old key from your secret store.
119+
- Once the above command completes successfully, remove the old encryption key
120+
from Coder's configuration and restart Coder once more. You can now safely
121+
delete the old key from your secret store.
122122

123123
## Disabling encryption
124124

125125
To disable encryption, perform the following actions:
126126

127-
1. Ensure you have a valid backup of your database. **Do not skip this step.**
127+
- Ensure you have a valid backup of your database. **Do not skip this step.**
128128

129-
1. Stop all active coderd instances. This will prevent new encrypted data from
130-
being written, which may cause the next step to fail.
129+
- Stop all active coderd instances. This will prevent new encrypted data from
130+
being written, which may cause the next step to fail.
131131

132-
1. Run [`coder server dbcrypt decrypt`](../cli/server_dbcrypt_decrypt.md). This
133-
command will decrypt all encrypted user tokens and revoke all active
134-
encryption keys.
132+
- Run [`coder server dbcrypt decrypt`](../cli/server_dbcrypt_decrypt.md). This
133+
command will decrypt all encrypted user tokens and revoke all active
134+
encryption keys.
135135

136-
> Note: for `decrypt` command, the equivalent environment variable for
137-
> `--keys` is `CODER_EXTERNAL_TOKEN_ENCRYPTION_DECRYPT_KEYS` and not
138-
> `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS`. This is explicitly named
139-
> differently to help prevent accidentally decrypting data.
136+
> Note: for `decrypt` command, the equivalent environment variable for
137+
> `--keys` is `CODER_EXTERNAL_TOKEN_ENCRYPTION_DECRYPT_KEYS` and not
138+
> `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS`. This is explicitly named differently
139+
> to help prevent accidentally decrypting data.
140140

141-
1. Remove all
142-
[external token encryption keys](../cli/server.md#--external-token-encryption-keys)
143-
from Coder's configuration.
141+
- Remove all
142+
[external token encryption keys](../cli/server.md#--external-token-encryption-keys)
143+
from Coder's configuration.
144144

145-
1. Start coderd. You can now safely delete the encryption keys from your secret
146-
store.
145+
- Start coderd. You can now safely delete the encryption keys from your secret
146+
store.
147147

148148
## Deleting Encrypted Data
149149

150150
> NOTE: This is a destructive operation.
151151

152152
To delete all encrypted data from your database, perform the following actions:
153153

154-
1. Ensure you have a valid backup of your database. **Do not skip this step.**
154+
- Ensure you have a valid backup of your database. **Do not skip this step.**
155155

156-
1. Stop all active coderd instances. This will prevent new encrypted data from
157-
being written.
156+
- Stop all active coderd instances. This will prevent new encrypted data from
157+
being written.
158158

159-
1. Run [`coder server dbcrypt delete`](../cli/server_dbcrypt_delete.md). This
160-
command will delete all encrypted user tokens and revoke all active
161-
encryption keys.
159+
- Run [`coder server dbcrypt delete`](../cli/server_dbcrypt_delete.md). This
160+
command will delete all encrypted user tokens and revoke all active encryption
161+
keys.
162162

163-
1. Remove all
164-
[external token encryption keys](../cli/server.md#--external-token-encryption-keys)
165-
from Coder's configuration.
163+
- Remove all
164+
[external token encryption keys](../cli/server.md#--external-token-encryption-keys)
165+
from Coder's configuration.
166166

167-
1. Start coderd. You can now safely delete the encryption keys from your secret
168-
store.
167+
- Start coderd. You can now safely delete the encryption keys from your secret
168+
store.
169169

170170
## Troubleshooting
171171

enterprise/coderd/proxyhealth/proxyhealth.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,27 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
289289
case err == nil && resp.StatusCode != http.StatusOK:
290290
// Unhealthy as we did reach the proxy but it got an unexpected response.
291291
status.Status = Unhealthy
292-
status.Report.Errors = []string{fmt.Sprintf("unexpected status code %d", resp.StatusCode)}
292+
var builder strings.Builder
293+
// This string is shown on the UI where newlines are respected.
294+
// This error message is not ever decoded programmatically, so keep it human-
295+
// readable.
296+
builder.WriteString(fmt.Sprintf("unexpected status code %d. ", resp.StatusCode))
297+
builder.WriteString(fmt.Sprintf("\nEncountered error, send a request to %q from the Coderd environment to debug this issue.", reqURL))
298+
err := codersdk.ReadBodyAsError(resp)
299+
if err != nil {
300+
var apiErr *codersdk.Error
301+
if xerrors.As(err, &apiErr) {
302+
builder.WriteString(fmt.Sprintf("\nError Message: %s\nError Detail: %s", apiErr.Message, apiErr.Detail))
303+
for _, v := range apiErr.Validations {
304+
// Pretty sure this is not possible from the called endpoint, but just in case.
305+
builder.WriteString(fmt.Sprintf("\n\tValidation: %s=%s", v.Field, v.Detail))
306+
}
307+
} else {
308+
builder.WriteString(fmt.Sprintf("\nError: %s", err.Error()))
309+
}
310+
}
311+
312+
status.Report.Errors = []string{builder.String()}
293313
case err != nil:
294314
// Request failed, mark the proxy as unreachable.
295315
status.Status = Unreachable

site/e2e/global.setup.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ test("create first user", async ({ page }) => {
1212
await page.getByTestId("trial").click();
1313
await page.getByTestId("create").click();
1414

15-
await expect(page).toHaveURL("/workspaces");
16-
15+
await expect(page).toHaveURL(/\/workspaces.*/);
1716
await page.context().storageState({ path: STORAGE_STATE });
1817
});

site/src/components/Filter/filter.tsx

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import MenuList from "@mui/material/MenuList";
2424
import { Loader } from "components/Loader/Loader";
2525
import Divider from "@mui/material/Divider";
2626
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";
27+
2728
import { useDebouncedFunction } from "hooks/debounce";
29+
import { useEffectEvent } from "hooks/hookPolyfills";
2830

2931
export type PresetFilter = {
3032
name: string;
@@ -33,45 +35,71 @@ export type PresetFilter = {
3335

3436
type FilterValues = Record<string, string | undefined>;
3537

38+
type UseFilterConfig = {
39+
/**
40+
* The fallback value to use in the event that no filter params can be parsed
41+
* from the search params object. This value is allowed to change on
42+
* re-renders.
43+
*/
44+
fallbackFilter?: string;
45+
searchParamsResult: ReturnType<typeof useSearchParams>;
46+
onUpdate?: (newValue: string) => void;
47+
};
48+
49+
const useFilterParamsKey = "filter";
50+
3651
export const useFilter = ({
37-
initialValue = "",
38-
onUpdate,
52+
fallbackFilter = "",
3953
searchParamsResult,
40-
}: {
41-
initialValue?: string;
42-
searchParamsResult: ReturnType<typeof useSearchParams>;
43-
onUpdate?: () => void;
44-
}) => {
54+
onUpdate,
55+
}: UseFilterConfig) => {
4556
const [searchParams, setSearchParams] = searchParamsResult;
46-
const query = searchParams.get("filter") ?? initialValue;
47-
const values = parseFilterQuery(query);
48-
49-
const update = (values: string | FilterValues) => {
50-
if (typeof values === "string") {
51-
searchParams.set("filter", values);
52-
} else {
53-
searchParams.set("filter", stringifyFilter(values));
54-
}
57+
const query = searchParams.get(useFilterParamsKey) ?? fallbackFilter;
58+
59+
// Stabilizing reference to setSearchParams from one central spot, just to be
60+
// on the extra careful side; don't want effects over-running. You would think
61+
// this would be overkill, but setSearchParams isn't stable out of the box
62+
const stableSetSearchParams = useEffectEvent(setSearchParams);
63+
64+
// Keep params synced with query, even as query changes from outside sources
65+
useEffect(() => {
66+
stableSetSearchParams((currentParams) => {
67+
const currentQuery = currentParams.get(useFilterParamsKey);
68+
69+
if (query === "") {
70+
currentParams.delete(useFilterParamsKey);
71+
} else if (currentQuery !== query) {
72+
currentParams.set(useFilterParamsKey, query);
73+
}
74+
75+
return currentParams;
76+
});
77+
}, [stableSetSearchParams, query]);
78+
79+
const update = (newValues: string | FilterValues) => {
80+
const serialized =
81+
typeof newValues === "string" ? newValues : stringifyFilter(newValues);
82+
83+
searchParams.set(useFilterParamsKey, serialized);
5584
setSearchParams(searchParams);
56-
if (onUpdate) {
57-
onUpdate();
85+
86+
if (onUpdate !== undefined) {
87+
onUpdate(serialized);
5888
}
5989
};
6090

6191
const { debounced: debounceUpdate, cancelDebounce } = useDebouncedFunction(
62-
(values: string | FilterValues) => update(values),
92+
update,
6393
500,
6494
);
6595

66-
const used = query !== "" && query !== initialValue;
67-
6896
return {
6997
query,
7098
update,
7199
debounceUpdate,
72100
cancelDebounce,
73-
values,
74-
used,
101+
values: parseFilterQuery(query),
102+
used: query !== "" && query !== fallbackFilter,
75103
};
76104
};
77105

0 commit comments

Comments
 (0)