- name
- mta-descriptor
- description
- Invoke this skill to generate MTA deployment descriptor (mtad.yaml) for Cloud Foundry. Creates mtad.yaml with proper service bindings based on migration skills applied. Always invoke LAST after all other migration skills.
- disable-model-invocation
- false
- allowed-tools
- Read, Edit, Write, Bash, Grep, Glob
# MTA Descriptor Generation
Generate a Multi-Target Application deployment descriptor (`mtad.yaml`) for deploying Neo-migrated applications to SAP BTP Cloud Foundry.
## Usage
```
Generate an mtad.yaml for my migrated application
Create MTA descriptor with XSUAA and HANA services
```
## Overview
The MTA descriptor (`mtad.yaml`) defines:
- Application modules (Java app, approuter)
- Required Cloud Foundry services (XSUAA, HANA, Destination, etc.)
- Service bindings between modules and services
- Build and deployment parameters
This skill analyzes which migration skills were applied to your application and generates the appropriate `mtad.yaml` configuration.
## Prerequisites
> **Working directory:** This skill must run inside the `-cf-migration` copy of your app, created by `jakarta-java25-migration` or `neo-to-cf-migration-orchestrator`. If your current directory does not end in `-cf-migration`, switch to it before proceeding.
Before invoking this skill, ensure you have invoked all required migration skills:
1. **jakarta-java25-migration** - `Use the jakarta-java25-migration skill` (REQUIRED)
2. **sdk-replacement** - `Use the sdk-replacement skill` (REQUIRED)
3. Any feature skills that apply to your application
Also required:
- Application successfully compiles: `mvn clean compile`
- Understanding of which CF services your application requires
## Determine Required Services
Based on the migration skills you've applied, identify required CF services:
| Migration Skill Applied | Required CF Service | Service Type |
|------------------------|---------------------|--------------|
| authentication-xsuaa | XSUAA | `org.cloudfoundry.managed-service` |
| destinations | Destination | `org.cloudfoundry.managed-service` |
| connectivity-onpremise | Destination + Connectivity | `org.cloudfoundry.managed-service` |
| persistence-hana | HANA Schema | `com.sap.xs.hana-schema` |
| document-management-sdm | SDM | `org.cloudfoundry.managed-service` |
| mail-destinations | (uses Destination service) | - |
| keystore-credstore | Credential Store | `org.cloudfoundry.managed-service` |
| monitoring-logging | (built-in CF logging) | - |
| tomee-runtime | (requires TARGET_RUNTIME property) | - |
## MTA Templates
### Template Selection
Choose the appropriate base template:
| Scenario | Template | When to Use |
|----------|----------|-------------|
| Basic app without web authentication | [mtad-base.yaml](assets/mtad-base.yaml) | Internal services, background jobs, APIs with technical auth |
| App with XSUAA and Approuter | [mtad-with-approuter.yaml](assets/mtad-with-approuter.yaml) | User-facing web applications requiring authentication |
| TomEE application | Add `TARGET_RUNTIME: tomee` | Apps using EJB or requiring TomEE runtime |
### Precondition — `pom.xml` MUST configure `maven-war-plugin` with `<warName>${project.artifactId}</warName>`
> **This skill owns the WAR filename rule (next section), which only holds
> if `pom.xml` actually writes the WAR under `<artifactId>.war`.** Maven's
> default WAR filename is `<artifactId>-<version>.war`, which does NOT
> match what this skill writes into `mtad.yaml` (`target/<artifactId>.war`)
> — the deploy then fails with:
>
> > `Error retrieving MTA: Error building MTA Archive:`
> > `file path .../target/<artifactId>.war not found`
>
> The other migration skills (`sdk-replacement`, `tomee-runtime`, …) are
> dependency-management skills and do not own build packaging. Verify and,
> if needed, add the `<build>` block below to `pom.xml` **before** emitting
> the descriptor.
Required `<build>` block in `pom.xml`:
```xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<warName>${project.artifactId}</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
```
> **Why `3.4.0` specifically:** the default `maven-war-plugin:2.2` is
> incompatible with Java 25 and fails with `Cannot access defaults field
> of Properties`. The Common Issues section below has the same fix.
**Self-check before continuing** — run this from the project root; it
must print exactly `1`:
```bash
grep -A 10 'maven-war-plugin' pom.xml | grep -c '<warName>${project.artifactId}</warName>'
```
If the count is `0`, the plugin is missing or `<warName>` is set to
something else (often the leftover `<warName>ROOT</warName>` from older
templates). Fix `pom.xml` before emitting `mtad.yaml`; the build will
otherwise succeed but the descriptor and the WAR will disagree at deploy
time.
If `pom.xml` legitimately needs a different `<warName>` (e.g. the project
must serve at `/` and uses `<warName>ROOT</warName>`), update the
descriptor's `path:` to match — the rule is "descriptor follows pom",
not "pom follows descriptor".
### WAR filename rule — read it from `pom.xml`, write it literally
The templates below show `path: target/<artifactId>.war` as a placeholder.
**Before emitting `mtad.yaml`, open `pom.xml`, read the `<artifactId>`
value, and substitute it literally** — for a project whose `<artifactId>`
is `connectivity`, write `path: target/connectivity.war`.
Why this matters in concrete terms:
- `cf deploy` reads `path:` as a **literal filesystem path** under the
descriptor's directory. It does NOT evaluate Maven property
expressions, so a descriptor that says `path: target/${project.artifactId}.war`
fails at deploy time with `Error building MTA Archive: file path
target/${project.artifactId}.war not found`.
- `maven-war-plugin` in the shipped pom templates is configured with
`<warName>${project.artifactId}</warName>` — Maven writes a WAR named
after the artifactId (e.g. `target/connectivity.war`). The descriptor
must match what Maven actually writes, not a placeholder string.
**Consequence to communicate downstream:** `sap_java_buildpack_jakarta` always serves the app at `/` (ROOT context). It additionally registers the app at `/<warName>/` only if `web.xml` has a servlet mapped to `url-pattern: /`. Integration tests and approuter destinations should use `/` — not `/<artifactId>/` — unless the app explicitly maps a default servlet.
### Template: Basic Application (mtad-base.yaml)
For applications without user authentication:
```yaml
_schema-version: "3.3"
ID: my-app
version: 1.0.0
parameters:
parameters:
enable-parallel-deployments: true
modules:
- name: my-app-backend
type: java.tomcat
path: target/<artifactId>.war # ← substitute the literal artifactId from pom.xml
parameters:
memory: 1024M
disk-quota: 1024M
buildpack: sap_java_buildpack_jakarta
properties:
# Pin SapMachine JRE 25 explicitly. Always set this — never rely on the buildpack's
# implicit JRE choice, and keep maven.compiler.target in pom.xml at the same major
# version. Use SAPMachineJRE (bundled, offline) — NOT SAPMachineJDK (heavyweight,
# online-only).
JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
JBP_CONFIG_SAP_MACHINE_JRE: "{ version: 25.+ }"
TARGET_RUNTIME: tomcat
build-parameters:
builder: maven
build-result: target/*.war
provides:
- name: backend-api
properties:
url: ${default-url}
requires:
# Add service bindings here based on needs
- name: my-app-hana # If using persistence-hana
- name: my-app-destination # If using destinations
resources:
# Add required services here
```
### Template: Application with XSUAA (mtad-with-approuter.yaml)
For user-facing web applications:
```yaml
_schema-version: "3.2"
ID: my-app
version: 0.0.1
parameters:
enable-parallel-deployments: true
modules:
# Approuter module for authentication
- name: my-app-approuter
type: nodejs
path: approuter
parameters:
memory: 256M
disk-quota: 256M
routes:
- route: '${protocol}://my-app.${default-domain}'
protocol: http1
properties:
XS_APP_LOG_LEVEL: debug
TENANT_HOST_PATTERN: '(.*).cfapps.sap.hana.ondemand.com'
CF_NODEJS_LOGGING_LEVEL: "info"
requires:
- name: my-app-xsuaa
- name: my-app-java-app
group: destinations
properties:
name: backend-app-destination
url: '~{neo-app-url}'
forwardAuthToken: true
# Backend Java application
- name: my-app-backend
type: java.tomcat
path: target/<artifactId>.war # ← substitute the literal artifactId from pom.xml
parameters:
memory: 1024M
disk-quota: 1024M
buildpack: sap_java_buildpack_jakarta
properties:
ENABLE_SECURITY_JAVA_API_V2: true
# Pin SapMachine JRE 25 explicitly. Always set this — never rely on the buildpack's
# implicit JRE choice, and keep maven.compiler.target in pom.xml at the same major
# version. Use SAPMachineJRE (bundled, offline) — NOT SAPMachineJDK (heavyweight,
# online-only).
JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
JBP_CONFIG_SAP_MACHINE_JRE: "{ version: 25.+ }"
TARGET_RUNTIME: tomcat
SET_LOGGING_LEVEL: 'ROOT: INFO'
provides:
- name: my-app-java-app
properties:
neo-app-url: '${default-url}'
requires:
- name: my-app-xsuaa
- name: my-app-destination
resources:
- name: my-app-xsuaa
type: org.cloudfoundry.managed-service
parameters:
service: xsuaa
service-plan: application
path: ./xs-security.json
- name: my-app-destination
type: org.cloudfoundry.managed-service
parameters:
service: destination
service-plan: lite
```
> **Critical:** The Java backend `path` must point to the actual WAR file Maven produces — read the `<artifactId>` from `pom.xml` and substitute it literally (e.g. `path: target/connectivity.war` for a project whose `artifactId` is `connectivity`). See the "WAR filename rule" callout above this section. Note: `sap_java_buildpack_jakarta` always serves the app at `/` — the WAR filename does not affect the runtime context path.
> **Note on `type: nodejs` vs `type: approuter.nodejs`:** Use `type: nodejs` when deploying the approuter with your own `approuter/` directory and `package.json` (which is the standard pattern for migrated Neo applications). The type `approuter.nodejs` is for the "managed approuter" pattern which is used only in specific multi-tenant scenarios.
## Service Configuration Examples
Add these resource definitions based on services your application needs:
### XSUAA (Authentication)
```yaml
resources:
- name: ${app-name}-xsuaa
type: org.cloudfoundry.managed-service
parameters:
service: xsuaa
service-plan: application
path: ./xs-security.json # Created by authentication-xsuaa skill
```
**Module requires:**
```yaml
requires:
- name: ${app-name}-xsuaa
```
### Destination Service
```yaml
resources:
- name: ${app-name}-destination
type: org.cloudfoundry.managed-service
parameters:
service: destination
service-plan: lite
```
**Module requires:**
```yaml
requires:
- name: ${app-name}-destination
```
### Connectivity Service (On-Premise)
```yaml
resources:
- name: ${app-name}-connectivity
type: org.cloudfoundry.managed-service
parameters:
service: connectivity
service-plan: lite
```
**Module requires:**
```yaml
requires:
- name: ${app-name}-connectivity
- name: ${app-name}-destination # Connectivity requires Destination
```
### HANA Schema
> **CRITICAL — do NOT add `service: hana` or `service-plan: hdi-shared`
> under `type: com.sap.xs.hana-schema`.**
>
> ❌ Anti-pattern that breaks deploy:
> ```yaml
> - name: ${app-name}-hana
> type: com.sap.xs.hana-schema
> parameters:
> service: hana # ← WRONG for hana-schema
> service-plan: hdi-shared # ← WRONG for hana-schema
> ```
> `service:` and `service-plan:` are keys for the GENERIC
> `type: org.cloudfoundry.managed-service` (xsuaa, destination, credstore,
> sdm, …). `com.sap.xs.hana-schema` is a dedicated MTA resource type that
> already implies the broker, the service, and the plan. Setting them
> explicitly causes the controller to reject the create-service call:
>
> > `CF-UnprocessableEntity(10008): Invalid service plan. ... Ensure that
> > the service plan is visible in your current space ...`
```yaml
resources:
- name: ${app-name}-hana
type: com.sap.xs.hana-schema
```
> **NO `parameters:` block on the hana-schema resource.** Specifically, do
> NOT set `schema-name:`, `service:`, or `service-plan:` here. MTA's
> `com.sap.xs.hana-schema` type does not support any of these at the
> resource level — the deployer drops them with
> `Parameter(s) "{<name>-hana=[schema-name]}" are not supported in the
> specified scope, or referenced by any other entities. These parameters
> are not processed and will be lost`. The deploy still completes, but
> the warning is noisy and signals a malformed descriptor. The reference
> scenarios use only the two-line form above.
**Module requires:**
```yaml
requires:
- name: ${app-name}-hana
```
View on GitHub