Persisting Data Between Bundle Actions

Persisting Data Between Bundle Actions

When you create a bundle, you often need to preserve data generated during one action (like install) for use in subsequent actions (like upgrade or uninstall). For example, you might create a virtual machine during installation and need its ID to delete it during uninstallation.

Porter provides two mechanisms to persist data between bundle actions:

  1. Parameter Sources - Reference outputs from previous actions as parameter values
  2. State Files - Automatically persist and restore files between bundle runs

This guide helps you understand when to use each approach and how to implement them.

Quick Comparison

Feature Parameter Sources State Files
Access in templates ✅ Yes (bundle.parameters.X) ❌ No (file-only access)
User can override ✅ Yes ❌ No
Transparency ✅ Visible in parameter sets ⚠️ Hidden from users
Best for IDs, connection strings, config values Tool state files (terraform.tfstate, kubeconfig)
Data types string, number, boolean, file, object file only

Approach 1: Parameter Sources

Parameter sources allow you to capture an output from one action and automatically inject it as a parameter in subsequent actions. This is ideal when you need to use the value in templates or allow users to override the value if needed.

How It Works

  1. During install, a step captures an output using jsonPath or regex from the command output
  2. You declare a bundle-level output to make this value available
  3. You define a parameter with a source field that references the output
  4. During upgrade or uninstall, Porter automatically provides the output’s last value
  5. You reference the value using ${bundle.parameters.PARAM_NAME} in your steps
  6. If the output doesn’t exist yet (first install), users can provide it via the parameter

Example: Virtual Machine ID

Here’s a complete example showing how to remember a VM instance ID:

schemaVersion: 1.0.1
name: my-vm-bundle
version: 0.1.0

mixins:
  - exec

# Declare the bundle-level output
outputs:
  - name: instance-id
    type: string
    description: The ID of the created virtual machine
    applyTo:
      - install

# Define a parameter that sources its value from the output
parameters:
  - name: instance-id
    type: string
    description: The virtual machine instance ID
    # This parameter only applies to upgrade and uninstall
    # During install, we generate it; we don't need it as input
    applyTo:
      - upgrade
      - uninstall
    source:
      output: instance-id

install:
  - exec:
      description: "Create virtual machine"
      command: create-vm
      arguments:
        - --name
        - myvm
        - --format
        - json
      # Capture the output from the command
      outputs:
        - name: instance-id
          jsonPath: "$.id"

upgrade:
  - exec:
      description: "Upgrade virtual machine"
      command: echo
      arguments:
        - "Upgrading VM: ${bundle.parameters.instance-id}"

uninstall:
  - exec:
      description: "Delete virtual machine"
      command: delete-vm
      arguments:
        - --id
        - "${bundle.parameters.instance-id}"

Using Parameter Values in Templates

One major advantage of parameter sources is that you can access them in templates:

upgrade:
  - exec:
      description: "Upgrade VM ${bundle.parameters.instance-id}"
      command: upgrade-vm
      arguments:
        - --id
        - "${bundle.parameters.instance-id}"

Sourcing From Dependencies

You can also source parameters from outputs generated by bundle dependencies:

parameters:
  - name: connection-string
    type: string
    source:
      dependency: mysql
      output: connstr

User Overrides

Users can override parameter source values if needed:

porter upgrade myvm --param instance-id=vm-12345

This is useful for recovery scenarios or when manually managing resources.

Approach 2: State Files

The state feature allows Porter to automatically persist files between bundle runs. This is ideal for tool-specific state files (like terraform.tfstate) or configuration files that don’t need to be exposed as parameters.

How It Works

  1. You declare which file paths should be persisted in the state section
  2. When a bundle completes, Porter saves any files at those paths
  3. On the next bundle run, Porter automatically restores those files
  4. Your bundle can read/write these files directly from the filesystem

Important Limitation

State files cannot be accessed via templates or environment variables. You cannot use bundle.parameters.X or $PARAMETER_NAME to access state data. The files are simply restored to the filesystem for your tools to read directly.

Example: Terraform State

Here’s a complete example using Terraform state files:

schemaVersion: 1.0.1
name: terraform-bundle
version: 0.1.0

mixins:
  - terraform

