Skip to main content

REUSE A MODEL WITH THE FACTORY PATTERN

This guide shows you how to define a single model and call it multiple times with different inputs to produce distinct resource instances. Use this when you have several resources of the same type — subnets, security groups, DNS records — that share the same schema but differ in their parameters.

Prerequisites

  • Swamp installed and on your PATH
  • An initialised repo (swamp repo init)
  • A model type installed for the resources you want to create
  • Familiarity with model definitions and workflow definitions

Decide whether the factory pattern fits

The factory pattern applies when multiple resources share a type and method signatures but differ only in their parameter values.

Situation Approach
4 subnets with different CIDRs and AZs Factory pattern (1 model)
3 EIPs with different tags Factory pattern (1 model)
A VPC and a subnet (different resource types) Separate models
Resources with different method signatures Separate models
Long-running methods that must run in parallel Factory pattern (separate locks)

Each model holds an exclusive per-model lock for the duration of a method execution. If two workflow steps target the same model, the second waits for the first to release the lock. The factory pattern sidesteps this: each call with a different instanceName produces a separate data instance, so concurrent steps do not contend.

Add an inputs schema to the model

Create the model, then add an inputs section with an instanceName property and whatever parameters vary between instances:

swamp model create @user/aws-subnet prod-subnet --json

Edit the model definition at the returned path:

name: prod-subnet
version: 1
tags: {}
inputs:
  properties:
    instanceName:
      type: string
      description: Unique name for this subnet instance
    cidrBlock:
      type: string
      description: CIDR block for the subnet
    availabilityZone:
      type: string
      description: AWS availability zone
  required: ["instanceName", "cidrBlock", "availabilityZone"]
globalArguments:
  name: ${{ inputs.instanceName }}
  VpcId: ${{ data.latest("prod-vpc", "main").attributes.VpcId }}
  CidrBlock: ${{ inputs.cidrBlock }}
  AvailabilityZone: ${{ inputs.availabilityZone }}
  Tags:
    - Key: Name
      Value: ${{ inputs.instanceName }}
methods:
  create:
    arguments: {}
  delete:
    arguments: {}

The name: ${{ inputs.instanceName }} line is what makes the pattern work. It sets the data instance name, so each call with a different instanceName produces a separately addressable data artifact.

Validate the model:

swamp model validate prod-subnet --json

Call the model multiple times from a workflow

Create a workflow with one step per instance. Steps within a job run in parallel:

name: create-subnets
version: 1
jobs:
  - name: create-subnets
    steps:
      - name: create-public-a
        task:
          type: model_method
          modelIdOrName: prod-subnet
          methodName: create
          inputs:
            instanceName: public-a
            cidrBlock: "10.0.1.0/24"
            availabilityZone: us-east-1a
      - name: create-public-b
        task:
          type: model_method
          modelIdOrName: prod-subnet
          methodName: create
          inputs:
            instanceName: public-b
            cidrBlock: "10.0.2.0/24"
            availabilityZone: us-east-1b
      - name: create-private-a
        task:
          type: model_method
          modelIdOrName: prod-subnet
          methodName: create
          inputs:
            instanceName: private-a
            cidrBlock: "10.0.3.0/24"
            availabilityZone: us-east-1a
      - name: create-private-b
        task:
          type: model_method
          modelIdOrName: prod-subnet
          methodName: create
          inputs:
            instanceName: private-b
            cidrBlock: "10.0.4.0/24"
            availabilityZone: us-east-1b

Run the workflow:

swamp workflow run create-subnets

All four steps execute concurrently. Each produces a distinct data instance (public-a, public-b, private-a, private-b) under the prod-subnet model.

Reference factory instance data downstream

Use data.latest() with the model name and instance name to read a specific factory output in a downstream model or workflow step:

globalArguments:
  subnetId: ${{ data.latest("prod-subnet", "public-a").attributes.SubnetId }}

To browse all instances produced by a factory model:

swamp data query 'modelName == "prod-subnet"' \
  --select '{"name": name, "subnet": attributes.SubnetId, "az": attributes.AvailabilityZone}'

Write delete steps for factory models

Delete steps must provide instanceName so the system knows which data instance to target. Other inputs are only required if the delete method's implementation accesses those globalArguments at runtime — unresolved expressions for inputs that are not provided are silently skipped.

name: delete-subnets
version: 1
jobs:
  - name: delete-subnets
    steps:
      - name: delete-public-a
        task:
          type: model_method
          modelIdOrName: prod-subnet
          methodName: delete
          inputs:
            instanceName: public-a
            identifier: ${{ data.latest("prod-subnet", "public-a").attributes.SubnetId }}
      - name: delete-public-b
        task:
          type: model_method
          modelIdOrName: prod-subnet
          methodName: delete
          inputs:
            instanceName: public-b
            identifier: ${{ data.latest("prod-subnet", "public-b").attributes.SubnetId }}

If the delete steps depend on other resources being removed first, use job-level dependsOn to enforce reverse dependency order — see dependsOn.

Reference

  • Model definitions — full schema for inputs, globalArguments, and methods
  • CEL expressionsdata.latest(), data.query(), and the inputs.* namespace
  • Workflows — job and step ordering, dependsOn, and parallel execution