Skip to content

feat: add resources_monitoring field to agent #331

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

Merged
merged 12 commits into from
Feb 7, 2025
28 changes: 28 additions & 0 deletions docs/resources/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ resource "kubernetes_pod" "dev" {
- `metadata` (Block List) Each `metadata` block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata))
- `motd_file` (String) The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be `"/etc/motd"`.
- `order` (Number) The order determines the position of agents in the UI presentation. The lowest order is shown first and agents with equal order are sorted by name (ascending order).
- `resources_monitoring` (Block Set, Max: 1) The resources monitoring configuration for this agent. (see [below for nested schema](#nestedblock--resources_monitoring))
- `shutdown_script` (String) A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a `coder_script` resource with `run_on_stop` set to `true`.
- `startup_script` (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a `coder_script` resource with `run_on_start` set to `true`.
- `startup_script_behavior` (String) This option sets the behavior of the `startup_script`. When set to `"blocking"`, the `startup_script` must exit before the workspace is ready. When set to `"non-blocking"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `"non-blocking"`, although `"blocking"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking).
Expand Down Expand Up @@ -116,3 +117,30 @@ Optional:
- `display_name` (String) The user-facing name of this value.
- `order` (Number) The order determines the position of agent metadata in the UI presentation. The lowest order is shown first and metadata with equal order are sorted by key (ascending order).
- `timeout` (Number) The maximum time the command is allowed to run in seconds.


<a id="nestedblock--resources_monitoring"></a>
### Nested Schema for `resources_monitoring`

Optional:

- `memory` (Block Set, Max: 1) The memory monitoring configuration for this agent. (see [below for nested schema](#nestedblock--resources_monitoring--memory))
- `volume` (Block Set) The volumes monitoring configuration for this agent. (see [below for nested schema](#nestedblock--resources_monitoring--volume))

<a id="nestedblock--resources_monitoring--memory"></a>
### Nested Schema for `resources_monitoring.memory`

Required:

- `enabled` (Boolean) Enable memory monitoring for this agent.
- `threshold` (Number) The memory usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100.


<a id="nestedblock--resources_monitoring--volume"></a>
### Nested Schema for `resources_monitoring.volume`

Required:

- `enabled` (Boolean) Enable volume monitoring for this agent.
- `path` (String) The path of the volume to monitor.
- `threshold` (Number) The volume usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100.
26 changes: 26 additions & 0 deletions examples/data-sources/coder_resources_monitoring/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
provider "coder" {}

data "coder_provisioner" "dev" {}

data "coder_workspace" "dev" {}

resource "coder_agent" "main" {
arch = data.coder_provisioner.dev.arch
os = data.coder_provisioner.dev.os
resources_monitoring {
memory {
enabled = true
threshold = 80
}
volume {
path = "/volume1"
enabled = true
threshold = 80
}
volume {
path = "/volume2"
enabled = true
threshold = 100
}
}
}
143 changes: 128 additions & 15 deletions provider/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package provider
import (
"context"
"fmt"
"path/filepath"
"reflect"
"strings"

"github.com/google/uuid"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -259,31 +261,142 @@ func agentResource() *schema.Resource {
ForceNew: true,
Optional: true,
},
"resources_monitoring": {
Type: schema.TypeSet,
Description: "The resources monitoring configuration for this agent.",
ForceNew: true,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"memory": {
Type: schema.TypeSet,
Description: "The memory monitoring configuration for this agent.",
ForceNew: true,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Description: "Enable memory monitoring for this agent.",
ForceNew: true,
Required: true,
},
"threshold": {
Type: schema.TypeInt,
Description: "The memory usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100.",
ForceNew: true,
Required: true,
ValidateFunc: validation.IntBetween(0, 100),
},
},
},
},
"volume": {
Type: schema.TypeSet,
Description: "The volumes monitoring configuration for this agent.",
ForceNew: true,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"path": {
Type: schema.TypeString,
Description: "The path of the volume to monitor.",
ForceNew: true,
Required: true,
ValidateDiagFunc: func(i interface{}, s cty.Path) diag.Diagnostics {
path, ok := i.(string)
if !ok {
return diag.Errorf("volume path must be a string")
}
if path == "" {
return diag.Errorf("volume path must not be empty")
}

if !filepath.IsAbs(i.(string)) {
return diag.Errorf("volume path must be an absolute path")
}

return nil
},
},
"enabled": {
Type: schema.TypeBool,
Description: "Enable volume monitoring for this agent.",
ForceNew: true,
Required: true,
},
"threshold": {
Type: schema.TypeInt,
Description: "The volume usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100.",
ForceNew: true,
Required: true,
ValidateFunc: validation.IntBetween(0, 100),
},
},
},
},
},
},
},
},
CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i any) error {
if !rd.HasChange("metadata") {
return nil
if rd.HasChange("metadata") {
keys := map[string]bool{}
metadata, ok := rd.Get("metadata").([]any)
if !ok {
return xerrors.Errorf("unexpected type %T for metadata, expected []any", rd.Get("metadata"))
}
for _, t := range metadata {
obj, ok := t.(map[string]any)
if !ok {
return xerrors.Errorf("unexpected type %T for metadata, expected map[string]any", t)
}
key, ok := obj["key"].(string)
if !ok {
return xerrors.Errorf("unexpected type %T for metadata key, expected string", obj["key"])
}
if keys[key] {
return xerrors.Errorf("duplicate agent metadata key %q", key)
}
keys[key] = true
}
}

keys := map[string]bool{}
metadata, ok := rd.Get("metadata").([]any)
if !ok {
return xerrors.Errorf("unexpected type %T for metadata, expected []any", rd.Get("metadata"))
}
for _, t := range metadata {
obj, ok := t.(map[string]any)
if rd.HasChange("resources_monitoring") {
monitors, ok := rd.Get("resources_monitoring").(*schema.Set)
if !ok {
return xerrors.Errorf("unexpected type %T for metadata, expected map[string]any", t)
return xerrors.Errorf("unexpected type %T for resources_monitoring.0.volume, expected []any", rd.Get("resources_monitoring.0.volume"))
}
key, ok := obj["key"].(string)

monitor := monitors.List()[0].(map[string]any)

volumes, ok := monitor["volume"].(*schema.Set)
if !ok {
return xerrors.Errorf("unexpected type %T for metadata key, expected string", obj["key"])
return xerrors.Errorf("unexpected type %T for resources_monitoring.0.volume, expected []any", monitor["volume"])
}
if keys[key] {
return xerrors.Errorf("duplicate agent metadata key %q", key)

paths := map[string]bool{}
for _, volume := range volumes.List() {
obj, ok := volume.(map[string]any)
if !ok {
return xerrors.Errorf("unexpected type %T for volume, expected map[string]any", volume)
}

// print path for debug purpose

path, ok := obj["path"].(string)
if !ok {
return xerrors.Errorf("unexpected type %T for volume path, expected string", obj["path"])
}
if paths[path] {
return xerrors.Errorf("duplicate volume path %q", path)
}
paths[path] = true
}
keys[key] = true
}

return nil
},
}
Expand Down
Loading
Loading