github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/command/format/state.go (about)

     1  package format
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    13  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    14  	"github.com/mitchellh/colorstring"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  // StateOpts are the options for formatting a state.
    19  type StateOpts struct {
    20  	// State is the state to format. This is required.
    21  	State *states.State
    22  
    23  	// Schemas are used to decode attributes. This is required.
    24  	Schemas *terraform.Schemas
    25  
    26  	// Color is the colorizer. This is optional.
    27  	Color *colorstring.Colorize
    28  }
    29  
    30  // State takes a state and returns a string
    31  func State(opts *StateOpts) string {
    32  	if opts.Color == nil {
    33  		panic("colorize not given")
    34  	}
    35  
    36  	if opts.Schemas == nil {
    37  		panic("schemas not given")
    38  	}
    39  
    40  	s := opts.State
    41  	if len(s.Modules) == 0 {
    42  		return "The state file is empty. No resources are represented."
    43  	}
    44  
    45  	buf := bytes.NewBufferString("[reset]")
    46  	p := blockBodyDiffPrinter{
    47  		buf:    buf,
    48  		color:  opts.Color,
    49  		action: plans.NoOp,
    50  	}
    51  
    52  	// Format all the modules
    53  	for _, m := range s.Modules {
    54  		formatStateModule(p, m, opts.Schemas)
    55  	}
    56  
    57  	// Write the outputs for the root module
    58  	m := s.RootModule()
    59  
    60  	if m.OutputValues != nil {
    61  		if len(m.OutputValues) > 0 {
    62  			p.buf.WriteString("Outputs:\n\n")
    63  		}
    64  
    65  		// Sort the outputs
    66  		ks := make([]string, 0, len(m.OutputValues))
    67  		for k := range m.OutputValues {
    68  			ks = append(ks, k)
    69  		}
    70  		sort.Strings(ks)
    71  
    72  		// Output each output k/v pair
    73  		for _, k := range ks {
    74  			v := m.OutputValues[k]
    75  			p.buf.WriteString(fmt.Sprintf("%s = ", k))
    76  			p.writeValue(v.Value, plans.NoOp, 0)
    77  			p.buf.WriteString("\n")
    78  		}
    79  	}
    80  
    81  	trimmedOutput := strings.TrimSpace(p.buf.String())
    82  	trimmedOutput += "[reset]"
    83  
    84  	return opts.Color.Color(trimmedOutput)
    85  
    86  }
    87  
    88  func formatStateModule(p blockBodyDiffPrinter, m *states.Module, schemas *terraform.Schemas) {
    89  	// First get the names of all the resources so we can show them
    90  	// in alphabetical order.
    91  	names := make([]string, 0, len(m.Resources))
    92  	for name := range m.Resources {
    93  		names = append(names, name)
    94  	}
    95  	sort.Strings(names)
    96  
    97  	// Go through each resource and begin building up the output.
    98  	for _, key := range names {
    99  		for k, v := range m.Resources[key].Instances {
   100  			// keep these in order to keep the current object first, and
   101  			// provide deterministic output for the deposed objects
   102  			type obj struct {
   103  				header   string
   104  				instance *states.ResourceInstanceObjectSrc
   105  			}
   106  			instances := []obj{}
   107  
   108  			addr := m.Resources[key].Addr
   109  
   110  			taintStr := ""
   111  			if v.Current != nil && v.Current.Status == 'T' {
   112  				taintStr = " (tainted)"
   113  			}
   114  
   115  			instances = append(instances,
   116  				obj{fmt.Sprintf("# %s:%s\n", addr.Absolute(m.Addr).Instance(k), taintStr), v.Current})
   117  
   118  			for dk, v := range v.Deposed {
   119  				instances = append(instances,
   120  					obj{fmt.Sprintf("# %s: (deposed object %s)\n", addr.Absolute(m.Addr).Instance(k), dk), v})
   121  			}
   122  
   123  			// Sort the instances for consistent output.
   124  			// Starting the sort from the second index, so the current instance
   125  			// is always first.
   126  			sort.Slice(instances[1:], func(i, j int) bool {
   127  				return instances[i+1].header < instances[j+1].header
   128  			})
   129  
   130  			for _, obj := range instances {
   131  				header := obj.header
   132  				instance := obj.instance
   133  				p.buf.WriteString(header)
   134  				if instance == nil {
   135  					// this shouldn't happen, but there's nothing to do here so
   136  					// don't panic below.
   137  					continue
   138  				}
   139  
   140  				var schema *configschema.Block
   141  				provider := m.Resources[key].ProviderConfig.ProviderConfig.StringCompact()
   142  				if _, exists := schemas.Providers[provider]; !exists {
   143  					// This should never happen in normal use because we should've
   144  					// loaded all of the schemas and checked things prior to this
   145  					// point. We can't return errors here, but since this is UI code
   146  					// we will try to do _something_ reasonable.
   147  					p.buf.WriteString(fmt.Sprintf("# missing schema for provider %q\n\n", provider))
   148  					continue
   149  				}
   150  
   151  				switch addr.Mode {
   152  				case addrs.ManagedResourceMode:
   153  					schema, _ = schemas.ResourceTypeConfig(
   154  						provider,
   155  						addr.Mode,
   156  						addr.Type,
   157  					)
   158  					if schema == nil {
   159  						p.buf.WriteString(fmt.Sprintf(
   160  							"# missing schema for provider %q resource type %s\n\n", provider, addr.Type))
   161  						continue
   162  					}
   163  
   164  					p.buf.WriteString(fmt.Sprintf(
   165  						"resource %q %q {",
   166  						addr.Type,
   167  						addr.Name,
   168  					))
   169  				case addrs.DataResourceMode:
   170  					schema, _ = schemas.ResourceTypeConfig(
   171  						provider,
   172  						addr.Mode,
   173  						addr.Type,
   174  					)
   175  					if schema == nil {
   176  						p.buf.WriteString(fmt.Sprintf(
   177  							"# missing schema for provider %q data source %s\n\n", provider, addr.Type))
   178  						continue
   179  					}
   180  
   181  					p.buf.WriteString(fmt.Sprintf(
   182  						"data %q %q {",
   183  						addr.Type,
   184  						addr.Name,
   185  					))
   186  				default:
   187  					// should never happen, since the above is exhaustive
   188  					p.buf.WriteString(addr.String())
   189  				}
   190  
   191  				val, err := instance.Decode(schema.ImpliedType())
   192  				if err != nil {
   193  					fmt.Println(err.Error())
   194  					break
   195  				}
   196  
   197  				path := make(cty.Path, 0, 3)
   198  				bodyWritten := p.writeBlockBodyDiff(schema, val.Value, val.Value, 2, path)
   199  				if bodyWritten {
   200  					p.buf.WriteString("\n")
   201  				}
   202  
   203  				p.buf.WriteString("}\n\n")
   204  			}
   205  		}
   206  	}
   207  	p.buf.WriteString("\n")
   208  }