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  }