Skip to main content

Deployment Repository Publishing

Publishing commits a generated remote-ansible package to a Git deployment repository, where a GitHub Actions workflow deploys it. This page explains what the review page sends, which repositories the server accepts, how a publish works, and how the deployment repository template consumes the package. Read it when you work on the publish action or set it up for a test server.

What publishing does​

On the review page, the Remote server (Ansible) target shows a Target name field. When publishing is configured, it also shows a Deployment repository field and a Deploy to <target> button. Both field values are remembered in the browser. The button sends the selection and the repository URL to POST /api/feature-model/deployment-package/publish. The server generates the package, commits it to <target-directory-root>/<target>/package/ on the configured branch of that repository, and answers with the commit. The client then downloads the same package as a ZIP file.

The publish and the download use the same generation request, and generation is deterministic, so the published files and the downloaded ZIP file are identical. When the repository refuses the publish, the client still downloads the package and shows the error. When the selection itself cannot be packaged, for example because it uses MySQL, the client shows one error card and downloads nothing.

GET /api/feature-model/deployment-package/publish-target tells the client whether publishing is configured, which default repository URL to prefill, and the branch and directory a publish uses. Without a usable configuration, the repository field and the button do not appear.

The target name is an exact identifier: 1 to 63 lowercase letters, digits, or hyphens, starting with a letter or a digit, for example test-vm. It names the package directory and the GitHub Environment the deployment runs in, so the server checks it instead of changing it. The field shows the Environment name and blocks both buttons while the name is invalid.

Configuration​

Publishing is configured under artemis.feature-model.deployment-repository. The committed application.yml switches it on and uses the project's deployment repository as the default URL.

PropertyMeaning
enabledSwitches publishing on
repository-urlDefault repository URL: prefilled in the field and used when a request has none; HTTPS, SSH, or file://, without credentials
allowed-repository-url-prefixesPrefixes every repository URL must start with; defaults to https://github.com/, git@github.com:, and ssh://git@github.com/
branchThe branch that receives the commits; defaults to main
target-directory-rootThe directory that holds one folder per target; defaults to deployments
expected-visibilityOptional public or private for a GitHub repository; the server refuses to push when it differs
author-name, author-emailThe author of the commits

Credentials are never configuration properties. The server reads them only from environment variables and never logs or returns them:

RemoteEnvironment variables
HTTPSFM_DEPLOYMENT_REPO_TOKEN, an access token
SSHFM_DEPLOYMENT_REPO_SSH_KEY_PATH, the path of a deploy key, and FM_DEPLOYMENT_REPO_KNOWN_HOSTS_PATH, the path of a known-hosts file

SSH uses strict host key checking with exactly this known-hosts file. file:// remotes need no credentials. The review page offers publishing when it is enabled and at least one transport is usable: the token is set, both SSH files are readable, or a file:// prefix is allowed.

Which repositories the server accepts​

A repository URL comes from the browser, and the server clones and pushes it with its own credentials. Two checks keep these credentials on the repositories they are meant for.

The URL policy runs before any Git operation, also for the default URL. The server trims the URL and refuses it when it contains whitespace, ?, #, or a .. segment, carries user information such as https://user@github.com/..., or does not start with an allowed prefix followed by a repository path. A file:// URL is accepted only when an operator adds a file:// prefix, which is what tests and local try-outs do.

The repository must also declare itself a deployment repository. The server reads deployment-repository.json at the root of the cloned branch before it changes anything:

{ "kind": "artemis-feature-model-deployment-repository", "packageContract": 1 }

packageContract must equal the major version of RuntimePackageConstants.REMOTE_ANSIBLE_PACKAGE_VERSION. A missing or unreadable file, another kind, or another contract refuses the publish, and nothing is committed or pushed.

How a publish works​

  1. The server resolves the repository URL from the request or the default, applies the URL policy, and checks the credential of the URL's transport.
  2. It clones the configured branch into a temporary directory, shallow and with only that branch. When the branch does not exist yet, it clones the default branch and creates the branch from it.
  3. It checks the marker file.
  4. It replaces the target's package directory completely with the generated files.
  5. When nothing changed, it stops and reports the existing commit as up to date.
  6. Otherwise it commits and pushes. It never force-pushes, so no existing commit can be lost.
  7. When the push fails because someone else pushed first, it starts again from a fresh clone, up to three attempts.

