Automated cleanup of orphaned OpenStack cloud resources via GitHub Actions.
This action performs scheduled cleanup of orphaned resources in OpenStack clouds:
- ποΈ Kubernetes clusters
- ποΈ Heat stacks
- ποΈ Server instances
- ποΈ Network ports
- ποΈ Volumes
- π‘οΈ Protects in-use images
- ποΈ Removes old images
- uses: lfreleng-actions/openstack-cron-action@main
with:
openstack_cloud: 'vex'
clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
jenkins_urls: 'https://jenkins.example.org/releng https://jenkins.example.org/sandbox'- uses: lfreleng-actions/openstack-cron-action@main
with:
openstack_cloud: ${{ vars.OPENSTACK_CLOUD || 'vex' }}
clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
jenkins_urls: ${{ vars.JENKINS_URLS || '' }}
failure_notification_email: ${{ vars.FAILURE_NOTIFICATION_EMAIL || '' }}- uses: lfreleng-actions/openstack-cron-action@main
with:
# Required
openstack_cloud: 'vex'
clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
# Optional - Jenkins integration
jenkins_urls: 'https://jenkins.example.org/releng https://jenkins.example.org/sandbox'
# Optional - Cleanup flags (all default to 'true')
cleanup_k8s_clusters: 'true'
cleanup_stacks: 'true'
cleanup_servers: 'true'
cleanup_ports: 'true'
cleanup_volumes: 'true'
protect_images: 'true'
cleanup_images: 'true'
# Optional - Parameters
image_cleanup_age: '30' # days
port_cleanup_age: '30 minutes ago'
python_version: '3.11'
build_timeout: '10' # minutes| Input | Description | Example |
|---|---|---|
openstack_cloud |
OpenStack cloud name from clouds.yaml | vex |
clouds_yaml |
OpenStack clouds.yaml configuration | See below |
Default for clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
(base64 encoded)
| Input | Description | Default |
|---|---|---|
jenkins_urls |
Space-separated list of Jenkins URLs to check for active builds | '' |
| Input | Description | Default |
|---|---|---|
cleanup_k8s_clusters |
Enable K8s cluster cleanup | true |
cleanup_stacks |
Enable OpenStack stack cleanup | true |
cleanup_servers |
Enable server/instance cleanup | true |
cleanup_ports |
Enable port cleanup | true |
cleanup_volumes |
Enable volume cleanup | true |
protect_images |
Enable protection of in-use images | true |
cleanup_images |
Enable old image cleanup | true |
| Input | Description | Default |
|---|---|---|
image_cleanup_age |
Age in days for image cleanup | 30 |
port_cleanup_age |
Age for port cleanup | 30 minutes ago |
python_version |
Python version to use | 3.11 |
build_timeout |
Build timeout in minutes | 10 |
| Input | Description | Default |
|---|---|---|
failure_notification_email |
Email address(es) to notify on failure (comma-separated) | '' (no email) |
failure_notification_prefix |
Email subject prefix for failure notifications | [OpenStack Cleanup] |
| Output | Description |
|---|---|
cleanup_summary |
Summary of cleanup operations performed |
resources_cleaned |
Number of resources cleaned up |
cleanup_status |
Overall cleanup status (success/failure) |
For project-agnostic deployments, use repository variables to avoid hard-coding project-specific values:
Create .github/workflows/openstack-cleanup.yaml:
---
name: OpenStack Cleanup
on:
schedule:
# Run every hour
- cron: '0 * * * *'
workflow_dispatch:
inputs:
openstack_cloud:
description: 'OpenStack cloud name'
required: false
default: 'vex'
jobs:
cleanup:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Run OpenStack cleanup
uses: lfreleng-actions/openstack-cron-action@main
with:
openstack_cloud: ${{ inputs.openstack_cloud || vars.OPENSTACK_CLOUD || 'vex' }}
clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
jenkins_urls: ${{ vars.JENKINS_URLS || '' }}
failure_notification_email: ${{ vars.FAILURE_NOTIFICATION_EMAIL || '' }}
failure_notification_prefix: ${{ vars.NOTIFICATION_PREFIX || '[OpenStack Cleanup]' }}Set these repository variables (Settings β Secrets and variables β Actions β Variables):
| Variable | Example Value | Description |
|---|---|---|
JENKINS_URLS |
https://jenkins.example.org/releng https://jenkins.example.org/sandbox |
Jenkins URLs to check |
FAILURE_NOTIFICATION_EMAIL |
releng@example.org |
Email for failure alerts |
NOTIFICATION_PREFIX |
[MyProject] |
Email subject prefix |
OPENSTACK_CLOUD |
vex |
Cloud name (optional) |
Using GitHub CLI:
gh variable set JENKINS_URLS \
--body "https://jenkins.example.org/releng https://jenkins.example.org/sandbox" \
--repo yourorg/yourrepo
gh variable set FAILURE_NOTIFICATION_EMAIL \
--body "releng@example.org" \
--repo yourorg/yourrepo
gh variable set NOTIFICATION_PREFIX \
--body "[MyProject]" \
--repo yourorg/yourrepoFor simpler deployments or when hard-coding values is acceptable:
---
name: OpenStack Cleanup
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
jobs:
cleanup:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Run OpenStack cleanup
uses: lfreleng-actions/openstack-cron-action@main
with:
openstack_cloud: 'vex'
clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
jenkins_urls: 'https://jenkins.example.org/releng'
failure_notification_email: 'releng@example.org'-
Create your
clouds.yamlfile:clouds: vex: auth: auth_url: https://api.openstack.example.org:5000/v3 username: your-username password: your-password project_id: your-project-id user_domain_name: Default project_domain_name: Default region_name: ca-ymq-1
-
Base64 encode it:
base64 -w 0 < clouds.yaml -
Add as GitHub Secret named
OPENSTACK_CLOUDS_YAML
- Identifies orphaned Kubernetes clusters
- Checks Jenkins for active builds using the cluster
- Removes clusters not in active use
- Identifies orphaned Heat stacks
- Checks Jenkins for active builds using the stack
- Removes stacks not in active use
- Identifies orphaned server instances
- Checks Jenkins for active minions
- Removes servers not registered in Jenkins
- Identifies orphaned network ports
- Removes ports older than configured age (default: 30 minutes)
- Identifies available (unattached) volumes
- Removes volumes not in use
- Identifies CI-managed images (prefixed with "ZZCI - ")
- Sets protection flag to prevent accidental deletion
- Identifies images older than configured age (default: 30 days)
- Removes old, unprotected images
- OpenStack cloud with API access
- Valid
clouds.yamlconfiguration - Python 3.11+ (automatically installed)
- Dependencies (automatically installed):
lftools[openstack]python-openstackclientpython-heatclientpython-magnumclientkubernetesnietyq
When you set jenkins_urls, the action will:
- Check each Jenkins URL for active builds
- Identify resources in use by active builds
- Skip cleanup of resources in active use
- Clean up truly orphaned resources
This prevents accidental deletion of resources needed by running jobs.
For project-agnostic deployments reused across projects:
DO β :
- Use
vars.JENKINS_URLSinstead of hard-coding Jenkins URLs - Use
vars.FAILURE_NOTIFICATION_EMAILfor project-specific notifications - Use repository variables for any project-specific configuration
DON'T β:
- Hard-code project-specific values in workflow files
- Embed organization-specific Jenkins URLs directly
- Hard-code notification email addresses
Project A (OpenDaylight):
gh variable set JENKINS_URLS \
--body "https://jenkins.opendaylight.org/releng https://jenkins.opendaylight.org/sandbox" \
--repo opendaylight/releng-builder
gh variable set FAILURE_NOTIFICATION_EMAIL \
--body "releng+ODL@linuxfoundation.org" \
--repo opendaylight/releng-builderProject B (ONAP):
gh variable set JENKINS_URLS \
--body "https://jenkins.onap.org/ci" \
--repo onap/ci-management
gh variable set FAILURE_NOTIFICATION_EMAIL \
--body "onap-releng@lists.onap.org" \
--repo onap/ci-managementSame workflow file works for both! No modifications needed.
Standalone Scheduled Job (This action):
- Uses a
scheduletrigger for automatic recurring runs - Uses
workflow_dispatchfor manual testing - Does NOT use
workflow_call(not called by other workflows) - Independent from Gerrit integration
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:Reusable Workflow (If you need to call from other workflows):
- Would use
workflow_callinstead - Not applicable for this standalone cleanup job
Solution: Ensure your clouds.yaml secret contains the specified cloud name.
Solution: Verify your OpenStack credentials in clouds.yaml are correct.
Solution:
- Check that cleanup flags use
true - Verify resources meet age requirements
- Check Jenkins integration isn't protecting resources
Solution: Increase build_timeout input if cleanup takes longer than 10 minutes.
See CONTRIBUTING.md for development setup and guidelines.
Apache-2.0 - See LICENSE for details.
- Issues: GitHub Issues
- Documentation: This README
- Source: GitHub Repository
- packer-build-action - Build OpenStack images
- lftools Documentation
- OpenStack CLI
Maintained by: The Linux Foundation Release Engineering Team
The action supports email notifications on failure, matching the Jenkins behavior.
In the action inputs:
- uses: lfit/lfreleng-actions/openstack-cron-action@main
with:
openstack_cloud: 'vex'
clouds_yaml: ${{ secrets.OPENSTACK_CLOUDS_YAML }}
failure_notification_email: 'releng+ODL@linuxfoundation.org'
failure_notification_prefix: '[releng]'In the caller workflow (builder-new):
The workflow includes an email notification step that sends emails on failure using the dawidd6/action-send-mail action.
Required Secrets (in calling repository):
SMTP_USERNAME- SMTP authentication usernameSMTP_PASSWORD- SMTP authentication passwordOPENSTACK_CLOUDS_YAML- OpenStack credentials
Email Content:
- Subject:
[releng] repo-name - OpenStack Cleanup - Build #X - FAILED - Body: Includes repository, workflow, run details, and link to logs
- Recipients: Configurable via
failure_notification_emailinput
Default Behavior:
- Notifications fire on failure
- No emails sent on success
- Matches Jenkins
global-jjb-email-notificationbehavior
By default, the action runs in quiet mode with minimal output. To enable verbose debug logging:
- uses: lfreleng-actions/openstack-cron-action@main
with:
enable_debug: true # Enable verbose debug logging
# ... other inputsDebug mode output: Shows detailed information about each operation Quiet mode output (default): Shows summaries alone (e.g., "β Deleted 3 servers: prd-123, snd-456, bastion-gh-789")
The action automatically generates a cleanup summary that appears in the GitHub Actions UI:
### π§Ή OpenStack Cleanup Summary
**Cloud**: vex
**Status**: β
Completed
**Timestamp**: 2026-01-20 08:00:00 UTC
#### Resources Cleaned
- π K8s Clusters: 0 deleted
- π Heat Stacks: 1 deleted
- π₯οΈ Servers: 3 deleted
- π Ports: 8 deleted
- πΎ Volumes: 2 deleted
- π‘οΈ Images Protected: 150 images
- ποΈ Old Images: 5 deleted
**Total Resources Cleaned**: 19