Got it — let’s restart clean and design this properly, without overengineering.
This is a simple, robust hybrid model that is easy to explain, implement, and scale.
🚀 Hybrid RPM Deployment Model (Clean Design)
🎯 Core Idea
We use BUILD_NUMBER as the only shared identifier across all systems.
Everything else is derived from it.
🔄 End-to-End Flow
Tekton (Build Stage)
│
├── builds RPM
├── generates manifest.json
├── uploads BOTH to Artifactory under BUILD_NUMBER
└── outputs BUILD_NUMBER
│
▼
Harness (Orchestration)
│
├── receives BUILD_NUMBER
├── downloads manifest.json from Artifactory
├── extracts rpm_name
└── passes rpm_name to Ansible
│
▼
Ansible (Deployment)
│
├── uses rpm_name
└── downloads RPM from Artifactory
└── installs / upgrades
📦 Artifactory Layout (IMPORTANT)
Each build is isolated:
/artifactory/repo/my-app/
└── 111-7cf39ab-leonid-comet/
├── manifest.json
└── my-app-1.2.3.rpm
👉 Key rule: BUILD_NUMBER is the folder key
🧾 Manifest Contract
Example manifest.json
{
"build_number": "111-7cf39ab-leonid-comet",
"rpm_name": "my-app-1.2.3.rpm",
"branch": "leonid-comet",
"git_commit": "7cf39ab"
}
🏗️ Tekton + Gradle Stage
1. Build RPM
./gradlew buildRpm
2. Generate manifest (Gradle task)
task generateManifest {
doLast {
def buildNumber = System.getenv("BUILD_NUMBER")
def rpmName = "${project.name}-${version}.rpm"
def manifest = [
build_number: buildNumber,
rpm_name: rpmName,
branch: buildNumber.split('-').last(),
git_commit: buildNumber.split('-')[1]
]
file("${buildDir}/manifest.json").text =
groovy.json.JsonOutput.prettyPrint(
groovy.json.JsonOutput.toJson(manifest)
)
}
}
3. Upload to Artifactory
curl -u user:pass -T build/my-app-1.2.3.rpm \
"https://artifactory/repo/my-app/${BUILD_NUMBER}/"
curl -u user:pass -T build/manifest.json \
"https://artifactory/repo/my-app/${BUILD_NUMBER}/"
4. Output build number (Tekton result)
echo "$BUILD_NUMBER" > $(results.build_number.path)
⚙️ Harness Stage
1. Receive build number
- set_fact:
build_number: "{{ lookup('env', 'BUILD_NUMBER') }}"
2. Download manifest
- name: Fetch manifest
get_url:
url: "https://artifactory/repo/my-app/{{ build_number }}/manifest.json"
dest: /tmp/manifest.json
3. Parse manifest
- set_fact:
manifest: "{{ lookup('file', '/tmp/manifest.json') | from_json }}"
4. Extract RPM name
- set_fact:
rpm_name: "{{ manifest.rpm_name }}"
🧰 Ansible Stage
1. Download RPM
- name: Download RPM
get_url:
url: "https://artifactory/repo/my-app/{{ build_number }}/{{ rpm_name }}"
dest: "/tmp/{{ rpm_name }}"
2. Install RPM
- name: Install package
command: rpm -Uvh /tmp/{{ rpm_name }}
🧠 Why this design works
✅ 1. Fully deterministic
Everything is tied to:
BUILD_NUMBER
No guessing. No regex. No “latest”.
✅ 2. Safe for parallel builds
Each run is isolated:
111-xxx/
112-yyy/
113-zzz/
No collisions possible.
✅ 3. Clean separation of responsibilities
| System | Responsibility |
|---|---|
| Tekton | Build + publish artifacts |
| Artifactory | Source of truth |
| Harness | orchestration |
| Ansible | deployment only |
✅ 4. Easy rollback
Just redeploy:
BUILD_NUMBER = previous value
✅ 5. No tight coupling between tools
Ansible never knows:
- how RPM is named
- how build was created
- how Gradle works
🚫 What this avoids
❌ hardcoded filenames
❌ wildcard searching in Artifactory
❌ shared “latest” directories
❌ pipeline-dependent variables everywhere
🎯 Final architecture statement (for team discussion)
We standardize on BUILD_NUMBER as the single global artifact key.
Each build publishes a self-contained manifest and RPM into Artifactory under this key.
Deployment stages resolve artifacts strictly via the manifest, ensuring deterministic, concurrent-safe, and fully reproducible deployments.
Top comments (0)