-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathapp.go
247 lines (231 loc) · 8.23 KB
/
app.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package provider
import (
"context"
"net/url"
"regexp"
"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"
)
var (
// appSlugRegex is the regex used to validate the slug of a coder_app
// resource. It must be a valid hostname and cannot contain two consecutive
// hyphens or start/end with a hyphen.
//
// This regex is duplicated in the Coder source code, so make sure to update
// it there as well.
//
// There are test cases for this regex in the Coder product.
appSlugRegex = regexp.MustCompile(`^[a-z0-9](-?[a-z0-9])*$`)
)
func appResource() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
Description: "Use this resource to define shortcuts to access applications in a workspace.",
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
resourceData.SetId(uuid.NewString())
diags := diag.Diagnostics{}
hiddenData := resourceData.Get("hidden")
if hidden, ok := hiddenData.(bool); !ok {
return diag.Errorf("hidden should be a bool")
} else if hidden {
if _, ok := resourceData.GetOk("display_name"); ok {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "`display_name` set when app is hidden",
})
}
if _, ok := resourceData.GetOk("icon"); ok {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "`icon` set when app is hidden",
})
}
if _, ok := resourceData.GetOk("order"); ok {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "`order` set when app is hidden",
})
}
}
return diags
},
ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
return nil
},
DeleteContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
return nil
},
Schema: map[string]*schema.Schema{
"agent_id": {
Type: schema.TypeString,
Description: "The `id` property of a `coder_agent` resource to associate with.",
ForceNew: true,
Required: true,
},
"command": {
Type: schema.TypeString,
Description: "A command to run in a terminal opening this app. In the web, " +
"this will open in a new tab. In the CLI, this will SSH and execute the command. " +
"Either `command` or `url` may be specified, but not both.",
ConflictsWith: []string{"url"},
Optional: true,
ForceNew: true,
},
"icon": {
Type: schema.TypeString,
Description: "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/<path>\"`.",
ForceNew: true,
Optional: true,
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
_, err := url.Parse(s)
if err != nil {
return nil, []error{err}
}
return nil, nil
},
},
"slug": {
Type: schema.TypeString,
Description: "A hostname-friendly name for the app. This is " +
"used in URLs to access the app. May contain " +
"alphanumerics and hyphens. Cannot start/end with a " +
"hyphen or contain two consecutive hyphens.",
ForceNew: true,
Required: true,
ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics {
valStr, ok := val.(string)
if !ok {
return diag.Errorf("expected string, got %T", val)
}
if !appSlugRegex.MatchString(valStr) {
return diag.Errorf(`invalid "coder_app" slug, must be a valid hostname (%q, cannot contain two consecutive hyphens or start/end with a hyphen): %q`, appSlugRegex.String(), valStr)
}
return nil
},
},
"display_name": {
Type: schema.TypeString,
Description: "A display name to identify the app. Defaults to the slug.",
ForceNew: true,
Optional: true,
},
"subdomain": {
Type: schema.TypeBool,
Description: "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`.",
ForceNew: true,
Optional: true,
},
"share": {
Type: schema.TypeString,
Description: "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).",
ForceNew: true,
Optional: true,
Default: "owner",
ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics {
valStr, ok := val.(string)
if !ok {
return diag.Errorf("expected string, got %T", val)
}
switch valStr {
case "owner", "authenticated", "public":
return nil
}
return diag.Errorf("invalid app share %q, must be one of \"owner\", \"authenticated\", \"public\"", valStr)
},
},
"cors_behavior": {
Type: schema.TypeString,
Default: "simple",
ForceNew: true,
Optional: true,
ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics {
valStr, ok := val.(string)
if !ok {
return diag.Errorf("expected string, got %T", val)
}
switch valStr {
case "simple", "passthru":
return nil
}
return diag.Errorf("invalid app CORS behavior %q, must be one of \"simple\", \"passthru\"", valStr)
},
},
"url": {
Type: schema.TypeString,
Description: "An external url if `external=true` or a URL to be proxied to from inside the workspace. " +
"This should be of the form `http://localhost:PORT[/SUBPATH]`. " +
"Either `command` or `url` may be specified, but not both.",
ForceNew: true,
Optional: true,
ConflictsWith: []string{"command"},
},
"external": {
Type: schema.TypeBool,
Description: "Specifies whether `url` is opened on the client machine " +
"instead of proxied through the workspace.",
Default: false,
ForceNew: true,
Optional: true,
ConflictsWith: []string{"healthcheck", "command", "subdomain", "share"},
},
"healthcheck": {
Type: schema.TypeSet,
Description: "HTTP health checking to determine the application readiness.",
ForceNew: true,
Optional: true,
MaxItems: 1,
ConflictsWith: []string{"command"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"url": {
Type: schema.TypeString,
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.",
ForceNew: true,
Required: true,
},
"interval": {
Type: schema.TypeInt,
Description: "Duration in seconds to wait between healthcheck requests.",
ForceNew: true,
Required: true,
},
"threshold": {
Type: schema.TypeInt,
Description: "Number of consecutive heathcheck failures before returning an unhealthy status.",
ForceNew: true,
Required: true,
},
},
},
},
"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,
},
"hidden": {
Type: schema.TypeBool,
Description: "Determines if the app is visible in the UI (minimum Coder version: v2.16).",
Default: false,
ForceNew: true,
Optional: true,
},
},
}
}