diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index e7d58a5d..c736f78a 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -34,6 +34,6 @@ resource "kubernetes_pod" "dev" { - `owner_email` (String) Email address of the workspace owner. - `owner_id` (String) UUID of the workspace owner. - `owner_oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. -- `owner_session_token` (String) Session token for interfacing with a Coder deployment. It is regenerated everytime a workspace is started. +- `owner_session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. - `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1. - `transition` (String) Either "start" or "stop". Use this to start/stop resources with "count". diff --git a/docs/resources/agent.md b/docs/resources/agent.md index d073f7c5..7e20ade4 100644 --- a/docs/resources/agent.md +++ b/docs/resources/agent.md @@ -20,6 +20,12 @@ resource "coder_agent" "dev" { os = "linux" arch = "amd64" dir = "/workspace" + display_apps { + vscode = true + vscode_insiders = false + web_terminal = true + ssh_helper = false + } } resource "kubernetes_pod" "dev" { @@ -49,6 +55,7 @@ resource "kubernetes_pod" "dev" { - `auth` (String) The authentication type the agent will use. Must be one of: "token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity". - `connection_timeout` (Number) Time in seconds until the agent is marked as timed out when a connection with the server cannot be established. A value of zero never marks the agent as timed out. - `dir` (String) The starting directory when a user creates a shell session. Defaults to $HOME. +- `display_apps` (Block Set, Max: 1) The list of built-in apps to display in the agent bar. (see [below for nested schema](#nestedblock--display_apps)) - `env` (Map of String) A mapping of environment variables to set inside the workspace. - `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)) @@ -66,6 +73,18 @@ resource "kubernetes_pod" "dev" { - `init_script` (String) Run this script on startup of an instance to initialize the agent. - `token` (String, Sensitive) Set the environment variable "CODER_AGENT_TOKEN" with this token to authenticate an agent. + +### Nested Schema for `display_apps` + +Optional: + +- `port_forwarding_helper` (Boolean) Display the port-forwarding helper button in the agent bar. +- `ssh_helper` (Boolean) Display the SSH helper button in the agent bar. +- `vscode` (Boolean) Display the VSCode Desktop app in the agent bar. +- `vscode_insiders` (Boolean) Display the VSCode Insiders app in the agent bar. +- `web_terminal` (Boolean) Display the web terminal app in the agent bar. + + ### Nested Schema for `metadata` diff --git a/examples/resources/coder_agent/resource.tf b/examples/resources/coder_agent/resource.tf index 2f1503fd..d5235a18 100644 --- a/examples/resources/coder_agent/resource.tf +++ b/examples/resources/coder_agent/resource.tf @@ -5,6 +5,12 @@ resource "coder_agent" "dev" { os = "linux" arch = "amd64" dir = "/workspace" + display_apps { + vscode = true + vscode_insiders = false + web_terminal = true + ssh_helper = false + } } resource "kubernetes_pod" "dev" { diff --git a/provider/agent.go b/provider/agent.go index 90dd0525..9b5ac3f3 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -23,6 +23,21 @@ func agentResource() *schema.Resource { if err != nil { return diag.FromErr(err) } + + if _, ok := resourceData.GetOk("display_apps"); !ok { + err = resourceData.Set("display_apps", []interface{}{ + map[string]bool{ + "vscode": true, + "vscode_insiders": false, + "web_terminal": true, + "ssh_helper": true, + "port_forwarding_helper": true, + }, + }) + if err != nil { + return diag.FromErr(err) + } + } return updateInitScript(resourceData, i) }, ReadWithoutTimeout: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { @@ -30,6 +45,21 @@ func agentResource() *schema.Resource { if err != nil { return diag.FromErr(err) } + if _, ok := resourceData.GetOk("display_apps"); !ok { + err = resourceData.Set("display_apps", []interface{}{ + map[string]bool{ + "vscode": true, + "vscode_insiders": false, + "web_terminal": true, + "ssh_helper": true, + "port_forwarding_helper": true, + }, + }) + if err != nil { + return diag.FromErr(err) + } + } + return updateInitScript(resourceData, i) }, DeleteContext: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { @@ -198,6 +228,53 @@ func agentResource() *schema.Resource { }, }, }, + "display_apps": { + Type: schema.TypeSet, + Description: "The list of built-in apps to display in the agent bar.", + ForceNew: true, + Optional: true, + MaxItems: 1, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "vscode": { + Type: schema.TypeBool, + Description: "Display the VSCode Desktop app in the agent bar.", + ForceNew: true, + Optional: true, + Default: true, + }, + "vscode_insiders": { + Type: schema.TypeBool, + Description: "Display the VSCode Insiders app in the agent bar.", + ForceNew: true, + Optional: true, + Default: false, + }, + "web_terminal": { + Type: schema.TypeBool, + Description: "Display the web terminal app in the agent bar.", + ForceNew: true, + Optional: true, + Default: true, + }, + "port_forwarding_helper": { + Type: schema.TypeBool, + Description: "Display the port-forwarding helper button in the agent bar.", + ForceNew: true, + Optional: true, + Default: true, + }, + "ssh_helper": { + Type: schema.TypeBool, + Description: "Display the SSH helper button in the agent bar.", + ForceNew: true, + Optional: true, + Default: true, + }, + }, + }, + }, }, } } diff --git a/provider/agent_test.go b/provider/agent_test.go index e3d96d1b..9026385c 100644 --- a/provider/agent_test.go +++ b/provider/agent_test.go @@ -1,14 +1,16 @@ package provider_test import ( + "fmt" "regexp" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" + + "github.com/coder/terraform-provider-coder/provider" ) func TestAgent(t *testing.T) { @@ -247,3 +249,181 @@ func TestAgent_Metadata(t *testing.T) { }}, }) } + +func TestAgent_DisplayApps(t *testing.T) { + t.Parallel() + t.Run("OK", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + // Test the fields with non-default values. + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + display_apps { + vscode = false + vscode_insiders = true + web_terminal = false + port_forwarding_helper = false + ssh_helper = false + } + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + + resource := state.Modules[0].Resources["coder_agent.dev"] + require.NotNil(t, resource) + + t.Logf("resource: %v", resource.Primary.Attributes) + + for _, app := range []string{ + "web_terminal", + "vscode_insiders", + "vscode", + "port_forwarding_helper", + "ssh_helper", + } { + key := fmt.Sprintf("display_apps.0.%s", app) + if app == "vscode_insiders" { + require.Equal(t, "true", resource.Primary.Attributes[key]) + } else { + require.Equal(t, "false", resource.Primary.Attributes[key]) + } + } + return nil + }, + }}, + }) + }) + + t.Run("Subset", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + // Test the fields with non-default values. + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + display_apps { + vscode_insiders = true + web_terminal = true + } + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + + resource := state.Modules[0].Resources["coder_agent.dev"] + require.NotNil(t, resource) + + t.Logf("resource: %v", resource.Primary.Attributes) + + for _, app := range []string{ + "web_terminal", + "vscode_insiders", + "vscode", + "port_forwarding_helper", + "ssh_helper", + } { + key := fmt.Sprintf("display_apps.0.%s", app) + require.Equal(t, "true", resource.Primary.Attributes[key]) + } + return nil + }, + }}, + }) + }) + + // Assert all the defaults are set correctly. + t.Run("Omitted", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + + resource := state.Modules[0].Resources["coder_agent.dev"] + require.NotNil(t, resource) + + t.Logf("resource: %v", resource.Primary.Attributes) + + for _, app := range []string{ + "web_terminal", + "vscode_insiders", + "vscode", + "port_forwarding_helper", + "ssh_helper", + } { + key := fmt.Sprintf("display_apps.0.%s", app) + if app == "vscode_insiders" { + require.Equal(t, "false", resource.Primary.Attributes[key]) + } else { + require.Equal(t, "true", resource.Primary.Attributes[key]) + } + } + return nil + }, + }}, + }) + }) + + t.Run("InvalidApp", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + // Test the fields with non-default values. + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + display_apps { + fake_app = false + vscode_insiders = true + web_terminal = false + port_forwarding_helper = false + ssh_helper = false + } + } + `, + ExpectError: regexp.MustCompile(`An argument named "fake_app" is not expected here.`), + }}, + }) + }) + +} diff --git a/provider/examples_test.go b/provider/examples_test.go index 263e65ce..9be7ce02 100644 --- a/provider/examples_test.go +++ b/provider/examples_test.go @@ -4,10 +4,11 @@ import ( "os" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" + + "github.com/coder/terraform-provider-coder/provider" ) func TestExamples(t *testing.T) { diff --git a/provider/workspace.go b/provider/workspace.go index 47f0e799..995cb6e4 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -137,7 +137,7 @@ func workspaceDataSource() *schema.Resource { "owner_session_token": { Type: schema.TypeString, Computed: true, - Description: "Session token for interfacing with a Coder deployment. It is regenerated everytime a workspace is started.", + Description: "Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.", }, }, }