package format import ( "github.com/hashicorp/terraform/internal/lang/marks" "github.com/zclconf/go-cty/cty" ) // ObjectValueID takes a value that is assumed to be an object representation // of some resource instance object and attempts to heuristically find an // attribute of it that is likely to be a unique identifier in the remote // system that it belongs to which will be useful to the user. // // If such an attribute is found, its name and string value intended for // display are returned. Both returned strings are empty if no such attribute // exists, in which case the caller should assume that the resource instance // address within the Terraform configuration is the best available identifier. // // This is only a best-effort sort of thing, relying on naming conventions in // our resource type schemas. The result is not guaranteed to be unique, but // should generally be suitable for display to an end-user anyway. // // This function will panic if the given value is not of an object type. func ObjectValueID(obj cty.Value) (k, v string) { if obj.IsNull() || !obj.IsKnown() { return "", "" } atys := obj.Type().AttributeTypes() switch { case atys["id"] == cty.String: v := obj.GetAttr("id") if v.HasMark(marks.Sensitive) { break } v, _ = v.Unmark() if v.IsKnown() && !v.IsNull() { return "id", v.AsString() } case atys["name"] == cty.String: // "name" isn't always globally unique, but if there isn't also an // "id" then it _often_ is, in practice. v := obj.GetAttr("name") if v.HasMark(marks.Sensitive) { break } v, _ = v.Unmark() if v.IsKnown() && !v.IsNull() { return "name", v.AsString() } } return "", "" } // ObjectValueName takes a value that is assumed to be an object representation // of some resource instance object and attempts to heuristically find an // attribute of it that is likely to be a human-friendly name in the remote // system that it belongs to which will be useful to the user. // // If such an attribute is found, its name and string value intended for // display are returned. Both returned strings are empty if no such attribute // exists, in which case the caller should assume that the resource instance // address within the Terraform configuration is the best available identifier. // // This is only a best-effort sort of thing, relying on naming conventions in // our resource type schemas. The result is not guaranteed to be unique, but // should generally be suitable for display to an end-user anyway. // // Callers that use both ObjectValueName and ObjectValueID at the same time // should be prepared to get the same attribute key and value from both in // some cases, since there is overlap betweek the id-extraction and // name-extraction heuristics. // // This function will panic if the given value is not of an object type. func ObjectValueName(obj cty.Value) (k, v string) { if obj.IsNull() || !obj.IsKnown() { return "", "" } atys := obj.Type().AttributeTypes() switch { case atys["name"] == cty.String: v := obj.GetAttr("name") if v.HasMark(marks.Sensitive) { break } v, _ = v.Unmark() if v.IsKnown() && !v.IsNull() { return "name", v.AsString() } case atys["tags"].IsMapType() && atys["tags"].ElementType() == cty.String: tags := obj.GetAttr("tags") if tags.IsNull() || !tags.IsWhollyKnown() || tags.HasMark(marks.Sensitive) { break } tags, _ = tags.Unmark() switch { case tags.HasIndex(cty.StringVal("name")).RawEquals(cty.True): v := tags.Index(cty.StringVal("name")) if v.HasMark(marks.Sensitive) { break } v, _ = v.Unmark() if v.IsKnown() && !v.IsNull() { return "tags.name", v.AsString() } case tags.HasIndex(cty.StringVal("Name")).RawEquals(cty.True): // AWS-style naming convention v := tags.Index(cty.StringVal("Name")) if v.HasMark(marks.Sensitive) { break } v, _ = v.Unmark() if v.IsKnown() && !v.IsNull() { return "tags.Name", v.AsString() } } } return "", "" } // ObjectValueIDOrName is a convenience wrapper around both ObjectValueID // and ObjectValueName (in that preference order) to try to extract some sort // of human-friendly descriptive string value for an object as additional // context about an object when it is being displayed in a compact way (where // not all of the attributes are visible.) // // Just as with the two functions it wraps, it is a best-effort and may return // two empty strings if no suitable attribute can be found for a given object. func ObjectValueIDOrName(obj cty.Value) (k, v string) { k, v = ObjectValueID(obj) if k != "" { return } k, v = ObjectValueName(obj) return }