Skip to content

Latest commit

 

History

History
435 lines (321 loc) · 16.6 KB

operators.md

File metadata and controls

435 lines (321 loc) · 16.6 KB

What are all the Spruce operators?

Additionally, there are operators that are specific to merging arrays. For more detail see the array merging documentation:

  • (( append )) - Adds the data to the end of the corresponding array in the root document.
  • (( prepend )) - Inserts the data at the beginning of the corresponding array in the root document.
  • (( insert )) - Inserts the data before or after a specified index, or object.
  • (( merge )) - Merges the data on top of an existing array based on a common key. This requires each element to be an object, all with the common key used for merging.
  • (( inline )) - Merges the data on top of an existing array, based on the indices of the array.
  • (( replace )) - Removes the existing array, and replaces it with the new one.
  • (( delete )) - Deletes data at a specific index, or objects identified by the value of a specified key.

Please note: You cannot use the convenient Spruce path syntax (path.to.your.property) in case one of the elements (e.g. named entry element) contains a dot as part of the actual key. The dot is in line with the YAML syntax, however it cannot be used since Spruce uses it as a separator internally. This also applies to operators, where it is not immediately obvious that a path is used like with the (( prune )) operator. As a workaround, depending on the actual use-case, it is often possible to replace the Spruce operator with a equivalent go-patch operator file.

Operator Arguments

Most spruce operators have arguments. There are three basic types to the arguments - literal values (strings/numbers/booleans), references (paths defining a datastructure in the root document), and environment variables. Arguments can also make use of a logical-or (||) to failover to other values. See our notes on environment variables and default values for more information on environment variables and the logical-or.

(( calc ))

Usage: (( calc EXPRESSION ))

The (( calc )) operator allows you to perform mathematical operations inside your YAML. You can reference other values in the data structure, as well as literal numerics. If you have more sophisticated calculations in mind, you can use these built-in functions inside your expressions: max, min, mod, pow, sqrt, floor, and ceil.

The expression passed to (( calc )) must however be a quoted string.

Example

(( cartesian-product ))

Usage: (( cartesian-product LITERAL|REFERENCE ... ))

The (( cartesian-product )) operator accepts an arbitrary number of arguments, outputting a list made up of the product of all its inputs. If the input is a literal, it is treated as a one-element set. If it is a reference, it must either be reference a literal type, or an array filled with only literal types.

Example

(( concat ))

Usage: (( concat LITERAL|REFERENCE ... ))

The (( concat )) operator has a role that may shock you. It concatenates values together into a string. You can pass it any number of arguments, literal or reference, as long as the reference is not an array/map.

Example

(( defer ))

Usage: (( defer ... ))

Ever wanted to use spruce to generate something with a spruce operator in it, or perhaps something that looks like a spruce operator, like a CredHub value? Defer can be used for that. When evaluated, it outputs the contents of the operator, minus the initial defer operator.

Example

(( empty ))

Usage: (( empty hash|map|array|list|string ))

This operator empties out the contents of the parent. Due to spruce's merging semantics, it can be a little tricky sometimes to overwrite an array with an empty aray, or a map with an empty map. Use this as a handy shortcut.

Example

(( file ))

Usage: (( file LITERAL|REFERENCE ))

Do you have a really long multi-line text file that you want to embed in your YAML? Perhaps it's an SSL cert, or a base64 encoded image? Of course you do! Enter the (( file )) operator. Instead of having to worry about the proper YAML indenting

  • multiline semantics when pasting your cert into your file, you can stick the contents in a file by itself, and provide a path to that file (absolute, or relative to where spruce is executed). The path provided can even be a reference to another part of the data structure, which might concat some values together to dynamically generate where the file will be.

Example:

$ cat <<EOF ca-prod.crt
-----BEGIN CERTIFICATE-----
cert-data
will-go
in-here
-----END CERTIFICATE-----
EOF