# Declare which files should be persisted
state:
  - name: tfstate
    path: terraform/terraform.tfstate
    description: Terraform state file
  - name: tfvars
    path: terraform/terraform.tfvars.json
    description: Terraform variables

install:
  - terraform:
      description: "Create infrastructure"
      input: false
      vars:
        location: eastus

upgrade:
  - terraform:
      description: "Update infrastructure"
      input: false
      # The terraform.tfstate file is automatically restored here
      # Terraform reads it directly from the filesystem

uninstall:
  - terraform:
      description: "Destroy infrastructure"
      input: false
      # The terraform.tfstate file is automatically restored here
      # Terraform needs it to know what to destroy

State File Paths

Relative paths in the state section are relative to the bundle directory (/cnab/app):

state:
  - name: kubeconfig
    path: .kube/config  # Resolves to /cnab/app/.kube/config
  - name: ssh-key
    path: /root/.ssh/id_rsa  # Absolute path

When State Files Are Saved

Porter saves state files when:

  • The bundle action completes successfully
  • The file exists at the specified path

Porter restores state files:

  • Before each bundle action runs (except the first install)

Choosing the Right Approach

Use this decision guide to choose the appropriate mechanism:

Use Parameter Sources When:

  • ✅ You need to use the value in templates (${bundle.parameters.X})
  • ✅ The data is a string, number, boolean, or small object/file
  • ✅ You want users to be able to see or override the value
  • ✅ The data represents an identifier, connection string, or configuration value

Examples: VM instance IDs, database connection strings, resource names, API endpoints, account IDs

Use State Files When:

  • ✅ A tool generates its own state file format (Terraform, Helm, etc.)
  • ✅ The file is large or binary
  • ✅ You don’t need to access the data in templates or as variables
  • ✅ You want to hide implementation details from users
  • ✅ The tool reads the file directly from a specific path

Examples: terraform.tfstate, kubeconfig, .terraform.lock.hcl, SSH keys, certificate files

Combining Both Approaches

You can use both mechanisms together in the same bundle. For example, you might:

  1. Use state for Terraform’s .tfstate file
  2. Use parameter sources to capture Terraform outputs (like resource IDs) for use in other commands
state:
  - name: tfstate
    path: terraform/terraform.tfstate

outputs:
  - name: cluster-id
    type: string

parameters:
  - name: cluster-id
    type: string
    source:
      output: cluster-id
    applyTo:
      - upgrade
      - uninstall

install:
  - terraform:
      description: "Create cluster"
  - exec:
      description: "Capture cluster ID"
      command: bash
      flags:
        c: |
          # Extract output from Terraform and save as Porter output
          CLUSTER_ID=$(terraform output -raw cluster_id)
          mkdir -p /cnab/app/porter/outputs
          echo -n "$CLUSTER_ID" > /cnab/app/porter/outputs/cluster-id          

upgrade:
  - exec:
      description: "Configure cluster ${bundle.parameters.cluster-id}"
      command: configure-cluster
      arguments:
        - "${bundle.parameters.cluster-id}"
  - terraform:
      description: "Update infrastructure"

Working with File Parameters

When using parameter sources with type: file, the file content is persisted and restored:

outputs:
  - name: ssh-key
    type: file
    path: /cnab/app/ssh-key.pem

parameters:
  - name: ssh-key
    type: file
    path: /cnab/app/ssh-key.pem
    source:
      output: ssh-key
    applyTo:
      - upgrade
      - uninstall

This gives you template/variable access plus file persistence. However, for purely file-based workflows where you don’t need template access, state is simpler.

Troubleshooting

Parameter source value is empty

If your parameter source isn’t working:

  1. Verify the output was actually created during the previous action
  2. Check that the output file path is correct: /cnab/app/porter/outputs/<output-name>
  3. Ensure the output definition exists in your manifest
  4. Review the bundle logs: porter installation logs show <installation-name>

State file not restored

If your state file isn’t being restored:

  1. Verify the file was created during the previous action at the exact path specified
  2. Check that the path in the state section matches exactly (including absolute vs. relative)
  3. Ensure the previous action completed successfully
  4. State files are only restored on subsequent runs, not during the first install

Cannot access state data in templates

This is expected behavior. State files cannot be accessed via bundle.parameters.X or environment variables. If you need template/variable access, use parameter sources instead.

See Also