| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package views | |
| import ( | |
| encJson "encoding/json" | |
| "fmt" | |
| "sort" | |
| "github.com/hashicorp/terraform/internal/command/arguments" | |
| "github.com/hashicorp/terraform/internal/moduleref" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| "github.com/xlab/treeprint" | |
| ) | |
| type Modules interface { | |
| // Display renders the list of module entries. | |
| Display(manifest moduleref.Manifest) int | |
| // Diagnostics renders early diagnostics, resulting from argument parsing. | |
| Diagnostics(diags tfdiags.Diagnostics) | |
| } | |
| func NewModules(vt arguments.ViewType, view *View) Modules { | |
| switch vt { | |
| case arguments.ViewJSON: | |
| return &ModulesJSON{view: view} | |
| case arguments.ViewHuman: | |
| return &ModulesHuman{view: view} | |
| default: | |
| panic(fmt.Sprintf("unknown view type %v", vt)) | |
| } | |
| } | |
| type ModulesHuman struct { | |
| view *View | |
| } | |
| var _ Modules = (*ModulesHuman)(nil) | |
| func (v *ModulesHuman) Display(manifest moduleref.Manifest) int { | |
| if len(manifest.Records) == 0 { | |
| v.view.streams.Println("No modules found in configuration.") | |
| return 0 | |
| } | |
| printRoot := treeprint.New() | |
| // ensure output is deterministic | |
| sort.Sort(manifest.Records) | |
| populateTreeNode(printRoot, &moduleref.Record{ | |
| Children: manifest.Records, | |
| }) | |
| v.view.streams.Println(fmt.Sprintf("\nModules declared by configuration:\n%s", printRoot.String())) | |
| return 0 | |
| } | |
| func populateTreeNode(tree treeprint.Tree, node *moduleref.Record) { | |
| for _, childNode := range node.Children { | |
| item := fmt.Sprintf("\"%s\"[%s]", childNode.Key, childNode.Source.String()) | |
| if childNode.Version != nil { | |
| item += fmt.Sprintf(" %s", childNode.Version) | |
| // Avoid rendering the version constraint if an exact version is given i.e. 'version = "1.2.3"' | |
| if childNode.VersionConstraints != nil && childNode.VersionConstraints.String() != childNode.Version.String() { | |
| item += fmt.Sprintf(" (%s)", childNode.VersionConstraints.String()) | |
| } | |
| } | |
| branch := tree.AddBranch(item) | |
| populateTreeNode(branch, childNode) | |
| } | |
| } | |
| func (v *ModulesHuman) Diagnostics(diags tfdiags.Diagnostics) { | |
| v.view.Diagnostics(diags) | |
| } | |
| type ModulesJSON struct { | |
| view *View | |
| } | |
| var _ Modules = (*ModulesHuman)(nil) | |
| func (v *ModulesJSON) Display(manifest moduleref.Manifest) int { | |
| var bytes []byte | |
| var err error | |
| flattenedManifest := flattenManifest(manifest) | |
| if bytes, err = encJson.Marshal(flattenedManifest); err != nil { | |
| v.view.streams.Eprintf("error marshalling manifest: %v", err) | |
| return 1 | |
| } | |
| v.view.streams.Println(string(bytes)) | |
| return 0 | |
| } | |
| // FlattenManifest returns the nested contents of a moduleref.Manifest in | |
| // a flattened format with the VersionConstraints and Children attributes | |
| // ommited for the purposes of the json format of the modules command | |
| func flattenManifest(m moduleref.Manifest) map[string]interface{} { | |
| var flatten func(records []*moduleref.Record) | |
| var recordList []map[string]string | |
| flatten = func(records []*moduleref.Record) { | |
| for _, record := range records { | |
| if record.Version != nil { | |
| recordList = append(recordList, map[string]string{ | |
| "key": record.Key, | |
| "source": record.Source.String(), | |
| "version": record.Version.String(), | |
| }) | |
| } else { | |
| recordList = append(recordList, map[string]string{ | |
| "key": record.Key, | |
| "source": record.Source.String(), | |
| "version": "", | |
| }) | |
| } | |
| if len(record.Children) > 0 { | |
| flatten(record.Children) | |
| } | |
| } | |
| } | |
| flatten(m.Records) | |
| ret := map[string]interface{}{ | |
| "format_version": m.FormatVersion, | |
| "modules": recordList, | |
| } | |
| return ret | |
| } | |
| func (v *ModulesJSON) Diagnostics(diags tfdiags.Diagnostics) { | |
| v.view.Diagnostics(diags) | |
| } |