Skip to content

Commit 0cf5381

Browse files
committed
implement license checks for external agents in workspace builds and templates
1 parent ba367a3 commit 0cf5381

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

enterprise/coderd/coderd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,18 @@ func (api *API) CheckBuildUsage(ctx context.Context, store database.Store, templ
952952
// When there are no licenses installed, a noop usage checker is used
953953
// instead.
954954

955+
// If the template version has an external agent, we need to check that the
956+
// license is entitled to this feature.
957+
if templateVersion.HasExternalAgent.Valid && templateVersion.HasExternalAgent.Bool {
958+
feature, ok := api.Entitlements.Feature(codersdk.FeatureWorkspaceExternalAgent)
959+
if !ok || !feature.Enabled {
960+
return wsbuilder.UsageCheckResponse{
961+
Permitted: false,
962+
Message: "You have a template which uses external agents but your license is not entitled to this feature. You will be unable to create new workspaces from these templates.",
963+
}, nil
964+
}
965+
}
966+
955967
// If the template version doesn't have an AI task, we don't need to check
956968
// usage.
957969
if !templateVersion.HasAITask.Valid || !templateVersion.HasAITask.Bool {

enterprise/coderd/license/license.go

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package license
33
import (
44
"context"
55
"crypto/ed25519"
6+
"database/sql"
67
"fmt"
78
"math"
89
"time"
@@ -94,10 +95,34 @@ func Entitlements(
9495
return codersdk.Entitlements{}, xerrors.Errorf("query active user count: %w", err)
9596
}
9697

98+
// nolint:gocritic // Getting external workspaces is a system function.
99+
externalWorkspaces, err := db.GetWorkspaces(dbauthz.AsSystemRestricted(ctx), database.GetWorkspacesParams{
100+
HasExternalAgent: sql.NullBool{
101+
Bool: true,
102+
Valid: true,
103+
},
104+
})
105+
if err != nil {
106+
return codersdk.Entitlements{}, xerrors.Errorf("query external workspaces: %w", err)
107+
}
108+
109+
// nolint:gocritic // Getting external templates is a system function.
110+
externalTemplates, err := db.GetTemplatesWithFilter(dbauthz.AsSystemRestricted(ctx), database.GetTemplatesWithFilterParams{
111+
HasExternalAgent: sql.NullBool{
112+
Bool: true,
113+
Valid: true,
114+
},
115+
})
116+
if err != nil {
117+
return codersdk.Entitlements{}, xerrors.Errorf("query external templates: %w", err)
118+
}
119+
97120
entitlements, err := LicensesEntitlements(ctx, now, licenses, enablements, keys, FeatureArguments{
98-
ActiveUserCount: activeUserCount,
99-
ReplicaCount: replicaCount,
100-
ExternalAuthCount: externalAuthCount,
121+
ActiveUserCount: activeUserCount,
122+
ReplicaCount: replicaCount,
123+
ExternalAuthCount: externalAuthCount,
124+
ExternalWorkspaceCount: int64(len(externalWorkspaces)),
125+
ExternalTemplateCount: int64(len(externalTemplates)),
101126
ManagedAgentCountFn: func(ctx context.Context, startTime time.Time, endTime time.Time) (int64, error) {
102127
// nolint:gocritic // Requires permission to read all workspaces to read managed agent count.
103128
return db.GetManagedAgentCount(dbauthz.AsSystemRestricted(ctx), database.GetManagedAgentCountParams{
@@ -114,9 +139,11 @@ func Entitlements(
114139
}
115140

116141
type FeatureArguments struct {
117-
ActiveUserCount int64
118-
ReplicaCount int
119-
ExternalAuthCount int
142+
ActiveUserCount int64
143+
ReplicaCount int
144+
ExternalAuthCount int
145+
ExternalWorkspaceCount int64
146+
ExternalTemplateCount int64
120147
// Unfortunately, managed agent count is not a simple count of the current
121148
// state of the world, but a count between two points in time determined by
122149
// the licenses.
@@ -418,6 +445,30 @@ func LicensesEntitlements(
418445
}
419446
}
420447

448+
if featureArguments.ExternalWorkspaceCount > 0 {
449+
feature := entitlements.Features[codersdk.FeatureWorkspaceExternalAgent]
450+
switch feature.Entitlement {
451+
case codersdk.EntitlementNotEntitled:
452+
entitlements.Errors = append(entitlements.Errors,
453+
"You have external workspaces but your license is not entitled to this feature.")
454+
case codersdk.EntitlementGracePeriod:
455+
entitlements.Warnings = append(entitlements.Warnings,
456+
"You have external workspaces but your license is expired.")
457+
}
458+
}
459+
460+
if featureArguments.ExternalTemplateCount > 0 {
461+
feature := entitlements.Features[codersdk.FeatureWorkspaceExternalAgent]
462+
switch feature.Entitlement {
463+
case codersdk.EntitlementNotEntitled:
464+
entitlements.Errors = append(entitlements.Errors,
465+
"You have templates which use external agents but your license is not entitled to this feature.")
466+
case codersdk.EntitlementGracePeriod:
467+
entitlements.Warnings = append(entitlements.Warnings,
468+
"You have templates which use external agents but your license is expired.")
469+
}
470+
}
471+
421472
// Managed agent warnings are applied based on usage period. We only
422473
// generate a warning if the license actually has managed agents.
423474
// Note that agents are free when unlicensed.

0 commit comments

Comments
 (0)