| # jsonformat | |
| This package contains functionality around formatting and displaying the JSON | |
| structured output produced by adding the `-json` flag to various Terraform | |
| commands. | |
| ## Terraform Structured Plan Renderer | |
| As of January 2023, this package contains only a single structure: the | |
| `Renderer`. | |
| The renderer accepts the JSON structured output produced by the | |
| `terraform show <plan-file> -json` command and writes it in a human-readable | |
| format. | |
| Implementation details and decisions for the `Renderer` are discussed in the | |
| following sections. | |
| ### Implementation | |
| There are two subpackages within the `jsonformat` renderer package. The `differ` | |
| package compares the `before` and `after` values of the given plan and produces | |
| `Diff` objects from the `computed` package. | |
| This approach is aimed at ensuring the process by which the plan difference is | |
| calculated is separated from the rendering itself. In this way it should be | |
| possible to modify the rendering or add new renderer formats without being | |
| concerned with the complex diff calculations. | |
| #### The `differ` package | |
| The `differ` package operates on `Change` objects. These are produced from | |
| `jsonplan.Change` objects (which are produced by the `terraform show` command). | |
| Each `jsonplan.Change` object represents a single resource within the overall | |
| Terraform configuration. | |
| The `differ` package will iterate through the `Change` objects and produce a | |
| single `Diff` that represents a processed summary of the changes described by | |
| the `Change`. You will see that the produced changes are nested so a change to a | |
| list attribute will contain a slice of changes, this is discussed in the | |
| "[The computed package](#the-computed-package)" section. | |
| ##### The `Change` object | |
| The `Change` objects contain raw Golang representations of JSON objects (generic | |
| `interface{}` fields). These are produced by parsing the `json.RawMessage` | |
| objects within the provided changes. | |
| The fields the differ cares about from the provided changes are: | |
| - `Before`: The value before the proposed change. | |
| - `After`: The value after the proposed change. | |
| - `Unknown`: If the value is being computed during the change. | |
| - `BeforeSensitive`: If the value was sensitive before the change. | |
| - `AfterSensitive`: If the value is sensitive after the change. | |
| - `ReplacePaths`: If the change is causing the overall resource to be replaced. | |
| In addition, the changes define two additional meta fields that they set and | |
| manipulate internally: | |
| - `BeforeExplicit`: If the value in `Before` is explicit or an implied result due to a change elsewhere. | |
| - `AfterExplicit`: If the value in `After` is explicit or an implied result due to a change elsewhere. | |
| The actual concrete type of each of the generic fields is determined by the | |
| overall schema. The changes are also recursive, this means as we iterate through | |
| the `Change` we create relevant child values based on the schema for the given | |
| resource. | |
| For example, the initial change is always a `block` type which means the | |
| `Before` and `After` values will actually be `map[string]interface{}` types | |
| mapping each attribute and block to their relevant values. The | |
| `Unknown`, `BeforeSensitive`, `AfterSensitive` values will all be either a | |
| `map[string]interface{}` which maps each attribute or nested block to their | |
| unknown and sensitive status, or it could simply be a `boolean` which generally | |
| means the entire block and all children are sensitive or computed. | |
| In total, a `Change` can represent the following types: | |
| - `Attribute` | |
| - `map`: Values will typically be `map[string]interface{}`. | |
| - `list`: Values will typically be `[]interface{}`. | |
| - `set`: Values will typically be `[]interface{}`. | |
| - `object`: Values will typically be `map[string]interface{}`. | |
| - `tuple`: Values will typically be `[]interface{}`. | |
| - `bool`: Values will typically be a `bool`. | |
| - `number`: Values will typically be a `float64`. | |
| - `string`: Values will typically be a `string`. | |
| - `Block`: Values will typically be `map[string]interface{}`, but they can be | |
| split between nested blocks and attributes. | |
| - `Output` | |
| - Outputs are interesting as we don't have a schema for them, as such they | |
| can be any JSON type. | |
| - We also use the Output type to represent dynamic attributes, since in both | |
| cases we work out the type based on the JSON representation instead of the | |
| schema. | |
| The `ReplacePaths` field is unique in that it's value doesn't actually change | |
| based on the schema - it's always a slice of index slices. An index in this | |
| context will either be an integer pointing to a child of a set or a list or a | |
| string pointing to the child of a map, object or block. As we iterate through | |
| the value we manipulate the outer slice to remove child slices where the index | |
| doesn't match and propagate paths that do match onto the children. | |
| *Quick note on explicit vs implicit:* In practice, it is only possible to get | |
| implicit changes when you manipulate a collection. That is to say child values | |
| of a modified collection will insert `nil` entries into the relevant before | |
| or after fields of their child changes to represent their values being deleted | |
| or created. It is also possible for users to explicitly put null values into | |
| their collections, and this behaviour is different to deleting an item in the | |
| collection. With the `BeforeExplicit` and `AfterExplicit` values we can tell the | |
| difference between whether this value was removed from a collection or this | |
| value was set to null in a collection. | |
| *Quick note on the go-cty Value and Type objects:* The `Before` and `After` | |
| fields are actually go-cty values, but we cannot convert them directly because | |
| of the HCP Terraform redacted endpoint. The redacted endpoint turns sensitive | |
| values into strings regardless of their types. Because of this, we cannot just | |
| do a direct conversion using the ctyjson package. We would have to iterate | |
| through the schema first, find the sensitive values and their mapped types, | |
| update the types inside the schema to strings, and then go back and do the | |
| overall conversion. This isn't including any of the more complicated parts | |
| around what happens if something was sensitive before and isn't sensitive after | |
| or vice versa. This would mean the type would need to change between the before | |
| and after value. It is in fact just easier to iterate through the values as | |
| generic JSON interfaces, and obfuscate the sensitive values as we never need to | |
| print them anyway. | |
| ##### Iterating through changes | |
| The `differ` package will recursively create child `Change` objects for the | |
| complex objects. | |
| There are two key subtypes of a `Change`: `SliceChange` and `MapChange`. | |
| `SliceChange` values are used by list, set, and tuple attributes. `MapChange` | |
| values are used by map and object attributes, and blocks. For what it is worth | |
| outputs and dynamic types can end up using both, but they're kind of special as | |
| the processing for dynamic types works out the type from the JSON struct and | |
| then just passes it into the relevant real types for actual processing. | |
| The two subtypes implement `GetChild` functions that retrieve a child change | |
| for a relevant index (`int` for slice, `string` for map). These functions build | |
| an entirely populated `Change` object, and the package will then recursively | |
| compute the change for the child (and all other children). When a complex change | |
| has all the children changes, it then passes that into the relevant complex | |
| diff type. | |
| #### The `computed` package | |
| A computed `Diff` should contain all the relevant information it needs to render | |
| itself. | |
| The `Diff` itself contains the action (eg. `Create`, `Delete`, `Update`), and | |
| whether this change is causing the overall resource to be replaced (read from | |
| the `ReplacePaths` field discussed in the previous section). The actual content | |
| of the diffs is passed directly into the internal renderer field. The internal | |
| renderer is then an implementation that knows the actual content of the changes | |
| and what they represent. | |
| For example to instantiate a diff resulting from updating a list of | |
| primitives: | |
| ```go | |
| listDiff := computed.NewDiff(renderers.List([]computed.Diff{ | |
| computed.NewDiff(renderers.Primitive(0.0, 0.0, cty.Number), plans.NoOp, false), | |
| computed.NewDiff(renderers.Primitive(1.0, nil, cty.Number), plans.Delete, false), | |
| computed.NewDiff(renderers.Primitive(nil, 4.0, cty.Number), plans.Create, false), | |
| computed.NewDiff(renderers.Primitive(2.0, 2.0, cty.Number), plans.NoOp, false) | |
| }, plans.Update, false)) | |
| ``` | |
| ##### The `RenderHuman` function | |
| Currently, there is only one way to render a change, and it is implemented via | |
| the `RenderHuman` function. In the future, there may be additional rendering | |
| capabilities, but for now the `RenderHuman` function just passes the call | |
| directly onto the internal renderer. | |
| Rendering the above diff with: `listDiff.RenderHuman(0, RenderOpts{})` would | |
| produce: | |
| ```text | |
| [ | |
| 0, | |
| - 1 -> null, | |
| + 4, | |
| 2, | |
| ] | |
| ``` | |
| Note, the render function itself doesn't print out metadata about its own change | |
| (eg. there's no `~` symbol in front of the opening bracket). The expectation is | |
| that parent changes control how child changes are rendered, so are responsible | |
| for deciding on their opening indentation, whether they have a key (as in maps, | |
| objects, and blocks), or how the action symbol is displayed. | |
| In the above example, the primitive renderer would print out only `1 -> null` | |
| while the surrounding list renderer is providing the indentation, the symbol and | |
| the line ending commas. | |
| ##### Implementing new diff types | |
| To implement a new diff type, you must implement the internal Renderer | |
| functionality. To do this you create a new implementation of the | |
| `computed.DiffRenderer`, make sure it accepts all the data you need, and | |
| implement the `RenderHuman` function (and any other additional render functions | |
| that may exist). | |
| Some changes publish warnings that should be displayed alongside them. | |
| If your new change has no warnings you can use the `NoWarningsRenderer` to avoid | |
| implementing the additional `Warnings` function. | |
| If/when new Renderer types are implemented, additional `Render` like functions | |
| will be added. You should implement all of these with your new change type. | |
| ##### Implementing new renderer types for changes | |
| As of January 2023, there is only a single type of renderer (the human-readable) | |
| renderer. As such, the `Diff` structure provides a single `RenderHuman` | |
| function. | |
| To implement a new renderer: | |
| 1. Add a new render function onto the internal `DiffRenderer` interface. | |
| 2. Add a new render function onto the `Diff` struct that passes the call onto | |
| the internal renderer. | |
| 3. Implement the new function on all the existing internal interfaces. | |
| Since each internal renderer contains all the information it needs to provide | |
| change information about itself, your new Render function should pass in | |
| anything it needs. | |
| ### New types of Renderer | |
| In the future, we may wish to add in different kinds of renderer, such as a | |
| compact renderer, or an interactive renderer. To do this, you'll need to modify | |
| the Renderer struct or create a new type of Renderer. | |
| The logic around creating the `Diff` structures will be shared (ie. calling | |
| into the differ package should be consistent across renderers). But when it | |
| comes to rendering the changes, I'd expect the `Diff` structures to implement | |
| additional functions that allow them to internally organise the data as required | |
| and return a relevant object. For the existing human-readable renderer that is | |
| simply a string, but for a future interactive renderer it might be a model from | |
| an MVC pattern. |