Tutorial: Building an Integration with the CLI¶
This tutorial walks through building a small OAA integration entirely
with oaa CLI commands — scaffolding the project, inspecting what the
framework offers, previewing and validating data, and finally checking
the result into a Veza tenant — without writing a full sync loop by hand
at any point along the way.
By the end, you’ll have a working integration skeleton for a fictional “BongaWonga” source, validated end-to-end against mocked data.
Prerequisites¶
oaa-runnerinstalled (uvx oaa versionshould print a version number)A terminal in an empty working directory
Step 1: Scaffold the project¶
Start with init, giving it a name and two source names:
oaa init bongawonga --source_names=employees,departments
This creates:
bongawonga/
README.md
config.yaml
sample.env
requirements.txt
modules/
bongawonga.py
bongawonga_hooks.py
bongawonga_mocks.py
cd into bongawonga/ for the rest of this tutorial.
Step 2: See what the framework offers¶
Before writing any mapping logic, look at what’s available to you. List the entity types you can map sources to:
oaa list_entity_types
Say your employees source should map to LocalUser. Check exactly
which fields that requires:
oaa show_entity --entity_type=LocalUser
And check which transform functions you can reference from
field_mapping:
oaa list_transforms
Step 3: Validate the config structure¶
Even before any source code is filled in, confirm the generated
config.yaml is structurally valid:
oaa validate_config --config_file=./config.yaml
And confirm every ${VAR} your config references (check sample.env
for the expected names) is set in your environment:
cp sample.env .env
# edit .env with real values, then export it, e.g.:
export $(grep -v '^#' .env | xargs)
oaa check_env --config_file=./config.yaml
Step 4: Preview data before mapping¶
With modules/bongawonga.py’s fetch() filled in (or left as the
generated mock-backed stub), preview what it returns for the employees
source:
oaa preview --source_name=employees --limit=5
Now edit config.yaml’s field_mapping for employees to map the
fields you saw, and preview again — this time after mapping:
oaa preview_mapped --source_name=employees --limit=5
If a mapped field doesn’t show up the way you expect, run
validate_mappings to check for a typo’d source field name:
oaa validate_mappings --config_file=./config.yaml
Step 5: Add a second source¶
Add the departments source’s connection details the same way, or use
generate_source if you’re adding a source that wasn’t part of the
original init:
oaa generate_source custom vendors --config_file=./config.yaml
This both adds the sources/connections blocks to config.yaml and
creates modules/vendors.py from the standard template, ready for you to
fill in fetch().
Step 6: Add a hook¶
Say you want to normalize a field right before it’s transformed. Scaffold
a hook function targeting the employees source:
oaa generate_hook PRE_TRANSFORM --target=employees
This creates modules/pre_transform_employees.py with the @hook
decorator already wired to OAAHookEvent.PRE_TRANSFORM. Fill in the
function body, then confirm it’s registered:
oaa list_hooks
Step 7: Diff against a known-good config¶
If you’re iterating on config.yaml and want to see exactly what changed
since your last known-good version:
cp config.yaml config.yaml.bak
# ... make edits ...
oaa diff_config --config_file=./config.yaml.bak --compare_file=./config.yaml
Step 8: Check the result in Veza¶
Once you’ve run a real sync (outside the scope of this tutorial — see
oaa run), confirm the provider landed correctly:
oaa list_providers
oaa show_provider --name=bongawonga
oaa provider_status --name=bongawonga
If you need to start over, delete the provider (this requires an explicit confirm, since it’s destructive):
oaa delete_provider --name=bongawonga --confirm=True
End-to-End Integration Validation¶
Before committing a new integration, you might start with one of the bundled samples and validate it end-to-end:
oaa show_sample --name=custom-app-users-groups-roles
cd custom-app-users-groups-roles
Run static checks first (no API credentials needed):
oaa validate_integration --path=. --skip_mock=True
If that passes, run the full pipeline including a mock-data run:
oaa validate_integration --path=.
Inspect the resulting payload:
oaa run --save-json=./payload.json --no-push
oaa anonymize --input=./payload.json --output=./safe.json
oaa validate_payload --payload=./payload.json --config=./config.yaml
The anonymize step redacts sensitive data (if any were accidentally included),
and validate_payload reports field presence, date formats, role permissions,
relationships, and shape diffs between your configured mappings and the actual
output.
If any step fails, parse_errors can help classify what went wrong:
oaa run --no-push 2>&1 | oaa parse_errors
oaa parse_errors --input=./run.log --category=field_mapping
Next steps¶
How to Use CLI Commands — task-oriented reference for each command group
CLI Commands Reference — full argument list for every command
Why CLI Commands — the design reasoning behind this command surface