Pulumi onboarding

This topic describes how to onboard existing resources into a Pulumi project.

Note: The onboarding. enhancement is available from version 26.4 and later

Overview

The Pulumi onboarding process is designed for a clean stack. It discovers existing resources from a Connect server and imports them into your Pulumi project, generating the corresponding infrastructure code automatically.

For a video illustrating Pulumi onboarding with AI-assisted code refactoring, see the Connect video gallery.

The steps for onboarding resources are described below:

Step 1: Locate the onboarding tools

Step 2: Set required environment variables

Step 3: Run the onboarding scripts

Prerequisites

This section describes the prerequisites for running the onboarding scripts.

To prepare your system for the onboarding scripts:

  1. Install the Connect Pulumi provider plugin. For details, see Pulumi installation and upgrade.

  2. Set up your Pulumi project. Use the workflow that matches your project type:

Step 1: Locate the onboarding tools

The onboarding tools are installed in the Utilities directory of your Connect installation:

  • mfcOnboardingPython.sh for Python projects.

  • mfcOnboardingYaml.sh for YAML projects.

  • mfcResourceDiscovery.sh for discovering existing resource IDs from the Connect server.

Run these tools directly from the installation directory and pass the path to your Pulumi project as an argument. Do not copy the tools into your Pulumi project. The scripts automatically locate your Connect installation.

Step 2: Set required environment variables

All onboarding scripts require the following environment variables:

Variable Required Description
MFC_CONNECT_URL Yes URL of your Connect server, for example http://localhost:8081.
MFC_ACCESS_TOKEN Yes Your Connect access token. For details, see Manage access tokens.
PULUMI_STACK Yes Name of the Pulumi stack to target.
PULUMI_CONFIG_PASSPHRASE No Passphrase used to encrypt and decrypt stack secrets when using a local backend (pulumi login --local). Set this variable to run non-interactively. If omitted, Pulumi prompts for the passphrase during the run.

Step 3: Run the onboarding scripts

Run the script that matches your project type and pass your Pulumi project directory as an argument.

For YAML projects:

Copy code
MFC_CONNECT_URL=<connect-url> MFC_ACCESS_TOKEN=<your-access-token> PULUMI_STACK=<your-stack> /opt/MicroFocusConnect/Utilities/mfcOnboardingYaml.sh <your-pulumi-project-dir>

For Python projects:

Copy code
MFC_CONNECT_URL=<connect-url> MFC_ACCESS_TOKEN=<your-access-token> PULUMI_STACK=<your-stack> /opt/MicroFocusConnect/Utilities/mfcOnboardingPython.sh <your-pulumi-project-dir>

If your stack uses secrets, include PULUMI_CONFIG_PASSPHRASE. Otherwise, Pulumi prompts for it:

Copy code
MFC_CONNECT_URL=<connect-url> MFC_ACCESS_TOKEN=<your-access-token> PULUMI_STACK=<your-stack> PULUMI_CONFIG_PASSPHRASE=<your-passphrase> /opt/MicroFocusConnect/Utilities/mfcOnboardingPython.sh <your-pulumi-project-dir>

Note: Place your Pulumi project under Git version control immediately after onboarding, and before making additional changes. This is important especially before using an AI assistant to refactor generated code into strongly typed objects. This creates a known checkpoint so you can review each change with git diff, and revert if a later manual or AI-assisted edit breaks behavior or drifts from the Connect configuration.

Refactor generated Python code

By default, pulumi import generates Python code using dictionary-style arguments for nested properties. While this works, it does not provide the same IDE autocompletion, inline help, and type safety as strongly typed inputs.

We recommend converting generated code to use the strongly typed *Args classes provided by the Connect Python SDK. For available resource types and related classes, see Pulumi integration.

You can use an AI coding assistant to perform this conversion automatically with the following prompt.

AI refactor prompt

Copy code
Refactor this Python Pulumi project.

