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.