On how to use Aeolus, please refer to the documentation.
Aeolus is a complementary tool to Artemis, the online platform for programming exercises. It is a domain-specific language for streamlining the configuration of continuous integration (CI) jobs for programming exercises. Aeolus provides a standard definition that is able to generate code for several targets, such as CLI, Jenkins, and Bamboo. This allows instructors to create custom CI jobs for their programming exercises without having to rewrite the configuration for each platform.
In the context of programming exercises, it is common to have a set of CI jobs that are executed for each student submission. These jobs typically include tasks such as compiling the code, running tests, and generating test results. In addition, there are often platform-specific tasks, such as setting up the environment, that need to be executed before
The idea is to define an input language, parse, and validate it and generate the CI configuration for multiple platforms (e.g. CLI, Bamboo, Jenkins, ...). And therefore be able to switch the CI platform without having to rewrite the configuration.
The system is still under development but already ready for use. We plan on supporting the following platforms:
- CLI
- Jenkins
- Bamboo
For now, we generate the jobs/build plans/scripts in the respective platform's language from two different input files, one for the set of jobs to be executed (called Windfile) and importable, reusable and shareable modular actionfiles.
A windfile looks like this:
api: v0.0.1
metadata:
name: example windfile
id: example-windfile
description: This is a windfile with an internal action
author: Andreas Resch
docker:
image: ls1tum/artemis-maven-template
tag: java17-20
volumes:
- ${WORKDIR}:/aeolus
gitCredentials: artemis_gitlab_admin_credentials
repositories:
aeolus:
url: https://github.com/ls1intum/Aeolus.git
branch: develop
path: aeolus
actions:
- name: set-java-container
script: set
- name: set-c-container
docker:
image: ghcr.io/ls1intum/artemis-c-docker
script: set
- name: internal-action
script: |
echo "This is an internal action"
- name: external-action
use: simple-action.yml
excludeDuring:
- working_time
- name: clean_up
script: |
rm -rf aeolus/
runAlways: true
An actionfile looks like this:
api: v0.0.1
metadata:
name: simple action
description: This is an action with a simple script
author:
name: Andreas Resch
email: [email protected]
steps:
- name: hello-world
parameters:
WHO_TO_GREET: "world"
excludeDuring:
- preparation
script: |
echo "Hello ${WHO_TO_GREET}"
The generated CLI script for the above example would look like this:
python main.py -dev generate -t cli -i windfile.yaml
#!/usr/bin/env bash
set -e
export AEOLUS_INITIAL_DIRECTORY=${PWD}
export REPOSITORY_URL="https://github.com/ls1intum/Aeolus.git"
setjavacontainer () {
echo '⚙️ executing setjavacontainer'
set
}
setccontainer () {
echo '⚙️ executing setccontainer'
set
}
internalaction () {
echo '⚙️ executing internalaction'
echo "This is an internal action"
}
externalaction_ () {
local _current_lifecycle="${1}"
if [[ "${_current_lifecycle}" == "working_time" ]]; then
echo "⚙️ skipping externalaction_ because it is excluded during working_time"
return 0
fi
if [[ "${_current_lifecycle}" == "preparation" ]]; then
echo "⚙️ skipping externalaction_ because it is excluded during preparation"
return 0
fi
echo '⚙️ executing externalaction_'
export WHO_TO_GREET="world"
echo "Hello ${WHO_TO_GREET}"
}
clean_up () {
echo '⚙️ executing clean_up'
rm -rf aeolus/
}
final_aeolus_post_action () {
set +e # from now on, we don't exit on errors
echo '⚙️ executing final_aeolus_post_action'
cd "${AEOLUS_INITIAL_DIRECTORY}"
clean_up "${_current_lifecycle}"
}
main () {
if [[ "${1}" == "aeolus_sourcing" ]]; then
return 0 # just source to use the methods in the subshell, no execution
fi
local _current_lifecycle="${1}"
local _script_name
_script_name=${BASH_SOURCE[0]:-$0}
trap final_aeolus_post_action EXIT
cd "${AEOLUS_INITIAL_DIRECTORY}"
bash -c "source ${_script_name} aeolus_sourcing; setjavacontainer \"${_current_lifecycle}\""
cd "${AEOLUS_INITIAL_DIRECTORY}"
bash -c "source ${_script_name} aeolus_sourcing; setccontainer \"${_current_lifecycle}\""
cd "${AEOLUS_INITIAL_DIRECTORY}"
bash -c "source ${_script_name} aeolus_sourcing; internalaction \"${_current_lifecycle}\""
cd "${AEOLUS_INITIAL_DIRECTORY}"
bash -c "source ${_script_name} aeolus_sourcing; externalaction_ \"${_current_lifecycle}\""
}
main "${@}"
And the generated Jenkinsfile would look like this:
python main.py -dev generate -t jenkins -i windfile.yaml
pipeline {
agent {
docker {
image 'ls1tum/artemis-maven-template:java17-20'
args '-v ${PWD}:/aeolus '
}
}
parameters {
string(name: 'current_lifecycle', defaultValue: 'working_time', description: 'The current stage')
}
environment {
REPOSITORY_URL = 'https://github.com/ls1intum/Aeolus.git'
}
stages {
stage('aeolus') {
steps {
dir('aeolus') {
checkout([$class: 'GitSCM',
branches: [[name: 'develop']],
doGenerateSubmoduleConfigurations: false,
userRemoteConfigs: [[
credentialsId: 'artemis_gitlab_admin_credentials',
name: 'aeolus',
url: "${REPOSITORY_URL}"
]]
])
}
}
}
stage('set-java-container') {
agent {
docker {
image 'ls1tum/artemis-maven-template:java17-20'
}
}
steps {
sh '''
set
'''
}
}
stage('set-c-container') {
agent {
docker {
image 'ghcr.io/ls1intum/artemis-c-docker:latest'
}
}
steps {
sh '''
set
'''
}
}
stage('internal-action') {
agent {
docker {
image 'ls1tum/artemis-maven-template:java17-20'
}
}
steps {
sh '''
echo "This is an internal action"
'''
}
}
stage('external-action_0') {
agent {
docker {
image 'ls1tum/artemis-maven-template:java17-20'
}
}
when {
allOf {
expression { params.current_lifecycle != 'working_time' }
expression { params.current_lifecycle != 'preparation' }
}
}
environment {
WHO_TO_GREET = 'world'
}
steps {
sh '''
echo "Hello ${WHO_TO_GREET}"
'''
}
}
}
post {
always {
sh '''
rm -rf aeolus/
'''
cleanWs()
}
}
}
And the generated Bamboo YAML specs would look like this:
python main.py -dev generate -t bamboo -i windfile.yaml
--- !!com.atlassian.bamboo.specs.util.BambooSpecProperties
rootEntity: !!com.atlassian.bamboo.specs.api.model.plan.PlanProperties
description: This is a windfile with an internal action
enabled: true
key:
key: WINDFILE
name: windfile
oid: null
pluginConfigurations:
- !!com.atlassian.bamboo.specs.api.model.plan.configuration.ConcurrentBuildsProperties
maximumNumberOfConcurrentBuilds: 1
useSystemWideDefault: true
dependenciesProperties:
childPlans: []
dependenciesConfigurationProperties:
blockingStrategy: NONE
enabledForBranches: true
requireAllStagesPassing: false
labels: []
notifications: []
planBranchConfiguration: null
planBranchManagementProperties:
branchIntegrationProperties:
enabled: false
gatekeeper: false
integrationBranch: null
pushOn: false
createPlanBranch:
matchingPattern: null
trigger: MANUAL
defaultTrigger: null
deletePlanBranch:
removeDeletedFromRepository: false
removeDeletedFromRepositoryPeriod: !!java.time.Duration 'PT168H'
removeInactiveInRepository: false
removeInactiveInRepositoryPeriod: !!java.time.Duration 'PT720H'
issueLinkingEnabled: true
notificationStrategy: NOTIFY_COMMITTERS
triggeringOption: INHERITED
project:
description: |-
This is a windfile with an internal action
---created using aeolus
key:
key: EXAMPLE
name: example windfile
oid: null
repositories: []
repositoryStoredSpecsData: null
sharedCredentials: []
variables: []
repositories:
- repositoryDefinition: !!com.atlassian.bamboo.specs.model.repository.git.GitRepositoryProperties
description: null
name: aeolus
oid: null
parent: null
project: null
repositoryViewerProperties: null
authenticationProperties: !!com.atlassian.bamboo.specs.model.repository.git.SharedCredentialsAuthenticationProperties
sharedCredentials:
name: artemis_gitlab_admin_credentials
oid: null
scope: GLOBAL
branch: develop
commandTimeout: !!java.time.Duration 'PT3H'
fetchWholeRepository: false
sshKeyAppliesToSubmodules: false
url: https://github.com/ls1intum/Aeolus.git
useLfs: false
useRemoteAgentCache: false
useShallowClones: true
useSubmodules: false
vcsChangeDetection:
changesetFilterPatternRegex: null
commitIsolationEnabled: false
configuration: {}
filterFilePatternOption: NONE
filterFilePatternRegex: null
maxRetries: 5
quietPeriod: !!java.time.Duration 'PT10S'
quietPeriodEnabled: false
verboseLogs: false
repositoryBranches: []
repositoryStoredSpecsData: null
stages:
- description: ''
finalStage: false
jobs:
- description: ''
enabled: true
key:
key: CHECKOUT1
name: Checkout
oid: null
pluginConfigurations: []
artifactSubscriptions: []
artifacts: []
cleanWorkingDirectory: false
dockerConfiguration:
dockerRunArguments: []
enabled: false
image: null
volumes: {}
finalTasks: []
requirements: []
tasks:
- !!com.atlassian.bamboo.specs.model.task.VcsCheckoutTaskProperties
conditions: []
description: Checkout Default Repository
enabled: true
requirements: []
checkoutItems:
- defaultRepository: false
path: aeolus
repository:
name: aeolus
oid: null
cleanCheckout: true
manualStage: false
name: Checkout
- description: ''
finalStage: false
jobs:
- description: ''
enabled: true
key:
key: SETJAVACONTAINER1
name: set-java-container
oid: null
pluginConfigurations: []
artifactSubscriptions: []
artifacts: []
cleanWorkingDirectory: false
dockerConfiguration:
dockerRunArguments: []
enabled: true
image: ls1tum/artemis-maven-template:java17-20
volumes:
${bamboo.working.directory}: /aeolus
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
finalTasks: []
requirements: []
tasks:
- !!com.atlassian.bamboo.specs.model.task.ScriptTaskProperties
conditions: []
description: set-java-container
enabled: true
requirements: []
argument: null
body: set
environmentVariables: null
interpreter: SHELL
location: INLINE
path: null
workingSubdirectory: null
manualStage: false
name: setjavacontainer
- description: ''
finalStage: false
jobs:
- description: ''
enabled: true
key:
key: SETCCONTAINER2
name: set-c-container
oid: null
pluginConfigurations: []
artifactSubscriptions: []
artifacts: []
cleanWorkingDirectory: false
dockerConfiguration:
dockerRunArguments: []
enabled: true
image: ghcr.io/ls1intum/artemis-c-docker:latest
volumes:
${bamboo.working.directory}: ${bamboo.working.directory}
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
finalTasks: []
requirements: []
tasks:
- !!com.atlassian.bamboo.specs.model.task.ScriptTaskProperties
conditions: []
description: set-c-container
enabled: true
requirements: []
argument: null
body: set
environmentVariables: null
interpreter: SHELL
location: INLINE
path: null
workingSubdirectory: null
manualStage: false
name: setccontainer
- description: ''
finalStage: false
jobs:
- description: ''
enabled: true
key:
key: INTERNALACTION3
name: internal-action
oid: null
pluginConfigurations: []
artifactSubscriptions: []
artifacts: []
cleanWorkingDirectory: false
dockerConfiguration:
dockerRunArguments: []
enabled: true
image: ls1tum/artemis-maven-template:java17-20
volumes:
${bamboo.working.directory}: /aeolus
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
finalTasks: []
requirements: []
tasks:
- !!com.atlassian.bamboo.specs.model.task.ScriptTaskProperties
conditions: []
description: internal-action
enabled: true
requirements: []
argument: null
body: |
echo "This is an internal action"
environmentVariables: null
interpreter: SHELL
location: INLINE
path: null
workingSubdirectory: null
manualStage: false
name: internalaction
- description: ''
finalStage: false
jobs:
- description: ''
enabled: true
key:
key: EXTERNALACTION04
name: external-action_0
oid: null
pluginConfigurations: []
artifactSubscriptions: []
artifacts: []
cleanWorkingDirectory: false
dockerConfiguration:
dockerRunArguments: []
enabled: true
image: ls1tum/artemis-maven-template:java17-20
volumes:
${bamboo.working.directory}: /aeolus
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
finalTasks: []
requirements: []
tasks:
- !!com.atlassian.bamboo.specs.model.task.ScriptTaskProperties
conditions: []
description: dummy task to prevent wrong result of build plan run
enabled: true
requirements: []
argument: null
body: echo "⚙️ Executing external-action_0 if stage is correct"
environmentVariables: null
interpreter: SHELL
location: INLINE
path: null
workingSubdirectory: null
- !!com.atlassian.bamboo.specs.model.task.ScriptTaskProperties
conditions:
- !!com.atlassian.bamboo.specs.api.model.plan.condition.AnyConditionProperties
atlassianPlugin:
completeModuleKey: com.atlassian.bamboo.plugins.bamboo-conditional-tasks:variableCondition
configuration:
variable: lifecycle_stage
operation: matches
value: ^.*[^(preparation)][^(working_time)].*
description: external-action_0
enabled: true
requirements: []
argument: null
body: echo "Hello ${WHO_TO_GREET}"
environmentVariables: WHO_TO_GREET=world
interpreter: SHELL
location: INLINE
path: null
workingSubdirectory: null
manualStage: false
name: externalaction0
- description: ''
finalStage: false
jobs:
- description: ''
enabled: true
key:
key: CLEANUP5
name: clean_up
oid: null
pluginConfigurations: []
artifactSubscriptions: []
artifacts: []
cleanWorkingDirectory: false
dockerConfiguration:
dockerRunArguments: []
enabled: true
image: ls1tum/artemis-maven-template:java17-20
volumes:
${bamboo.working.directory}: /aeolus
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
finalTasks:
- !!com.atlassian.bamboo.specs.model.task.ScriptTaskProperties
conditions: []
description: clean_up
enabled: true
requirements: []
argument: null
body: |
rm -rf aeolus/
environmentVariables: null
interpreter: SHELL
location: INLINE
path: null
workingSubdirectory: null
requirements: []
tasks: []
manualStage: false
name: cleanup
triggers: []
variables:
- createOnly: false
name: lifecycle_stage
value: working_time
specModelVersion: 9.4.2
...
The windfile is the main input file for the Aeolus system. It contains the configuration for the CI jobs and references to actionfiles. The yaml specification for the windfile can be found here.
In a windfile, you can define jobs of different types. Currently, there are four types of jobs:
script
: A job that is defined in the windfile itself
- name: script-action
script: |
echo "This is an internal action"
external
: A job that is defined in an actionfile
- name: external-action
use: simple-action.yml
file
: A job that includes a file e.g. a bash script
- name: file-action
file: file-action.sh
platform
: A job that includes a platform-specific script, used to prepare the CI system for the eventual execution of the other jobs or platform specific actions. The example shown below is translated into a TestParser task in Bamboo which parses the test results of the other actions, this is only needed in Bamboo.
- name: platform-action
parameters:
ignore_time: false
test_results: '**/test-results/test/*.xml'
platform: bamboo
kind: junit
runAlways: true
Every type of job can have the following properties:
excludeDuring
: A list of lifecycles during which the job should be excluded from execution, possible options are:preparation
working_time
post_deadline
evaluation
all
Feature | CLI/Bash | Jenkins | Bamboo |
---|---|---|---|
simple script generation | ✅ | ✅ | ✅ |
usage of environment variables | ✅ | ✅ | ✅ |
lifecycle parameter | ✅ | ✅ | ✅ |
Feature | CLI/Bash | Jenkins | Bamboo |
---|---|---|---|
build trigger | ✅ | ✅ | ✅ |
single docker image | ✅ | ✅ | ✅ |
multiple docker images | ❌ | ✅ | ✅ |
translating back to Aeolus | ❌ | ❌ | ✅ |
publishing results/artifacts | ✅ | ✅ | ✅ |
If you have build plans in Bamboo and want to migrate away, or simply edit these plans, aeolus can help you.
You can use the translate
command to translate a build plan into a windfile. This windfile can then be used to
generate the build plan again, or to edit it and generate a new build plan.
To be able to translate a build plan, you need access to it's definition using an access token. You can create an
access token in Bamboo under Profile
-> Personal access tokens
. You can then use this token to translate the build plan
with the CLI tool using the following command:
python main.py translate -k <bamboo-build-plan-key> --url <bamboo-url> -t <bamboo-token>
The tool will connect to bamboo, retrieve the build plan and translate it into a windfile. The windfile will be printed to stdout.