Skip to content

Commit 5de380a

Browse files
authored
chore: Split up the provider file (#55)
It was getting huge!
1 parent 9a12972 commit 5de380a

12 files changed

+947
-821
lines changed

internal/provider/agent.go

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"reflect"
8+
"strings"
9+
10+
"github.com/google/uuid"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
14+
)
15+
16+
func agentResource() *schema.Resource {
17+
return &schema.Resource{
18+
Description: "Use this resource to associate an agent.",
19+
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
20+
// This should be a real authentication token!
21+
resourceData.SetId(uuid.NewString())
22+
err := resourceData.Set("token", uuid.NewString())
23+
if err != nil {
24+
return diag.FromErr(err)
25+
}
26+
return updateInitScript(resourceData, i)
27+
},
28+
ReadWithoutTimeout: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
29+
err := resourceData.Set("token", uuid.NewString())
30+
if err != nil {
31+
return diag.FromErr(err)
32+
}
33+
return updateInitScript(resourceData, i)
34+
},
35+
DeleteContext: func(c context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
36+
return nil
37+
},
38+
Schema: map[string]*schema.Schema{
39+
"init_script": {
40+
Type: schema.TypeString,
41+
Computed: true,
42+
Description: "Run this script on startup of an instance to initialize the agent.",
43+
},
44+
"arch": {
45+
Type: schema.TypeString,
46+
ForceNew: true,
47+
Required: true,
48+
Description: `The architecture the agent will run on. Must be one of: "amd64", "armv7", "arm64".`,
49+
ValidateFunc: validation.StringInSlice([]string{"amd64", "armv7", "arm64"}, false),
50+
},
51+
"auth": {
52+
Type: schema.TypeString,
53+
Default: "token",
54+
ForceNew: true,
55+
Optional: true,
56+
Description: `The authentication type the agent will use. Must be one of: "token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity".`,
57+
ValidateFunc: validation.StringInSlice([]string{"token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity"}, false),
58+
},
59+
"dir": {
60+
Type: schema.TypeString,
61+
ForceNew: true,
62+
Optional: true,
63+
Description: "The starting directory when a user creates a shell session. Defaults to $HOME.",
64+
},
65+
"env": {
66+
ForceNew: true,
67+
Description: "A mapping of environment variables to set inside the workspace.",
68+
Type: schema.TypeMap,
69+
Optional: true,
70+
},
71+
"os": {
72+
Type: schema.TypeString,
73+
ForceNew: true,
74+
Required: true,
75+
Description: `The operating system the agent will run on. Must be one of: "linux", "darwin", or "windows".`,
76+
ValidateFunc: validation.StringInSlice([]string{"linux", "darwin", "windows"}, false),
77+
},
78+
"startup_script": {
79+
ForceNew: true,
80+
Description: "A script to run after the agent starts.",
81+
Type: schema.TypeString,
82+
Optional: true,
83+
},
84+
"token": {
85+
ForceNew: true,
86+
Sensitive: true,
87+
Description: `Set the environment variable "CODER_AGENT_TOKEN" with this token to authenticate an agent.`,
88+
Type: schema.TypeString,
89+
Computed: true,
90+
},
91+
},
92+
}
93+
}
94+
95+
func agentInstanceResource() *schema.Resource {
96+
return &schema.Resource{
97+
Description: "Use this resource to associate an instance ID with an agent for zero-trust " +
98+
"authentication. This association is done automatically for \"google_compute_instance\", " +
99+
"\"aws_instance\", \"azurerm_linux_virtual_machine\", and " +
100+
"\"azurerm_windows_virtual_machine\" resources.",
101+
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
102+
resourceData.SetId(uuid.NewString())
103+
return nil
104+
},
105+
ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
106+
return nil
107+
},
108+
DeleteContext: func(c context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
109+
return nil
110+
},
111+
Schema: map[string]*schema.Schema{
112+
"agent_id": {
113+
Type: schema.TypeString,
114+
Description: `The "id" property of a "coder_agent" resource to associate with.`,
115+
ForceNew: true,
116+
Required: true,
117+
},
118+
"instance_id": {
119+
ForceNew: true,
120+
Required: true,
121+
Description: `The instance identifier of a provisioned resource.`,
122+
Type: schema.TypeString,
123+
},
124+
},
125+
}
126+
}
127+
128+
// updateInitScript fetches parameters from a "coder_agent" to produce the
129+
// agent script from environment variables.
130+
func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
131+
config, valid := i.(config)
132+
if !valid {
133+
return diag.Errorf("config was unexpected type %q", reflect.TypeOf(i).String())
134+
}
135+
auth, valid := resourceData.Get("auth").(string)
136+
if !valid {
137+
return diag.Errorf("auth was unexpected type %q", reflect.TypeOf(resourceData.Get("auth")))
138+
}
139+
operatingSystem, valid := resourceData.Get("os").(string)
140+
if !valid {
141+
return diag.Errorf("os was unexpected type %q", reflect.TypeOf(resourceData.Get("os")))
142+
}
143+
arch, valid := resourceData.Get("arch").(string)
144+
if !valid {
145+
return diag.Errorf("arch was unexpected type %q", reflect.TypeOf(resourceData.Get("arch")))
146+
}
147+
accessURL, err := config.URL.Parse("/")
148+
if err != nil {
149+
return diag.Errorf("parse access url: %s", err)
150+
}
151+
script := os.Getenv(fmt.Sprintf("CODER_AGENT_SCRIPT_%s_%s", operatingSystem, arch))
152+
if script != "" {
153+
script = strings.ReplaceAll(script, "${ACCESS_URL}", accessURL.String())
154+
script = strings.ReplaceAll(script, "${AUTH_TYPE}", auth)
155+
}
156+
err = resourceData.Set("init_script", script)
157+
if err != nil {
158+
return diag.FromErr(err)
159+
}
160+
return nil
161+
}