When expected-visibility is set, the server asks the GitHub API for the visibility of each repository once after startup, before the first push to it.

The deployment repository template​

Administrators create a deployment repository from the public template ls1intum/artemis-feature-model-deployment-template. Its README is the administrator guide. The template contains the marker file, a pinned Ansible toolchain, and two workflows:

  • Deploy package runs for every push to deployment that changes a package, and on demand. It finds the single changed target, checks the package against the marker, and deploys inside the GitHub Environment named after the target. Before it connects to the VM, it lists every missing connection value and package value in the run summary.
  • Check connection tests the SSH access to a target's VM and reports its host keys.

The workflow reads the package through a fixed set of paths: playbook.yml, requirements.yml, preflight.sh, inventory/hosts, inventory/group_vars/artemistests_without_*.yml, metadata/package-manifest.json, and metadata/env-references.json. Each entry of env-references.json carries the variable's kind and an administrator-facing description from the binding catalog, and the package README shows the same list as a table. A change to any of these paths or fields is a change of the package contract: raise the major version of REMOTE_ANSIBLE_PACKAGE_VERSION and the template's packageContract together.

GitHub holds workflows that read all secrets at once, so the template's workflow names every secret a package can reference. When the binding catalog gains a secret, add it to the template's deploy.yml as well; older copies then stop before deploying and ask their administrators to update the workflow.

Errors​

Publishing errors use codes that start with PUBLISH_:

CodeStatusMeaning
PUBLISH_NOT_CONFIGURED409Publishing is off, or the credential of the URL's transport is missing
PUBLISH_REQUIRES_REPOSITORY_URL400Neither the request nor the configuration names a repository
PUBLISH_REPOSITORY_NOT_ALLOWED400The URL violates the URL policy; the message lists the allowed prefixes
PUBLISH_WRONG_DEPLOYMENT_MODE400The request is not for remote-ansible
PUBLISH_REQUIRES_TARGET_NAME400The request has no target name
PUBLISH_INVALID_TARGET_NAME400The target name is not an exact identifier
PUBLISH_NOT_A_DEPLOYMENT_REPOSITORY409The marker file is missing or declares another kind or contract
PUBLISH_REJECTED409The repository rejected the push
PUBLISH_AUTH_FAILED502The repository refused the credential
PUBLISH_VISIBILITY_MISMATCH409The repository's visibility differs from expected-visibility

An invalid or unsupported selection fails with the same ARTIFACT_GENERATION_* errors as the download.

Try it locally​

You can publish to a local bare repository. Create one whose main branch holds the marker file:

git init --bare -b main /tmp/deployments.git
git clone /tmp/deployments.git /tmp/deployments-seed
printf '{ "kind": "artemis-feature-model-deployment-repository", "packageContract": 1 }\n' > /tmp/deployments-seed/deployment-repository.json
git -C /tmp/deployments-seed add deployment-repository.json
git -C /tmp/deployments-seed commit -m "add the deployment repository marker"
git -C /tmp/deployments-seed push origin main

Then start the server with this repository as the default and its directory as an allowed prefix:

./gradlew bootRun --args='--artemis.feature-model.deployment-repository.repository-url=file:///tmp/deployments.git --artemis.feature-model.deployment-repository.allowed-repository-url-prefixes=file:///tmp/'

In the Configurator, switch the database to PostgreSQL in the advanced tree, open the review page, choose Remote server (Ansible), enter a target name, and click Deploy to <target>. git --git-dir=/tmp/deployments.git log deployment shows the new commit.

Where it lives in the code​

PathResponsibility
export/web/DeploymentPackageResourceThe publish and publish-target endpoints
export/dto/DeploymentPackagePublishRequestThe publish request: generation request plus repository URL
export/service/DeploymentPackagePublishServiceGenerates the package and starts the publish
export/service/DeploymentRepositoryPublisherTarget name, marker check, clone, commit, push, and retries
export/service/DeploymentRepositoryUrlPolicyThe URL policy
export/service/DeploymentRepositorySshTransportSSH key and host key handling
export/service/DeploymentRepositoryPropertiesThe configuration properties
shared/exception/DeploymentRepositoryPublishExceptionThe PUBLISH_* errors

Paths are relative to src/main/java/de/tum/cit/aet/artemis/featuremodel/.

Search documentation