github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/repl/session.go (about) 1 package repl 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 "github.com/hashicorp/terraform/internal/lang" 13 "github.com/hashicorp/terraform/internal/lang/marks" 14 "github.com/hashicorp/terraform/internal/lang/types" 15 "github.com/hashicorp/terraform/internal/tfdiags" 16 ) 17 18 // Session represents the state for a single REPL session. 19 type Session struct { 20 // Scope is the evaluation scope where expressions will be evaluated. 21 Scope *lang.Scope 22 } 23 24 // Handle handles a single line of input from the REPL. 25 // 26 // This is a stateful operation if a command is given (such as setting 27 // a variable). This function should not be called in parallel. 28 // 29 // The return value is the output and the error to show. 30 func (s *Session) Handle(line string) (string, bool, tfdiags.Diagnostics) { 31 switch { 32 case strings.TrimSpace(line) == "": 33 return "", false, nil 34 case strings.TrimSpace(line) == "exit": 35 return "", true, nil 36 case strings.TrimSpace(line) == "help": 37 ret, diags := s.handleHelp() 38 return ret, false, diags 39 default: 40 ret, diags := s.handleEval(line) 41 return ret, false, diags 42 } 43 } 44 45 func (s *Session) handleEval(line string) (string, tfdiags.Diagnostics) { 46 var diags tfdiags.Diagnostics 47 48 // Parse the given line as an expression 49 expr, parseDiags := hclsyntax.ParseExpression([]byte(line), "<console-input>", hcl.Pos{Line: 1, Column: 1}) 50 diags = diags.Append(parseDiags) 51 if parseDiags.HasErrors() { 52 return "", diags 53 } 54 55 val, valDiags := s.Scope.EvalExpr(expr, cty.DynamicPseudoType) 56 diags = diags.Append(valDiags) 57 if valDiags.HasErrors() { 58 return "", diags 59 } 60 61 // The TypeType mark is used only by the console-only `type` function, in 62 // order to smuggle the type of a given value back here. We can then 63 // display a representation of the type directly. 64 if marks.Contains(val, marks.TypeType) { 65 val, _ = val.UnmarkDeep() 66 67 valType := val.Type() 68 switch { 69 case valType.Equals(types.TypeType): 70 // An encapsulated type value, which should be displayed directly. 71 valType := val.EncapsulatedValue().(*cty.Type) 72 return typeString(*valType), diags 73 default: 74 diags = diags.Append(tfdiags.Sourceless( 75 tfdiags.Error, 76 "Invalid use of type function", 77 "The console-only \"type\" function cannot be used as part of an expression.", 78 )) 79 return "", diags 80 } 81 } 82 83 return FormatValue(val, 0), diags 84 } 85 86 func (s *Session) handleHelp() (string, tfdiags.Diagnostics) { 87 text := ` 88 The Terraform console allows you to experiment with Terraform interpolations. 89 You may access resources in the state (if you have one) just as you would 90 from a configuration. For example: "aws_instance.foo.id" would evaluate 91 to the ID of "aws_instance.foo" if it exists in your state. 92 93 Type in the interpolation to test and hit <enter> to see the result. 94 95 To exit the console, type "exit" and hit <enter>, or use Control-C or 96 Control-D. 97 ` 98 99 return strings.TrimSpace(text), nil 100 } 101 102 // Modified copy of TypeString from go-cty: 103 // https://github.com/zclconf/go-cty-debug/blob/master/ctydebug/type_string.go 104 // 105 // TypeString returns a string representation of a given type that is 106 // reminiscent of Go syntax calling into the cty package but is mainly 107 // intended for easy human inspection of values in tests, debug output, etc. 108 // 109 // The resulting string will include newlines and indentation in order to 110 // increase the readability of complex structures. It always ends with a 111 // newline, so you can print this result directly to your output. 112 func typeString(ty cty.Type) string { 113 var b strings.Builder 114 writeType(ty, &b, 0) 115 return b.String() 116 } 117 118 func writeType(ty cty.Type, b *strings.Builder, indent int) { 119 switch { 120 case ty == cty.NilType: 121 b.WriteString("nil") 122 return 123 case ty.IsObjectType(): 124 atys := ty.AttributeTypes() 125 if len(atys) == 0 { 126 b.WriteString("object({})") 127 return 128 } 129 attrNames := make([]string, 0, len(atys)) 130 for name := range atys { 131 attrNames = append(attrNames, name) 132 } 133 sort.Strings(attrNames) 134 b.WriteString("object({\n") 135 indent++ 136 for _, name := range attrNames { 137 aty := atys[name] 138 b.WriteString(indentSpaces(indent)) 139 fmt.Fprintf(b, "%s: ", name) 140 writeType(aty, b, indent) 141 b.WriteString(",\n") 142 } 143 indent-- 144 b.WriteString(indentSpaces(indent)) 145 b.WriteString("})") 146 case ty.IsTupleType(): 147 etys := ty.TupleElementTypes() 148 if len(etys) == 0 { 149 b.WriteString("tuple([])") 150 return 151 } 152 b.WriteString("tuple([\n") 153 indent++ 154 for _, ety := range etys { 155 b.WriteString(indentSpaces(indent)) 156 writeType(ety, b, indent) 157 b.WriteString(",\n") 158 } 159 indent-- 160 b.WriteString(indentSpaces(indent)) 161 b.WriteString("])") 162 case ty.IsCollectionType(): 163 ety := ty.ElementType() 164 switch { 165 case ty.IsListType(): 166 b.WriteString("list(") 167 case ty.IsMapType(): 168 b.WriteString("map(") 169 case ty.IsSetType(): 170 b.WriteString("set(") 171 default: 172 // At the time of writing there are no other collection types, 173 // but we'll be robust here and just pass through the GoString 174 // of anything we don't recognize. 175 b.WriteString(ty.FriendlyName()) 176 return 177 } 178 // Because object and tuple types render split over multiple 179 // lines, a collection type container around them can end up 180 // being hard to see when scanning, so we'll generate some extra 181 // indentation to make a collection of structural type more visually 182 // distinct from the structural type alone. 183 complexElem := ety.IsObjectType() || ety.IsTupleType() 184 if complexElem { 185 indent++ 186 b.WriteString("\n") 187 b.WriteString(indentSpaces(indent)) 188 } 189 writeType(ty.ElementType(), b, indent) 190 if complexElem { 191 indent-- 192 b.WriteString(",\n") 193 b.WriteString(indentSpaces(indent)) 194 } 195 b.WriteString(")") 196 default: 197 // For any other type we'll just use its GoString and assume it'll 198 // follow the usual GoString conventions. 199 b.WriteString(ty.FriendlyName()) 200 } 201 } 202 203 func indentSpaces(level int) string { 204 return strings.Repeat(" ", level) 205 }