Skip to content

Commit e0da308

Browse files
ocobleseqxcprivitere
authored andcommitted
update to metal-go and add all required fields and validations for Device
Signed-off-by: ocobleseqx <oscar.cobles@eu.equinix.com>
1 parent 58abbc6 commit e0da308

3 files changed

Lines changed: 195 additions & 19 deletions

File tree

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1-
# terraform import equinix_metal_device.{{.Hostname}} {{.ID}}
2-
resource "equinix_metal_device" "{{.Hostname}}" {
3-
plan = "{{.Plan.Slug}}"
4-
hostname = "{{.Hostname}}"
5-
billing_cycle = "{{.BillingCycle}}"
6-
metro = "{{.Metro.Code}}"
7-
operating_system = "{{.OS.Slug}}"
8-
project_id = "{{.Project.ID}}"
9-
10-
tags = {{.Tags}}
1+
# terraform import equinix_metal_device.example {{.Id}}
2+
resource "equinix_metal_device" "example" {
3+
always_pxe = {{.AlwaysPxe}}
4+
billing_cycle = {{.BillingCycle}}
5+
custom_data = {{.Customdata | nullIfNilOrEmpty}}
6+
description = {{.Description | nullIfNilOrEmpty}}
7+
force_detach_volumes = false
8+
{{- if .HardwareReservation }}
9+
hardware_reservation_id = {{.HardwareReservation.Id }}
10+
{{ else }}
11+
hardware_reservation_id = null
12+
{{- end }}
13+
hostname = {{.Hostname}}
14+
ipxe_script_url = {{.IpxeScriptUrl}}
15+
metro = {{.Metro.Code}}
16+
operating_system = {{.OperatingSystem.Slug}}
17+
plan = {{.Plan.Slug}}
18+
project_id = {{ hrefToID .Project.Href}}
19+
project_ssh_key_ids = null
20+
storage = {{.Storage | nullIfNilOrEmpty}}
21+
tags = {{.Tags}}
22+
termination_time = {{.TerminationTime | nullIfNilOrEmpty}}
23+
user_data = {{.Userdata | nullIfNilOrEmpty}} # sensitive
24+
user_ssh_key_ids = null
25+
wait_for_reservation_deprovision = false
1126
}

internal/outputs/terraform/format.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package terraform
33
import (
44
"bytes"
55
_ "embed"
6+
"fmt"
7+
"html"
68
"html/template"
79
"path"
8-
9-
"github.com/packethost/packngo"
10+
metal "github.com/equinix-labs/metal-go/metal/v1"
1011
)
1112

1213
var (
@@ -23,21 +24,29 @@ func many(s string) string {
2324

2425
func Marshal(i interface{}) ([]byte, error) {
2526
f := ""
26-
switch i.(type) {
27-
case *packngo.Device:
27+
28+
switch v := i.(type) {
29+
case *metal.Device:
30+
fmt.Printf("single device")
2831
f = deviceFormat
29-
case []packngo.Device:
32+
case []metal.Device:
33+
fmt.Printf("devices")
3034
f = many(deviceFormat)
31-
case *packngo.Project:
35+
case *metal.Project:
3236
f = projectFormat
33-
case []packngo.Project:
37+
case []metal.Project:
3438
f = many(projectFormat)
39+
default:
40+
return nil, fmt.Errorf("%v is not compatible with terraform output", v)
3541
}
3642

43+
addQuotesToString(i)
44+
3745
tmpl, err := template.New("terraform").Funcs(template.FuncMap{
3846
"hrefToID": func(href string) string {
39-
return path.Base(href)
47+
return fmt.Sprintf("\"%s", path.Base(href))
4048
},
49+
"nullIfNilOrEmpty": nullIfNilOrEmpty,
4150
}).Parse(f)
4251
if err != nil {
4352
return nil, err
@@ -47,5 +56,6 @@ func Marshal(i interface{}) ([]byte, error) {
4756
if err != nil {
4857
return nil, err
4958
}
50-
return buf.Bytes(), nil
59+
result := html.UnescapeString(buf.String())
60+
return []byte(result), nil
5161
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package terraform
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"time"
7+
)
8+
9+
func addQuotesToString(v interface{}) {
10+
val := reflect.ValueOf(v)
11+
12+
switch val.Kind() {
13+
case reflect.Ptr:
14+
val = val.Elem()
15+
if val.Kind() != reflect.Struct {
16+
return
17+
}
18+
19+
if val.Type() == reflect.TypeOf(new(string)) {
20+
oldValue := val.Elem().String()
21+
newValue := fmt.Sprintf(`"%s"`, oldValue)
22+
val.Elem().SetString(newValue)
23+
return
24+
}
25+
case reflect.String:
26+
oldValue := val.String()
27+
newValue := fmt.Sprintf(`"%s"`, oldValue)
28+
val.SetString(newValue)
29+
return
30+
case reflect.Slice:
31+
for i := 0; i < val.Len(); i++ {
32+
elem := val.Index(i)
33+
if elem.Kind() == reflect.Struct || (elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
34+
addQuotesToString(elem.Interface())
35+
}
36+
}
37+
return
38+
case reflect.Map:
39+
for _, key := range val.MapKeys() {
40+
elem := val.MapIndex(key)
41+
if elem.Kind() == reflect.Struct || (elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
42+
addQuotesToString(elem.Interface())
43+
}
44+
}
45+
return
46+
default:
47+
return
48+
}
49+
50+
for i := 0; i < val.NumField(); i++ {
51+
field := val.Field(i)
52+
53+
switch field.Kind() {
54+
case reflect.String:
55+
oldValue := field.String()
56+
newValue := fmt.Sprintf(`"%s"`, oldValue)
57+
field.SetString(newValue)
58+
case reflect.Ptr:
59+
if field.IsNil() {
60+
continue
61+
}
62+
// Check if the pointer is to a string
63+
if field.Type().Elem() == reflect.TypeOf("") {
64+
oldValue := field.Elem().String()
65+
newValue := fmt.Sprintf(`"%s"`, oldValue)
66+
field.Elem().SetString(newValue)
67+
} else {
68+
// Exclude *time.Time from recursion
69+
if field.Type() != reflect.TypeOf(&time.Time{}) {
70+
addQuotesToString(field.Interface())
71+
}
72+
}
73+
case reflect.Struct:
74+
// Exclude time.Time from recursion
75+
if field.Type() != reflect.TypeOf(time.Time{}) {
76+
addQuotesToString(field.Interface())
77+
}
78+
}
79+
}
80+
}
81+
82+
func nullIfNilOrEmpty(v interface{}) interface{} {
83+
if v == nil {
84+
return "null"
85+
}
86+
87+
// Use reflection to check if the value is an empty value (e.g., empty string, empty slice, or empty map)
88+
val := reflect.ValueOf(v)
89+
switch val.Kind() {
90+
case reflect.String:
91+
if val.String() == "\"\"" {
92+
return "null"
93+
}
94+
case reflect.Array, reflect.Slice, reflect.Map:
95+
if val.Len() == 0 {
96+
return "null"
97+
}
98+
case reflect.Ptr:
99+
if val.IsNil() || val.IsZero() {
100+
return "null"
101+
}
102+
103+
elem := val.Elem()
104+
105+
// Check if it's a pointer to a string
106+
if elem.Kind() == reflect.String && elem.String() == "\"\"" {
107+
return "null"
108+
}
109+
110+
switch elem.Kind() {
111+
case reflect.Struct:
112+
if isPointerStructEmpty(elem) {
113+
return "null"
114+
}
115+
case reflect.Array, reflect.Slice:
116+
if elem.Len() == 0 {
117+
return "null"
118+
}
119+
case reflect.Map:
120+
if elem.Len() == 0 {
121+
return "null"
122+
}
123+
}
124+
}
125+
126+
return v
127+
}
128+
129+
func isPointerStructEmpty(structVal reflect.Value) bool {
130+
// Iterate through the struct fields
131+
for i := 0; i < structVal.NumField(); i++ {
132+
field := structVal.Field(i)
133+
134+
// You can define custom logic to determine if a field is empty
135+
// For example, check if a string field is empty, or if a slice/map field is empty
136+
switch field.Kind() {
137+
case reflect.String:
138+
if field.String() != "" {
139+
return false
140+
}
141+
case reflect.Slice, reflect.Map:
142+
if field.Len() > 0 {
143+
return false
144+
}
145+
// Add more cases for other field types as needed
146+
}
147+
}
148+
149+
// All fields are empty
150+
return true
151+
}

0 commit comments

Comments
 (0)