internal/provider/agent_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package provider_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/coder/terraform-provider-coder/internal/provider"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestAgent(t *testing.T) {
14+
t.Parallel()
15+
resource.Test(t, resource.TestCase{
16+
Providers: map[string]*schema.Provider{
17+
"coder": provider.New(),
18+
},
19+
IsUnitTest: true,
20+
Steps: []resource.TestStep{{
21+
Config: `
22+
provider "coder" {
23+
url = "https://example.com"
24+
}
25+
resource "coder_agent" "new" {
26+
os = "linux"
27+
arch = "amd64"
28+
auth = "aws-instance-identity"
29+
dir = "/tmp"
30+
env = {
31+
hi = "test"
32+
}
33+
startup_script = "echo test"
34+
}
35+
`,
36+
Check: func(state *terraform.State) error {
37+
require.Len(t, state.Modules, 1)
38+
require.Len(t, state.Modules[0].Resources, 1)
39+
resource := state.Modules[0].Resources["coder_agent.new"]
40+
require.NotNil(t, resource)
41+
for _, key := range []string{
42+
"token",
43+
"os",
44+
"arch",
45+
"auth",
46+
"dir",
47+
"env.hi",
48+
"startup_script",
49+
} {
50+
value := resource.Primary.Attributes[key]
51+
t.Logf("%q = %q", key, value)
52+
require.NotNil(t, value)
53+
require.Greater(t, len(value), 0)
54+
}
55+
return nil
56+
},
57+
}},
58+
})
59+
}
60+
61+
func TestAgentInstance(t *testing.T) {
62+
t.Parallel()
63+
resource.Test(t, resource.TestCase{
64+
Providers: map[string]*schema.Provider{
65+
"coder": provider.New(),
66+
},
67+
IsUnitTest: true,
68+
Steps: []resource.TestStep{{
69+
Config: `
70+
provider "coder" {
71+
url = "https://example.com"
72+
}
73+
resource "coder_agent" "dev" {
74+
os = "linux"
75+
arch = "amd64"
76+
}
77+
resource "coder_agent_instance" "new" {
78+
agent_id = coder_agent.dev.id
79+
instance_id = "hello"
80+
}
81+
`,
82+
Check: func(state *terraform.State) error {
83+
require.Len(t, state.Modules, 1)
84+
require.Len(t, state.Modules[0].Resources, 2)
85+
resource := state.Modules[0].Resources["coder_agent_instance.new"]
86+
require.NotNil(t, resource)
87+
for _, key := range []string{
88+
"agent_id",
89+
"instance_id",
90+
} {
91+
value := resource.Primary.Attributes[key]
92+
t.Logf("%q = %q", key, value)
93+
require.NotNil(t, value)
94+
require.Greater(t, len(value), 0)
95+
}
96+
return nil
97+
},
98+
}},
99+
})
100+
}

