diff --git a/docs/resources/agent.md b/docs/resources/agent.md index d465c173..23966105 100644 --- a/docs/resources/agent.md +++ b/docs/resources/agent.md @@ -43,6 +43,8 @@ resource "coder_agent" "dev" { timeout = 1 order = 1 } + + order = 1 } resource "kubernetes_pod" "dev" { @@ -77,11 +79,11 @@ resource "kubernetes_pod" "dev" { - `login_before_ready` (Boolean, Deprecated) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the startup_script is done and has exited. When enabled, users may see an incomplete workspace when logging in. - `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. -- `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. -- `shutdown_script_timeout` (Number) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. -- `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. -- `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. -- `startup_script_timeout` (Number) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. +- `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. +- `shutdown_script_timeout` (Number, Deprecated) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. +- `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). +- `startup_script_timeout` (Number, Deprecated) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. - `troubleshooting_url` (String) A URL to a document with instructions for troubleshooting problems with the agent. ### Read-Only @@ -114,5 +116,5 @@ Required: Optional: - `display_name` (String) The user-facing name of this value. -- `order` (Number) The order determines the position of agent metadata in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by key (ascending order). +- `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. diff --git a/docs/resources/app.md b/docs/resources/app.md index 53a8c127..631eb1ec 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -73,6 +73,7 @@ resource "coder_app" "intellij" { - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. - `name` (String, Deprecated) A display name to identify the app. +- `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). - `relative_path` (Boolean, Deprecated) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. - `share` (String) Determines the "level" which the application is shared at. Valid levels are "owner" (default), "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. Defaults to false. diff --git a/docs/resources/script.md b/docs/resources/script.md index 51213aac..a16b39f5 100644 --- a/docs/resources/script.md +++ b/docs/resources/script.md @@ -19,17 +19,17 @@ Use this resource to run a script from an agent. - `agent_id` (String) The "id" property of a "coder_agent" resource to associate with. - `display_name` (String) The display name of the script to display logs in the dashboard. -- `script` (String) The script to run. +- `script` (String) The content of the script that will be run. ### Optional - `cron` (String) The cron schedule to run the script on. This is a cron expression. - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. - `log_path` (String) The path of a file to write the logs to. If relative, it will be appended to tmp. -- `run_on_start` (Boolean) This option defines whether or not the script should run when the agent starts. -- `run_on_stop` (Boolean) This option defines whether or not the script should run when the agent stops. -- `start_blocks_login` (Boolean) This option defines whether or not the user can (by default) login to the workspace before this script completes running on start. When enabled, users may see an incomplete workspace when logging in. -- `timeout` (Number) Time in seconds until the agent lifecycle status is marked as timed out, this happens when the script has not completed (exited) in the given time. +- `run_on_start` (Boolean) This option defines whether or not the script should run when the agent starts. The script should exit when it is done to signal that the agent is ready. +- `run_on_stop` (Boolean) This option defines whether or not the script should run when the agent stops. The script should exit when it is done to signal that the workspace can be stopped. +- `start_blocks_login` (Boolean) This option determines whether users can log in immediately or must wait for the workspace to finish running this script upon startup. If not enabled, users may encounter an incomplete workspace when logging in. This option only sets the default, the user can still manually override the behavior. +- `timeout` (Number) Time in seconds that the script is allowed to run. If the script does not complete within this time, the script is terminated and the agent lifecycle status is marked as timed out. A value of zero (default) means no timeout. ### Read-Only diff --git a/examples/resources/coder_agent/resource.tf b/examples/resources/coder_agent/resource.tf index 0ed69ea3..6ccb07bf 100644 --- a/examples/resources/coder_agent/resource.tf +++ b/examples/resources/coder_agent/resource.tf @@ -28,6 +28,8 @@ resource "coder_agent" "dev" { timeout = 1 order = 1 } + + order = 1 } resource "kubernetes_pod" "dev" { diff --git a/provider/agent.go b/provider/agent.go index b814e15b..f02462a8 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -17,7 +17,7 @@ import ( func agentResource() *schema.Resource { return &schema.Resource{ Description: "Use this resource to associate an agent.", - CreateContext: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { + CreateContext: func(_ context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { // This should be a real authentication token! resourceData.SetId(uuid.NewString()) err := resourceData.Set("token", uuid.NewString()) @@ -121,7 +121,7 @@ func agentResource() *schema.Resource { }, "startup_script": { ForceNew: true, - Description: "A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready.", + Description: `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.`, Type: schema.TypeString, Optional: true, }, @@ -131,13 +131,14 @@ func agentResource() *schema.Resource { ForceNew: true, Optional: true, Description: "Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time.", + Deprecated: "This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.", ValidateFunc: validation.IntAtLeast(1), }, "shutdown_script": { Type: schema.TypeString, ForceNew: true, Optional: true, - Description: "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.", + Description: `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.`, }, "shutdown_script_timeout": { Type: schema.TypeInt, @@ -145,6 +146,7 @@ func agentResource() *schema.Resource { ForceNew: true, Optional: true, Description: "Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time.", + Deprecated: "This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.", ValidateFunc: validation.IntAtLeast(1), }, "token": { @@ -195,7 +197,7 @@ func agentResource() *schema.Resource { Type: schema.TypeString, ForceNew: true, Optional: true, - Description: "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.", + Description: `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).`, ValidateFunc: validation.StringInSlice([]string{"blocking", "non-blocking"}, false), ConflictsWith: []string{"login_before_ready"}, }, @@ -241,7 +243,7 @@ func agentResource() *schema.Resource { }, "order": { Type: schema.TypeInt, - Description: "The order determines the position of agent metadata in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by key (ascending order).", + Description: "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).", ForceNew: true, Optional: true, }, diff --git a/provider/app.go b/provider/app.go index d8471788..bd2eb21e 100644 --- a/provider/app.go +++ b/provider/app.go @@ -196,6 +196,12 @@ func appResource() *schema.Resource { }, }, }, + "order": { + Type: schema.TypeInt, + Description: "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order).", + ForceNew: true, + Optional: true, + }, }, } } diff --git a/provider/app_test.go b/provider/app_test.go index 3d4f44bb..f17513e1 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -44,6 +44,7 @@ func TestApp(t *testing.T) { interval = 5 threshold = 6 } + order = 4 } `, Check: func(state *terraform.State) error { @@ -64,6 +65,7 @@ func TestApp(t *testing.T) { "healthcheck.0.url", "healthcheck.0.interval", "healthcheck.0.threshold", + "order", } { value := resource.Primary.Attributes[key] t.Logf("%q = %q", key, value) diff --git a/provider/script.go b/provider/script.go index 2db4829f..4a05440e 100644 --- a/provider/script.go +++ b/provider/script.go @@ -16,15 +16,19 @@ var ScriptCRONParser = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cr func scriptResource() *schema.Resource { return &schema.Resource{ Description: "Use this resource to run a script from an agent.", - CreateContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + CreateContext: func(_ context.Context, rd *schema.ResourceData, _ interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) runOnStart, _ := rd.Get("run_on_start").(bool) + startBlocksLogin, _ := rd.Get("start_blocks_login").(bool) runOnStop, _ := rd.Get("run_on_stop").(bool) cron, _ := rd.Get("cron").(string) if !runOnStart && !runOnStop && cron == "" { return diag.Errorf("at least one of run_on_start, run_on_stop, or cron must be set") } + if !runOnStart && startBlocksLogin { + return diag.Errorf("start_blocks_login can only be set if run_on_start is true") + } return nil }, ReadContext: schema.NoopContext, @@ -60,14 +64,14 @@ func scriptResource() *schema.Resource { ForceNew: true, Type: schema.TypeString, Required: true, - Description: "The script to run.", + Description: "The content of the script that will be run.", }, "cron": { ForceNew: true, Type: schema.TypeString, Optional: true, Description: "The cron schedule to run the script on. This is a cron expression.", - ValidateFunc: func(i interface{}, s string) ([]string, []error) { + ValidateFunc: func(i interface{}, _ string) ([]string, []error) { v, ok := i.(string) if !ok { return []string{}, []error{fmt.Errorf("got type %T instead of string", i)} @@ -84,28 +88,28 @@ func scriptResource() *schema.Resource { Default: false, ForceNew: true, Optional: true, - Description: "This option defines whether or not the user can (by default) login to the workspace before this script completes running on start. When enabled, users may see an incomplete workspace when logging in.", + Description: "This option determines whether users can log in immediately or must wait for the workspace to finish running this script upon startup. If not enabled, users may encounter an incomplete workspace when logging in. This option only sets the default, the user can still manually override the behavior.", }, "run_on_start": { Type: schema.TypeBool, Default: false, ForceNew: true, Optional: true, - Description: "This option defines whether or not the script should run when the agent starts.", + Description: "This option defines whether or not the script should run when the agent starts. The script should exit when it is done to signal that the agent is ready.", }, "run_on_stop": { Type: schema.TypeBool, Default: false, ForceNew: true, Optional: true, - Description: "This option defines whether or not the script should run when the agent stops.", + Description: "This option defines whether or not the script should run when the agent stops. The script should exit when it is done to signal that the workspace can be stopped.", }, "timeout": { Type: schema.TypeInt, Default: 0, ForceNew: true, Optional: true, - Description: "Time in seconds until the agent lifecycle status is marked as timed out, this happens when the script has not completed (exited) in the given time.", + Description: "Time in seconds that the script is allowed to run. If the script does not complete within this time, the script is terminated and the agent lifecycle status is marked as timed out. A value of zero (default) means no timeout.", ValidateFunc: validation.IntAtLeast(1), }, }, diff --git a/provider/script_test.go b/provider/script_test.go index 4ecbacb6..937c6008 100644 --- a/provider/script_test.go +++ b/provider/script_test.go @@ -73,3 +73,64 @@ func TestScriptNeverRuns(t *testing.T) { }}, }) } + +func TestScriptStartBlocksLoginRequiresRunOnStart(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_script" "example" { + agent_id = "" + display_name = "Hey" + script = "Wow" + run_on_stop = true + start_blocks_login = true + } + `, + ExpectError: regexp.MustCompile(`start_blocks_login can only be set if run_on_start is true`), + }}, + }) + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_script" "example" { + agent_id = "" + display_name = "Hey" + script = "Wow" + start_blocks_login = true + run_on_start = true + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + script := state.Modules[0].Resources["coder_script.example"] + require.NotNil(t, script) + t.Logf("script attributes: %#v", script.Primary.Attributes) + for key, expected := range map[string]string{ + "agent_id": "", + "display_name": "Hey", + "script": "Wow", + "start_blocks_login": "true", + "run_on_start": "true", + } { + require.Equal(t, expected, script.Primary.Attributes[key]) + } + return nil + }, + }}, + }) +}