DataOps Platform

Develop a Blueprint

Anatomy of a blueprint repository and the complete specification of the YAML Blueprint Manifest: parameters, validation, UI hints, protected resources, composition, target repositories, and instantiation routes.

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

  1. Set spec, specVersion, name, and version. Add displayName and description so the catalog and the form are readable.
  2. Declare every input the templates need under parameters, including validation and form hints.
  3. 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.
  4. Route files with instantiation: one type: root entry for the parent blueprint, and one type: module entry for each composed child.
  5. If the blueprint reuses other published blueprints, list them under composition and map every required child parameter.
  6. Optionally list protectedResources for 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 in parameters. 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 naming may 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 .vm suffix 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 is blueprint-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[].key values are unique, and exactly one entry has isRoot: true.
  • instantiation is non-empty and contains exactly one type: root entry.
  • composition[].module values are unique. Module names in composition and in instantiation match both ways.
  • Every targets list is non-empty. When a list has more than one entry, each entry sets sourcePath.
  • Every repo matches 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.
  • parameterMapping entries use { $param: parentKey } or { value: actualValue }, and $param names 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 protectedResources list. The parent names the final paths.
  • Each targetRepositories[].naming, when present, has a pattern with valid ${key} syntax and at least one placeholder, references only reserved parameters declared in parameters, and sets enforcement, if any, to binding or suggested. 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.