github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonformat/README.md (about) 1 # jsonformat 2 3 This package contains functionality around formatting and displaying the JSON 4 structured output produced by adding the `-json` flag to various Terraform 5 commands. 6 7 ## Terraform Structured Plan Renderer 8 9 As of January 2023, this package contains only a single structure: the 10 `Renderer`. 11 12 The renderer accepts the JSON structured output produced by the 13 `terraform show <plan-file> -json` command and writes it in a human-readable 14 format. 15 16 Implementation details and decisions for the `Renderer` are discussed in the 17 following sections. 18 19 ### Implementation 20 21 There are two subpackages within the `jsonformat` renderer package. The `differ` 22 package compares the `before` and `after` values of the given plan and produces 23 `Diff` objects from the `computed` package. 24 25 This approach is aimed at ensuring the process by which the plan difference is 26 calculated is separated from the rendering itself. In this way it should be 27 possible to modify the rendering or add new renderer formats without being 28 concerned with the complex diff calculations. 29 30 #### The `differ` package 31 32 The `differ` package operates on `Change` objects. These are produced from 33 `jsonplan.Change` objects (which are produced by the `terraform show` command). 34 Each `jsonplan.Change` object represents a single resource within the overall 35 Terraform configuration. 36 37 The `differ` package will iterate through the `Change` objects and produce a 38 single `Diff` that represents a processed summary of the changes described by 39 the `Change`. You will see that the produced changes are nested so a change to a 40 list attribute will contain a slice of changes, this is discussed in the 41 "[The computed package](#the-computed-package)" section. 42 43 ##### The `Change` object 44 45 The `Change` objects contain raw Golang representations of JSON objects (generic 46 `interface{}` fields). These are produced by parsing the `json.RawMessage` 47 objects within the provided changes. 48 49 The fields the differ cares about from the provided changes are: 50 51 - `Before`: The value before the proposed change. 52 - `After`: The value after the proposed change. 53 - `Unknown`: If the value is being computed during the change. 54 - `BeforeSensitive`: If the value was sensitive before the change. 55 - `AfterSensitive`: If the value is sensitive after the change. 56 - `ReplacePaths`: If the change is causing the overall resource to be replaced. 57 58 In addition, the changes define two additional meta fields that they set and 59 manipulate internally: 60 61 - `BeforeExplicit`: If the value in `Before` is explicit or an implied result due to a change elsewhere. 62 - `AfterExplicit`: If the value in `After` is explicit or an implied result due to a change elsewhere. 63 64 The actual concrete type of each of the generic fields is determined by the 65 overall schema. The changes are also recursive, this means as we iterate through 66 the `Change` we create relevant child values based on the schema for the given 67 resource. 68 69 For example, the initial change is always a `block` type which means the 70 `Before` and `After` values will actually be `map[string]interface{}` types 71 mapping each attribute and block to their relevant values. The 72 `Unknown`, `BeforeSensitive`, `AfterSensitive` values will all be either a 73 `map[string]interface{}` which maps each attribute or nested block to their 74 unknown and sensitive status, or it could simply be a `boolean` which generally 75 means the entire block and all children are sensitive or computed. 76 77 In total, a `Change` can represent the following types: 78 79 - `Attribute` 80 - `map`: Values will typically be `map[string]interface{}`. 81 - `list`: Values will typically be `[]interface{}`. 82 - `set`: Values will typically be `[]interface{}`. 83 - `object`: Values will typically be `map[string]interface{}`. 84 - `tuple`: Values will typically be `[]interface{}`. 85 - `bool`: Values will typically be a `bool`. 86 - `number`: Values will typically be a `float64`. 87 - `string`: Values will typically be a `string`. 88 - `Block`: Values will typically be `map[string]interface{}`, but they can be 89 split between nested blocks and attributes. 90 - `Output` 91 - Outputs are interesting as we don't have a schema for them, as such they 92 can be any JSON type. 93 - We also use the Output type to represent dynamic attributes, since in both 94 cases we work out the type based on the JSON representation instead of the 95 schema. 96 97 The `ReplacePaths` field is unique in that it's value doesn't actually change 98 based on the schema - it's always a slice of index slices. An index in this 99 context will either be an integer pointing to a child of a set or a list or a 100 string pointing to the child of a map, object or block. As we iterate through 101 the value we manipulate the outer slice to remove child slices where the index 102 doesn't match and propagate paths that do match onto the children. 103 104 *Quick note on explicit vs implicit:* In practice, it is only possible to get 105 implicit changes when you manipulate a collection. That is to say child values 106 of a modified collection will insert `nil` entries into the relevant before 107 or after fields of their child changes to represent their values being deleted 108 or created. It is also possible for users to explicitly put null values into 109 their collections, and this behaviour is different to deleting an item in the 110 collection. With the `BeforeExplicit` and `AfterExplicit` values we can tell the 111 difference between whether this value was removed from a collection or this 112 value was set to null in a collection. 113 114 *Quick note on the go-cty Value and Type objects:* The `Before` and `After` 115 fields are actually go-cty values, but we cannot convert them directly because 116 of the Terraform Cloud redacted endpoint. The redacted endpoint turns sensitive 117 values into strings regardless of their types. Because of this, we cannot just 118 do a direct conversion using the ctyjson package. We would have to iterate 119 through the schema first, find the sensitive values and their mapped types, 120 update the types inside the schema to strings, and then go back and do the 121 overall conversion. This isn't including any of the more complicated parts 122 around what happens if something was sensitive before and isn't sensitive after 123 or vice versa. This would mean the type would need to change between the before 124 and after value. It is in fact just easier to iterate through the values as 125 generic JSON interfaces, and obfuscate the sensitive values as we never need to 126 print them anyway. 127 128 ##### Iterating through changes 129 130 The `differ` package will recursively create child `Change` objects for the 131 complex objects. 132 133 There are two key subtypes of a `Change`: `SliceChange` and `MapChange`. 134 `SliceChange` values are used by list, set, and tuple attributes. `MapChange` 135 values are used by map and object attributes, and blocks. For what it is worth 136 outputs and dynamic types can end up using both, but they're kind of special as 137 the processing for dynamic types works out the type from the JSON struct and 138 then just passes it into the relevant real types for actual processing. 139 140 The two subtypes implement `GetChild` functions that retrieve a child change 141 for a relevant index (`int` for slice, `string` for map). These functions build 142 an entirely populated `Change` object, and the package will then recursively 143 compute the change for the child (and all other children). When a complex change 144 has all the children changes, it then passes that into the relevant complex 145 diff type. 146 147 #### The `computed` package 148 149 A computed `Diff` should contain all the relevant information it needs to render 150 itself. 151 152 The `Diff` itself contains the action (eg. `Create`, `Delete`, `Update`), and 153 whether this change is causing the overall resource to be replaced (read from 154 the `ReplacePaths` field discussed in the previous section). The actual content 155 of the diffs is passed directly into the internal renderer field. The internal 156 renderer is then an implementation that knows the actual content of the changes 157 and what they represent. 158 159 For example to instantiate a diff resulting from updating a list of 160 primitives: 161 162 ```go 163 listDiff := computed.NewDiff(renderers.List([]computed.Diff{ 164 computed.NewDiff(renderers.Primitive(0.0, 0.0, cty.Number), plans.NoOp, false), 165 computed.NewDiff(renderers.Primitive(1.0, nil, cty.Number), plans.Delete, false), 166 computed.NewDiff(renderers.Primitive(nil, 4.0, cty.Number), plans.Create, false), 167 computed.NewDiff(renderers.Primitive(2.0, 2.0, cty.Number), plans.NoOp, false) 168 }, plans.Update, false)) 169 ``` 170 171 ##### The `RenderHuman` function 172 173 Currently, there is only one way to render a change, and it is implemented via 174 the `RenderHuman` function. In the future, there may be additional rendering 175 capabilities, but for now the `RenderHuman` function just passes the call 176 directly onto the internal renderer. 177 178 Rendering the above diff with: `listDiff.RenderHuman(0, RenderOpts{})` would 179 produce: 180 181 ```text 182 [ 183 0, 184 - 1 -> null, 185 + 4, 186 2, 187 ] 188 ``` 189 190 Note, the render function itself doesn't print out metadata about its own change 191 (eg. there's no `~` symbol in front of the opening bracket). The expectation is 192 that parent changes control how child changes are rendered, so are responsible 193 for deciding on their opening indentation, whether they have a key (as in maps, 194 objects, and blocks), or how the action symbol is displayed. 195 196 In the above example, the primitive renderer would print out only `1 -> null` 197 while the surrounding list renderer is providing the indentation, the symbol and 198 the line ending commas. 199 200 ##### Implementing new diff types 201 202 To implement a new diff type, you must implement the internal Renderer 203 functionality. To do this you create a new implementation of the 204 `computed.DiffRenderer`, make sure it accepts all the data you need, and 205 implement the `RenderHuman` function (and any other additional render functions 206 that may exist). 207 208 Some changes publish warnings that should be displayed alongside them. 209 If your new change has no warnings you can use the `NoWarningsRenderer` to avoid 210 implementing the additional `Warnings` function. 211 212 If/when new Renderer types are implemented, additional `Render` like functions 213 will be added. You should implement all of these with your new change type. 214 215 ##### Implementing new renderer types for changes 216 217 As of January 2023, there is only a single type of renderer (the human-readable) 218 renderer. As such, the `Diff` structure provides a single `RenderHuman` 219 function. 220 221 To implement a new renderer: 222 223 1. Add a new render function onto the internal `DiffRenderer` interface. 224 2. Add a new render function onto the `Diff` struct that passes the call onto 225 the internal renderer. 226 3. Implement the new function on all the existing internal interfaces. 227 228 Since each internal renderer contains all the information it needs to provide 229 change information about itself, your new Render function should pass in 230 anything it needs. 231 232 ### New types of Renderer 233 234 In the future, we may wish to add in different kinds of renderer, such as a 235 compact renderer, or an interactive renderer. To do this, you'll need to modify 236 the Renderer struct or create a new type of Renderer. 237 238 The logic around creating the `Diff` structures will be shared (ie. calling 239 into the differ package should be consistent across renderers). But when it 240 comes to rendering the changes, I'd expect the `Diff` structures to implement 241 additional functions that allow them to internally organise the data as required 242 and return a relevant object. For the existing human-readable renderer that is 243 simply a string, but for a future interactive renderer it might be a model from 244 an MVC pattern.