Data Contracts in Practice
Progress
0%
ende
Getting Started
  • Welcome15′
  • Setup20′
Part A · The Source Data Product
  • 1.Put Your Data Under Contract60′
  • 2.Data Contract Evolution30′
  • 3.Describe Your Data Product20′
Part B · The Consumer-Aligned Data Product
  • 4.Design Contract-First30′
  • 5.Implement Your Data Product25′
  • 6.Consumer-Driven Contracts30′
Part C · Automate
  • 7.CI/CD with GitHub Actions45′
Part D · Data Platformoptional
  • 8.Publish to Entropy Data40′
  • 9.Semantics25′
Wrap-up
  • Wrap-up10′
Part D · Data Platformoptional

Exercise 8 · Publish to Entropy Data

Make your data products discoverable on a data product platform.

~40 min0 of 8 steps done
Previous
CI/CD with GitHub Actions
Next
Semantics
Maintained byEntropy Data

YAML files in Git work well for a single team. But how do other teams discover your data products, browse the contracts, and request access? That's what a data product platform is for. In this exercise, you publish everything from Parts A and B to Entropy Data with the Entropy Data CLI.

You will learn
  • Connect the Entropy Data CLI to a cloud organization or a local Community Edition
  • Publish teams, data contracts, and data products
  • Model the dependency between two data products as an access agreement
  • Publish test results to show whether a contract is upheld
Optional part

Part D is optional and uses a commercial platform with a free tier. The free Community Edition runs fully locally. Everything you built so far works without it.

Get access

Create an organization

Pick one option.

Option 1: Cloud. Go to app.entropy-data.com, create an account, and set up an organization named tutorial-<yourname>, e.g. tutorial-simon. Names are unique across the platform. Use lowercase letters, digits, and hyphens only. In the organization settings, create an API key with organization write permissions.

Option 2: Community Edition, locally. Start it in Docker and run the setup script. It creates an account, an organization named acme, and an API key:

docker compose -f entropy-data-ce/docker-compose.yaml up -d
./scripts/setup-entropy-data-ce.sh
…
Creating account [email protected] ...
Logging in ...
Creating organization acme ...
Creating organization API key ...
Writing ENTROPY_DATA_API_KEY and ENTROPY_DATA_HOST to .env ...

Done. Log in at http://localhost:8081 with [email protected] / workshop (organization: acme)
Verify the CLI connection with: entropy-data connection test

Log in at http://localhost:8081 with [email protected] / workshop. Below, use acme wherever it says tutorial-<yourname>.

Configure the connection

The install script from Setup already installed the Entropy Data CLI. Check:

entropy-data --version
entropy-data 0.3.13

Cloud: set your API key as an environment variable in your terminal:

export ENTROPY_DATA_API_KEY=ed_...
export ENTROPY_DATA_HOST=https://api.entropy-data.com
Never commit your API key

Your fork is public, and .env is tracked in Git. Anything you push there is visible to everyone and stays in the history. So set the key in your shell instead. Shell variables take precedence over the placeholders in .env. Repeat these lines in every new terminal.

Community Edition: the setup script wrote the key and ENTROPY_DATA_HOST=http://localhost:8081 into .env. Keep it out of Git anyway: check git status before you commit and undo the change with git checkout .env. Never run git add .env.

Test the connection:

entropy-data connection test
Connection successful.

Publish

Create your teams

Contracts and data products name a team as owner. The team must exist before you publish anything that references it:

cat <<EOF | entropy-data teams put order_data_team --file -
id: order_data_team
name: Order Data Team
type: team
description: Owns the orders data
EOF

cat <<EOF | entropy-data teams put purchasing_analytics_team --file -
id: purchasing_analytics_team
name: Purchasing Analytics Team
type: team
description: Builds analytical data products for purchasing
EOF

entropy-data teams list
Team 'order_data_team' saved.
Team 'purchasing_analytics_team' saved.
                                   teams (page 0)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
┃ ID                        ┃ Name                      ┃ Type             ┃ Parent ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
│ governance-group          │ Governance Group          │ Governance Group │        │
│ platform-team             │ Platform Team             │ Platform Team    │        │
│ order_data_team           │ Order Data Team           │ team             │        │
│ purchasing_analytics_team │ Purchasing Analytics Team │ team             │        │
└───────────────────────────┴───────────────────────────┴──────────────────┴────────┘

The team.name in your ODCS and ODPS files must match the team ID, e.g. order_data_team.

Publish your data contracts

The ID argument must match the id field in each contract:

entropy-data datacontracts put orders_v1 --file orders_v1.odcs.yaml
entropy-data datacontracts put orders_v2 --file orders_v2.odcs.yaml
entropy-data datacontracts put sku_sales_per_year --file sku_sales_per_year.odcs.yaml
entropy-data datacontracts put orders_v2_consumer_sku_sales --file orders_v2.consumer_sku_sales.odcs.yaml

entropy-data datacontracts list
Data contract 'orders_v1' saved.
Open https://app.entropy-data.com/tutorial-simon/datacontracts/orders_v1
Data contract 'orders_v2' saved.
Open https://app.entropy-data.com/tutorial-simon/datacontracts/orders_v2
Data contract 'sku_sales_per_year' saved.
Open https://app.entropy-data.com/tutorial-simon/datacontracts/sku_sales_per_year
Data contract 'orders_v2_consumer_sku_sales' saved.
Open https://app.entropy-data.com/tutorial-simon/datacontracts/orders_v2_consumer_sku_sales
                                  datacontracts (page 0)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ID                           ┃ Title              ┃ Version ┃ Owner                     ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ orders_v1                    │ Orders             │ 1.0.0   │ order_data_team           │
