Skip to content

Commit 53e5081

Browse files
committed
Initial support for metadata in provisioner API and Terraform provisioner
1 parent f310aeb commit 53e5081

File tree

11 files changed

+739
-185
lines changed

11 files changed

+739
-185
lines changed

codersdk/workspaceresources.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ const (
2121
)
2222

2323
type WorkspaceResource struct {
24-
ID uuid.UUID `json:"id"`
25-
CreatedAt time.Time `json:"created_at"`
26-
JobID uuid.UUID `json:"job_id"`
27-
Transition WorkspaceTransition `json:"workspace_transition"`
28-
Type string `json:"type"`
29-
Name string `json:"name"`
30-
Agents []WorkspaceAgent `json:"agents,omitempty"`
24+
ID uuid.UUID `json:"id"`
25+
CreatedAt time.Time `json:"created_at"`
26+
JobID uuid.UUID `json:"job_id"`
27+
Transition WorkspaceTransition `json:"workspace_transition"`
28+
Type string `json:"type"`
29+
Name string `json:"name"`
30+
Agents []WorkspaceAgent `json:"agents,omitempty"`
31+
Metadata []WorkspaceResourceMetadata `json:"metadata,omitempty"`
32+
}
33+
34+
type WorkspaceResourceMetadata struct {
35+
Key string `json:"key"`
36+
Value string `json:"value"`
37+
Sensitive bool `json:"sensitive"`
3138
}
3239

3340
type WorkspaceAgent struct {

provisioner/terraform/resources.go

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ type agentAppAttributes struct {
3333
RelativePath bool `mapstructure:"relative_path"`
3434
}
3535

36+
// A mapping of attributes on the "coder_metadata" resource.
37+
type metadataAttributes struct {
38+
ResourceID string `mapstructure:"resource_id"`
39+
Items []metadataItem `mapstructure:"pair"`
40+
}
41+
42+
type metadataItem struct {
43+
Key string `mapstructure:"key"`
44+
Value string `mapstructure:"value"`
45+
Sensitive bool `mapstructure:"sensitive"`
46+
}
47+
3648
// ConvertResources consumes Terraform state and a GraphViz representation produced by
3749
// `terraform graph` to produce resources consumable by Coder.
3850
func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Resource, error) {
@@ -48,16 +60,30 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
4860
resources := make([]*proto.Resource, 0)
4961
resourceAgents := map[string][]*proto.Agent{}
5062

51-
// Indexes Terraform resources by it's label. The label
52-
// is what "terraform graph" uses to reference nodes.
63+
// Indexes Terraform resources by their label and ID.
64+
// The label is what "terraform graph" uses to reference nodes, and the ID
65+
// is used by "coder_metadata" resources to refer to their targets. (The ID
66+
// field is only available when reading a state file, and not when reading a
67+
// plan file.)
5368
tfResourceByLabel := map[string]*tfjson.StateResource{}
69+
resourceLabelByID := map[string]string{}
5470
var findTerraformResources func(mod *tfjson.StateModule)
5571
findTerraformResources = func(mod *tfjson.StateModule) {
5672
for _, module := range mod.ChildModules {
5773
findTerraformResources(module)
5874
}
5975
for _, resource := range mod.Resources {
60-
tfResourceByLabel[convertAddressToLabel(resource.Address)] = resource
76+
label := convertAddressToLabel(resource.Address)
77+
// index by label
78+
tfResourceByLabel[label] = resource
79+
// index by ID, if it exists
80+
id, ok := resource.AttributeValues["id"]
81+
if ok {
82+
idString, ok := id.(string)
83+
if ok {
84+
resourceLabelByID[idString] = label
85+
}
86+
}
6187
}
6288
}
6389
findTerraformResources(module)
@@ -205,23 +231,58 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
205231
}
206232
}
207233

