Skip to content

Commit 8931a23

Browse files
committed
connect entitlement
1 parent 365fbc3 commit 8931a23

File tree

8 files changed

+83
-50
lines changed

8 files changed

+83
-50
lines changed

codersdk/features.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@ const (
1515
)
1616

1717
const (
18-
FeatureUserLimit = "user_limit"
19-
FeatureAuditLog = "audit_log"
20-
FeatureBrowserOnly = "browser_only"
21-
FeatureSCIM = "scim"
18+
FeatureUserLimit = "user_limit"
19+
FeatureAuditLog = "audit_log"
20+
FeatureBrowserOnly = "browser_only"
21+
FeatureSCIM = "scim"
22+
FeatureWorkspaceQuota = "workspace_quota"
2223
)
2324

24-
var FeatureNames = []string{FeatureUserLimit, FeatureAuditLog, FeatureBrowserOnly, FeatureSCIM}
25+
var FeatureNames = []string{
26+
FeatureUserLimit,
27+
FeatureAuditLog,
28+
FeatureBrowserOnly,
29+
FeatureSCIM,
30+
FeatureWorkspaceQuota,
31+
}
2532

2633
type Feature struct {
2734
Entitlement Entitlement `json:"entitlement"`

enterprise/cli/server.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ func server() *cobra.Command {
1818
auditLogging bool
1919
browserOnly bool
2020
scimAuthHeader string
21+
workspaceQuota int
2122
)
2223
cmd := agpl.Server(func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, error) {
2324
api, err := coderd.New(ctx, &coderd.Options{
24-
AuditLogging: auditLogging,
25-
BrowserOnly: browserOnly,
26-
SCIMAPIKey: []byte(scimAuthHeader),
27-
Options: options,
25+
AuditLogging: auditLogging,
26+
BrowserOnly: browserOnly,
27+
SCIMAPIKey: []byte(scimAuthHeader),
28+
WorkspaceQuota: workspaceQuota,
29+
Options: options,
2830
})
2931
if err != nil {
3032
return nil, err
@@ -39,6 +41,8 @@ func server() *cobra.Command {
3941
"Whether Coder only allows connections to workspaces via the browser. "+enterpriseOnly)
4042
cliflag.StringVarP(cmd.Flags(), &scimAuthHeader, "scim-auth-header", "", "CODER_SCIM_API_KEY", "",
4143
"Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication. "+enterpriseOnly)
44+
cliflag.IntVarP(cmd.Flags(), &workspaceQuota, "workspace-quota", "", "CODER_WORKSPACE_QUOTA", 0,
45+
"Whether Coder applies a limit on how many workspaces each user can create. "+enterpriseOnly)
4246

4347
return cmd
4448
}

enterprise/coderd/coderd.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ func New(ctx context.Context, options *Options) (*API, error) {
4343
Entitlement: codersdk.EntitlementNotEntitled,
4444
Enabled: false,
4545
},
46-
auditLogs: codersdk.EntitlementNotEntitled,
47-
browserOnly: codersdk.EntitlementNotEntitled,
48-
scim: codersdk.EntitlementNotEntitled,
46+
auditLogs: codersdk.EntitlementNotEntitled,
47+
browserOnly: codersdk.EntitlementNotEntitled,
48+
scim: codersdk.EntitlementNotEntitled,
49+
workspaceQuota: codersdk.EntitlementNotEntitled,
4950
},
5051
cancelEntitlementsLoop: cancelFunc,
5152
}
@@ -96,8 +97,10 @@ type Options struct {
9697

9798
AuditLogging bool
9899
// Whether to block non-browser connections.
99-
BrowserOnly bool
100-
SCIMAPIKey []byte
100+
BrowserOnly bool
101+
SCIMAPIKey []byte
102+
WorkspaceQuota int
103+
101104
EntitlementsUpdateInterval time.Duration
102105
Keys map[string]ed25519.PublicKey
103106
}
@@ -112,11 +115,12 @@ type API struct {
112115
}
113116

