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 }