CONTEXT
- Entrypoint: __main__.py, currently builds all resources using plain dicts.
- Provider SDK: pulumi_mfcpulumiprovider, located at
  ./python-sdk/python/pulumi_mfcpulumiprovider/.
  Read _inputs.py (nested input types) and the per-resource modules (top-level
  *Args) and treat them as the ONLY source of truth for class names and fields.

TASK

1. Typed args: replace every dict with the matching typed *Args class from the
   SDK -- top-level resource args and all nested types (field mappings,
   conditions, calculated values, filters, maps, etc.).
   - EXCEPTION: fields declared as free-form string maps (Mapping[str, str]),
     e.g. datasource_properties and secret_datasource_properties, stay as
     plain dicts.
   - If no matching *Args class exists, LEAVE THE DICT AS-IS and list it in the
     final summary. Never invent class names or field names.

2. Convert inline and one-to-one. No shared helpers, loops, constants, or
   reusable configs -- one dict in, one typed object out, in the same position.

3. File layout -- one resource per file:
     resources/__init__.py
     resources/datasources/__init__.py
     resources/datasources/<variable_name>.py
     resources/usermaps/__init__.py
     resources/usermaps/<variable_name>.py
     resources/logicalgroups/__init__.py
     resources/logicalgroups/<variable_name>.py
     resources/connections/__init__.py
     resources/connections/<variable_name>.py
   File name = the Python variable currently holding the resource, verbatim
   (alm_datasource -> resources/datasources/alm_datasource.py).
   Each file constructs exactly one resource and assigns it to a module-level
   variable with the SAME name as in the original __main__.py.

4. Explicit re-exports -- package __init__.py files are the public surface.
   No dynamic discovery: no pkgutil, no importlib, no globals() manipulation.
   Every resource is listed by hand so the inventory is greppable and reviewable.

   Each subpackage __init__.py imports its own resource modules, alphabetically,
   and declares __all__. Example, resources/datasources/__init__.py:

       from resources.datasources.alm_datasource import alm_datasource
       from resources.datasources.crm_datasource import crm_datasource

       __all__ = ["alm_datasource", "crm_datasource"]

   resources/__init__.py aggregates the four subpackages and builds __all__ by
   concatenating theirs -- do not re-list individual names at the root:

       from resources import datasources, logicalgroups, usermaps, connections
       from resources.datasources import *
       from resources.logicalgroups import *
       from resources.usermaps import *
       from resources.connections import *

       __all__ = [
           *datasources.__all__,
           *logicalgroups.__all__,
           *usermaps.__all__,
           *connections.__all__,
       ]

   Importing a resource module is what registers the resource with Pulumi -- the
   constructor runs at module top level. A module not reachable from
   resources/__init__.py is a resource that does not exist in the stack.
   A resource is CONSTRUCTED once, in its own module; the imports above only
   bind names to that same object and do not create duplicates.

5. __main__.py becomes imports + exports ONLY -- the one place that imports from
   the package rather than from modules:

       import pulumi
       from resources import *          # noqa: F401,F403

INVARIANTS (must hold exactly)
- Every Pulumi logical name (1st constructor arg) is byte-identical.
- Every property value is identical; execution_order values unchanged;
  list order in mappings/conditions/filters unchanged.
- No resources added, removed, or renamed.
- No new dependencies between resources.
- Every resource in the original __main__.py has exactly one file and exactly
  one entry in its subpackage __all__.

Manually import resources

This section describes how to handle advanced cases not covered by the onboarding scripts.

To import resources manually:

  1. Generate the resource ID file:

    Copy code
    mfcResourceDiscovery.sh -discoverResources --connect-url <connect-url> --access-token <your-access-token> --out resources.json
  2. Import with Pulumi:

    Copy code
    # Python projects
    pulumi --stack <your-stack> import --file resources.json --out __main__.py --skip-preview

    # YAML projects
    pulumi --stack <your-stack> import --file resources.json --out imported.yaml --skip-preview

    Note: For YAML projects, merge imported.yaml into Pulumi.yaml under resources.

See also