diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index aa318a5f857c7..2c00030489dbc 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -14,6 +14,12 @@ SUBCOMMANDS: PostgreSQL deployment. OPTIONS: + --agent-metadata-min-interval duration, $CODER_AGENT_METADATA_MIN_INTERVAL (default: 0s) + Minimum interval for agent metadata collection. Template-defined + intervals below this value will cause template import to fail. + Existing workspaces with lower intervals will be silently upgraded on + restart. Set to 0 to disable enforcement. + --allow-workspace-renames bool, $CODER_ALLOW_WORKSPACE_RENAMES (default: false) DEPRECATED: Allow users to rename their workspaces. Use only for temporary compatibility reasons, this will be removed in a future diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index a9e6058a3eef2..eddc720fbcc26 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -485,6 +485,11 @@ sshKeygenAlgorithm: ed25519 # URL to use for agent troubleshooting when not set in the template. # (default: https://coder.com/docs/admin/templates/troubleshooting, type: url) agentFallbackTroubleshootingURL: https://coder.com/docs/admin/templates/troubleshooting +# Minimum interval for agent metadata collection. Template-defined intervals below +# this value will cause template import to fail. Existing workspaces with lower +# intervals will be silently upgraded on restart. Set to 0 to disable enforcement. +# (default: 0s, type: duration) +agentMetadataMinInterval: 0s # Disable workspace apps that are not served from subdomains. Path-based apps can # make requests to the Coder API and pose a security risk when the workspace # serves malicious JavaScript. This is recommended for security purposes if a diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index b8e3331ecd1f2..ed73d0da32232 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -14172,6 +14172,9 @@ const docTemplate = `{ "agent_fallback_troubleshooting_url": { "$ref": "#/definitions/serpent.URL" }, + "agent_metadata_min_interval": { + "type": "integer" + }, "agent_stat_refresh_interval": { "type": "integer" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 396a704a06119..29c599984d7b7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12756,6 +12756,9 @@ "agent_fallback_troubleshooting_url": { "$ref": "#/definitions/serpent.URL" }, + "agent_metadata_min_interval": { + "type": "integer" + }, "agent_stat_refresh_interval": { "type": "integer" }, diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index c4598beaf8399..5090d1a75a4e3 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1613,7 +1613,11 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro slog.F("resource_type", resource.Type), slog.F("transition", transition)) - if err := InsertWorkspaceResource(ctx, db, jobID, transition, resource, telemetrySnapshot); err != nil { + if err := InsertWorkspaceResource(ctx, db, jobID, transition, resource, telemetrySnapshot, + InsertWorkspaceResourceWithValidationMode(ValidationModeStrict), + InsertWorkspaceResourceWithDeploymentValues(s.DeploymentValues), + InsertWorkspaceResourceWithLogger(s.Logger), + ); err != nil { return xerrors.Errorf("insert resource: %w", err) } } @@ -2014,6 +2018,9 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro // Ensure that the agent IDs we set previously // are written to the database. InsertWorkspaceResourceWithAgentIDsFromProto(), + InsertWorkspaceResourceWithValidationMode(ValidationModeUpgrade), + InsertWorkspaceResourceWithDeploymentValues(s.DeploymentValues), + InsertWorkspaceResourceWithLogger(s.Logger), ) if err != nil { return xerrors.Errorf("insert provisioner job: %w", err) @@ -2623,8 +2630,23 @@ func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store, return nil } +// ValidationMode determines how agent metadata interval validation is enforced. +type ValidationMode int + +const ( + // ValidationModeStrict fails the operation if metadata intervals are below the minimum. + // Used for template imports. + ValidationModeStrict ValidationMode = iota + // ValidationModeUpgrade silently upgrades metadata intervals to meet the minimum. + // Used for workspace builds. + ValidationModeUpgrade +) + type insertWorkspaceResourceOptions struct { useAgentIDsFromProto bool + validationMode ValidationMode + deploymentValues *codersdk.DeploymentValues + logger slog.Logger } // InsertWorkspaceResourceOption represents a functional option for @@ -2639,6 +2661,27 @@ func InsertWorkspaceResourceWithAgentIDsFromProto() InsertWorkspaceResourceOptio } } +// InsertWorkspaceResourceWithValidationMode sets the validation mode for agent metadata intervals. +func InsertWorkspaceResourceWithValidationMode(mode ValidationMode) InsertWorkspaceResourceOption { + return func(opts *insertWorkspaceResourceOptions) { + opts.validationMode = mode + } +} + +// InsertWorkspaceResourceWithDeploymentValues sets the deployment values for validation. +func InsertWorkspaceResourceWithDeploymentValues(dv *codersdk.DeploymentValues) InsertWorkspaceResourceOption { + return func(opts *insertWorkspaceResourceOptions) { + opts.deploymentValues = dv + } +} + +// InsertWorkspaceResourceWithLogger sets the logger for logging validation actions. +func InsertWorkspaceResourceWithLogger(logger slog.Logger) InsertWorkspaceResourceOption { + return func(opts *insertWorkspaceResourceOptions) { + opts.logger = logger + } +} + func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.UUID, transition database.WorkspaceTransition, protoResource *sdkproto.Resource, snapshot *telemetry.Snapshot, opt ...InsertWorkspaceResourceOption) error { opts := &insertWorkspaceResourceOptions{} for _, o := range opt { @@ -2776,13 +2819,39 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent)) for _, md := range prAgent.Metadata { + interval := md.Interval + + // Apply minimum interval validation if configured + if opts.deploymentValues != nil && opts.deploymentValues.AgentMetadataMinInterval.Value() > 0 { + minInterval := opts.deploymentValues.AgentMetadataMinInterval.Value() + minIntervalSeconds := int64(minInterval.Seconds()) + + if interval < minIntervalSeconds { + if opts.validationMode == ValidationModeStrict { + // Template import - fail the operation + return xerrors.Errorf( + "agent %q metadata %q interval %ds is below minimum required %ds", + prAgent.Name, md.Key, interval, minIntervalSeconds, + ) + } + // Workspace build - upgrade silently + opts.logger.Info(ctx, "upgrading agent metadata interval to meet minimum", + slog.F("agent", prAgent.Name), + slog.F("metadata_key", md.Key), + slog.F("original_interval_seconds", interval), + slog.F("upgraded_interval_seconds", minIntervalSeconds), + ) + interval = minIntervalSeconds + } + } + p := database.InsertWorkspaceAgentMetadataParams{ WorkspaceAgentID: agentID, DisplayName: md.DisplayName, Script: md.Script, Key: md.Key, Timeout: md.Timeout, - Interval: md.Interval, + Interval: interval, // #nosec G115 - Order represents a display order value that's always small and fits in int32 DisplayOrder: int32(md.Order), } diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 4dc8621736b5c..09eb4530e4af5 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -20,11 +20,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" + "go.uber.org/mock/gomock" "golang.org/x/oauth2" "golang.org/x/xerrors" "google.golang.org/protobuf/types/known/timestamppb" "storj.io/drpc" + "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/coderd" "github.com/coder/coder/v2/coderd/util/ptr" @@ -37,6 +39,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbgen" + "github.com/coder/coder/v2/coderd/database/dbmock" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/pubsub" @@ -4469,3 +4472,390 @@ func seedPreviousWorkspaceStartWithAITask(ctx context.Context, t testing.TB, db }) return nil } + +// setupAgentMetadataTest creates common test fixtures for agent metadata interval tests. +func setupAgentMetadataTest(t *testing.T, minInterval time.Duration) ( + ctx context.Context, + db *dbmock.MockStore, + logger slog.Logger, + deploymentValues *codersdk.DeploymentValues, + jobID, resourceID, agentID uuid.UUID, +) { + t.Helper() + ctx = context.Background() + ctrl := gomock.NewController(t) + db = dbmock.NewMockStore(ctrl) + logger = slogtest.Make(t, nil).Leveled(slog.LevelDebug) + + deploymentValues = &codersdk.DeploymentValues{} + deploymentValues.AgentMetadataMinInterval = serpent.Duration(minInterval) + + jobID = uuid.New() + resourceID = uuid.New() + agentID = uuid.New() + + // Mock successful resource insertion + db.EXPECT(). + InsertWorkspaceResource(gomock.Any(), gomock.Any()). + Return(database.WorkspaceResource{ + ID: resourceID, + JobID: jobID, + }, nil) + + // Mock agent insertion + db.EXPECT(). + InsertWorkspaceAgent(gomock.Any(), gomock.Any()). + Return(database.WorkspaceAgent{ + ID: agentID, + ResourceID: resourceID, + Name: "main", + CreatedAt: dbtime.Now(), + }, nil) + + return ctx, db, logger, deploymentValues, jobID, resourceID, agentID +} + +// mockSuccessfulAgentInsertion mocks all the database calls needed for successful agent insertion. +func mockSuccessfulAgentInsertion(db *dbmock.MockStore) { + // Mock log sources insertion + db.EXPECT(). + InsertWorkspaceAgentLogSources(gomock.Any(), gomock.Any()). + Return([]database.WorkspaceAgentLogSource{}, nil) + + // Mock scripts insertion + db.EXPECT(). + InsertWorkspaceAgentScripts(gomock.Any(), gomock.Any()). + Return([]database.WorkspaceAgentScript{}, nil) + + // Mock resource metadata insertion + db.EXPECT(). + InsertWorkspaceResourceMetadata(gomock.Any(), gomock.Any()). + Return([]database.WorkspaceResourceMetadatum{}, nil) +} + +// TestAgentMetadataMinInterval_TemplateImportStrict tests that InsertWorkspaceResource +// fails when ValidationModeStrict is used and metadata intervals are below the minimum. +func TestAgentMetadataMinInterval_TemplateImportStrict(t *testing.T) { + t.Parallel() + + t.Run("FailsWhenBelowMinimum", func(t *testing.T) { + t.Parallel() + ctx, db, logger, deploymentValues, jobID, _, _ := setupAgentMetadataTest(t, 60*time.Second) + + // Create resource with agent that has metadata interval below minimum + resource := &sdkproto.Resource{ + Name: "example", + Type: "aws_instance", + Agents: []*sdkproto.Agent{ + { + Name: "main", + Auth: &sdkproto.Agent_Token{}, + Metadata: []*sdkproto.Agent_Metadata{ + { + Key: "cpu", + DisplayName: "CPU Usage", + Script: "echo 50", + Interval: 30, // 30 seconds - below the 60s minimum + Timeout: 5, + }, + }, + }, + }, + } + + // Should fail with ValidationModeStrict + err := provisionerdserver.InsertWorkspaceResource( + ctx, + db, + jobID, + database.WorkspaceTransitionStart, + resource, + &telemetry.Snapshot{}, + provisionerdserver.InsertWorkspaceResourceWithValidationMode(provisionerdserver.ValidationModeStrict), + provisionerdserver.InsertWorkspaceResourceWithDeploymentValues(deploymentValues), + provisionerdserver.InsertWorkspaceResourceWithLogger(logger), + ) + + require.Error(t, err) + require.ErrorContains(t, err, `agent "main" metadata "cpu" interval 30s is below minimum required 60s`) + }) + + t.Run("SucceedsWhenAtOrAboveMinimum", func(t *testing.T) { + t.Parallel() + ctx, db, logger, deploymentValues, jobID, _, _ := setupAgentMetadataTest(t, 60*time.Second) + mockSuccessfulAgentInsertion(db) + + // Mock metadata insertion - should be called twice + // Note: We use gomock.Any() for params because agent IDs are generated at runtime + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Verify the interval is correct (at minimum) + require.Equal(t, int64(60), params.Interval, "Expected interval to be at minimum (60s)") + require.Equal(t, "cpu", params.Key) + return nil + }) + + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Verify the interval is correct (above minimum) + require.Equal(t, int64(120), params.Interval, "Expected interval to be preserved (120s)") + require.Equal(t, "memory", params.Key) + return nil + }) + + // Create resource with agent that has metadata at and above minimum + resource := &sdkproto.Resource{ + Name: "example", + Type: "aws_instance", + Agents: []*sdkproto.Agent{ + { + Name: "main", + Auth: &sdkproto.Agent_Token{}, + Metadata: []*sdkproto.Agent_Metadata{ + { + Key: "cpu", + DisplayName: "CPU Usage", + Script: "echo 50", + Interval: 60, // At minimum + Timeout: 5, + }, + { + Key: "memory", + DisplayName: "Memory Usage", + Script: "echo 2048", + Interval: 120, // Above minimum + Timeout: 5, + }, + }, + }, + }, + } + + // Should succeed + err := provisionerdserver.InsertWorkspaceResource( + ctx, + db, + jobID, + database.WorkspaceTransitionStart, + resource, + &telemetry.Snapshot{}, + provisionerdserver.InsertWorkspaceResourceWithValidationMode(provisionerdserver.ValidationModeStrict), + provisionerdserver.InsertWorkspaceResourceWithDeploymentValues(deploymentValues), + provisionerdserver.InsertWorkspaceResourceWithLogger(logger), + ) + + require.NoError(t, err) + }) + + t.Run("NoEnforcementWhenMinimumIsZero", func(t *testing.T) { + t.Parallel() + ctx, db, logger, deploymentValues, jobID, _, _ := setupAgentMetadataTest(t, 0) + mockSuccessfulAgentInsertion(db) + + // Mock metadata insertion - original interval should be preserved + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Verify the interval is preserved (1s) + require.Equal(t, int64(1), params.Interval, "Expected interval to be preserved (1s)") + require.Equal(t, "cpu", params.Key) + return nil + }) + + // Create resource with very low interval + resource := &sdkproto.Resource{ + Name: "example", + Type: "aws_instance", + Agents: []*sdkproto.Agent{ + { + Name: "main", + Auth: &sdkproto.Agent_Token{}, + Metadata: []*sdkproto.Agent_Metadata{ + { + Key: "cpu", + DisplayName: "CPU Usage", + Script: "echo 50", + Interval: 1, // Very low + Timeout: 5, + }, + }, + }, + }, + } + + // Should succeed since enforcement is disabled + err := provisionerdserver.InsertWorkspaceResource( + ctx, + db, + jobID, + database.WorkspaceTransitionStart, + resource, + &telemetry.Snapshot{}, + provisionerdserver.InsertWorkspaceResourceWithValidationMode(provisionerdserver.ValidationModeStrict), + provisionerdserver.InsertWorkspaceResourceWithDeploymentValues(deploymentValues), + provisionerdserver.InsertWorkspaceResourceWithLogger(logger), + ) + + require.NoError(t, err) + }) +} + +// TestAgentMetadataMinInterval_WorkspaceBuildUpgrade tests that InsertWorkspaceResource +// silently upgrades intervals when ValidationModeUpgrade is used. +func TestAgentMetadataMinInterval_WorkspaceBuildUpgrade(t *testing.T) { + t.Parallel() + + t.Run("SilentlyUpgradesWhenBelowMinimum", func(t *testing.T) { + t.Parallel() + ctx, db, logger, deploymentValues, jobID, _, _ := setupAgentMetadataTest(t, 60*time.Second) + mockSuccessfulAgentInsertion(db) + + // Mock metadata insertion - both should be upgraded to 60s + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Verify interval is upgraded to minimum (60s) + require.Equal(t, int64(60), params.Interval, "Expected interval to be upgraded to 60s") + require.Equal(t, "cpu", params.Key) + return nil + }) + + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Verify interval is upgraded to minimum (60s) + require.Equal(t, int64(60), params.Interval, "Expected interval to be upgraded to 60s") + require.Equal(t, "memory", params.Key) + return nil + }) + + // Create resource with intervals below minimum + resource := &sdkproto.Resource{ + Name: "example", + Type: "aws_instance", + Agents: []*sdkproto.Agent{ + { + Name: "main", + Auth: &sdkproto.Agent_Token{}, + Metadata: []*sdkproto.Agent_Metadata{ + { + Key: "cpu", + DisplayName: "CPU Usage", + Script: "echo 50", + Interval: 30, // Below minimum + Timeout: 5, + }, + { + Key: "memory", + DisplayName: "Memory Usage", + Script: "echo 2048", + Interval: 10, // Below minimum + Timeout: 5, + }, + }, + }, + }, + } + + // Should succeed with ValidationModeUpgrade (not fail) + err := provisionerdserver.InsertWorkspaceResource( + ctx, + db, + jobID, + database.WorkspaceTransitionStart, + resource, + &telemetry.Snapshot{}, + provisionerdserver.InsertWorkspaceResourceWithValidationMode(provisionerdserver.ValidationModeUpgrade), + provisionerdserver.InsertWorkspaceResourceWithDeploymentValues(deploymentValues), + provisionerdserver.InsertWorkspaceResourceWithLogger(logger), + ) + + require.NoError(t, err) + }) + + t.Run("PreservesIntervalsAtOrAboveMinimum", func(t *testing.T) { + t.Parallel() + ctx, db, logger, deploymentValues, jobID, _, _ := setupAgentMetadataTest(t, 30*time.Second) + mockSuccessfulAgentInsertion(db) + + // Mock metadata insertions with expected intervals + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // First call: verify "below" is upgraded to 30s + require.Equal(t, int64(30), params.Interval, "Expected 'below' interval to be upgraded to 30s") + require.Equal(t, "below", params.Key) + return nil + }) + + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Second call: verify "at" stays at 30s + require.Equal(t, int64(30), params.Interval, "Expected 'at' interval to stay at 30s") + require.Equal(t, "at", params.Key) + return nil + }) + + db.EXPECT(). + InsertWorkspaceAgentMetadata(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, params database.InsertWorkspaceAgentMetadataParams) error { + // Third call: verify "above" stays at 120s + require.Equal(t, int64(120), params.Interval, "Expected 'above' interval to stay at 120s") + require.Equal(t, "above", params.Key) + return nil + }) + + // Create resource with mixed intervals + resource := &sdkproto.Resource{ + Name: "example", + Type: "aws_instance", + Agents: []*sdkproto.Agent{ + { + Name: "main", + Auth: &sdkproto.Agent_Token{}, + Metadata: []*sdkproto.Agent_Metadata{ + { + Key: "below", + DisplayName: "Below Minimum", + Script: "echo below", + Interval: 10, // Below minimum + Timeout: 5, + }, + { + Key: "at", + DisplayName: "At Minimum", + Script: "echo at", + Interval: 30, // At minimum + Timeout: 5, + }, + { + Key: "above", + DisplayName: "Above Minimum", + Script: "echo above", + Interval: 120, // Above minimum + Timeout: 5, + }, + }, + }, + }, + } + + // Should succeed and preserve/upgrade as expected + err := provisionerdserver.InsertWorkspaceResource( + ctx, + db, + jobID, + database.WorkspaceTransitionStart, + resource, + &telemetry.Snapshot{}, + provisionerdserver.InsertWorkspaceResourceWithValidationMode(provisionerdserver.ValidationModeUpgrade), + provisionerdserver.InsertWorkspaceResourceWithDeploymentValues(deploymentValues), + provisionerdserver.InsertWorkspaceResourceWithLogger(logger), + ) + + require.NoError(t, err) + }) +} diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 0dd082ab5eebc..97c6648a7efc2 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -476,6 +476,7 @@ type DeploymentValues struct { MetricsCacheRefreshInterval serpent.Duration `json:"metrics_cache_refresh_interval,omitempty" typescript:",notnull"` AgentStatRefreshInterval serpent.Duration `json:"agent_stat_refresh_interval,omitempty" typescript:",notnull"` AgentFallbackTroubleshootingURL serpent.URL `json:"agent_fallback_troubleshooting_url,omitempty" typescript:",notnull"` + AgentMetadataMinInterval serpent.Duration `json:"agent_metadata_min_interval,omitempty" typescript:",notnull"` BrowserOnly serpent.Bool `json:"browser_only,omitempty" typescript:",notnull"` SCIMAPIKey serpent.String `json:"scim_api_key,omitempty" typescript:",notnull"` ExternalTokenEncryptionKeys serpent.StringArray `json:"external_token_encryption_keys,omitempty" typescript:",notnull"` @@ -2683,6 +2684,17 @@ func (c *DeploymentValues) Options() serpent.OptionSet { Value: &c.AgentFallbackTroubleshootingURL, YAML: "agentFallbackTroubleshootingURL", }, + { + Name: "Agent Metadata Minimum Interval", + Description: `Minimum interval for agent metadata collection. Template-defined intervals below this value will cause template import to fail. Existing workspaces with lower intervals will be silently upgraded on restart. Set to 0 to disable enforcement.`, + Flag: "agent-metadata-min-interval", + Env: "CODER_AGENT_METADATA_MIN_INTERVAL", + YAML: "agentMetadataMinInterval", + Hidden: false, + Default: (0 * time.Second).String(), + Value: &c.AgentMetadataMinInterval, + Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), + }, { Name: "Browser Only", Description: "Whether Coder only allows connections to workspaces via the browser.", diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md index 3ea0180ae1454..8916a6e551ed2 100644 --- a/docs/reference/api/general.md +++ b/docs/reference/api/general.md @@ -160,6 +160,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "scheme": "string", "user": {} }, + "agent_metadata_min_interval": 0, "agent_stat_refresh_interval": 0, "ai": { "bridge": { diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index bd00d79c4b40b..4954b7dab9a72 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -2844,6 +2844,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "scheme": "string", "user": {} }, + "agent_metadata_min_interval": 0, "agent_stat_refresh_interval": 0, "ai": { "bridge": { @@ -3367,6 +3368,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "scheme": "string", "user": {} }, + "agent_metadata_min_interval": 0, "agent_stat_refresh_interval": 0, "ai": { "bridge": { @@ -3781,6 +3783,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `additional_csp_policy` | array of string | false | | | | `address` | [serpent.HostPort](#serpenthostport) | false | | Deprecated: Use HTTPAddress or TLS.Address instead. | | `agent_fallback_troubleshooting_url` | [serpent.URL](#serpenturl) | false | | | +| `agent_metadata_min_interval` | integer | false | | | | `agent_stat_refresh_interval` | integer | false | | | | `ai` | [codersdk.AIConfig](#codersdkaiconfig) | false | | | | `allow_workspace_renames` | boolean | false | | | diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index 4ba8c026fb299..38f0f631bd0f4 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1067,6 +1067,17 @@ Two optional fields can be set in the Strict-Transport-Security header; 'include The algorithm to use for generating ssh keys. Accepted values are "ed25519", "ecdsa", or "rsa4096". +### --agent-metadata-min-interval + +| | | +|-------------|-------------------------------------------------| +| Type | duration | +| Environment | $CODER_AGENT_METADATA_MIN_INTERVAL | +| YAML | agentMetadataMinInterval | +| Default | 0s | + +Minimum interval for agent metadata collection. Template-defined intervals below this value will cause template import to fail. Existing workspaces with lower intervals will be silently upgraded on restart. Set to 0 to disable enforcement. + ### --browser-only | | | diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 32db725d93f77..b1dd9e755d8da 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -15,6 +15,12 @@ SUBCOMMANDS: PostgreSQL deployment. OPTIONS: + --agent-metadata-min-interval duration, $CODER_AGENT_METADATA_MIN_INTERVAL (default: 0s) + Minimum interval for agent metadata collection. Template-defined + intervals below this value will cause template import to fail. + Existing workspaces with lower intervals will be silently upgraded on + restart. Set to 0 to disable enforcement. + --allow-workspace-renames bool, $CODER_ALLOW_WORKSPACE_RENAMES (default: false) DEPRECATED: Allow users to rename their workspaces. Use only for temporary compatibility reasons, this will be removed in a future diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6cb14744035cc..bca79c20ca06e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1753,6 +1753,7 @@ export interface DeploymentValues { readonly metrics_cache_refresh_interval?: number; readonly agent_stat_refresh_interval?: number; readonly agent_fallback_troubleshooting_url?: string; + readonly agent_metadata_min_interval?: number; readonly browser_only?: boolean; readonly scim_api_key?: string; readonly external_token_encryption_keys?: string;