Skip to content

Commit d813aec

Browse files
authored
Put tgc files under mmv1 management (#13109)
1 parent 0af0ba8 commit d813aec

File tree

3 files changed

+682
-0
lines changed

3 files changed

+682
-0
lines changed

mmv1/provider/terraform_tgc.go

+2
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,8 @@ func (tgc TerraformGoogleConversion) CopyCommonFiles(outputFolder string, genera
414414
"ancestrymanager/ancestryutil_test.go": "third_party/tgc/ancestrymanager/ancestryutil_test.go",
415415
"converters/google/convert.go": "third_party/tgc/convert.go",
416416
"converters/google/convert_test.go": "third_party/tgc/convert_test.go",
417+
"tfdata/fake_resource_data.go": "third_party/tgc/tfdata/fake_resource_data.go",
418+
"tfdata/fake_resource_data_test.go": "third_party/tgc/tfdata/fake_resource_data_test.go",
417419
"converters/google/resources/services/compute/compute_instance_group.go": "third_party/tgc/services/compute/compute_instance_group.go",
418420
"converters/google/resources/services/dataflow/job.go": "third_party/tgc/services/dataflow/job.go",
419421
"converters/google/resources/services/resourcemanager/service_account_key.go": "third_party/tgc/services/resourcemanager/service_account_key.go",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// In order to interact with resource converters, we need to be able to create
16+
// "terraform resource data" that supports a very limited subset of the API actually
17+
// used during the conversion process.
18+
package tfdata
19+
20+
import (
21+
"fmt"
22+
"reflect"
23+
"strconv"
24+
"strings"
25+
"time"
26+
27+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
28+
)
29+
30+
// Must be set to the same value as the internal typeObject const
31+
const typeObject schema.ValueType = 8
32+
33+
// This is more or less equivalent to the internal getResult struct
34+
// used by schema.ResourceData
35+
type getResult struct {
36+
Value interface{}
37+
Computed bool
38+
Exists bool
39+
Schema *schema.Schema
40+
}
41+
42+
// Compare to https://github.com/hashicorp/terraform-plugin-sdk/blob/97b4465/helper/schema/resource_data.go#L15
43+
type FakeResourceData struct {
44+
reader schema.FieldReader
45+
kind string
46+
schema map[string]*schema.Schema
47+
}
48+
49+
// Kind returns the type of resource (i.e. "google_storage_bucket").
50+
func (d *FakeResourceData) Kind() string {
51+
return d.kind
52+
}
53+
54+
// Id returns the ID of the resource from state.
55+
func (d *FakeResourceData) Id() string {
56+
return ""
57+
}
58+
59+
func (d *FakeResourceData) getRaw(key string) getResult {
60+
var parts []string
61+
if key != "" {
62+
parts = strings.Split(key, ".")
63+
}
64+
return d.get(parts)
65+
}
66+
67+
func (d *FakeResourceData) get(addr []string) getResult {
68+
r, err := d.reader.ReadField(addr)
69+
if err != nil {
70+
panic(err)
71+
}
72+
73+
var s *schema.Schema
74+
if schemaPath := addrToSchema(addr, d.schema); len(schemaPath) > 0 {
75+
s = schemaPath[len(schemaPath)-1]
76+
}
77+
if r.Value == nil && s != nil {
78+
r.Value = r.ValueOrZero(s)
79+
}
80+
81+
return getResult{
82+
Value: r.Value,
83+
Computed: r.Computed,
84+
Exists: r.Exists,
85+
Schema: s,
86+
}
87+
}
88+
89+
// Get reads a single field by key.
90+
func (d *FakeResourceData) Get(name string) interface{} {
91+
val, _ := d.GetOk(name)
92+
return val
93+
}
94+
95+
// Get reads a single field by key and returns a boolean indicating
96+
// whether the field exists.
97+
func (d *FakeResourceData) GetOk(name string) (interface{}, bool) {
98+
r := d.getRaw(name)
99+
exists := r.Exists && !r.Computed
100+
101+
if exists {
102+
// Verify that it's not the zero-value
103+
value := r.Value
104+
zero := r.Schema.Type.Zero()
105+
106+
if eq, ok := value.(schema.Equal); ok {
107+
exists = !eq.Equal(zero)
108+
} else {
109+
exists = !reflect.DeepEqual(value, zero)
110+
}
111+
}
112+
113+
return r.Value, exists
114+
}
115+
116+
func (d *FakeResourceData) GetOkExists(key string) (interface{}, bool) {
117+
r := d.getRaw(key)
118+
exists := r.Exists && !r.Computed
119+
return r.Value, exists
120+
}
121+
122+
// These methods are required by some mappers but we don't actually have (or need)
123+
// implementations for them.
124+
func (d *FakeResourceData) HasChange(string) bool { return false }
125+
func (d *FakeResourceData) Set(string, interface{}) error { return nil }
126+
func (d *FakeResourceData) SetId(string) {}
127+
func (d *FakeResourceData) GetProviderMeta(interface{}) error { return nil }
128+
func (d *FakeResourceData) Timeout(key string) time.Duration { return time.Duration(1) }
129+
130+
func NewFakeResourceData(kind string, resourceSchema map[string]*schema.Schema, values map[string]interface{}) *FakeResourceData {
131+
state := map[string]string{}
132+
var address []string
133+
attributes(values, address, state, resourceSchema)
134+
reader := &schema.MapFieldReader{
135+
Map: schema.BasicMapReader(state),
136+
Schema: resourceSchema,
137+
}
138+
return &FakeResourceData{
139+
kind: kind,
140+
schema: resourceSchema,
141+
reader: reader,
142+
}
143+
}
144+
145+
// addrToSchema finds the final element schema for the given address
146+
// and the given schema. It returns all the schemas that led to the final
147+
// schema. These are in order of the address (out to in).
148+
// NOTE: This function was copied from the terraform library:
149+
// github.com/hashicorp/terraform/helper/schema/field_reader.go
150+
func addrToSchema(addr []string, schemaMap map[string]*schema.Schema) []*schema.Schema {
151+
current := &schema.Schema{
152+
Type: typeObject,
153+
Elem: schemaMap,
154+
}
155+
156+
// If we aren't given an address, then the user is requesting the
157+
// full object, so we return the special value which is the full object.
158+
if len(addr) == 0 {
159+
return []*schema.Schema{current}
160+
}
161+
162+
result := make([]*schema.Schema, 0, len(addr))
163+
for len(addr) > 0 {
164+
k := addr[0]
165+
addr = addr[1:]
166+
167+
REPEAT:
168+
// We want to trim off the first "typeObject" since its not a
169+
// real lookup that people do. i.e. []string{"foo"} in a structure
170+
// isn't {typeObject, typeString}, its just a {typeString}.
171+
if len(result) > 0 || current.Type != typeObject {
172+
result = append(result, current)
173+
}
174+
175+
switch t := current.Type; t {
176+
case schema.TypeBool, schema.TypeInt, schema.TypeFloat, schema.TypeString:
177+
if len(addr) > 0 {
178+
return nil
179+
}
180+
case schema.TypeList, schema.TypeSet:
181+
isIndex := len(addr) > 0 && addr[0] == "#"
182+
183+
switch v := current.Elem.(type) {
184+
case *schema.Resource:
185+
current = &schema.Schema{
186+
Type: typeObject,
187+
Elem: v.Schema,
188+
}
189+
case *schema.Schema:
190+
current = v
191+
case schema.ValueType:
192+
current = &schema.Schema{Type: v}
193+
default:
194+
// we may not know the Elem type and are just looking for the
195+
// index
196+
if isIndex {
197+
break
198+
}
199+
200+
if len(addr) == 0 {
201+
// we've processed the address, so return what we've
202+
// collected
203+
return result
204+
}
205+
206+
if len(addr) == 1 {
207+
if _, err := strconv.Atoi(addr[0]); err == nil {
208+
// we're indexing a value without a schema. This can
209+
// happen if the list is nested in another schema type.
210+
// Default to a TypeString like we do with a map
211+
current = &schema.Schema{Type: schema.TypeString}
212+
break
213+
}
214+
}
215+
216+
return nil
217+
}
218+
219+
// If we only have one more thing and the next thing
220+
// is a #, then we're accessing the index which is always
221+
// an int.
222+
if isIndex {
223+
current = &schema.Schema{Type: schema.TypeInt}
224+
break
225+
}
226+
227+
case schema.TypeMap:
228+
if len(addr) > 0 {
229+
switch v := current.Elem.(type) {
230+
case schema.ValueType:
231+
current = &schema.Schema{Type: v}
232+
default:
233+
// maps default to string values. This is all we can have
234+
// if this is nested in another list or map.
235+
current = &schema.Schema{Type: schema.TypeString}
236+
}
237+
}
238+
case typeObject:
239+
// If we're already in the object, then we want to handle Sets
240+
// and Lists specially. Basically, their next key is the lookup
241+
// key (the set value or the list element). For these scenarios,
242+
// we just want to skip it and move to the next element if there
243+
// is one.
244+
if len(result) > 0 {
245+
lastType := result[len(result)-2].Type
246+
if lastType == schema.TypeSet || lastType == schema.TypeList {
247+
if len(addr) == 0 {
248+
break
249+
}
250+
251+
k = addr[0]
252+
addr = addr[1:]
253+
}
254+
}
255+
256+
m := current.Elem.(map[string]*schema.Schema)
257+
val, ok := m[k]
258+
if !ok {
259+
return nil
260+
}
261+
262+
current = val
263+
goto REPEAT
264+
}
265+
}
266+
267+
return result
268+
}
269+
270+
// attributes function takes json parsed JSON object (value param) and fill map[string]string with it's
271+
// content (state param) for example JSON:
272+
//
273+
// {
274+
// "foo": {
275+
// "name" : "value"
276+
// },
277+
// "list": ["item1", "item2"]
278+
// }
279+
//
280+
// will be translated to map with following key/value set:
281+
//
282+
// foo.name => "value"
283+
// list.# => 2
284+
// list.0 => "item1"
285+
// list.1 => "item2"
286+
//
287+
// Map above will be passed to schema.BasicMapReader that have all appropriate logic to read fields
288+
// correctly during conversion to CAI.
289+
func attributes(value interface{}, address []string, state map[string]string, schemas map[string]*schema.Schema) {
290+
schemaArr := addrToSchema(address, schemas)
291+
if len(schemaArr) == 0 {
292+
return
293+
}
294+
sch := schemaArr[len(schemaArr)-1]
295+
addr := strings.Join(address, ".")
296+
// int is special case, can't use handle it in main switch because number will be always parsed from JSON as float
297+
// need to identify it by schema.TypeInt and convert to int from int or float
298+
if sch.Type == schema.TypeInt && value != nil {
299+
switch value.(type) {
300+
case int:
301+
state[addr] = strconv.Itoa(value.(int))
302+
case float64:
303+
state[addr] = strconv.Itoa(int(value.(float64)))
304+
case float32:
305+
state[addr] = strconv.Itoa(int(value.(float32)))
306+
}
307+
return
308+
}
309+
310+
switch value.(type) {
311+
case nil:
312+
defaultValue, err := sch.DefaultValue()
313+
if err != nil {
314+
panic(fmt.Sprintf("error getting default value for %v", address))
315+
}
316+
if defaultValue == nil {
317+
defaultValue = sch.ZeroValue()
318+
}
319+
attributes(defaultValue, address, state, schemas)
320+
case float64:
321+
state[addr] = strconv.FormatFloat(value.(float64), 'f', 6, 64)
322+
case float32:
323+
state[addr] = strconv.FormatFloat(value.(float64), 'f', 6, 32)
324+
case string:
325+
state[addr] = value.(string)
326+
case bool:
327+
state[addr] = strconv.FormatBool(value.(bool))
328+
case int:
329+
state[addr] = strconv.Itoa(value.(int))
330+
case []interface{}:
331+
arr := value.([]interface{})
332+
countAddr := addr + ".#"
333+
state[countAddr] = strconv.Itoa(len(arr))
334+
for i, e := range arr {
335+
addr := append(address, strconv.Itoa(i))
336+
attributes(e, addr, state, schemas)
337+
}
338+
case map[string]interface{}:
339+
m := value.(map[string]interface{})
340+
for k, v := range m {
341+
addr := append(address, k)
342+
attributes(v, addr, state, schemas)
343+
}
344+
case *schema.Set:
345+
set := value.(*schema.Set)
346+
attributes(set.List(), address, state, schemas)
347+
default:
348+
panic(fmt.Sprintf("unrecognized type %T", value))
349+
}
350+
}

0 commit comments

Comments
 (0)