234+
// Associate metadata blocks with resources.
235+
resourceMetadata := map[string][]*proto.Resource_Metadata{}
236+
for label, resource := range tfResourceByLabel {
237+
if resource.Type != "coder_metadata" {
238+
continue
239+
}
240+
var attrs metadataAttributes
241+
err = mapstructure.Decode(resource.AttributeValues, &attrs)
242+
if err != nil {
243+
return nil, xerrors.Errorf("decode metadata attributes: %w", err)
244+
}
245+
246+
if attrs.ResourceID == "" {
247+
// TODO: detect this as an error
248+
// At plan time, change.after_unknown.resource_id should be "true".
249+
// At provision time, values.resource_id should be set.
250+
continue
251+
}
252+
targetLabel, ok := resourceLabelByID[attrs.ResourceID]
253+
if !ok {
254+
return nil, xerrors.Errorf("attribute %s.resource_id = %q does not refer to a valid resource", label, attrs.ResourceID)
255+
}
256+
257+
for _, item := range attrs.Items {
258+
resourceMetadata[targetLabel] = append(resourceMetadata[targetLabel],
259+
&proto.Resource_Metadata{
260+
Key: item.Key,
261+
Value: item.Value,
262+
Sensitive: item.Sensitive,
263+
})
264+
}
265+
}
266+
208267
for _, resource := range tfResourceByLabel {
209268
if resource.Mode == tfjson.DataResourceMode {
210269
continue
211270
}
212-
if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" {
271+
if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" || resource.Type == "coder_metadata" {
213272
continue
214273
}
274+
label := convertAddressToLabel(resource.Address)
215275

216-
agents, exists := resourceAgents[convertAddressToLabel(resource.Address)]
276+
agents, exists := resourceAgents[label]
217277
if exists {
218278
applyAutomaticInstanceID(resource, agents)
219279
}
220280

221281
resources = append(resources, &proto.Resource{
222-
Name: resource.Name,
223-
Type: resource.Type,
224-
Agents: agents,
282+
Name: resource.Name,
283+
Type: resource.Type,
284+
Agents: agents,
285+
Metadata: resourceMetadata[label],
225286
})
226287
}
227288

provisioner/terraform/resources_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/coder/coder/cryptorand"
1515
"github.com/coder/coder/provisioner/terraform"
1616
"github.com/coder/coder/provisionersdk/proto"
17+
18+
protobuf "github.com/golang/protobuf/proto"
1719
)
1820

1921
func TestConvertResources(t *testing.T) {
@@ -114,6 +116,15 @@ func TestConvertResources(t *testing.T) {
114116
Auth: &proto.Agent_Token{},
115117
}},
116118
}},
119+
// Tests fetching metadata about workspace resources.
120+
"resource-metadata": {{
121+
Name: "about",
122+
Type: "null_resource",
123+
Metadata: []*proto.Resource_Metadata{{
124+
Key: "hello",
125+
Value: "world",
126+
}},
127+
}},
117128
} {
118129
folderName := folderName
119130
expected := expected
@@ -134,7 +145,16 @@ func TestConvertResources(t *testing.T) {
134145
resources, err := terraform.ConvertResources(tfPlan.PlannedValues.RootModule, string(tfPlanGraph))
135146
require.NoError(t, err)
136147
sortResources(resources)
137-
resourcesWant, err := json.Marshal(expected)
148+
149+
// plan does not contain metadata, so clone expected and remove it
150+
var expectedNoMetadata []*proto.Resource
151+
for _, resource := range expected {
152+
resourceCopy, _ := protobuf.Clone(resource).(*proto.Resource)
153+
resourceCopy.Metadata = nil
154+
expectedNoMetadata = append(expectedNoMetadata, resourceCopy)
155+
}
156+
157+
resourcesWant, err := json.Marshal(expectedNoMetadata)
138158
require.NoError(t, err)
139159
resourcesGot, err := json.Marshal(resources)
140160
require.NoError(t, err)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
version = "0.4.4"
6+
}
7+
}
8+
}
9+
10+
resource "coder_agent" "main" {
11+
os = "linux"
12+
arch = "amd64"
13+
}
14+
15+
resource "null_resource" "about" {}
16+
17+
resource "coder_metadata" "about_info" {
18+
resource_id = null_resource.about.id
19+
pair {
20+
key = "hello"
21+
value = "world"
22+
}
23+
}

provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)