Some checks failed
particle-os CI / Test particle-os (push) Failing after 1s
particle-os CI / Integration Test (push) Has been skipped
particle-os CI / Security & Quality (push) Failing after 1s
Test particle-os Basic Functionality / test-basic (push) Failing after 1s
Tests / test (1.21.x) (push) Failing after 2s
Tests / test (1.22.x) (push) Failing after 1s
particle-os CI / Build and Release (push) Has been skipped
662 lines
17 KiB
Markdown
662 lines
17 KiB
Markdown
# CI/CD Integration Guide for particle-os
|
|
|
|
particle-os is designed to be a first-class CI/CD tool for OS image building. This guide shows you how to integrate particle-os into any CI/CD system.
|
|
|
|
## 🚀 Why particle-os is CI/CD Friendly
|
|
|
|
### **Built-in CI/CD Features:**
|
|
- ✅ **Non-interactive mode** - No prompts, pure automation
|
|
- ✅ **JSON output** - Machine-readable build results
|
|
- ✅ **Quiet mode** - Suppressed non-essential output
|
|
- ✅ **Proper exit codes** - Standard status codes for CI systems
|
|
- ✅ **Structured output** - Predictable, parseable results
|
|
- ✅ **Artifact management** - Organized output files
|
|
|
|
### **CI/CD Usage Pattern:**
|
|
```bash
|
|
# Basic CI/CD build
|
|
particle-os build --json --quiet recipes/debian-server.yml
|
|
|
|
# With custom output and cleanup
|
|
particle-os build --json --quiet --output /artifacts/server.img --clean recipes/debian-server.yml
|
|
```
|
|
|
|
## 🔧 GitHub Actions Integration
|
|
|
|
### **Basic OS Image Build Workflow:**
|
|
|
|
```yaml
|
|
# .github/workflows/build-os.yml
|
|
name: Build OS Images
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths: ['recipes/**', 'src/**']
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
build-images:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
recipe: [debian-server, debian-desktop, debian-gaming]
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v4
|
|
with:
|
|
go-version: '1.21'
|
|
|
|
- name: Build particle-os
|
|
run: |
|
|
cd bib
|
|
go build -o particle-os cmd/particle_os/main.go
|
|
|
|
- name: Build OS Image
|
|
run: |
|
|
sudo ./bib/particle-os build --json --quiet --clean recipes/${{ matrix.recipe }}.yml
|
|
|
|
- name: Parse build results
|
|
id: build-results
|
|
run: |
|
|
BUILD_OUTPUT=$(sudo ./bib/particle-os build --json --quiet --clean recipes/${{ matrix.recipe }}.yml)
|
|
echo "build_output<<EOF" >> $GITHUB_OUTPUT
|
|
echo "$BUILD_OUTPUT" >> $GITHUB_OUTPUT
|
|
echo "EOF" >> $GITHUB_OUTPUT
|
|
|
|
# Extract success status
|
|
SUCCESS=$(echo "$BUILD_OUTPUT" | jq -r '.success')
|
|
echo "success=$SUCCESS" >> $GITHUB_OUTPUT
|
|
|
|
# Extract image path
|
|
IMAGE_PATH=$(echo "$BUILD_OUTPUT" | jq -r '.image_path')
|
|
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
|
|
|
|
- name: Upload artifacts
|
|
if: steps.build-results.outputs.success == 'true'
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ${{ matrix.recipe }}-images
|
|
path: |
|
|
${{ steps.build-results.outputs.image_path }}
|
|
/tmp/particle-os-build/output/*.raw
|
|
/tmp/particle-os-build/output/*.qcow2
|
|
/tmp/particle-os-build/output/*.vmdk
|
|
retention-days: 30
|
|
|
|
- name: Build summary
|
|
run: |
|
|
echo "## Build Results for ${{ matrix.recipe }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Status**: ${{ steps.build-results.outputs.success }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Image**: ${{ steps.build-results.outputs.image_path }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Recipe**: ${{ matrix.recipe }}" >> $GITHUB_STEP_SUMMARY
|
|
```
|
|
|
|
### **Advanced Multi-Stage Workflow:**
|
|
|
|
```yaml
|
|
# .github/workflows/advanced-os-build.yml
|
|
name: Advanced OS Image Pipeline
|
|
|
|
on:
|
|
push:
|
|
tags: ['v*']
|
|
|
|
jobs:
|
|
validate:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Validate recipes
|
|
run: |
|
|
cd bib && go build -o particle-os cmd/particle_os/main.go
|
|
cd ..
|
|
for recipe in recipes/*.yml; do
|
|
echo "Validating $recipe..."
|
|
./bib/particle-os validate "$recipe"
|
|
done
|
|
|
|
build-base:
|
|
needs: validate
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Build base images
|
|
run: |
|
|
cd bib && go build -o particle-os cmd/particle_os/main.go
|
|
cd ..
|
|
sudo ./bib/particle-os build --json --quiet --clean recipes/debian-minimal.yml
|
|
|
|
build-variants:
|
|
needs: [validate, build-base]
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
variant: [server, desktop, gaming]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Build variant
|
|
run: |
|
|
cd bib && go build -o particle-os cmd/particle_os/main.go
|
|
cd ..
|
|
sudo ./bib/particle-os build --json --quiet --clean recipes/debian-${{ matrix.variant }}.yml
|
|
|
|
test-images:
|
|
needs: build-variants
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Download artifacts
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
pattern: debian-*-images
|
|
|
|
- name: Test image boot
|
|
run: |
|
|
# Basic image validation
|
|
for img in *.qcow2; do
|
|
echo "Testing $img..."
|
|
qemu-img info "$img"
|
|
# Add more validation as needed
|
|
done
|
|
|
|
release:
|
|
needs: test-images
|
|
runs-on: ubuntu-latest
|
|
if: startsWith(github.ref, 'refs/tags/')
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Download artifacts
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
pattern: debian-*-images
|
|
|
|
- name: Create release
|
|
uses: softprops/action-gh-release@v1
|
|
with:
|
|
files: |
|
|
debian-*-images/*.qcow2
|
|
debian-*-images/*.raw
|
|
debian-*-images/*.vmdk
|
|
body: |
|
|
## OS Images Built with particle-os
|
|
|
|
This release contains Debian-based OS images built using particle-os.
|
|
|
|
### Available Images:
|
|
- **debian-server**: Server-optimized Debian
|
|
- **debian-desktop**: Desktop Debian with GNOME
|
|
- **debian-gaming**: Gaming-optimized Debian
|
|
|
|
### Build Details:
|
|
- Built with particle-os v${{ github.ref_name }}
|
|
- Base: Debian Trixie
|
|
- Formats: raw, qcow2, vmdk
|
|
```
|
|
|
|
## 🔧 GitLab CI Integration
|
|
|
|
### **Basic GitLab CI Pipeline:**
|
|
|
|
```yaml
|
|
# .gitlab-ci.yml
|
|
stages:
|
|
- validate
|
|
- build
|
|
- test
|
|
- deploy
|
|
|
|
variables:
|
|
WORK_DIR: "/tmp/particle-os-build"
|
|
|
|
validate-recipes:
|
|
stage: validate
|
|
script:
|
|
- cd bib && go build -o particle-os cmd/particle_os/main.go
|
|
- cd ..
|
|
- for recipe in recipes/*.yml; do
|
|
echo "Validating $recipe..."
|
|
./bib/particle-os validate "$recipe"
|
|
done
|
|
artifacts:
|
|
paths:
|
|
- bib/particle-os
|
|
expire_in: 1 hour
|
|
|
|
build-server:
|
|
stage: build
|
|
script:
|
|
- sudo ./bib/particle-os build --json --quiet --clean recipes/debian-server.yml
|
|
artifacts:
|
|
paths:
|
|
- /tmp/particle-os-build/output/
|
|
expire_in: 1 week
|
|
dependencies:
|
|
- validate-recipes
|
|
|
|
build-desktop:
|
|
stage: build
|
|
script:
|
|
- sudo ./bib/particle-os build --json --quiet --clean recipes/debian-desktop.yml
|
|
artifacts:
|
|
paths:
|
|
- /tmp/particle-os-build/output/
|
|
expire_in: 1 week
|
|
dependencies:
|
|
- validate-recipes
|
|
|
|
test-images:
|
|
stage: test
|
|
script:
|
|
- echo "Testing built images..."
|
|
- for img in /tmp/particle-os-build/output/*.qcow2; do
|
|
echo "Validating $img..."
|
|
qemu-img info "$img"
|
|
done
|
|
dependencies:
|
|
- build-server
|
|
- build-desktop
|
|
|
|
deploy-images:
|
|
stage: deploy
|
|
script:
|
|
- echo "Deploying images to artifact storage..."
|
|
- # Add your deployment logic here
|
|
dependencies:
|
|
- test-images
|
|
only:
|
|
- main
|
|
```
|
|
|
|
## 🔧 Jenkins Integration
|
|
|
|
### **Jenkins Pipeline Script:**
|
|
|
|
```groovy
|
|
// Jenkinsfile
|
|
pipeline {
|
|
agent any
|
|
|
|
environment {
|
|
WORKSPACE_DIR = '/tmp/particle-os-build'
|
|
RECIPES_DIR = 'recipes'
|
|
}
|
|
|
|
stages {
|
|
stage('Setup') {
|
|
steps {
|
|
sh 'cd bib && go build -o particle-os cmd/particle_os/main.go'
|
|
sh 'cd ..'
|
|
}
|
|
}
|
|
|
|
stage('Validate Recipes') {
|
|
steps {
|
|
script {
|
|
def recipes = sh(
|
|
script: "find ${RECIPES_DIR} -name '*.yml' -type f",
|
|
returnStdout: true
|
|
).trim().split('\n')
|
|
|
|
for (recipe in recipes) {
|
|
echo "Validating ${recipe}..."
|
|
sh "./bib/particle-os validate ${recipe}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Build Images') {
|
|
parallel {
|
|
stage('Build Server') {
|
|
steps {
|
|
sh '''
|
|
sudo ./bib/particle-os build --json --quiet --clean recipes/debian-server.yml
|
|
# Parse JSON output for Jenkins
|
|
BUILD_OUTPUT=$(sudo ./bib/particle-os build --json --quiet --clean recipes/debian-server.yml)
|
|
echo "BUILD_OUTPUT=${BUILD_OUTPUT}" > build.env
|
|
'''
|
|
archiveArtifacts artifacts: 'build.env'
|
|
}
|
|
}
|
|
|
|
stage('Build Desktop') {
|
|
steps {
|
|
sh '''
|
|
sudo ./bib/particle-os build --json --quiet --clean recipes/debian-desktop.yml
|
|
BUILD_OUTPUT=$(sudo ./bib/particle-os build --json --quiet --clean recipes/debian-desktop.yml)
|
|
echo "BUILD_OUTPUT=${BUILD_OUTPUT}" > build.env
|
|
'''
|
|
archiveArtifacts artifacts: 'build.env'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Test Images') {
|
|
steps {
|
|
sh '''
|
|
echo "Testing built images..."
|
|
for img in /tmp/particle-os-build/output/*.qcow2; do
|
|
echo "Validating $img..."
|
|
qemu-img info "$img"
|
|
done
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Archive Artifacts') {
|
|
steps {
|
|
archiveArtifacts artifacts: '/tmp/particle-os-build/output/**/*'
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
always {
|
|
cleanWs()
|
|
}
|
|
success {
|
|
echo "All OS images built successfully!"
|
|
}
|
|
failure {
|
|
echo "OS image build failed!"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🔧 Generic CI/CD Integration
|
|
|
|
### **Shell Script for Any CI/CD System:**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# build-os-images.sh - Generic CI/CD script for particle-os
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
RECIPE_DIR="recipes"
|
|
OUTPUT_DIR="/artifacts"
|
|
BUILD_LOG="/tmp/build.log"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log() {
|
|
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
|
|
}
|
|
|
|
warn() {
|
|
echo -e "${YELLOW}[$(date +'%Y-%m %H:%M:%S')] WARNING: $1${NC}"
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
|
exit 1
|
|
}
|
|
|
|
# Build particle-os binary
|
|
build_particle_os() {
|
|
log "Building particle-os binary..."
|
|
cd bib
|
|
if ! go build -o particle-os cmd/particle_os/main.go; then
|
|
error "Failed to build particle-os binary"
|
|
fi
|
|
cd ..
|
|
log "particle-os binary built successfully"
|
|
}
|
|
|
|
# Validate all recipes
|
|
validate_recipes() {
|
|
log "Validating all recipes..."
|
|
local failed_recipes=()
|
|
|
|
for recipe in "$RECIPE_DIR"/*.yml; do
|
|
if [[ -f "$recipe" ]]; then
|
|
log "Validating $recipe..."
|
|
if ! ./bib/particle-os validate "$recipe"; then
|
|
failed_recipes+=("$recipe")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ ${#failed_recipes[@]} -gt 0 ]]; then
|
|
error "Recipe validation failed for: ${failed_recipes[*]}"
|
|
fi
|
|
|
|
log "All recipes validated successfully"
|
|
}
|
|
|
|
# Build OS image from recipe
|
|
build_image() {
|
|
local recipe="$1"
|
|
local recipe_name=$(basename "$recipe" .yml)
|
|
|
|
log "Building image from recipe: $recipe_name"
|
|
|
|
# Build with CI/CD flags
|
|
local build_output
|
|
if ! build_output=$(sudo ./bib/particle-os build --json --quiet --clean "$recipe" 2>&1); then
|
|
error "Build failed for $recipe_name"
|
|
fi
|
|
|
|
# Parse JSON output
|
|
local success
|
|
local image_path
|
|
local exit_code
|
|
|
|
success=$(echo "$build_output" | jq -r '.success // false')
|
|
image_path=$(echo "$build_output" | jq -r '.image_path // ""')
|
|
exit_code=$(echo "$build_output" | jq -r '.exit_code // 1')
|
|
|
|
if [[ "$success" != "true" ]] || [[ "$exit_code" != "0" ]]; then
|
|
error "Build failed for $recipe_name (exit code: $exit_code)"
|
|
fi
|
|
|
|
if [[ -z "$image_path" ]]; then
|
|
error "No image path returned for $recipe_name"
|
|
fi
|
|
|
|
log "Build successful for $recipe_name: $image_path"
|
|
|
|
# Copy to artifacts directory
|
|
if [[ -n "$OUTPUT_DIR" ]]; then
|
|
mkdir -p "$OUTPUT_DIR/$recipe_name"
|
|
cp "$image_path" "$OUTPUT_DIR/$recipe_name/"
|
|
|
|
# Copy all output formats
|
|
local work_dir
|
|
work_dir=$(echo "$build_output" | jq -r '.work_directory // ""')
|
|
if [[ -n "$work_dir" ]] && [[ -d "$work_dir/output" ]]; then
|
|
cp "$work_dir/output"/* "$OUTPUT_DIR/$recipe_name/" 2>/dev/null || true
|
|
fi
|
|
|
|
log "Artifacts copied to $OUTPUT_DIR/$recipe_name"
|
|
fi
|
|
|
|
# Output build info for CI/CD system
|
|
echo "BUILD_SUCCESS_$recipe_name=true" >> "$GITHUB_ENV" 2>/dev/null || true
|
|
echo "IMAGE_PATH_$recipe_name=$image_path" >> "$GITHUB_ENV" 2>/dev/null || true
|
|
}
|
|
|
|
# Main build process
|
|
main() {
|
|
log "Starting OS image build process..."
|
|
|
|
# Check prerequisites
|
|
if ! command -v go &> /dev/null; then
|
|
error "Go is not installed"
|
|
fi
|
|
|
|
if ! command -v jq &> /dev/null; then
|
|
error "jq is not installed"
|
|
fi
|
|
|
|
# Build particle-os
|
|
build_particle_os
|
|
|
|
# Validate recipes
|
|
validate_recipes
|
|
|
|
# Build all images
|
|
local build_count=0
|
|
local success_count=0
|
|
|
|
for recipe in "$RECIPE_DIR"/*.yml; do
|
|
if [[ -f "$recipe" ]]; then
|
|
build_count=$((build_count + 1))
|
|
if build_image "$recipe"; then
|
|
success_count=$((success_count + 1))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Summary
|
|
log "Build process completed: $success_count/$build_count images built successfully"
|
|
|
|
if [[ $success_count -eq $build_count ]]; then
|
|
log "All images built successfully!"
|
|
exit 0
|
|
else
|
|
error "Some images failed to build"
|
|
fi
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|
|
```
|
|
|
|
## 🔧 Docker Integration
|
|
|
|
### **Dockerfile for CI/CD:**
|
|
|
|
```dockerfile
|
|
# Dockerfile.ci
|
|
FROM debian:bookworm-slim
|
|
|
|
# Install dependencies
|
|
RUN apt-get update && apt-get install -y \
|
|
golang-go \
|
|
jq \
|
|
qemu-utils \
|
|
podman \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Set working directory
|
|
WORKDIR /app
|
|
|
|
# Copy source code
|
|
COPY . .
|
|
|
|
# Build particle-os
|
|
RUN cd bib && go build -o particle-os cmd/particle_os/main.go
|
|
|
|
# Create recipes directory
|
|
RUN mkdir -p /recipes
|
|
|
|
# Set entrypoint
|
|
ENTRYPOINT ["./bib/particle-os"]
|
|
|
|
# Default command
|
|
CMD ["--help"]
|
|
```
|
|
|
|
### **Docker Compose for CI/CD:**
|
|
|
|
```yaml
|
|
# docker-compose.ci.yml
|
|
version: '3.8'
|
|
|
|
services:
|
|
particle-os-builder:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.ci
|
|
volumes:
|
|
- ./recipes:/recipes
|
|
- ./artifacts:/artifacts
|
|
- /var/lib/containers:/var/lib/containers
|
|
environment:
|
|
- RECIPES_DIR=/recipes
|
|
- OUTPUT_DIR=/artifacts
|
|
command: ["build", "--json", "--quiet", "--clean", "/recipes/debian-server.yml"]
|
|
privileged: true # Required for container operations
|
|
```
|
|
|
|
## 🔧 Best Practices for CI/CD
|
|
|
|
### **1. Always Use CI/CD Flags:**
|
|
```bash
|
|
# ✅ Good - CI/CD friendly
|
|
particle-os build --json --quiet --clean recipes/debian-server.yml
|
|
|
|
# ❌ Bad - Human-friendly only
|
|
particle-os build recipes/debian-server.yml
|
|
```
|
|
|
|
### **2. Parse JSON Output:**
|
|
```bash
|
|
# Extract build information
|
|
BUILD_OUTPUT=$(particle-os build --json --quiet recipes/debian-server.yml)
|
|
SUCCESS=$(echo "$BUILD_OUTPUT" | jq -r '.success')
|
|
IMAGE_PATH=$(echo "$BUILD_OUTPUT" | jq -r '.image_path')
|
|
EXIT_CODE=$(echo "$BUILD_OUTPUT" | jq -r '.exit_code')
|
|
|
|
if [[ "$SUCCESS" == "true" ]] && [[ "$EXIT_CODE" == "0" ]]; then
|
|
echo "Build successful: $IMAGE_PATH"
|
|
else
|
|
echo "Build failed"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
### **3. Handle Artifacts Properly:**
|
|
```bash
|
|
# Create organized artifact structure
|
|
mkdir -p artifacts/$RECIPE_NAME
|
|
cp "$IMAGE_PATH" artifacts/$RECIPE_NAME/
|
|
cp /tmp/particle-os-build/output/* artifacts/$RECIPE_NAME/
|
|
```
|
|
|
|
### **4. Clean Up After Builds:**
|
|
```bash
|
|
# Always use --clean flag in CI/CD
|
|
particle-os build --json --quiet --clean recipes/debian-server.yml
|
|
|
|
# Or manually clean up
|
|
sudo rm -rf /tmp/particle-os-build
|
|
```
|
|
|
|
### **5. Set Proper Exit Codes:**
|
|
```bash
|
|
# Check exit code from particle-os
|
|
if particle-os build --json --quiet recipes/debian-server.yml; then
|
|
echo "Build successful"
|
|
exit 0
|
|
else
|
|
echo "Build failed"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
## 🚀 Next Steps
|
|
|
|
Now that you have CI/CD integration examples, you can:
|
|
|
|
1. **Choose your CI/CD system** and implement the appropriate example
|
|
2. **Customize the workflows** for your specific needs
|
|
3. **Add more validation** and testing steps
|
|
4. **Implement artifact management** for your deployment pipeline
|
|
5. **Add monitoring** and alerting for build failures
|
|
|
|
particle-os is designed to work seamlessly in any CI/CD environment, providing reliable, reproducible OS image builds with machine-readable output perfect for automation.
|