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