114117
type entitlements struct {
115-
hasLicense bool
116-
activeUsers codersdk.Feature
117-
auditLogs codersdk.Entitlement
118-
browserOnly codersdk.Entitlement
119-
scim codersdk.Entitlement
118+
hasLicense bool
119+
activeUsers codersdk.Feature
120+
auditLogs codersdk.Entitlement
121+
browserOnly codersdk.Entitlement
122+
scim codersdk.Entitlement
123+
workspaceQuota codersdk.Entitlement
120124
}
121125

122126
func (api *API) Close() error {
@@ -140,9 +144,10 @@ func (api *API) updateEntitlements(ctx context.Context) error {
140144
Enabled: false,
141145
Entitlement: codersdk.EntitlementNotEntitled,
142146
},
143-
auditLogs: codersdk.EntitlementNotEntitled,
144-
scim: codersdk.EntitlementNotEntitled,
145-
browserOnly: codersdk.EntitlementNotEntitled,
147+
auditLogs: codersdk.EntitlementNotEntitled,
148+
scim: codersdk.EntitlementNotEntitled,
149+
browserOnly: codersdk.EntitlementNotEntitled,
150+
workspaceQuota: codersdk.EntitlementNotEntitled,
146151
}
147152

148153
// Here we loop through licenses to detect enabled features.
@@ -181,6 +186,9 @@ func (api *API) updateEntitlements(ctx context.Context) error {
181186
if claims.Features.SCIM > 0 {
182187
entitlements.scim = entitlement
183188
}
189+
if claims.Features.WorkspaceQuota > 0 {
190+
entitlements.workspaceQuota = entitlement
191+
}
184192
}
185193

186194
if entitlements.auditLogs != api.entitlements.auditLogs {
@@ -205,6 +213,15 @@ func (api *API) updateEntitlements(ctx context.Context) error {
205213
api.AGPL.WorkspaceClientCoordinateOverride.Store(&handler)
206214
}
207215

216+
// TODO(f0ssel)
217+
if entitlements.workspaceQuota != api.entitlements.workspaceQuota {
218+
// var handler func(rw http.ResponseWriter) bool
219+
if entitlements.workspaceQuota != codersdk.EntitlementNotEntitled && api.WorkspaceQuota > 0 {
220+
// handler = api.shouldBlockNonBrowserConnections
221+
}
222+
// api.AGPL.WorkspaceClientCoordinateOverride.Store(&handler)
223+
}
224+
208225
api.entitlements = entitlements
209226

210227
return nil
@@ -260,6 +277,15 @@ func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) {
260277
"Browser only connections are enabled but your license for this feature is expired.")
261278
}
262279

280+
resp.Features[codersdk.FeatureWorkspaceQuota] = codersdk.Feature{
281+
Entitlement: entitlements.workspaceQuota,
282+
Enabled: api.WorkspaceQuota > 0,
283+
}
284+
if entitlements.workspaceQuota == codersdk.EntitlementGracePeriod && api.WorkspaceQuota > 0 {
285+
resp.Warnings = append(resp.Warnings,
286+
"Workspace quotas are enabled but your license for this feature is expired.")
287+
}
288+
263289
httpapi.Write(ctx, rw, http.StatusOK, resp)
264290
}
265291

enterprise/coderd/licenses.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ var key20220812 []byte
4545
var Keys = map[string]ed25519.PublicKey{"2022-08-12": ed25519.PublicKey(key20220812)}
4646

4747
type Features struct {
48-
UserLimit int64 `json:"user_limit"`
49-
AuditLog int64 `json:"audit_log"`
50-
BrowserOnly int64 `json:"browser_only"`
51-
SCIM int64 `json:"scim"`
48+
UserLimit int64 `json:"user_limit"`
49+
AuditLog int64 `json:"audit_log"`
50+
BrowserOnly int64 `json:"browser_only"`
51+
SCIM int64 `json:"scim"`
52+
WorkspaceQuota int64 `json:"workspace_quota"`
5253
}
5354

