Skip to content

Commit

Permalink
feat: add default_apps field to coder_agent resource (#147)
Browse files Browse the repository at this point in the history
- Enables a template admin to configure which apps are displayed
  to the user in the dashboard.
  • Loading branch information
sreya committed Aug 30, 2023
1 parent 188ffef commit 233ea60
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/data-sources/workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".
19 changes: 19 additions & 0 deletions docs/resources/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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))
Expand All @@ -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.

<a id="nestedblock--display_apps"></a>
### 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.


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

Expand Down
6 changes: 6 additions & 0 deletions examples/resources/coder_agent/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
77 changes: 77 additions & 0 deletions provider/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,43 @@ 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 {
err := resourceData.Set("token", uuid.NewString())
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 {
Expand Down Expand Up @@ -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,
},
},
},
},
},
}
}
Expand Down
182 changes: 181 additions & 1 deletion provider/agent_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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.`),
}},
})
})

}
3 changes: 2 additions & 1 deletion provider/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion provider/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
},
},
}
Expand Down

0 comments on commit 233ea60

Please sign in to comment.