$ cat <<EOF > config.yml
files:
  ca: (( concat "ca-" environment ".crt")

environment: prod

load_ca_from_file_ref: (( file files.ca ))
load_ca_from_file: (( file "ca-prod.crt" ))
EOF

$ spruce merge config.yml
environment: prod
files:
  ca: ca-prod.crt
load_ca_from_file: |
  -----BEGIN CERTIFICATE-----
  cert-data
  will-go
  in-here
  -----END CERTIFICATE-----
load_ca_from_file_ref: |
  -----BEGIN CERTIFICATE-----
  cert-data
  will-go
  in-here
  -----END CERTIFICATE-----

(( grab ))

Usage: (( grab LITERAL|REFERENCE ))

Trying to DRY up your config file, so that you don't have to change the same property 15 times? (( grab )) can help! It pulls the contents of whatever reference you give it, and stores them as the value of the key it's assigned to. You can also pass literal values to (( grab )), mostly so you can have default values if desired, but it's entirely possible to simply username: (( grab "admin" )). I have no idea why you might want to do that though, since it's much more typing than username: admin...

Example

(( inject ))

Usage: (( inject REFERENCE ))

This works a bit like (( grab )), except that the contents of what it retrieves are placed at the same level as the key which called the (( inject )) operator. The key containing the (( inject )) operator is then removed. In many cases, you probably want to use (( grab )) instead, as it is much more intuitive and easy to troubleshoot. However, if you want to inject a bunch of data, but override parts of the data being injected on a case by case basis, this operator will be helpful.

Example

(( join ))

Usage: (( join LITERAL|REFERENCE ... ))

Sure, (( concat )) is great, but what if I have a list of strings that I want as a single line? Like a users list, authorities, or similar. Do I have to concat that piece by piece? Nope, you can use join to concatenate a list into one entry.

Example

(( keys ))

Usage: (( keys REFERENCE ))

Do you need to generate a list containing all the keys of part of your datastructure? Enter (( keys )). Pass it a reference to part of your datastructure that is a hash/map, and it will return an array of all of the keys inside it.

Example

(( load ))

Usage: (( load LITERAL|REFERENCE ))

Similar to the (( file )) operator, this operator takes the content of another file to insert it into the main tree structure. However, (( load )) does not use the content as-is, but expects to parse valid YAML (or JSON). Like the (( file )) operator, you do not have to worry about indentation as Spruce will cover that for you.

Note: Spruce will not evaluate any Spruce operators that might be in the file that is loaded, because any path used by grab or similar would be ambigious in respect to the document root to be used. If you need to load a file with Spruce operators in it, you have to run a pre-processing step to evaluate the file first with another Spruce run.

Example:

$ cat <<EOF >list.yml
---
- one
- two
...
EOF

$ cat <<EOF >config.yml
---
list: (( load "list.yml" ))
...
EOF

$ spruce merge config.yml
list:
- one
- two

(( negate ))

Usage: (( negate LITERAL|REFERENCE ))

This operator takes a bool and negates it: this allows you to DRY up your config file if you have properties which are inversely related. As with (( grab )), you can specify literal values (but why would you?).

(( param ))

Usage: (( param LITERAL ))

Sometimes, you may want to start with a good starting-point template, but require other YAML files to provide certain values. Parameters to the rescue!

Example

(( prune ))

Usage: (( prune ))

If you have a need to force the cleanup of data from the final output, but don't want to rely on the end-user always specifying the necessary --prune flags, you can make use (( prune ))s to clear out the bloated data. Please note: Both the CLI flag as well as the operator will not work if one path element contains a dot as the actual name, e.g. 10.local: (( prune )) will not work. You have to use the go-patch equivalent instruction instead.

Example

(( shuffle ))

Usage: (( shuffle LITERAL | REFERENCE [other [args]] ))

This operator flattens all of its arguments once, into a single list, and then shuffles them randomly. This is useful for switching the order of availability zones for a first approximation for AZ load balancing.

(( sort ))

Usage: (( sort [by KEY] ))

This operator enables sorting simple lists like lists of strings, or numbers as well as lists of maps that follow the known contract of containing an identifying entry each, like name, key, or id. As always, name is the default for named-entry lists if no sort key is specified. The (( sort )) operator works similar to the (( prune )) operator as more of an annotation than an actual operator that immediately performs an action: The path at which the sort operator is used will be marked for evaluation in the post-processing phase. That means the sorting will only take place once after all files are merged.

The (( sort )) operator will fail in case of:

  • lists that do not contain strings, numbers or maps (for example lists of lists)
  • inhomogeneous types (mixing strings and numbers)
  • named-entry maps that do not have a single identifying entry

(( static_ips ))

Usage: (( static_ips INTEGER ... ))

Even with BOSH Links, and Cloud Config, it's still occsionally necessary to have static IPs in your manifest. This operator makes the IP calculation fairly easy, and should be familiar to anyone who has used spiff to do this in the past. You give (( static_ips )) a list of indexes. spruce will look through the root document, and find the relevant IP ranges for static IPs for the network of a VM, and pull in as many as are needed based on the instance count. It even supports BOSH AZs fairly well.

Example

(( stringify ))

Usage: (( stringify REFERENCE ))

There are use cases, especially with Kubernetes resources like config maps, where one needs to place a piece of a YAML structure as a multiline string. If you happen to have the actual data already in your current file, you can avoid duplicating the content by simply referencing the part of the YAML and the (( stringify ... )) operator correctly marshals the data into your tree structure.

Example

(( ips ))

Usage: (( ips IP_OR_CIDR INDEX [COUNT] ))

Sometimes you need to reference IP addresses that aren't managed by BOSH, so static_ips aren't much help. ips has you covered. It lets you perform simple addition on IP addresses. Pass it an IP and an index, and it will add INDEX to the IP. If you pass a CIDR instead of an IP, the calculation starts from the start of the network. I.e. (( ips "10.0.0.10/24" 2 )) will yield "10.0.0.2". (( ips "10.0.0.10" 2 )) will yield "10.0.0.12". If you also specify COUNT, you get a list of IP's instead. A negative index and an IP will count backwards. A negative index and a CIDR will start from the end of the given network.

Example

(( vault ))

Usage: (( vault LITERAL|REFERENCE ... ))

Have sensitive material in your manifests that you don't want stored in the repo that your configs are in? What do you mean 'No'? Everybody does. The (( vault )) operator lets you store that data in Vault, and spruce will retrieve it at merge time. Simply specify a vault path in the secret backend as the argument, and away it goes. If needed, you can pull in references to concatenate with info, resulting in an easy way to dynamically look up Vault paths.

Example

(( awsparam ))

Usage: (( awsparam LITERAL|REFERENCE ... ))

The (( awsparam )) operator will let you pull a value from AWS SSM Parameter Store at merge time. Specify the parameter store path in one or more arguments that will be joined to form the whole path and spruce will fetch it for you. Optionally you may pass ?key=... to extract a sub-key where the parameter store value is valid JSON or YAML.

Example

(( awssecret ))

Usage: (( awssecret LITERAL|REFERENCE ... ))

The (( awssecret )) operator will let you pull a value from AWS Secrets Manager at merge time. Specify the secret name or ARN in one or more arguments that will be joined to form the whole identifier and spruce will fetch it for you. Optionally you may specify a sub-key to extract with ?key=... where the secret value is valid JSON or YAML and either a stage or version with ?stage=... / ?version=... respectively to fetch a specific stage or version.

You may combine these additional arguments with &; for example secret/name?key=subkey&stage=AWSPREVIOUS.

Example

(( base64 ))

Usage: (( base64 LITERAL|REFERENCE ... ))

When working with configs that require lots of base64 encoding, it can be quite tedious to manually encode and decode the values when trying to inspect or update the existing configuration. The ((base64)) operator allows for merge time base64 encoding of string literals specified directly, or by reference.

Example

(( base64-decode ))

Usage: (( base64-decode LITERAL|REFERENCE ... ))

The (( base64-decode )) operator allows for merge-time decoding of base64-encoded string literals specified directly, or by reference.

Example:

encoded_string_1: Zm9v
decoded_string_1: (( base64-decode encoded_string_1 ))
decoded_string_2: (( base64-decode "YmFy" ))
decoded_properties_file_contents: (( concat "fookey=" decoded_string_1 "\nbarkey=" decoded_string_2 "\n" ))
encoded_properties_file_contents: (( base64 decoded_properties_file_contents ))