5455
type Claims struct {

site/src/api/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ export enum FeatureNames {
2020
AuditLog = "audit_log",
2121
UserLimit = "user_limit",
2222
BrowserOnly = "browser_only",
23+
SCIM = "scim",
24+
WorkspaceQuota = "workspace_quota",
2325
}

site/src/components/NavbarView/NavbarView.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
108108
</div>
109109
</div>
110110
<Stack direction="row" className={styles.profileButton}>
111-
<div className={styles.quota} >
112-
<WorkspaceQuota count={1} limit={2}/>
111+
<div className={styles.quota}>
112+
<WorkspaceQuota count={1} limit={2} />
113113
</div>
114114

115-
<div className={styles.profileButton}>
116-
{user && <UserDropdown user={user} onSignOut={onSignOut} />}
117-
</div>
115+
<div className={styles.profileButton}>
116+
{user && <UserDropdown user={user} onSignOut={onSignOut} />}
117+
</div>
118118
</Stack>
119119
</nav>
120120
)
@@ -126,7 +126,7 @@ const useStyles = makeStyles((theme) => ({
126126
display: "flex",
127127
justifyContent: "space-between",
128128
alignItems: "center",
129-
alignContent: 'center',
129+
alignContent: "center",
130130
height: navHeight,
131131
background: theme.palette.background.paper,
132132
"@media (display-mode: standalone)": {

site/src/components/WorkspaceQuota/WorkspaceQuota.stories.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,3 @@ Loading.args = {
2525
count: undefined,
2626
limit: undefined,
2727
}
28-
29-

site/src/components/WorkspaceQuota/WorkspaceQuota.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import LinearProgress from '@material-ui/core/LinearProgress';
2-
import { FC } from "react"
1+
import Box from "@material-ui/core/Box"
2+
import LinearProgress from "@material-ui/core/LinearProgress"
33
import { makeStyles } from "@material-ui/core/styles"
4+
import Skeleton from "@material-ui/lab/Skeleton"
5+
import { Stack } from "components/Stack/Stack"
6+
import { FC } from "react"
47
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
5-
import Skeleton from '@material-ui/lab/Skeleton';
6-
import { Stack } from 'components/Stack/Stack';
7-
import Box from "@material-ui/core/Box";
88

99
export const Language = {
1010
of: "of",
@@ -25,11 +25,9 @@ export const WorkspaceQuota: FC<WorkspaceQuotaProps> = ({ count, limit }) => {
2525
return (
2626
<Box>
2727
<Stack spacing={1} className={styles.stack}>
28-
<LinearProgress
29-
color="primary"
30-
/>
28+
<LinearProgress color="primary" />
3129
<div className={styles.label}>
32-
<Skeleton className={styles.skeleton}/>
30+
<Skeleton className={styles.skeleton} />
3331
</div>
3432
</Stack>
3533
</Box>
@@ -45,13 +43,10 @@ export const WorkspaceQuota: FC<WorkspaceQuotaProps> = ({ count, limit }) => {
4543
return (
4644
<Box>
4745
<Stack spacing={1} className={styles.stack}>
48-
<LinearProgress
49-
value={value}
50-
variant="determinate"
51-
color="primary"
52-
/>
46+
<LinearProgress value={value} variant="determinate" color="primary" />
5347
<div className={styles.label}>
54-
{count} {Language.of} {limit} {limit === 1 ? Language.workspaceUsed : Language.workspacesUsed }
48+
{count} {Language.of} {limit}{" "}
49+
{limit === 1 ? Language.workspaceUsed : Language.workspacesUsed}
5550
</div>
5651
</Stack>
5752
</Box>
@@ -60,7 +55,7 @@ export const WorkspaceQuota: FC<WorkspaceQuotaProps> = ({ count, limit }) => {
6055

6156
const useStyles = makeStyles((theme) => ({
6257
stack: {
63-
display: 'inline-flex',
58+
display: "inline-flex",
6459
},
6560
label: {
6661
fontFamily: MONOSPACE_FONT_FAMILY,

0 commit comments

Comments
 (0)