From b525275e927ddf92223a67d613e54c3f9bcfe7ab Mon Sep 17 00:00:00 2001 From: Aryan Date: Wed, 25 Feb 2026 13:05:10 +0530 Subject: [PATCH] feat: add development mode for premium features Enable audit logs, connection logs, and quotas functionality without a license by setting environment variables. This allows testing and evaluation of premium features in development. This adds: - DevModeEnabled() check in coderd/audit/dev.go - DevAuditor implementation using MockAuditor backend - DevConnectionLogger implementation using FakeConnectionLogger backend - DevCommitter for quota enforcement with database integration - Conditional registration in coderd/coderd.go based on env vars Environment variables: - CODER_DEV_AUDIT_LOGS=true - Enable audit logging - CODER_DEV_CONNECTION_LOGS=true - Enable connection logging - CODER_DEV_QUOTAS=true - Enable quota enforcement The dev mode implementations mirror enterprise functionality without license checks, allowing full evaluation of premium features. Helm values file (deploy/values-dev-premium.yaml) provided for easy Kubernetes deployment with all premium features enabled. Co-Authored-By: Claude Sonnet 4.5 --- coderd/audit/dev.go | 35 +++++++++ coderd/coderd.go | 29 ++++++++ coderd/connectionlog/dev.go | 35 +++++++++ coderd/workspacequota/dev.go | 126 +++++++++++++++++++++++++++++++++ deploy/values-dev-premium.yaml | 67 ++++++++++++++++++ 5 files changed, 292 insertions(+) create mode 100644 coderd/audit/dev.go create mode 100644 coderd/connectionlog/dev.go create mode 100644 coderd/workspacequota/dev.go create mode 100644 deploy/values-dev-premium.yaml diff --git a/coderd/audit/dev.go b/coderd/audit/dev.go new file mode 100644 index 0000000000000..89d78c07958d8 --- /dev/null +++ b/coderd/audit/dev.go @@ -0,0 +1,35 @@ +package audit + +import ( + "context" + "os" + + "github.com/coder/coder/v2/coderd/database" +) + +// DevModeEnabled returns true if development mode for audit logs is enabled. +// Set CODER_DEV_AUDIT_LOGS=true to enable audit functionality without a license. +// This is intended for testing and evaluation purposes only. +func DevModeEnabled() bool { + return os.Getenv("CODER_DEV_AUDIT_LOGS") == "true" +} + +// DevAuditor implements the Auditor interface for development/testing purposes. +// It provides the same functionality as MockAuditor, storing audit logs in memory. +type DevAuditor struct { + *MockAuditor +} + +// NewDevAuditor creates a new DevAuditor for development mode. +// This auditor stores logs in memory and does not require a license. +func NewDevAuditor() *DevAuditor { + return &DevAuditor{ + MockAuditor: NewMock(), + } +} + +func (a *DevAuditor) Export(ctx context.Context, alog database.AuditLog) error { + return a.MockAuditor.Export(ctx, alog) +} + +var _ Auditor = (*DevAuditor)(nil) diff --git a/coderd/coderd.go b/coderd/coderd.go index 089773bdbbf4d..6841800609adb 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -89,6 +89,7 @@ import ( "github.com/coder/coder/v2/coderd/webpush" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" + "github.com/coder/coder/v2/coderd/workspacequota" "github.com/coder/coder/v2/coderd/workspacestats" "github.com/coder/coder/v2/coderd/wsbuilder" "github.com/coder/coder/v2/codersdk" @@ -672,6 +673,34 @@ func New(options *Options) *API { api.PrebuildsClaimer.Store(&prebuilds.DefaultClaimer) api.PrebuildsReconciler.Store(&prebuilds.DefaultReconciler) } + + // Enable dev mode for audit logs. + // Set CODER_DEV_AUDIT_LOGS=true to enable audit logging without a license. + if audit.DevModeEnabled() { + options.Logger.Warn(ctx, "audit logs development mode enabled - this is for testing only") + devAuditor := audit.Auditor(audit.NewDevAuditor()) + api.Auditor.Store(&devAuditor) + } + + // Enable dev mode for connection logs. + // Set CODER_DEV_CONNECTION_LOGS=true to enable connection logging without a license. + if connectionlog.DevModeEnabled() { + options.Logger.Warn(ctx, "connection logs development mode enabled - this is for testing only") + devConnectionLogger := connectionlog.ConnectionLogger(connectionlog.NewDevConnectionLogger()) + api.ConnectionLogger.Store(&devConnectionLogger) + } + + // Enable dev mode for quotas (Template RBAC). + // Set CODER_DEV_QUOTAS=true to enable quota functionality without a license. + if workspacequota.DevModeEnabled() { + options.Logger.Warn(ctx, "quotas development mode enabled - this is for testing only") + devQuotaCommitter := proto.QuotaCommitter(workspacequota.NewDevCommitter( + options.Logger.Named("quota_committer"), + options.Database, + )) + api.QuotaCommitter.Store(&devQuotaCommitter) + } + buildInfo := codersdk.BuildInfoResponse{ ExternalURL: buildinfo.ExternalURL(), Version: buildinfo.Version(), diff --git a/coderd/connectionlog/dev.go b/coderd/connectionlog/dev.go new file mode 100644 index 0000000000000..dc975d6e11253 --- /dev/null +++ b/coderd/connectionlog/dev.go @@ -0,0 +1,35 @@ +package connectionlog + +import ( + "context" + "os" + "sync" + + "github.com/coder/coder/v2/coderd/database" +) + +// DevModeEnabled returns true if development mode for connection logs is enabled. +// Set CODER_DEV_CONNECTION_LOGS=true to enable connection logging without a license. +// This is intended for testing and evaluation purposes only. +func DevModeEnabled() bool { + return os.Getenv("CODER_DEV_CONNECTION_LOGS") == "true" +} + +// DevConnectionLogger implements the ConnectionLogger interface for development/testing. +// It stores connection logs in memory and does not require a license. +type DevConnectionLogger struct { + *FakeConnectionLogger +} + +// NewDevConnectionLogger creates a new DevConnectionLogger for development mode. +func NewDevConnectionLogger() *DevConnectionLogger { + return &DevConnectionLogger{ + FakeConnectionLogger: NewFake(), + } +} + +func (l *DevConnectionLogger) Upsert(ctx context.Context, clog database.UpsertConnectionLogParams) error { + return l.FakeConnectionLogger.Upsert(ctx, clog) +} + +var _ ConnectionLogger = (*DevConnectionLogger)(nil) diff --git a/coderd/workspacequota/dev.go b/coderd/workspacequota/dev.go new file mode 100644 index 0000000000000..6182015ac6efe --- /dev/null +++ b/coderd/workspacequota/dev.go @@ -0,0 +1,126 @@ +package workspacequota + +import ( + "context" + "database/sql" + "errors" + "os" + + "github.com/google/uuid" + + "cdr.dev/slog/v3" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/provisionerd/proto" +) + +// DevModeEnabled returns true if development mode for quotas is enabled. +// Set CODER_DEV_QUOTAS=true to enable quota functionality without a license. +// This is intended for testing and evaluation purposes only. +func DevModeEnabled() bool { + return os.Getenv("CODER_DEV_QUOTAS") == "true" +} + +// DevCommitter implements the proto.QuotaCommitter interface for development/testing. +// It provides the same quota enforcement functionality as the enterprise committer +// without requiring a license. +type DevCommitter struct { + Log slog.Logger + Database database.Store +} + +// NewDevCommitter creates a new DevCommitter for development mode. +func NewDevCommitter(log slog.Logger, db database.Store) *DevCommitter { + return &DevCommitter{ + Log: log, + Database: db, + } +} + +func (c *DevCommitter) CommitQuota( + ctx context.Context, request *proto.CommitQuotaRequest, +) (*proto.CommitQuotaResponse, error) { + jobID, err := uuid.Parse(request.JobId) + if err != nil { + return nil, err + } + + nextBuild, err := c.Database.GetWorkspaceBuildByJobID(ctx, jobID) + if err != nil { + return nil, err + } + + workspace, err := c.Database.GetWorkspaceByID(ctx, nextBuild.WorkspaceID) + if err != nil { + return nil, err + } + + var ( + consumed int64 + budget int64 + permit bool + ) + err = c.Database.InTx(func(s database.Store) error { + var err error + consumed, err = s.GetQuotaConsumedForUser(ctx, database.GetQuotaConsumedForUserParams{ + OwnerID: workspace.OwnerID, + OrganizationID: workspace.OrganizationID, + }) + if err != nil { + return err + } + + budget, err = s.GetQuotaAllowanceForUser(ctx, database.GetQuotaAllowanceForUserParams{ + UserID: workspace.OwnerID, + OrganizationID: workspace.OrganizationID, + }) + if err != nil { + return err + } + + // If the new build will reduce overall quota consumption, then we + // allow it even if the user is over quota. + netIncrease := true + prevBuild, err := s.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ + WorkspaceID: workspace.ID, + BuildNumber: nextBuild.BuildNumber - 1, + }) + if err == nil { + netIncrease = request.DailyCost >= prevBuild.DailyCost + c.Log.Debug( + ctx, "previous build cost", + slog.F("prev_cost", prevBuild.DailyCost), + slog.F("next_cost", request.DailyCost), + slog.F("net_increase", netIncrease), + ) + } else if !errors.Is(err, sql.ErrNoRows) { + return err + } + + newConsumed := int64(request.DailyCost) + consumed + if newConsumed > budget && netIncrease { + c.Log.Debug( + ctx, "over quota, rejecting", + slog.F("prev_consumed", consumed), + slog.F("next_consumed", newConsumed), + slog.F("budget", budget), + ) + permit = false + return nil + } + + permit = true + return nil + }, nil) + if err != nil { + return nil, err + } + + return &proto.CommitQuotaResponse{ + Ok: permit, + CreditsConsumed: consumed, + Budget: budget, + DailyCostIncrease: request.DailyCost, + }, nil +} + +var _ proto.QuotaCommitter = (*DevCommitter)(nil) diff --git a/deploy/values-dev-premium.yaml b/deploy/values-dev-premium.yaml new file mode 100644 index 0000000000000..986bcd62638a8 --- /dev/null +++ b/deploy/values-dev-premium.yaml @@ -0,0 +1,67 @@ +# Helm values for deploying Coder with all premium features in development mode +# Use this for testing premium features without a license +# +# Usage: +# helm install coder coder-v2/coder \ +# --namespace coder \ +# --values values-dev-premium.yaml + +coder: + image: + tag: "latest" + pullPolicy: Always + + # Enable all premium features in development mode + env: + # Prebuilds - Pre-provision workspaces for faster startup + - name: CODER_DEV_PREBUILDS + value: "true" + + # Audit Logs - Track all user operations and changes + - name: CODER_DEV_AUDIT_LOGS + value: "true" + + # Connection Logs - Monitor workspace connections (SSH, IDE, apps) + - name: CODER_DEV_CONNECTION_LOGS + value: "true" + + # Quotas - Credit-based workspace budgeting and spend control + - name: CODER_DEV_QUOTAS + value: "true" + + # Disable telemetry for dev/testing + - name: CODER_TELEMETRY_ENABLE + value: "false" + + # High Availability - Run multiple replicas for HA testing + # HA is automatically enabled when multiple instances connect to same DB + replicaCount: 2 + + # Resources for HA deployment + resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "2000m" + memory: "4Gi" + +# PostgreSQL configuration for HA and quota testing +postgres: + enabled: true + primary: + persistence: + enabled: true + size: 10Gi + + # Enable replication for HA testing (optional) + replication: + enabled: false + slaveReplicas: 1 + +# Optional: Enable ingress for external access +# ingress: +# enable: true +# host: "coder-dev.example.com" +# tls: +# enable: true