# Auf Entropy Data veröffentlichen

Deine Datenprodukte auf einer Datenproduktplattform auffindbar machen.

Source: https://learn.datacontract.com/de/publish/

YAML-Dateien in Git reichen für ein einzelnes Team. Aber wie finden *andere* Teams deine Datenprodukte, lesen die Kontrakte und beantragen Zugriff?
Dafür gibt es eine **Datenproduktplattform**.
In dieser Übung veröffentlichst du alles aus Teil A und B mit der [Entropy Data CLI](https://github.com/entropy-data/entropy-data-cli) auf [Entropy Data](https://entropy-data.com).

**You will learn:**
- Die Entropy Data CLI mit einer Cloud-Organisation oder einer lokalen Community Edition verbinden
- Teams, Datenkontrakte und Datenprodukte veröffentlichen
- Die Abhängigkeit zwischen zwei Datenprodukten als Access Agreement abbilden
- Testergebnisse veröffentlichen, um zu zeigen, ob ein Kontrakt eingehalten wird

> **Optionaler Teil**
>
> Teil D ist optional und nutzt eine kommerzielle Plattform mit kostenlosem Einstieg. Die kostenlose **Community Edition** läuft komplett lokal. Alles bisher Gebaute funktioniert auch ohne.

## Zugang einrichten

### Organisation anlegen

Wähle **eine** Option.

**Option 1: Cloud.** Geh auf [app.entropy-data.com](https://app.entropy-data.com), leg ein Konto an und erstelle eine Organisation namens `tutorial-<deinname>`, z. B. `tutorial-simon`.
Namen sind plattformweit eindeutig. Erlaubt sind Kleinbuchstaben, Ziffern und Bindestriche.
Erstelle in den Organisationseinstellungen einen **API Key** mit Schreibrechten für die Organisation.

**Option 2: Community Edition, lokal.** Starte sie in Docker und führe das Setup-Skript aus. Es legt ein Konto, eine Organisation namens `acme` und einen API Key an:

macOS / Linux:

```bash
docker compose -f entropy-data-ce/docker-compose.yaml up -d
./scripts/setup-entropy-data-ce.sh
```

Windows (PowerShell):

```powershell
docker compose -f entropy-data-ce/docker-compose.yaml up -d
powershell -ExecutionPolicy Bypass -File scripts\setup-entropy-data-ce.ps1
```

Output:

```text
…
Creating account workshop@example.com ...
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 workshop@example.com / workshop (organization: acme)
Verify the CLI connection with: entropy-data connection test
```

Melde dich unter [http://localhost:8081](http://localhost:8081) mit `workshop@example.com` / `workshop` an.
Wo unten `tutorial-<deinname>` steht, nimmst du `acme`.

### Verbindung konfigurieren

Das Install-Skript aus dem [Setup](https://learn.datacontract.com/de/setup/) hat die Entropy Data CLI schon installiert. Prüfe es:

macOS / Linux:

```bash
entropy-data --version
```

Windows (PowerShell):

```powershell
entropy-data --version
```

Output:

```text
entropy-data 0.3.13
```

**Cloud:** Setz deinen API Key als Umgebungsvariable in deinem Terminal:

macOS / Linux:

```bash
export ENTROPY_DATA_API_KEY=ed_...
export ENTROPY_DATA_HOST=https://api.entropy-data.com
```

Windows (PowerShell):

```powershell
$env:ENTROPY_DATA_API_KEY = "ed_..."
$env:ENTROPY_DATA_HOST = "https://api.entropy-data.com"
```

> **Committe niemals deinen API Key**
>
> Dein Fork ist öffentlich, und `.env` ist in Git getrackt. Alles, was du dort pushst, kann jeder sehen, und es bleibt in der Historie.
> Setz den Key deshalb in deiner Shell. Shell-Variablen haben Vorrang vor den Platzhaltern in `.env`.
> Wiederhole diese Zeilen in jedem neuen Terminal.

**Community Edition:** Das Setup-Skript hat den Key und `ENTROPY_DATA_HOST=http://localhost:8081` in `.env` geschrieben. Halte ihn trotzdem aus Git heraus: Prüfe vor jedem Commit `git status` und mach die Änderung mit `git checkout .env` rückgängig. Führe nie `git add .env` aus.

Teste die Verbindung:

macOS / Linux:

```bash
entropy-data connection test
```

Windows (PowerShell):

```powershell
entropy-data connection test
```

Output:

```text
Connection successful.
```

## Veröffentlichen

### Teams anlegen

Kontrakte und Datenprodukte nennen ein Team als Owner. Das Team muss existieren, bevor du etwas veröffentlichst, das darauf verweist:

macOS / Linux:

```bash
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
```

Windows (PowerShell):

```powershell
@'
id: order_data_team
name: Order Data Team
type: team
description: Owns the orders data
'@ | entropy-data teams put order_data_team --file -

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

entropy-data teams list
```

Output:

```text
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             │        │
└───────────────────────────┴───────────────────────────┴──────────────────┴────────┘
```

Der `team.name` in deinen ODCS- und ODPS-Dateien muss der Team-ID entsprechen, z. B. `order_data_team`.

### Datenkontrakte veröffentlichen

Das ID-Argument muss dem Feld `id` im jeweiligen Kontrakt entsprechen:

macOS / Linux:

```bash
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
```

Windows (PowerShell):

```powershell
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
```

Output:

```text
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 │
└──────────────────────────────┴────────────────────┴─────────┴───────────────────────────┘
```

### Datenprodukte veröffentlichen

Entropy Data unterstützt ODPS nativ, du veröffentlichst die Dateien also unverändert.
Das ID-Argument muss dem Feld `id` in der Datei entsprechen:

macOS / Linux:

```bash
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
```

Windows (PowerShell):

```powershell
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
```

Output:

```text
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**
>
> Wegen eines aktuellen Bugs in Entropy Data schlägt das Veröffentlichen fehl, wenn die ODPS-Datei `inputPorts` enthält. Entferne den Abschnitt `inputPorts` aus `sku_sales_per_year.odps.yaml` direkt vor dem Veröffentlichen. Der nächste Schritt bildet die Abhängigkeit anders ab.

> **Alternative: die Data Product CLI**
>
> `dataproduct publish` prüft die Datei gegen das ODPS-Schema und veröffentlicht sie in einem Schritt. Sie liest `ENTROPY_DATA_API_KEY` und `ENTROPY_DATA_HOST` genauso und nimmt die ID aus der Datei:
>
> macOS / Linux:
>
> ```bash
> dataproduct publish orders.odps.yaml
> ```
>
> Windows (PowerShell):
>
> ```powershell
> dataproduct publish orders.odps.yaml
> ```
>
> Output:
>
> ```text
> ✅ Published data product successfully
> ```

### Datenprodukte verbinden

Auf der Plattform ist die Abhängigkeit ein **Access Agreement**: SKU Sales konsumiert den Output-Port `orders_v2` von Orders. Leg es direkt genehmigt an:

macOS / Linux:

```bash
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
```

Windows (PowerShell):

```powershell
@'
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
'@ | entropy-data access put sku_sales_consumes_orders --file -
```

Output:

```text
Access agreement 'sku_sales_consumes_orders' saved.
```

Mit Status `approved` und einem `startDate` in der Vergangenheit ist das Agreement aktiv.

> **Warum ein Access Agreement?**
>
> Ein Input-Port sagt: *„Ich lese diese Daten.“* Ein Access Agreement ergänzt: *„…und der Owner hat das für diesen Zweck genehmigt.“*
> So kennt der Owner von Orders alle Konsumenten und kann sie vor Änderungen warnen.

## Erkunden

### Plattform erkunden

Öffne die Weboberfläche ([app.entropy-data.com](https://app.entropy-data.com) oder [localhost:8081](http://localhost:8081)):

- Finde deine beiden Datenprodukte und ihre Output-Ports.
- Öffne den Kontrakt `sku_sales_per_year` und vergleiche ihn mit dem Data Contract Editor.
- Folge dem Access Agreement von SKU Sales zu Orders. Die Abhängigkeit aus [Contract-first entwerfen](https://learn.datacontract.com/de/contract-first/) ist jetzt ein Link in der Datenproduktkarte.
- Wie würde jemand ein Datenprodukt über SKUs finden, ohne zu wissen, dass es existiert?

![Die Liste der Datenprodukte: Orders mit zwei Output-Ports, SKU Sales mit einem](https://learn.datacontract.com/screenshots/publish-data-products.webp)

![SKU Sales konsumiert Orders: das Access Agreement als Link in der Datenproduktkarte](https://learn.datacontract.com/screenshots/publish-sku-sales-product.webp)

### Testergebnisse veröffentlichen

Die Plattform zeigt, ob ein Kontrakt *aktuell* eingehalten wird, wenn du ihr Testergebnisse schickst.
Führe deine Tests mit `--publish` und dem Test-Results-Endpunkt deines Hosts aus:

Cloud:

macOS / Linux:

```bash
datacontract test sku_sales_per_year.odcs.yaml --publish https://api.entropy-data.com/api/test-results
```

Windows (PowerShell):

```powershell
datacontract test sku_sales_per_year.odcs.yaml --publish https://api.entropy-data.com/api/test-results
```

Output:

```text
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:

macOS / Linux:

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

Windows (PowerShell):

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

Die CLI schickt deinen API Key nur mit, wenn die URL zum Host in `ENTROPY_DATA_HOST` gehört. Die Ergebnisse findest du auf der Seite des Kontrakts.

![Die Seite des Kontrakts mit den veröffentlichten Testergebnissen unter Data Quality](https://learn.datacontract.com/screenshots/publish-contract-test-results.webp)

**Solution: publish.sh**

```bash title=publish.sh
#!/bin/bash
# Publishes all workshop artifacts to Entropy Data.
# Requires ENTROPY_DATA_API_KEY and ENTROPY_DATA_HOST (set via .env in the repository root).
set -e
cd "$(dirname "$0")"
if [ -f ../../.env ]; then set -a; . ../../.env; set +a; fi

entropy-data connection test

# teams must exist before contracts/products that reference them as owner
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

entropy-data datacontracts put orders_v1 --file ../exercise1/orders_v1.odcs.yaml
entropy-data datacontracts put orders_v2 --file ../exercise2/orders_v2.odcs.yaml
entropy-data datacontracts put sku_sales_per_year --file ../exercise4/sku_sales_per_year.odcs.yaml
entropy-data datacontracts put orders_v2_consumer_sku_sales --file ../exercise6/orders_v2.consumer_sku_sales.odcs.yaml

entropy-data dataproducts put orders --file ../exercise3/orders.odps.yaml

# WORKAROUND for a bug in Entropy Data: ODPS input ports currently (wrongly) require a
# sourceSystemId customProperty - strip the inputPorts section right before publishing
# and model the dependency as an approved access agreement instead.
sed '/^inputPorts:/,/^outputPorts:/{/^outputPorts:/!d;}' ../exercise4/sku_sales_per_year.odps.yaml \
  | entropy-data dataproducts put sku_sales --file -

# status approved + a startDate in the past makes the agreement active (the active flag is computed)
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

# re-run all contract tests and publish the results to the configured Entropy Data host
export DATACONTRACT_POSTGRES_USERNAME=workshop
export DATACONTRACT_POSTGRES_PASSWORD=workshop
datacontract test ../exercise1/orders_v1.odcs.yaml --publish "${ENTROPY_DATA_HOST:-https://api.entropy-data.com}/api/test-results"
datacontract test ../exercise2/orders_v2.odcs.yaml --publish "${ENTROPY_DATA_HOST:-https://api.entropy-data.com}/api/test-results"
datacontract test ../exercise4/sku_sales_per_year.odcs.yaml --publish "${ENTROPY_DATA_HOST:-https://api.entropy-data.com}/api/test-results"
datacontract test ../exercise6/orders_v2.consumer_sku_sales.odcs.yaml --publish "${ENTROPY_DATA_HOST:-https://api.entropy-data.com}/api/test-results"

entropy-data datacontracts list
entropy-data dataproducts list
entropy-data access list
```

**Quick check:** Wohin gehört der Entropy Data API Key, wenn du in einem öffentlichen Fork arbeitest?

- In die getrackte .env, die ist für Zugangsdaten gedacht
- In eine Umgebungsvariable deiner Shell oder in CI in ein GitHub-Actions-Secret (correct)
- In den Datenkontrakt, neben den Server
- In die .env, aber vor dem Push wieder löschen

Richtig. Alles, was in ein öffentliches Repository committet wird, ist öffentlich, inklusive Historie. Secrets gehören in deine Umgebung oder in den Secret Store deines CI-Systems.

## Bonus

- **Testergebnisse aus der CI veröffentlichen.** Geh in deinem Fork auf GitHub zu **Settings → Secrets and variables → Actions** und leg ein Repository Secret `ENTROPY_DATA_API_KEY` an. Gib es im Workflow aus [CI/CD mit GitHub Actions](https://learn.datacontract.com/de/ci-cd/) an den Test-Schritt weiter und ergänze `--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
  ```

  Das geht nur mit der Cloud: Die Runner von GitHub erreichen deine lokale Community Edition nicht.
- **Git bleibt die Quelle der Wahrheit.** Schau dir `entropy-data datacontracts import-from-git --help` an. Wie würdest du Kontrakte bei jedem Merge auf `main` automatisch veröffentlichen?
