1package format
2
3import (
4	"github.com/zclconf/go-cty/cty"
5)
6
7// ObjectValueID takes a value that is assumed to be an object representation
8// of some resource instance object and attempts to heuristically find an
9// attribute of it that is likely to be a unique identifier in the remote
10// system that it belongs to which will be useful to the user.
11//
12// If such an attribute is found, its name and string value intended for
13// display are returned. Both returned strings are empty if no such attribute
14// exists, in which case the caller should assume that the resource instance
15// address within the Terraform configuration is the best available identifier.
16//
17// This is only a best-effort sort of thing, relying on naming conventions in
18// our resource type schemas. The result is not guaranteed to be unique, but
19// should generally be suitable for display to an end-user anyway.
20//
21// This function will panic if the given value is not of an object type.
22func ObjectValueID(obj cty.Value) (k, v string) {
23	if obj.IsNull() || !obj.IsKnown() {
24		return "", ""
25	}
26
27	atys := obj.Type().AttributeTypes()
28
29	switch {
30
31	case atys["id"] == cty.String:
32		v := obj.GetAttr("id")
33		if v.HasMark("sensitive") {
34			break
35		}
36		v, _ = v.Unmark()
37
38		if v.IsKnown() && !v.IsNull() {
39			return "id", v.AsString()
40		}
41
42	case atys["name"] == cty.String:
43		// "name" isn't always globally unique, but if there isn't also an
44		// "id" then it _often_ is, in practice.
45		v := obj.GetAttr("name")
46		if v.HasMark("sensitive") {
47			break
48		}
49		v, _ = v.Unmark()
50
51		if v.IsKnown() && !v.IsNull() {
52			return "name", v.AsString()
53		}
54	}
55
56	return "", ""
57}
58
59// ObjectValueName takes a value that is assumed to be an object representation
60// of some resource instance object and attempts to heuristically find an
61// attribute of it that is likely to be a human-friendly name in the remote
62// system that it belongs to which will be useful to the user.
63//
64// If such an attribute is found, its name and string value intended for
65// display are returned. Both returned strings are empty if no such attribute
66// exists, in which case the caller should assume that the resource instance
67// address within the Terraform configuration is the best available identifier.
68//
69// This is only a best-effort sort of thing, relying on naming conventions in
70// our resource type schemas. The result is not guaranteed to be unique, but
71// should generally be suitable for display to an end-user anyway.
72//
73// Callers that use both ObjectValueName and ObjectValueID at the same time
74// should be prepared to get the same attribute key and value from both in
75// some cases, since there is overlap betweek the id-extraction and
76// name-extraction heuristics.
77//
78// This function will panic if the given value is not of an object type.
79func ObjectValueName(obj cty.Value) (k, v string) {
80	if obj.IsNull() || !obj.IsKnown() {
81		return "", ""
82	}
83
84	atys := obj.Type().AttributeTypes()
85
86	switch {
87
88	case atys["name"] == cty.String:
89		v := obj.GetAttr("name")
90		if v.HasMark("sensitive") {
91			break
92		}
93		v, _ = v.Unmark()
94
95		if v.IsKnown() && !v.IsNull() {
96			return "name", v.AsString()
97		}
98
99	case atys["tags"].IsMapType() && atys["tags"].ElementType() == cty.String:
100		tags := obj.GetAttr("tags")
101		if tags.IsNull() || !tags.IsWhollyKnown() || tags.HasMark("sensitive") {
102			break
103		}
104		tags, _ = tags.Unmark()
105
106		switch {
107		case tags.HasIndex(cty.StringVal("name")).RawEquals(cty.True):
108			v := tags.Index(cty.StringVal("name"))
109			if v.HasMark("sensitive") {
110				break
111			}
112			v, _ = v.Unmark()
113
114			if v.IsKnown() && !v.IsNull() {
115				return "tags.name", v.AsString()
116			}
117		case tags.HasIndex(cty.StringVal("Name")).RawEquals(cty.True):
118			// AWS-style naming convention
119			v := tags.Index(cty.StringVal("Name"))
120			if v.HasMark("sensitive") {
121				break
122			}
123			v, _ = v.Unmark()
124
125			if v.IsKnown() && !v.IsNull() {
126				return "tags.Name", v.AsString()
127			}
128		}
129	}
130
131	return "", ""
132}
133
134// ObjectValueIDOrName is a convenience wrapper around both ObjectValueID
135// and ObjectValueName (in that preference order) to try to extract some sort
136// of human-friendly descriptive string value for an object as additional
137// context about an object when it is being displayed in a compact way (where
138// not all of the attributes are visible.)
139//
140// Just as with the two functions it wraps, it is a best-effort and may return
141// two empty strings if no suitable attribute can be found for a given object.
142func ObjectValueIDOrName(obj cty.Value) (k, v string) {
143	k, v = ObjectValueID(obj)
144	if k != "" {
145		return
146	}
147	k, v = ObjectValueName(obj)
148	return
149}
150