mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd): add support for external agents to API's and provisioner (#19286)
This pull request introduces support for external workspace management, allowing users to register and manage workspaces that are provisioned and managed outside of the Coder. Depends on: https://github.com/coder/terraform-provider-coder/pull/424 * GET /api/v2/init-script - Gets the agent initialization script * By default, it returns a script for Linux (amd64), but with query parameters (os and arch) you can get the init script for different platforms * GET /api/v2/workspaces/{workspace}/external-agent/{agent}/credentials - Gets credentials for an external agent **(enterprise)** * Updated queries to filter workspaces/templates by the has_external_agent field
This commit is contained in:
+49
-37
@@ -506,6 +506,15 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractNotificationTemplateParam(options.Database),
|
||||
).Put("/notifications/templates/{notification_template}/method", api.updateNotificationTemplateMethod)
|
||||
|
||||
r.Route("/workspaces/{workspace}/external-agent", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractWorkspaceParam(options.Database),
|
||||
api.RequireFeatureMW(codersdk.FeatureWorkspaceExternalAgent),
|
||||
)
|
||||
r.Get("/{agent}/credentials", api.workspaceExternalAgentCredentials)
|
||||
})
|
||||
})
|
||||
|
||||
if len(options.SCIMAPIKey) != 0 {
|
||||
@@ -920,17 +929,9 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
}
|
||||
reloadedEntitlements.Features[codersdk.FeatureExternalTokenEncryption] = featureExternalTokenEncryption
|
||||
|
||||
// If there's a license installed, we will use the enterprise build
|
||||
// limit checker.
|
||||
// This checker currently only enforces the managed agent limit.
|
||||
if reloadedEntitlements.HasLicense {
|
||||
var checker wsbuilder.UsageChecker = api
|
||||
api.AGPL.BuildUsageChecker.Store(&checker)
|
||||
} else {
|
||||
// Don't check any usage, just like AGPL.
|
||||
var checker wsbuilder.UsageChecker = wsbuilder.NoopUsageChecker{}
|
||||
api.AGPL.BuildUsageChecker.Store(&checker)
|
||||
}
|
||||
// Always use the enterprise usage checker
|
||||
var checker wsbuilder.UsageChecker = api
|
||||
api.AGPL.BuildUsageChecker.Store(&checker)
|
||||
|
||||
return reloadedEntitlements, nil
|
||||
})
|
||||
@@ -939,9 +940,17 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
var _ wsbuilder.UsageChecker = &API{}
|
||||
|
||||
func (api *API) CheckBuildUsage(ctx context.Context, store database.Store, templateVersion *database.TemplateVersion) (wsbuilder.UsageCheckResponse, error) {
|
||||
// We assume that if this function is called, a valid license is installed.
|
||||
// When there are no licenses installed, a noop usage checker is used
|
||||
// instead.
|
||||
// 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.
|
||||
@@ -951,32 +960,35 @@ func (api *API) CheckBuildUsage(ctx context.Context, store database.Store, templ
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Otherwise, we need to check that we haven't breached the managed agent
|
||||
// When unlicensed, we need to check that we haven't breached the managed agent
|
||||
// limit.
|
||||
managedAgentLimit, ok := api.Entitlements.Feature(codersdk.FeatureManagedAgentLimit)
|
||||
if !ok || !managedAgentLimit.Enabled || managedAgentLimit.Limit == nil || managedAgentLimit.UsagePeriod == nil {
|
||||
return wsbuilder.UsageCheckResponse{
|
||||
Permitted: false,
|
||||
Message: "Your license is not entitled to managed agents. Please contact sales to continue using managed agents.",
|
||||
}, nil
|
||||
}
|
||||
// Unlicensed deployments are allowed to use unlimited managed agents.
|
||||
if api.Entitlements.HasLicense() {
|
||||
managedAgentLimit, ok := api.Entitlements.Feature(codersdk.FeatureManagedAgentLimit)
|
||||
if !ok || !managedAgentLimit.Enabled || managedAgentLimit.Limit == nil || managedAgentLimit.UsagePeriod == nil {
|
||||
return wsbuilder.UsageCheckResponse{
|
||||
Permitted: false,
|
||||
Message: "Your license is not entitled to managed agents. Please contact sales to continue using managed agents.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// This check is intentionally not committed to the database. It's fine if
|
||||
// it's not 100% accurate or allows for minor breaches due to build races.
|
||||
// nolint:gocritic // Requires permission to read all workspaces to read managed agent count.
|
||||
managedAgentCount, err := store.GetManagedAgentCount(agpldbauthz.AsSystemRestricted(ctx), database.GetManagedAgentCountParams{
|
||||
StartTime: managedAgentLimit.UsagePeriod.Start,
|
||||
EndTime: managedAgentLimit.UsagePeriod.End,
|
||||
})
|
||||
if err != nil {
|
||||
return wsbuilder.UsageCheckResponse{}, xerrors.Errorf("get managed agent count: %w", err)
|
||||
}
|
||||
// This check is intentionally not committed to the database. It's fine if
|
||||
// it's not 100% accurate or allows for minor breaches due to build races.
|
||||
// nolint:gocritic // Requires permission to read all workspaces to read managed agent count.
|
||||
managedAgentCount, err := store.GetManagedAgentCount(agpldbauthz.AsSystemRestricted(ctx), database.GetManagedAgentCountParams{
|
||||
StartTime: managedAgentLimit.UsagePeriod.Start,
|
||||
EndTime: managedAgentLimit.UsagePeriod.End,
|
||||
})
|
||||
if err != nil {
|
||||
return wsbuilder.UsageCheckResponse{}, xerrors.Errorf("get managed agent count: %w", err)
|
||||
}
|
||||
|
||||
if managedAgentCount >= *managedAgentLimit.Limit {
|
||||
return wsbuilder.UsageCheckResponse{
|
||||
Permitted: false,
|
||||
Message: "You have breached the managed agent limit in your license. Please contact sales to continue using managed agents.",
|
||||
}, nil
|
||||
if managedAgentCount >= *managedAgentLimit.Limit {
|
||||
return wsbuilder.UsageCheckResponse{
|
||||
Permitted: false,
|
||||
Message: "You have breached the managed agent limit in your license. Please contact sales to continue using managed agents.",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return wsbuilder.UsageCheckResponse{
|
||||
|
||||
Reference in New Issue
Block a user