Skip to content

feat(coderd): add support for external agents to API's and provisioner #19286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: kacpersaw/feat-coder-attach-database
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
implement license checks for external agents in workspace builds and …
…templates
  • Loading branch information
kacpersaw committed Aug 12, 2025
commit 0cf5381082c7e0cf9603dbffb3c85334a9cabdbc
12 changes: 12 additions & 0 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,18 @@ func (api *API) CheckBuildUsage(ctx context.Context, store database.Store, templ
// When there are no licenses installed, a noop usage checker is used
// instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dude to this comment above (and the entitlements code in the function above), external agents won't be blocked if there are no licenses installed (e.g. an enterprise build in free mode). Is that what you want? If it's not, I can walk you through a few changes to fix this.


// If the template version has an external agent, we need to check that the
// license is entitled to this feature.
if templateVersion.HasExternalAgent.Valid && templateVersion.HasExternalAgent.Bool {
feature, ok := api.Entitlements.Feature(codersdk.FeatureWorkspaceExternalAgent)
if !ok || !feature.Enabled {
return wsbuilder.UsageCheckResponse{
Permitted: false,
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.",
}, nil
}
}

// If the template version doesn't have an AI task, we don't need to check
// usage.
if !templateVersion.HasAITask.Valid || !templateVersion.HasAITask.Bool {
Expand Down
63 changes: 57 additions & 6 deletions enterprise/coderd/license/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package license
import (
"context"
"crypto/ed25519"
"database/sql"
"fmt"
"math"
"time"
Expand Down Expand Up @@ -94,10 +95,34 @@ func Entitlements(
return codersdk.Entitlements{}, xerrors.Errorf("query active user count: %w", err)
}

// nolint:gocritic // Getting external workspaces is a system function.
externalWorkspaces, err := db.GetWorkspaces(dbauthz.AsSystemRestricted(ctx), database.GetWorkspacesParams{
HasExternalAgent: sql.NullBool{
Bool: true,
Valid: true,
},
})
if err != nil {
return codersdk.Entitlements{}, xerrors.Errorf("query external workspaces: %w", err)
}

// nolint:gocritic // Getting external templates is a system function.
externalTemplates, err := db.GetTemplatesWithFilter(dbauthz.AsSystemRestricted(ctx), database.GetTemplatesWithFilterParams{
HasExternalAgent: sql.NullBool{
Bool: true,
Valid: true,
},
})
if err != nil {
return codersdk.Entitlements{}, xerrors.Errorf("query external templates: %w", err)
}

entitlements, err := LicensesEntitlements(ctx, now, licenses, enablements, keys, FeatureArguments{
ActiveUserCount: activeUserCount,
ReplicaCount: replicaCount,
ExternalAuthCount: externalAuthCount,
ActiveUserCount: activeUserCount,
ReplicaCount: replicaCount,
ExternalAuthCount: externalAuthCount,
ExternalWorkspaceCount: int64(len(externalWorkspaces)),
ExternalTemplateCount: int64(len(externalTemplates)),
ManagedAgentCountFn: func(ctx context.Context, startTime time.Time, endTime time.Time) (int64, error) {
// nolint:gocritic // Requires permission to read all workspaces to read managed agent count.
return db.GetManagedAgentCount(dbauthz.AsSystemRestricted(ctx), database.GetManagedAgentCountParams{
Expand All @@ -114,9 +139,11 @@ func Entitlements(
}

type FeatureArguments struct {
ActiveUserCount int64
ReplicaCount int
ExternalAuthCount int
ActiveUserCount int64
ReplicaCount int
ExternalAuthCount int
ExternalWorkspaceCount int64
ExternalTemplateCount int64
// Unfortunately, managed agent count is not a simple count of the current
// state of the world, but a count between two points in time determined by
// the licenses.
Expand Down Expand Up @@ -418,6 +445,30 @@ func LicensesEntitlements(
}
}

if featureArguments.ExternalWorkspaceCount > 0 {
feature := entitlements.Features[codersdk.FeatureWorkspaceExternalAgent]
switch feature.Entitlement {
case codersdk.EntitlementNotEntitled:
entitlements.Errors = append(entitlements.Errors,
"You have external workspaces but your license is not entitled to this feature.")
case codersdk.EntitlementGracePeriod:
entitlements.Warnings = append(entitlements.Warnings,
"You have external workspaces but your license is expired.")
}
}

if featureArguments.ExternalTemplateCount > 0 {
feature := entitlements.Features[codersdk.FeatureWorkspaceExternalAgent]
switch feature.Entitlement {
case codersdk.EntitlementNotEntitled:
entitlements.Errors = append(entitlements.Errors,
"You have templates which use external agents but your license is not entitled to this feature.")
case codersdk.EntitlementGracePeriod:
entitlements.Warnings = append(entitlements.Warnings,
"You have templates which use external agents but your license is expired.")
}
}

// Managed agent warnings are applied based on usage period. We only
// generate a warning if the license actually has managed agents.
// Note that agents are free when unlicensed.
Expand Down
Loading