internal/provider/app.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"net/url"
6+
7+
"github.com/google/uuid"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
)
11+
12+
func appResource() *schema.Resource {
13+
return &schema.Resource{
14+
Description: "Use this resource to define shortcuts to access applications in a workspace.",
15+
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
16+
resourceData.SetId(uuid.NewString())
17+
return nil
18+
},
19+
ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
20+
return nil
21+
},
22+
DeleteContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
23+
return nil
24+
},
25+
Schema: map[string]*schema.Schema{
26+
"agent_id": {
27+
Type: schema.TypeString,
28+
Description: `The "id" property of a "coder_agent" resource to associate with.`,
29+
ForceNew: true,
30+
Required: true,
31+
},
32+
"command": {
33+
Type: schema.TypeString,
34+
Description: "A command to run in a terminal opening this app. In the web, " +
35+
"this will open in a new tab. In the CLI, this will SSH and execute the command. " +
36+
"Either \"command\" or \"url\" may be specified, but not both.",
37+
ConflictsWith: []string{"url"},
38+
Optional: true,
39+
ForceNew: true,
40+
},
41+
"icon": {
42+
Type: schema.TypeString,
43+
Description: "A URL to an icon that will display in the dashboard. View built-in " +
44+
"icons here: https://github.com/coder/coder/tree/main/site/static/icons. Use a " +
45+
"built-in icon with `data.coder_workspace.me.access_url + \"/icons/<path>\"`.",
46+
ForceNew: true,
47+
Optional: true,
48+
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
49+
_, err := url.Parse(s)
50+
if err != nil {
51+
return nil, []error{err}
52+
}
53+
return nil, nil
54+
},
55+
},
56+
"name": {
57+
Type: schema.TypeString,
58+
Description: "A display name to identify the app.",
59+
ForceNew: true,
60+
Optional: true,
61+
},
62+
"relative_path": {
63+
Type: schema.TypeBool,
64+
Description: "Specifies whether the URL will be accessed via a relative " +
65+
"path or wildcard. Use if wildcard routing is unavailable.",
66+
ForceNew: true,
67+
Optional: true,
68+
ConflictsWith: []string{"command"},
69+
},
70+
"url": {
71+
Type: schema.TypeString,
72+
Description: "A URL to be proxied to from inside the workspace. " +
73+
"Either \"command\" or \"url\" may be specified, but not both.",
74+
ForceNew: true,
75+
Optional: true,
76+
ConflictsWith: []string{"command"},
77+
},
78+
"healthcheck": {
79+
Type: schema.TypeSet,
80+
Description: "HTTP health checking to determine the application readiness.",
81+
ForceNew: true,
82+
Optional: true,
83+
MaxItems: 1,
84+
ConflictsWith: []string{"command"},
85+
Elem: &schema.Resource{
86+
Schema: map[string]*schema.Schema{
87+
"url": {
88+
Type: schema.TypeString,
89+
Description: "HTTP address used determine the application readiness. A successful health check is a HTTP response code less than 500 returned before healthcheck.interval seconds.",
90+
ForceNew: true,
91+
Required: true,
92+
},
93+
"interval": {
94+
Type: schema.TypeInt,
95+
Description: "Duration in seconds to wait between healthcheck requests.",
96+
ForceNew: true,
97+
Required: true,
98+
},
99+
"threshold": {
100+
Type: schema.TypeInt,
101+
Description: "Number of consecutive heathcheck failures before returning an unhealthy status.",
102+
ForceNew: true,
103+
Required: true,
104+
},
105+
},
106+
},
107+
},
108+
},
109+
}
110+
}

0 commit comments

Comments
 (0)