-
Notifications
You must be signed in to change notification settings - Fork 54
/
Copy pathlabels.go
176 lines (157 loc) · 5.59 KB
/
labels.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
// Copyright 2016-2023, Pulumi Corporation. All rights reserved.
package gcp
import (
"context"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/walk"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
)
// GCP has many resources with labels called "terraform_labels". We want to change those
// to "pulumiLabels".
//
// fixLabelNames traverses the TF schema of resources and data sources to find fields
// called "terraform_labels", overriding these with "pulumiLabels" via
// tfbridge.SchemaInfo.
func fixLabelNames(prov *tfbridge.ProviderInfo) {
toUpdate := map[string][]resource.PropertyPath{}
visitor := func(ctx tfbridge.PropertyVisitInfo) (tfbridge.PropertyVisitResult, error) {
if ctx.Root.PulumiToken() == "" { // Skip unmapped resources and functions
return tfbridge.PropertyVisitResult{}, nil
}
path := ctx.SchemaPath()
key, ok := path[len(path)-1].(walk.GetAttrStep)
if !ok {
return tfbridge.PropertyVisitResult{}, nil
}
switch key.Name {
case "terraform_labels":
ctx.SchemaInfo().Name = "pulumiLabels"
// We only apply this transform for resources
if root, ok := ctx.Root.(tfbridge.VisitResourceRoot); ok {
schemaPath := tfbridge.SchemaPathToPropertyPath(path,
root.Schema.Schema(),
root.Info.Fields)
toUpdate[root.TfToken] = append(toUpdate[root.TfToken], schemaPath)
}
ctx.SchemaInfo().Secret = tfbridge.True()
// This field represents an aggregate of resource-level `labels` and provider-level `defaultLabels` fields.
// To avoid leaking secrets set via Input on those fields, we mark this field as unconditionally Secret.
case "effective_labels":
ctx.SchemaInfo().Secret = tfbridge.True()
default:
return tfbridge.PropertyVisitResult{}, nil
}
return tfbridge.PropertyVisitResult{HasEffect: true}, nil
}
tfbridge.MustTraverseProperties(prov, "fix-label-names", visitor)
for token, labelPaths := range toUpdate {
prov.Resources[token].TransformFromState =
composeTransform(prov.Resources[token].TransformFromState,
ensureLabelPathsExist(labelPaths))
}
}
func composeTransform(f1, f2 tfbridge.PropertyTransform) tfbridge.PropertyTransform {
switch {
case f1 == nil:
return f2
case f2 == nil:
return f1
}
// f1 and f2 are not nil, so apply them both in order.
return func(ctx context.Context, m resource.PropertyMap) (resource.PropertyMap, error) {
m, err := f1(ctx, m)
if err != nil {
return m, err
}
return f2(ctx, m)
}
}
// pulumiLabels represents a field added upstream via a custom diff, as terraform_labels.
// There is a bug: if this field is nil in the Pulumi state, it will never be added to the state,
// creating a permanent diff.
// See also: https://github.com/pulumi/pulumi-gcp/issues/1314
func ensureLabelPathsExist(paths []resource.PropertyPath) tfbridge.PropertyTransform {
return func(ctx context.Context, prop resource.PropertyMap) (resource.PropertyMap, error) {
obj := resource.NewObjectProperty(prop)
for _, path := range expandPathSet(paths, obj) {
// If a `pulumiLabels` field is not set at the expected path, we will set it to an empty property Map,
// so Diff can detect it.
if _, found := path.Get(obj); !found {
path.Set(obj, resource.NewObjectProperty(resource.PropertyMap{}))
}
}
return obj.ObjectValue(), nil
}
}
func expandPathSet(paths []resource.PropertyPath, value resource.PropertyValue) []resource.PropertyPath {
expanded := make([]resource.PropertyPath, 0, len(paths))
for _, path := range paths {
expanded = append(expanded, expandGlob(path, value)...)
}
return expanded
}
// Expands any globs "*" in path according to value.
//
// If a glob doesn't match any value, the empty set is returned. Non-glob path elements do
// not need to match values to be returned. As an example, consider this PropertyPath:
//
// foo["*"].bar
//
// Given the value `{ "foo": [ {}, { "v1": 0 } ] }`, `expandGlob(path, value)` would return
// `[foo[0].bar, foo[1].bar]`.
//
// Given the value `{ "missing": true }`, `expandGlob(path, value)` would return the empty
// list: `[]`.
//
// The order or returned paths is non-deterministic due to dictionary iteration.
func expandGlob(path resource.PropertyPath, value resource.PropertyValue) []resource.PropertyPath {
for i, seg := range path {
seg, ok := seg.(string)
// If we don't have a value or seg isn't a glob, continue.
if !ok || seg != "*" {
continue
}
// Get the item that encloses seg.
v, ok := path[:i].Get(value)
if !ok {
// We have failed to expand, which means that the glob
// returns empty.
return nil
}
if v.IsSecret() {
v = v.SecretValue().Element
}
switch {
case v.IsArray():
results := make([]resource.PropertyPath, 0, len(v.ArrayValue()))
for el, val := range v.ArrayValue() {
results = append(results,
addPrefix(append(path[:i], el),
expandGlob(path[i+1:], val))...)
}
return results
case v.IsObject():
results := make([]resource.PropertyPath, 0, len(v.ObjectValue()))
for el, val := range v.ObjectValue() {
results = append(results,
addPrefix(append(path[:i], string(el)),
expandGlob(path[i+1:], val))...)
}
return results
// v is not glob-able, so we expand to nothing.
default:
return nil
}
}
return []resource.PropertyPath{path}
}
func addPrefix(prefix resource.PropertyPath, ends []resource.PropertyPath) []resource.PropertyPath {
results := make([]resource.PropertyPath, 0, len(ends))
for _, end := range ends {
cp := make(resource.PropertyPath, len(prefix)+len(end))
copy(cp, prefix)
copy(cp[len(prefix):], end)
results = append(results, cp)
}
return results
}