│ orders_v2                    │ Orders             │ 2.0.0   │ order_data_team           │
│ sku_sales_per_year           │ SKU Sales per Year │ 1.0.0   │ purchasing_analytics_team │
│ orders_v2_consumer_sku_sales │ Orders (SKU Sales) │ 2.0.0   │ purchasing_analytics_team │
└──────────────────────────────┴────────────────────┴─────────┴───────────────────────────┘
Publish your data products

Entropy Data supports ODPS natively, so you publish the files as they are. The ID argument must match the id field in the file:

entropy-data dataproducts put orders --file orders.odps.yaml
entropy-data dataproducts put sku_sales --file sku_sales_per_year.odps.yaml

entropy-data dataproducts list
Data product 'orders' saved.
Data product 'sku_sales' saved.
                    dataproducts (page 0)
┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ID        ┃ Title     ┃ Status ┃ Owner                     ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ orders    │ Orders    │ active │ order_data_team           │
│ sku_sales │ SKU Sales │ draft  │ purchasing_analytics_team │
└───────────┴───────────┴────────┴───────────────────────────┘
Workaround

Due to a current bug in Entropy Data, publishing fails if the ODPS file contains inputPorts. Remove the inputPorts section from sku_sales_per_year.odps.yaml right before you publish. The next step models the dependency another way.

Alternative: the Data Product CLI

dataproduct publish validates the file against the ODPS schema and publishes it in one go. It reads ENTROPY_DATA_API_KEY and ENTROPY_DATA_HOST the same way and takes the ID from the file:

dataproduct publish orders.odps.yaml
✅ Published data product successfully
Connect the data products

On the platform, the dependency is an access agreement: SKU Sales consumes the orders_v2 output port of Orders. Create it directly as approved:

cat <<EOF | entropy-data access put sku_sales_consumes_orders --file -
dataUsageAgreementSpecification: 0.0.1
id: sku_sales_consumes_orders
info:
  purpose: SKU Sales aggregates orders and line items per SKU and year for the purchasing team
  status: approved
  startDate: "2026-01-01"
provider:
  dataProductId: orders
  outputPortId: orders_v2
  dataContractId: orders_v2
consumer:
  dataProductId: sku_sales
EOF
Access agreement 'sku_sales_consumes_orders' saved.

With status approved and a startDate in the past, the agreement is active.

Why an access agreement?

An input port says "I read this data." An access agreement adds "…and the owner approved it, for this purpose." So the owner of Orders knows every consumer and can warn them before changes ship.

Explore

Explore the platform

Open the web UI (app.entropy-data.com or localhost:8081):

  • Find your two data products and their output ports.
  • Open the sku_sales_per_year contract and compare it with the Data Contract Editor.
  • Follow the access agreement from SKU Sales to Orders. The dependency from Design Contract-First is now a link in the data product map.
  • How would someone find a data product about SKUs without knowing it exists?

The data product list: Orders with two output ports, SKU Sales with one

SKU Sales consumes Orders: the access agreement as a link in the data product map

Publish test results

The platform shows whether a contract is currently upheld, if you send it test results. Run your tests with --publish and your host's test results endpoint:

Cloud:

datacontract test sku_sales_per_year.odcs.yaml --publish https://api.entropy-data.com/api/test-results
Testing sku_sales_per_year.odcs.yaml
🚀 Open https://app.entropy-data.com/tutorial-simon/datacontracts/sku_sales_per_year/servers/postgres/checks
Server: postgres (type=postgres, host=localhost, port=5433, database=workshop, schema=analytics)
╭────────┬──────────────────────────────────────────────────┬─────────────┬─────────╮
│ Result │ Check                                            │ Field       │ Details │
├────────┼──────────────────────────────────────────────────┼─────────────┼─────────┤
│ passed │ Ensure the view has data                         │             │         │
│ passed │ Check that field 'order_count' is present        │ order_count │         │
│ …      │                                                  │             │         │
│ passed │ Ensure year is plausible                         │ year        │         │
╰────────┴──────────────────────────────────────────────────┴─────────────┴─────────╯
🟢 data contract is valid. Run 15 checks. Took 0.58 seconds.

Community Edition:

datacontract test sku_sales_per_year.odcs.yaml --publish http://localhost:8081/api/test-results

The CLI sends your API key only if the URL belongs to the host in ENTROPY_DATA_HOST. Find the results on the contract page in the UI.

The contract page with the published test results under Data Quality

Quick check
Where should the Entropy Data API key go when you work in a public fork?

Bonus

  • Publish test results from CI. In your fork on GitHub, go to Settings → Secrets and variables → Actions and add a repository secret ENTROPY_DATA_API_KEY. In the workflow from CI/CD with GitHub Actions, pass it to the test step and add --publish:

    YAML
    - name: Test data contracts
      env:
        ENTROPY_DATA_API_KEY: ${{ secrets.ENTROPY_DATA_API_KEY }}
        ENTROPY_DATA_HOST: https://api.entropy-data.com
      run: datacontract ci *.odcs.yaml --publish https://api.entropy-data.com/api/test-results

    This works only with the cloud: GitHub's runners can't reach your local Community Edition.

  • Keep Git as the source of truth. Look at entropy-data datacontracts import-from-git --help. How would you publish contracts automatically on every merge to main?