| package format |
| |
| import ( |
| "bytes" |
| "fmt" |
| "sort" |
| "strings" |
| |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/plans" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/terraform" |
| "github.com/mitchellh/colorstring" |
| ) |
| |
| // StateOpts are the options for formatting a state. |
| type StateOpts struct { |
| // State is the state to format. This is required. |
| State *states.State |
| |
| // Schemas are used to decode attributes. This is required. |
| Schemas *terraform.Schemas |
| |
| // Color is the colorizer. This is optional. |
| Color *colorstring.Colorize |
| } |
| |
| // State takes a state and returns a string |
| func State(opts *StateOpts) string { |
| if opts.Color == nil { |
| panic("colorize not given") |
| } |
| |
| if opts.Schemas == nil { |
| panic("schemas not given") |
| } |
| |
| s := opts.State |
| if len(s.Modules) == 0 { |
| return "The state file is empty. No resources are represented." |
| } |
| |
| buf := bytes.NewBufferString("[reset]") |
| p := blockBodyDiffPrinter{ |
| buf: buf, |
| color: opts.Color, |
| action: plans.NoOp, |
| verbose: true, |
| } |
| |
| // Format all the modules |
| for _, m := range s.Modules { |
| formatStateModule(p, m, opts.Schemas) |
| } |
| |
| // Write the outputs for the root module |
| m := s.RootModule() |
| |
| if m.OutputValues != nil { |
| if len(m.OutputValues) > 0 { |
| p.buf.WriteString("Outputs:\n\n") |
| } |
| |
| // Sort the outputs |
| ks := make([]string, 0, len(m.OutputValues)) |
| for k := range m.OutputValues { |
| ks = append(ks, k) |
| } |
| sort.Strings(ks) |
| |
| // Output each output k/v pair |
| for _, k := range ks { |
| v := m.OutputValues[k] |
| p.buf.WriteString(fmt.Sprintf("%s = ", k)) |
| if v.Sensitive { |
| p.buf.WriteString("(sensitive value)") |
| } else { |
| p.writeValue(v.Value, plans.NoOp, 0) |
| } |
| p.buf.WriteString("\n") |
| } |
| } |
| |
| trimmedOutput := strings.TrimSpace(p.buf.String()) |
| trimmedOutput += "[reset]" |
| |
| return opts.Color.Color(trimmedOutput) |
| |
| } |
| |
| func formatStateModule(p blockBodyDiffPrinter, m *states.Module, schemas *terraform.Schemas) { |
| // First get the names of all the resources so we can show them |
| // in alphabetical order. |
| names := make([]string, 0, len(m.Resources)) |
| for name := range m.Resources { |
| names = append(names, name) |
| } |
| sort.Strings(names) |
| |
| // Go through each resource and begin building up the output. |
| for _, key := range names { |
| for k, v := range m.Resources[key].Instances { |
| // keep these in order to keep the current object first, and |
| // provide deterministic output for the deposed objects |
| type obj struct { |
| header string |
| instance *states.ResourceInstanceObjectSrc |
| } |
| instances := []obj{} |
| |
| addr := m.Resources[key].Addr |
| resAddr := addr.Resource |
| |
| taintStr := "" |
| if v.Current != nil && v.Current.Status == 'T' { |
| taintStr = " (tainted)" |
| } |
| |
| instances = append(instances, |
| obj{fmt.Sprintf("# %s:%s\n", addr.Instance(k), taintStr), v.Current}) |
| |
| for dk, v := range v.Deposed { |
| instances = append(instances, |
| obj{fmt.Sprintf("# %s: (deposed object %s)\n", addr.Instance(k), dk), v}) |
| } |
| |
| // Sort the instances for consistent output. |
| // Starting the sort from the second index, so the current instance |
| // is always first. |
| sort.Slice(instances[1:], func(i, j int) bool { |
| return instances[i+1].header < instances[j+1].header |
| }) |
| |
| for _, obj := range instances { |
| header := obj.header |
| instance := obj.instance |
| p.buf.WriteString(header) |
| if instance == nil { |
| // this shouldn't happen, but there's nothing to do here so |
| // don't panic below. |
| continue |
| } |
| |
| var schema *configschema.Block |
| |
| provider := m.Resources[key].ProviderConfig.Provider |
| if _, exists := schemas.Providers[provider]; !exists { |
| // This should never happen in normal use because we should've |
| // loaded all of the schemas and checked things prior to this |
| // point. We can't return errors here, but since this is UI code |
| // we will try to do _something_ reasonable. |
| p.buf.WriteString(fmt.Sprintf("# missing schema for provider %q\n\n", provider.String())) |
| continue |
| } |
| |
| switch resAddr.Mode { |
| case addrs.ManagedResourceMode: |
| schema, _ = schemas.ResourceTypeConfig( |
| provider, |
| resAddr.Mode, |
| resAddr.Type, |
| ) |
| if schema == nil { |
| p.buf.WriteString(fmt.Sprintf( |
| "# missing schema for provider %q resource type %s\n\n", provider, resAddr.Type)) |
| continue |
| } |
| |
| p.buf.WriteString(fmt.Sprintf( |
| "resource %q %q {", |
| resAddr.Type, |
| resAddr.Name, |
| )) |
| case addrs.DataResourceMode: |
| schema, _ = schemas.ResourceTypeConfig( |
| provider, |
| resAddr.Mode, |
| resAddr.Type, |
| ) |
| if schema == nil { |
| p.buf.WriteString(fmt.Sprintf( |
| "# missing schema for provider %q data source %s\n\n", provider, resAddr.Type)) |
| continue |
| } |
| |
| p.buf.WriteString(fmt.Sprintf( |
| "data %q %q {", |
| resAddr.Type, |
| resAddr.Name, |
| )) |
| default: |
| // should never happen, since the above is exhaustive |
| p.buf.WriteString(resAddr.String()) |
| } |
| |
| val, err := instance.Decode(schema.ImpliedType()) |
| if err != nil { |
| fmt.Println(err.Error()) |
| break |
| } |
| |
| path := make(cty.Path, 0, 3) |
| result := p.writeBlockBodyDiff(schema, val.Value, val.Value, 2, path) |
| if result.bodyWritten { |
| p.buf.WriteString("\n") |
| } |
| |
| p.buf.WriteString("}\n\n") |
| } |
| } |
| } |
| p.buf.WriteString("\n") |
| } |