What it means to develop a blueprint
Developing a Blueprint in Blindata means designing a reusable, production-ready template that standardizes how data products are created across your organization.
At a high level, a Blueprint is not just code - it’s a codified set of best practices. It captures how infrastructure, pipelines, governance, and configuration should be defined, validated, and deployed, and makes that knowledge available as a self-service asset for other teams.
Blueprints rely on the Apache Velocity template engine to render dynamic content. Files in the repository can include template expressions and use the .vm suffix. During instantiation, these templates are processed by replacing variables with the values provided through the manifest parameters, producing fully configured files in the generated data product repository.
The contract Blindata reads is blueprint/manifest.yaml (path configurable at registration). The smallest valid monorepo manifest declares identity, one logical repository, and a single root route:
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: hello-blueprint
displayName: Hello Blueprint
version: 1.0.0
targetRepositories:
- key: main
isRoot: true
instantiation:
- type: root
targets:
- repo: main
Add parameters, composition, and extra targetRepositories keys as your template needs them. The Blueprint Manifest
section documents every field; Manifest examples
shows fuller layouts.
When you develop a Blueprint, you are:
- Abstracting complexity into a reusable template
- Defining inputs (parameters) that make the template flexible
- Embedding governance and controls directly into the setup
- Automating provisioning of data product components
- Enabling consistency across all generated data products
From a practical perspective, developing a Blueprint consists of three main activities:
- Structuring the repository: Organizing your Git repository to include all the assets needed to provision a data product (infrastructure, pipelines, CI/CD, etc.).
- Defining the Blueprint Manifest: Writing the
manifest.yaml, which acts as the contract between the template and its users — declaring parameters, validation rules, UI behavior, composition, and where the generated files are written. - Designing for reuse: Ensuring the template is generic, configurable, and versioned so it can be instantiated multiple times across different contexts.
Blueprint repository layout
A blueprint is a regular Git repository. Blindata only needs a few known files to register, publish, and instantiate it - specifically the Blueprint Manifest, the README, and (optionally) the Data Product Descriptor template written in Apache Velocity. Everything else in the repository is fully under your control: you are free to organize folders, tooling, and assets as your team sees fit.
A possible example of a well-structured blueprint repository is shown below. Treat it as a reference layout, not a requirement: Blindata does not enforce, validate, or manage any of the non-blueprint folders (infrastructure, pipelines, notebooks, CI/CD, etc.) - they live in your Git repository and are maintained by you like any other codebase.
repository/
├── blueprint/
│ ├── manifest.yaml # Blueprint Manifest
│ └── README.md # Rendered in the "Documentation" tab
├── descriptor.json.vm # Apache Velocity descriptor template
├── infrastructure/ # Example folder — Terraform, or any IaC you ship with the template
├── pipeline/ # Example folder — transformations (dlt, dbt, ...), jobs, notebooks
├── .github/workflows/ # Example folder — CI/CD
└── ... (any other content your template needs)
Only the paths to the manifest, the README, and the descriptor template are known to Blindata, and they are configurable when you register the blueprint . All other files and folders shown above are just illustrative examples of content you might ship with a blueprint - their structure, naming, and lifecycle are entirely managed inside your Git repository.
The Blueprint Manifest (blueprint/manifest.yaml by convention) is the single source of truth that drives how Blindata renders, validates, and instantiates the blueprint. Everything else in the repository is template content, rendered on instantiation with the parameters collected from the user.
Blueprint Manifest
The manifest is the contract of a blueprint. It tells Blindata which questions to ask the user, which child blueprints to include, which Git repositories will receive the result, and which generated files must stay unchanged.
Write it as YAML (blueprint/manifest.yaml by convention). YAML is the usual choice because it supports comments and multi-line text. The file must follow the odm-blueprint-manifest specification, currently specVersion: 1.0.0.
After instantiation, parent lineage is kept only on the root repository (the one marked isRoot: true). The published manifest is snapshotted under .odm/blueprint/, and the parameter values used for that run are recorded on the data product descriptor. The other repositories contain the files routed to them. See Multi-repository handling
.
The location of the data product descriptor template is configured when you register the blueprint . It is not a field of the manifest. When a descriptor template is configured, Blindata renders it onto the root repository automatically. Do not add an instantiation route for that file.
How to write a manifest
- Set
spec,specVersion,name, andversion. AdddisplayNameanddescriptionso the catalog and the form are readable. - Declare every input the templates need under
parameters, including validation and form hints. - Name the logical destination repositories under
targetRepositories. Mark exactly one as the root. Optionally give each one a naming convention for the Git repository created for it. - Route files with
instantiation: onetype: rootentry for the parent blueprint, and onetype: moduleentry for each composed child. - If the blueprint reuses other published blueprints, list them under
compositionand map every required child parameter. - Optionally list
protectedResourcesfor files that must stay as the blueprint produced them. How that check runs is described in Policy Enforcement .
Physical Git URLs, and whether Blindata may create a repository, are chosen when someone instantiates the blueprint. The manifest only names logical keys.
Catalog labels and updates
Catalog labels are not fields of the manifest. Assign them when you register the blueprint, or later from the catalog. See Blueprint labels .
A later update of a data product uses this same manifest. targetRepositories, composition, and instantiation describe both the first instantiation and the update, and an update keeps those repository keys, the root, and the routes. See Update a blueprint data product
.
The ui.label field below is the title of a form input. It is unrelated to catalog labels.
Core schema
| Field | Type | Required | Description |
|---|---|---|---|
spec |
String | Yes | Specification name. Must be odm-blueprint-manifest. |
specVersion |
String | Yes | Semantic version of this manifest schema. Use 1.0.0. |
name |
String | Yes | Machine-readable identifier of the blueprint. |
displayName |
String | No | Human-readable name. |
version |
String | Yes | Semantic version of this blueprint release (for example 1.2.0). |
description |
String | No | Short summary of what the blueprint provisions. |
parameters |
Array | No | Inputs collected from the user at instantiation time. |
protectedResources |
Array | No | Destination paths that must stay as generated. See Policy Enforcement . |
composition |
Array | No | Child blueprints included with the parent. |
targetRepositories |
Array | Yes | Logical repository keys resolved to real Git repositories at instantiation time. |
instantiation |
Array | Yes | Routes that copy blueprint files into those repositories. |
specVersion and version must be semantic versions (MAJOR.MINOR.PATCH, with an optional pre-release suffix).
Parameters
Each parameter is a key the templates can reference, plus its type, whether the user must supply it, an optional default, validation rules, and form hints.
| Field | Type | Required | Description |
|---|---|---|---|
key |
String | Yes | Variable name injected into templates. Must be unique in parameters. |
type |
Enum | No | One of string, integer, boolean, array, object. Defaults to string. |
required |
Boolean | No | Whether the user must provide a value. Defaults to false. |
default |
Any | No | Fallback when no value is provided. Must match type. |
validation |
Object | No | Constraints evaluated before instantiation. |
ui |
Object | No | Presentation hints for the collection form. |
Validation
validation is checked against the value supplied by the user before templates are rendered.
| Field | Applies to | Description |
|---|---|---|
allowedValues |
Scalar types | The value must equal one of the listed entries. |
format |
Strings | A well-known format such as hostname, uri, or email. |
pattern |
Strings | Regular expression the value must match. |
min |
Numbers, strings, arrays | Minimum numeric value, or minimum length / item count. |
max |
Numbers, strings, arrays | Maximum numeric value, or maximum length / item count. |
schemaRef is not supported. Use the fields in the table above.
Form presentation (ui)
ui controls how Blindata lays out the parameter form during instantiation and on the blueprint detail page. All four standard fields are optional strings. Extra keys on ui are kept so a future client can read them; stick to the four fields unless you know the client uses another key.
| Field | Purpose |
|---|---|
group |
Splits the form into sections using a /-separated path (for example Networking / Firewall). Spaces around / are ignored. Parameters with no group are shown after every grouped section. |
label |
Short title next to the input. When absent, Blindata shows key. This is a form title, not a catalog label
. |
description |
Helper text shown as a caption or inline help. |
formType |
Suggests the control. Combine it with type and validation. |
Supported formType values:
type |
Supported formType |
Notes |
|---|---|---|
string |
text (default), textarea, dropdown |
Use dropdown together with validation.allowedValues. When allowedValues is set, a choice control is used even if formType is omitted. Use textarea for multi-line input. min / max limit string length. |
integer |
number (default), text |
text is still stored as an integer. min / max limit the number. |
boolean |
checkbox, switch |
Omit formType for the default two-state control. |
array |
json (default), stringList |
stringList is a row editor for an array of strings. min / max limit the item count. |
object |
json (default) |
Structured editor. |
Prefer the values in this table. Other formType strings are not portable.
Protected resources
protectedResources names files, directories, or globs that must stay as the blueprint wrote them. Each path is relative to an instantiated destination repository, after templates have been rendered.
| Field | Type | Required | Description |
|---|---|---|---|
path |
String | Yes | File, directory, or glob relative to the destination repository root (for example infrastructure/core/** or docs/architecture.md). |
repository |
String | No | Logical destination. Must match a targetRepositories[].key. Omit it (or leave it blank) to use the repository marked isRoot: true. |
A blueprint published as a module must leave protectedResources empty. The parent blueprint lists the final destination paths, including files that came from a module.
How the publication check uses this list is described in Policy Enforcement .
Composition
Composition lets a parent blueprint include other published blueprints as modules. composition says which blueprints to include and which values they receive. instantiation says where their files are written. A module does not inherit the parent parameter set: every value is passed explicitly, the same way a Terraform module receives its inputs.
A module must itself be a published blueprint with a single target repository and no composition of its own.
| Field | Type | Required | Description |
|---|---|---|---|
module |
String | Yes | Alias for this child. Unique in the manifest. Referenced by instantiation[].moduleName. |
blueprintName |
String | Yes | Identifier of the child blueprint. |
blueprintVersion |
String | Yes | Published release of the child blueprint. |
parameterMapping |
Object | No | Maps each child parameter key to either a parent parameter or a fixed value. |
Each entry in parameterMapping is an object with exactly one of these fields:
| Shape | Meaning |
|---|---|
{ $param: parentKey } |
Take the value from the parent parameter parentKey (the value supplied at instantiation, otherwise the parent default). parentKey must be declared on the parent. |
{ value: actualValue } |
Copy this literal. actualValue may be a string, number, boolean, object, or array. It is not looked up on the parent. |
A bare string, number, or boolean is invalid. An object that sets both $param and value, or neither, is invalid. Extra properties beside the one discriminant are ignored. String concatenation and other expressions are out of scope: if a value must be derived, declare it as a parent parameter.
When the parent is published, parameterMapping must include every child parameter that has no default. Child parameters that declare a default may be omitted.
Target repositories
targetRepositories is the list of logical destinations. At instantiation time each key is mapped to a real Git repository. One key is a monorepo layout. Several keys are a polyrepo layout. The layout follows from this list.
| Field | Type | Required | Description |
|---|---|---|---|
key |
String | Yes | Unique alias (for example main-repository or pipeline-repo). Referenced by instantiation[].targets[].repo and, optionally, by protectedResources[].repository. |
description |
String | No | Default description of the Git repository when the user creates one for this key during instantiation. |
isRoot |
Boolean | No | Defaults to false. Exactly one entry must set isRoot: true. That key is the data-product root: lineage, the rendered descriptor, and the primary repository pointer. |
naming |
Object | No | Naming convention of the Git repository bound to this key. See Repository naming conventions . |
Every declared key must appear on at least one route. A key that no route uses is rejected.
Repository naming conventions
naming lets the blueprint decide the name of the Git repository bound to a key, so every data product instantiated from the blueprint follows the same convention. It is optional and set per targetRepositories[] entry: each key may declare its own pattern and enforcement mode.
| Field | Type | Required | Description |
|---|---|---|---|
pattern |
String | Yes | Repository name template: literal text plus ${key} placeholders. |
enforcement |
Enum | No | binding or suggested. Defaults to binding. |
parameters:
# Reserved keys must be declared to be used in a pattern
- key: dpDomain
type: string
- key: dpName
type: string
targetRepositories:
- key: main-repository
isRoot: true
naming:
pattern: "${dpDomain}-${dpName}"
enforcement: binding # or: suggested; defaults to binding when omitted
- key: infra-repo
naming:
pattern: "${dpDomain}-${dpName}-infra"
enforcement: suggested
For a data product with domain sales and name customer-analytics, the repositories are named sales-customer-analytics and sales-customer-analytics-infra.
Pattern rules. These are checked when the blueprint version is published:
- A pattern is literal text plus
${key}placeholders, with at least one placeholder. Every$must start a${...}placeholder. Velocity directives, method calls, and filters are not allowed. - A placeholder can only reference a reserved parameter
(
dpDomain,dpName,dpFqn,dpDisplayName,dpDescription,dpOwnerId,dpOwnerName) that the manifest declares inparameters. Other parameters are not supported for now. - Two keys must not use the same pattern.
- A blueprint published as a module must not declare
naming. The parent decides where module files are written.
Enforcement. The instantiation wizard resolves the pattern with the reserved values of the data product and of the user running the wizard:
enforcement |
Create a new repository | Select an existing repository |
|---|---|---|
binding |
The name is the resolved pattern and cannot be edited. | Only repositories whose name equals the resolved name exactly (case-sensitive) can be selected. |
suggested |
The resolved pattern is pre-filled and can be edited. | Any repository can be selected. |
When a referenced value is empty for the data product, a binding key is blocked and the wizard names the missing variable. A suggested key is left without a name for the user to type.
Choosing variables. Resolved names are not transformed: Blindata does not lowercase them or replace spaces and special characters. Use values that are valid repository names on your Git provider. dpName is a technical name and is usually safe. Domains, display names, and descriptions may contain spaces or characters such as & that the provider rejects, and under binding the user cannot correct the name in the wizard. dpOwnerId and dpOwnerName come from the profile of the user running the wizard, so the same data product gets different names depending on who instantiates it.
Scope.
- Conventions are enforced by the Blindata instantiation wizards (from the blueprint detail page, and when a data product is initialized from a blueprint in the Data Product Builder). Clients that call the instantiate API directly are not checked.
- Conventions apply only when a new data product is instantiated. Updating
a data product to a version with a different convention never renames or blocks its repositories, and
namingmay change between versions. - A convention governs the repository name only. Restrict the organizations or groups where repositories can be created in the Git provider settings.
- Without
naming, the default names described in Data product repositories apply.
Instantiation
instantiation is a list of routing entries. Each entry copies a part of a blueprint into one or more destination repositories.
| Field | Type | Required | Description |
|---|---|---|---|
type |
Enum | Yes | root routes files from the parent blueprint. module routes files from a child declared in composition. |
moduleName |
String | Conditional | Required when type is module. Must match composition[].module. Omit it when type is root. |
targets |
Array | Yes | One or more routes. Must not be empty. |
Each item in targets:
| Field | Type | Required | Description |
|---|---|---|---|
sourcePath |
String | Conditional | Directory relative to the blueprint being routed (the parent for type: root, the child for type: module). Defaults to ./. Required on every item when targets has more than one entry. |
repo |
String | Yes | Must match a targetRepositories[].key. |
destinationPath |
String | No | Directory relative to that destination repository. Defaults to ./. |
The contents of sourcePath are written into destinationPath. Paths are relative to the repository root: a leading / and .. are rejected. Leading ./ and trailing slashes are treated as the same path.
On one repository key, destinations must be siblings. Two routes must not write the same path, and one destination must not sit inside another (for example ./ together with data-plane/storage on the same key). Repeat the same repo when several slices belong in one repository, and give each slice its own sibling destinationPath.
The list must contain exactly one entry with type: root. Every composition[].module needs a matching type: module entry, and every type: module entry must name a module that exists in composition.
Four layouts fall out of targetRepositories and composition:
| Layout | Repositories | Composition | What you declare |
|---|---|---|---|
| Monorepo, no composition | 1 | no | One key with isRoot: true, and a single type: root route. |
| Monorepo + composition | 1 | yes | The same key, a type: root route, and one type: module route per child, each with its own destinationPath. |
| Polyrepo, no composition | several | no | Several keys, one of them isRoot: true, and several targets on the type: root entry. Each target sets sourcePath explicitly. |
| Polyrepo + composition | several | yes | Several keys, plus type: module routes that may point at different keys. |
Multi-repository handling
A blueprint is still one template repository. Multi-repository means the data product it produces can be written into more than one Git repository, and a parent blueprint can also copy files in from other published blueprints. How the Data Product Builder lists those repositories, and how a published release tags each of them, is described in Multi-repository data products .
Advanced / technical. The rest of this section is the exact routing and repository behavior. The tables above stay the field reference.
Blueprint sources and routes
The blueprint you register lives in one Git repository. targetRepositories does not split that source. It names the logical destinations of the generated data product. One key is a monorepo layout. Several keys are a polyrepo layout. Exactly one entry sets isRoot: true. That key is the data-product root: the primary repository, the rendered descriptor, and parent lineage.
instantiation routes files into those keys. An entry with type: root copies a directory of the parent blueprint. sourcePath is relative to the parent repository (default ./), repo must be a targetRepositories[].key, and destinationPath is relative to that destination (default ./). An entry with type: module does the same for a child named in composition. sourcePath is then relative to the child blueprint repository, and moduleName matches composition[].module. There is exactly one type: root entry. When a targets list has more than one item, every item sets sourcePath. On one key, destinations are siblings: the same path twice, or one path inside another, is rejected.
Composition pulls those children from other published blueprints. composition names each child (blueprintName, blueprintVersion) and passes parameters explicitly with parameterMapping. The child’s own targetRepositories are not used. The parent decides where the child’s files land. A child must be a published module with a single target repository, no composition of its own, an empty protectedResources list, and the same Git provider as the parent.
After the files are rendered, parent lineage is written only on the root repository:
- When a descriptor template was configured at registration
, it is rendered onto the root at that path with the
.vmsuffix removed. Do not add an instantiation route for that file. The descriptor’s blueprint section then records the parent blueprint identity and the parameter values used for this run. - The published parent manifest is snapshotted as
.odm/blueprint/blueprint-manifest.yaml. That file is the published manifest. Parameter values are not substituted into it; they live on the descriptor. If the parent README is present in the rendered root tree, it is moved into.odm/blueprint/as well. - Other destination repositories do not receive
.odm/blueprint/.
A composed module’s manifest and README are placed under .odm/<module alias>/ on the destination repository that received that module (moved there when the route already copied them, otherwise copied from the module source). They are not stored under .odm/blueprint/.
Data product repositories
At instantiation
, the wizard asks for one Git repository per targetRepositories[].key. The key with isRoot: true is Main Repository. It becomes the data product’s primary repository and is the only one that receives the descriptor and .odm/blueprint/. Every other key is an Additional repository. The key is shown read-only and cannot be renamed in the wizard. The manifest description of that key is the default repository description when you create one.
When you create repositories, a key with a naming convention
takes its name from that convention. Without one, the main name defaults to the data product name, and each additional name defaults to the data product name, a hyphen, and the key (for example customer-analytics-infra-repo). The organization chosen for the main repository is copied onto additional repositories you are still creating that do not yet have an owner. The same Git repository cannot be assigned to two keys. Each key has its own optional target branch; an empty branch uses that repository’s default branch. Repositories are created or selected one by one. A failure on a later key does not undo a repository that was already created.
An update does not create repositories and does not change which Git repository is bound to a key. Every key declared on the next manifest must already be configured on the data product. Each repository is updated on its own:
- The update branch is
update/blueprint-v{next}and the checkpoint tag isblueprint-v{next}. The names are the same on every repository, because each name lives in a different remote. - Only the routes for that key are rendered. Parent lineage (the descriptor blueprint section and
.odm/blueprint/) is rewritten only on the main repository. A module sidecar under.odm/<module alias>/is rewritten on the repository that receives that module. - Open Pull Request after update is one switch for the whole run. The main repository’s pull request targets the branch selected in the Data Product Builder. Each additional repository’s pull request targets that repository’s configured default branch, or the builder branch when no default branch is stored.
- A repository whose new render matches its current checkpoint does not get an update branch. The others still do. Blindata does not merge the pull requests, and a failure on one repository does not undo repositories that were already updated.
The layout cannot change between the version the data product is on and the next version. These must stay the same: the set of targetRepositories[].key values, which key has isRoot: true, single-repository versus multi-repository, whether composition is used, each root route (sourcePath, repo, destinationPath), and each module’s alias, blueprintName, and module routes. Parameter values, template file contents, parameterMapping, a key’s description and naming, and a module’s blueprintVersion may change. Adding a key, dropping a module, or moving a route requires a new instantiation.
Publication checks
read protectedResources[].repository as a logical key, not as a Git URL. Omitted or blank selects the main repository (isRoot: true) and compares that path on the main repository’s published tag. A set value must be a declared key and selects the additional repository stored for that key, compared at that repository’s own published tag. Only repositories named by the list are read. The parent list is the one enforced. Protect module output by its final destination path on the repository that received it, including .odm/<module alias>/ when those files must stay locked. The descriptor and .odm/blueprint/ exist only on the main repository, so pointing those paths at another key does not match a rendered file.
Checks applied to the manifest
Blindata reports every problem it finds, together with a short hint. The structural rules are:
targetRepositories[].keyvalues are unique, and exactly one entry hasisRoot: true.instantiationis non-empty and contains exactly onetype: rootentry.composition[].modulevalues are unique. Module names incompositionand ininstantiationmatch both ways.- Every
targetslist is non-empty. When a list has more than one entry, each entry setssourcePath. - Every
repomatches a declared key, and every declared key is used by at least one route. protectedResources[].repository, when set, matches a declared key.- The pair (
repo,destinationPath) is unique. Destinations on the same key do not nest. parameterMappingentries use{ $param: parentKey }or{ value: actualValue }, and$paramnames a parameter declared on the parent.- At publish time, every child parameter without a default is present in
parameterMapping. - Composed modules are published blueprints with one repository and no composition of their own.
- A blueprint used as a module has an empty
protectedResourceslist. The parent names the final paths. - Each
targetRepositories[].naming, when present, has apatternwith valid${key}syntax and at least one placeholder, references only reserved parameters declared inparameters, and setsenforcement, if any, tobindingorsuggested. No two keys share the same pattern. - A blueprint published as a module declares no
naming.
Manifest examples
The examples below are source blueprint manifests (spec: odm-blueprint-manifest, specVersion: 1.0.0). Parameter lists are short on purpose; a production manifest declares every input the templates reference.
Monorepo, no composition
One repository receives the whole parent blueprint.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: analytics-lakehouse
displayName: Analytics Lakehouse Blueprint
version: 1.0.0
description: Provisions storage and compute defaults for an analytics data product.
parameters:
- key: environment
type: string
required: true
validation:
allowedValues: [dev, staging, prod]
ui:
group: General Configuration
label: Environment
description: Deployment stage for this data product.
formType: dropdown
- key: retentionDays
type: integer
default: 90
validation:
min: 1
max: 3650
ui:
group: Storage
label: Data retention (days)
formType: number
protectedResources:
- path: infrastructure/core/**
- path: docs/architecture.md
targetRepositories:
- key: main-repository
description: Target repository for all data product assets
isRoot: true
instantiation:
- type: root
targets:
- sourcePath: ./
repo: main-repository
destinationPath: ./
Monorepo + composition
Parent and child files land in one repository, in sibling directories. parameterMapping passes parent parameters with $param and a fixed region with value.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: full-stack-dp
version: 2.1.0
description: Parent blueprint composing storage and serving modules into one repo.
parameters:
- key: projectSlug
type: string
required: true
validation:
pattern: '^[a-z][a-z0-9-]{1,62}$'
ui:
group: General Configuration
label: Project slug
formType: text
- key: enablePiiMasking
type: boolean
default: true
ui:
group: Security
label: Enable PII masking
protectedResources:
- path: data-plane/storage/**
- path: .odm/storage/**
- path: app/serving/**
targetRepositories:
- key: main-repository
description: Single repository containing application and infrastructure
isRoot: true
composition:
- module: storage
blueprintName: odm-blueprint-s3-lake
blueprintVersion: 3.0.1
parameterMapping:
bucketPrefix: { $param: projectSlug }
encryptAtRest: { $param: enablePiiMasking }
region: { value: eu-west-1 }
- module: serving
blueprintName: odm-blueprint-api-skeleton
blueprintVersion: 1.4.0
parameterMapping:
serviceName: { $param: projectSlug }
instantiation:
- type: root
targets:
- sourcePath: ./
repo: main-repository
destinationPath: core/
- type: module
moduleName: storage
targets:
- sourcePath: ./
repo: main-repository
destinationPath: data-plane/storage
- type: module
moduleName: serving
targets:
- sourcePath: ./
repo: main-repository
destinationPath: app/serving
core/, data-plane/storage, and app/serving are siblings on main-repository. The protected paths name those destination directories (and the composed module files placed under .odm/storage/).
Polyrepo, no composition
One blueprint is split across two repositories. The type: root entry has several targets, so each one sets sourcePath. app-repo is the root, so a protected path with no repository refers to it. infra-repo is named explicitly.
If the two targets on infra-repo used nested paths (for example ./ and governance/policies), the manifest would be rejected. terraform/ and governance/policies are siblings.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: split-stack-template
version: 0.5.0
parameters:
- key: awsRegion
type: string
required: true
validation:
allowedValues: [eu-west-1, eu-central-1, us-east-1]
ui:
label: AWS region
formType: dropdown
targetRepositories:
- key: infra-repo
description: Target repository for infrastructure code and policies
- key: app-repo
description: Target repository for application code
isRoot: true
protectedResources:
- path: application/** # root shorthand: app-repo
- path: terraform/**
repository: infra-repo
instantiation:
- type: root
targets:
- sourcePath: terraform/
repo: infra-repo
destinationPath: terraform/
- sourcePath: policies/
repo: infra-repo
destinationPath: governance/policies
- sourcePath: application/
repo: app-repo
destinationPath: application/
The contents of application/ are written into application/ on app-repo, which is the path protected by the root shorthand. terraform/** is protected on infra-repo by name.
Polyrepo + composition
Each module is routed to its own repository. The parent files go to a sibling directory on the root repository (pipeline-repo), next to the ingest module, so the two destinations do not nest.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: mesh-polyrepo-parent
version: 1.3.0
parameters:
- key: dataDomain
type: string
required: true
ui:
group: Governance
label: Data domain
formType: text
targetRepositories:
- key: pipeline-repo
description: Target repository for data pipeline components
isRoot: true
- key: api-repo
description: Target repository for API serving components
protectedResources:
- path: pipelines/batch/** # root shorthand: pipeline-repo
- path: services/consumer/**
repository: api-repo
composition:
- module: ingest
blueprintName: odm-blueprint-ingest-batch
blueprintVersion: 2.0.0
parameterMapping:
domain: { $param: dataDomain }
- module: consume
blueprintName: odm-blueprint-consumer-api
blueprintVersion: 1.1.0
parameterMapping:
domain: { $param: dataDomain }
instantiation:
- type: module
moduleName: ingest
targets:
- sourcePath: ./
repo: pipeline-repo
destinationPath: pipelines/batch
- type: module
moduleName: consume
targets:
- sourcePath: ./
repo: api-repo
destinationPath: services/consumer
- type: root
targets:
- sourcePath: ./
repo: pipeline-repo
destinationPath: core
pipelines/batch and core are siblings on pipeline-repo. services/consumer is the only destination on api-repo.
Reserved parameters
Declare these keys in parameters when your templates need data product or owner context. Blindata pre-fills them from the data product and the current user profile, and still lets the user change them before instantiation. See the Instantiation Guide
.
A reserved key is an ordinary parameter: set type, required, and ui the same way as any other input. Templates only see it when it is declared.
| Reserved key | Source | Purpose |
|---|---|---|
dpDomain |
Data Product metadata | Business domain the data product belongs to. |
dpName |
Data Product metadata | Technical name (lowercase alphanumeric and dashes). |
dpFqn |
Data Product metadata | Unique identifier / URN. |
dpDisplayName |
Data Product metadata | Human-readable display name. |
dpDescription |
Data Product metadata | High-level description. |
dpOwnerId |
User profile | Owner username / user identifier. |
dpOwnerName |
User profile | Owner display name. |