diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5791581..e2f6563 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,19 @@ jobs: run: | python -m pip install --upgrade pip pip install -r build_requirements.txt + + # Install system package tools based on platform + if [ "${{ matrix.platform }}" = "linux" ]; then + sudo apt-get update + sudo apt-get install -y dpkg-dev + elif [ "${{ matrix.platform }}" = "win" ]; then + # WiX tools would need to be installed here for Windows MSI creation + echo "WiX tools installation would go here for MSI creation" + elif [ "${{ matrix.platform }}" = "macos" ]; then + # pkgbuild is available by default on macOS + echo "pkgbuild is available by default on macOS" + fi + shell: bash - name: Build executable run: | diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..c929157 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,134 @@ +name: Build and Push Docker Images + +on: + push: + branches: [ main, develop ] + tags: [ 'v*' ] + pull_request: + branches: [ main ] + +env: + REGISTRY: ghcr.io + REPO: stackvista/agent-integrations-finder + IMAGE_NAME: ghcr.io/stackvista/agent-integrations-finder + +jobs: + docker-build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/stackvista/agent-integrations-finder + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r build_requirements.txt + + - name: Build and push Docker images + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build individual architecture images + if: github.event_name != 'pull_request' + run: | + # Build amd64 image + docker buildx build \ + --platform linux/amd64 \ + --tag ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-amd64 \ + --push \ + . + + # Build arm64 image + docker buildx build \ + --platform linux/arm64 \ + --tag ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-arm64 \ + --push \ + . + + # Add version tags if this is a release (tag push) + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION_TAG="${{ github.ref_name }}" + echo "Creating version tags: $VERSION_TAG" + + # Tag amd64 image with version + docker tag ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-amd64 \ + ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG-amd64 + docker push ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG-amd64 + + # Tag arm64 image with version + docker tag ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-arm64 \ + ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG-arm64 + docker push ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG-arm64 + fi + + - name: Create multi-architecture manifest + if: github.event_name != 'pull_request' + run: | + # Create manifest with git SHA + docker buildx imagetools create -t ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }} \ + ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-amd64 \ + ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-arm64 + + # Create manifest with version tag if this is a release + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION_TAG="${{ github.ref_name }}" + echo "Creating version manifest: $VERSION_TAG" + docker buildx imagetools create -t ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG \ + ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG-amd64 \ + ghcr.io/stackvista/agent-integrations-finder:$VERSION_TAG-arm64 + fi + + - name: Show image info + run: | + echo "Built images:" + docker images ghcr.io/stackvista/agent-integrations-finder || true + echo "" + echo "Available tags:" + echo "- ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}" + echo "- ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-amd64" + echo "- ghcr.io/stackvista/agent-integrations-finder:${{ github.sha }}-arm64" + if [[ "${{ github.ref_name }}" != "${{ github.sha }}" ]]; then + echo "- ghcr.io/stackvista/agent-integrations-finder:${{ github.ref_name }}" + echo "- ghcr.io/stackvista/agent-integrations-finder:${{ github.ref_name }}-amd64" + echo "- ghcr.io/stackvista/agent-integrations-finder:${{ github.ref_name }}-arm64" + fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1eeab77 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# Multi-stage Dockerfile for Agent Integrations Finder +# Stage 1: Build stage +FROM python:3.11-slim AS builder + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + make \ + zip \ + tar \ + gzip \ + dpkg-dev \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy requirements and install Python dependencies +COPY requirements.txt build_requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt -r build_requirements.txt + +# Copy source code +COPY . . + +# Build the executable for the target architecture +ARG TARGETARCH +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + python build.py linux-x86_64; \ + elif [ "$TARGETARCH" = "arm64" ]; then \ + python build.py linux-aarch64; \ + else \ + echo "Unsupported architecture: $TARGETARCH"; \ + exit 1; \ + fi + +# Stage 2: Runtime stage +FROM python:3.11-slim AS runtime + +# Install minimal runtime dependencies +RUN apt-get update && apt-get install -y \ + libglib2.0-0 \ + && rm -rf /var/lib/apt/lists/* + +# Create non-root user +RUN useradd --create-home --shell /bin/bash app + +# Set working directory +WORKDIR /app + +# Copy Python dependencies from builder stage +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages/ + +# Copy the application source +COPY --from=builder /app/integrations_finder.py /app/ + +# Switch to non-root user +USER app + +# Set environment variables +ENV PYTHONPATH=/usr/local/lib/python3.11/site-packages +ENV PATH=/usr/local/bin:$PATH + +# Default command +ENTRYPOINT ["python", "/app/integrations_finder.py"] +CMD ["--help"] diff --git a/Makefile b/Makefile index 6d8b9d4..ff37379 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,82 @@ -# Makefile for SUSE Observability Integrations Finder -# Cross-platform build targets +# Makefile for Agent Integrations Finder +# Cross-platform build and packaging targets -.PHONY: help clean install-deps build-all build-linux-x86_64 build-linux-aarch64 build-macos-x86_64 build-macos-aarch64 build-win-x86_64 +.PHONY: help clean install-deps install-system-deps build-all package-all \ + build-linux-x86_64 build-linux-aarch64 build-macos-x86_64 build-macos-aarch64 build-win-x86_64 \ + package-linux-x86_64 package-linux-aarch64 package-macos-x86_64 package-macos-aarch64 package-win-x86_64 \ + deb-linux-x86_64 deb-linux-aarch64 \ + msi-win-x86_64 pkg-macos-x86_64 pkg-macos-aarch64 \ + docker-build docker-push docker-amd64 docker-arm64 docker-cleanup \ + test run-gui run-cli # Default target help: - @echo "SUSE Observability Integrations Finder - Build Targets" + @echo "Agent Integrations Finder - Build and Packaging Targets" @echo "" - @echo "Available targets:" - @echo " install-deps - Install build dependencies" - @echo " clean - Clean build artifacts" - @echo " build-all - Build for all platforms" - @echo " build-linux-x86_64 - Build Linux x86_64 executable" - @echo " build-linux-aarch64 - Build Linux aarch64 executable" - @echo " build-macos-x86_64 - Build macOS x86_64 executable" - @echo " build-macos-aarch64 - Build macOS aarch64 executable" - @echo " build-win-x86_64 - Build Windows x86_64 executable" + @echo "Dependencies:" + @echo " install-deps - Install Python build dependencies" + @echo " install-system-deps - Install system packaging tools (Linux)" + @echo "" + @echo "Build Targets:" + @echo " build-all - Build executables for all platforms" + @echo " build-linux-x86_64 - Build Linux x86_64 executable" + @echo " build-linux-aarch64 - Build Linux aarch64 executable" + @echo " build-macos-x86_64 - Build macOS x86_64 executable" + @echo " build-macos-aarch64 - Build macOS aarch64 executable" + @echo " build-win-x86_64 - Build Windows x86_64 executable" + @echo "" + @echo "Package Targets:" + @echo " package-all - Build and package for all platforms" + @echo " package-linux-x86_64 - Build and package Linux x86_64 (.tar.gz, .deb)" + @echo " package-linux-aarch64- Build and package Linux aarch64 (.tar.gz, .deb)" + @echo " package-macos-x86_64 - Build and package macOS x86_64 (.tar.gz, .pkg)" + @echo " package-macos-aarch64- Build and package macOS aarch64 (.tar.gz, .pkg)" + @echo " package-win-x86_64 - Build and package Windows x86_64 (.zip, .msi)" + @echo "" + @echo "Docker Targets:" + @echo " docker-build - Build Docker images for all architectures" + @echo " docker-push - Build and push Docker images to registry" + @echo " docker-amd64 - Build Docker image for amd64 only" + @echo " docker-arm64 - Build Docker image for arm64 only" + @echo " docker-cleanup - Clean up local Docker images" + @echo "" + @echo "Individual Package Targets:" + @echo " deb-linux-x86_64 - Create .deb package for Linux x86_64" + @echo " deb-linux-aarch64 - Create .deb package for Linux aarch64" + @echo " msi-win-x86_64 - Create .msi package for Windows x86_64" + @echo " pkg-macos-x86_64 - Create .pkg package for macOS x86_64" + @echo " pkg-macos-aarch64 - Create .pkg package for macOS aarch64" + @echo "" + @echo "Development Targets:" + @echo " test - Run tests" + @echo " run-gui - Run GUI application" + @echo " run-cli - Run CLI application with demo" + @echo " clean - Clean build artifacts" @echo "" @echo "Examples:" - @echo " make build-linux-x86_64" - @echo " make build-all" + @echo " make install-deps install-system-deps" + @echo " make package-linux-x86_64" + @echo " make deb-linux-x86_64" + @echo " make docker-build" + @echo " make docker-push" + @echo " make package-all" + @echo " make test" -# Install build dependencies +# Install Python build dependencies install-deps: - @echo "Installing build dependencies..." + @echo "Installing Python build dependencies..." pip install -r build_requirements.txt - @echo "Build dependencies installed." + @echo "Python build dependencies installed." + +# Install system packaging tools (Linux only) +install-system-deps: + @echo "Installing system packaging tools..." + @if [ "$$(uname)" = "Linux" ]; then \ + sudo apt-get update && sudo apt-get install -y dpkg-dev; \ + echo "System packaging tools installed."; \ + else \ + echo "System packaging tools installation skipped (not on Linux)"; \ + fi # Clean build artifacts clean: @@ -33,39 +84,145 @@ clean: rm -rf build/ rm -rf dist/ rm -rf packages/ + rm -rf temp_*/ rm -f *.spec @echo "Clean complete." -# Build all platforms +# Build all platforms (executables only) build-all: build-linux-x86_64 build-linux-aarch64 build-macos-x86_64 build-macos-aarch64 build-win-x86_64 @echo "All builds completed!" +# Package all platforms (executables + system packages) +package-all: package-linux-x86_64 package-linux-aarch64 package-macos-x86_64 package-macos-aarch64 package-win-x86_64 + @echo "All packages created!" + @echo "Available packages:" + @ls -la packages/ 2>/dev/null || echo "No packages found" + # Linux x86_64 build-linux-x86_64: @echo "Building Linux x86_64..." python3 build.py linux-x86_64 +package-linux-x86_64: build-linux-x86_64 + @echo "Packaging Linux x86_64 completed" + # Linux aarch64 build-linux-aarch64: @echo "Building Linux aarch64..." python3 build.py linux-aarch64 +package-linux-aarch64: build-linux-aarch64 + @echo "Packaging Linux aarch64 completed" + # macOS x86_64 build-macos-x86_64: @echo "Building macOS x86_64..." python3 build.py macos-x86_64 +package-macos-x86_64: build-macos-x86_64 + @echo "Packaging macOS x86_64 completed" + # macOS aarch64 build-macos-aarch64: @echo "Building macOS aarch64..." python3 build.py macos-aarch64 +package-macos-aarch64: build-macos-aarch64 + @echo "Packaging macOS aarch64 completed" + # Windows x86_64 build-win-x86_64: @echo "Building Windows x86_64..." python3 build.py win-x86_64 +package-win-x86_64: build-win-x86_64 + @echo "Packaging Windows x86_64 completed" + +# Individual package format targets +# Linux .deb packages +deb-linux-x86_64: build-linux-x86_64 + @echo "Creating .deb package for Linux x86_64..." + @python3 build.py linux-x86_64 --create-deb-only + +deb-linux-aarch64: build-linux-aarch64 + @echo "Creating .deb package for Linux aarch64..." + @python3 build.py linux-aarch64 --create-deb-only + + + +# Windows .msi package +msi-win-x86_64: build-win-x86_64 + @echo "Creating .msi package for Windows x86_64..." + @python3 build.py win-x86_64 --create-msi-only + +# macOS .pkg packages +pkg-macos-x86_64: build-macos-x86_64 + @echo "Creating .pkg package for macOS x86_64..." + @python3 build.py macos-x86_64 --create-pkg-only + +pkg-macos-aarch64: build-macos-aarch64 + @echo "Creating .pkg package for macOS aarch64..." + @python3 build.py macos-aarch64 --create-pkg-only + +# Docker targets +docker-build: + @echo "Building Docker images for all architectures..." + @./docker-build.sh build + +docker-push: + @echo "Building and pushing Docker images to registry..." + @./docker-build.sh push + +docker-amd64: + @echo "Building Docker image for amd64..." + @./docker-build.sh amd64 + +docker-arm64: + @echo "Building Docker image for arm64..." + @./docker-build.sh arm64 + +docker-cleanup: + @echo "Cleaning up Docker images..." + @./docker-build.sh cleanup + +# Development targets +test: + @echo "Running tests..." + python3 test_finder.py + @echo "Tests completed." + +run-gui: + @echo "Starting GUI application..." + python3 integrations_finder.py gui + +run-cli: + @echo "Running CLI demo..." + python3 integrations_finder.py find a1b2c3d4 + # Quick build for current platform build-current: @echo "Building for current platform..." python3 build.py all + +# Show package information +list-packages: + @echo "Available packages:" + @if [ -d packages ]; then \ + ls -la packages/; \ + else \ + echo "No packages directory found. Run 'make package-all' first."; \ + fi + +# Show build information +info: + @echo "Agent Integrations Finder Build Information" + @echo "==========================================" + @echo "Python version: $$(python3 --version)" + @echo "Platform: $$(uname -s) $$(uname -m)" + @echo "Build directory: $$(pwd)" + @echo "" + @echo "Available build tools:" + @command -v dpkg-deb >/dev/null 2>&1 && echo "✓ dpkg-deb (for .deb packages)" || echo "✗ dpkg-deb (for .deb packages)" + @command -v pkgbuild >/dev/null 2>&1 && echo "✓ pkgbuild (for .pkg packages)" || echo "✗ pkgbuild (for .pkg packages)" + @command -v candle >/dev/null 2>&1 && echo "✓ WiX candle (for .msi packages)" || echo "✗ WiX candle (for .msi packages)" + @command -v light >/dev/null 2>&1 && echo "✓ WiX light (for .msi packages)" || echo "✗ WiX light (for .msi packages)" diff --git a/PACKAGING.md b/PACKAGING.md new file mode 100644 index 0000000..cef2e02 --- /dev/null +++ b/PACKAGING.md @@ -0,0 +1,127 @@ +# Packaging System + +The Agent Integrations Finder build system creates multiple package formats for different platforms and use cases. + +## Package Formats + +### Linux Packages + +#### Archive Packages +- **`.tar.gz`**: Portable archive containing the executable and dependencies + - `agent-integrations-finder-linux-x86_64.tar.gz` + - `agent-integrations-finder-linux-aarch64.tar.gz` + +#### System Packages +- **`.deb`**: Debian/Ubuntu package for easy installation + - `agent-integrations-finder_1.0.0_amd64.deb` + - `agent-integrations-finder_1.0.0_aarch64.deb` + - Installs executable to `/usr/local/bin/agent-integrations-finder` + - Dependencies in `/usr/local/bin/_internal/` + + + +### Windows Packages + +#### Archive Packages +- **`.zip`**: Portable archive containing the executable and dependencies + - `agent-integrations-finder-win-x86_64.zip` + +#### System Packages +- **`.msi`**: Microsoft Installer package for easy installation + - `agent-integrations-finder-1.0.0-x86_64.msi` + - Installs to `C:\Program Files\agent-integrations-finder\` + - Creates Start Menu shortcuts + - Handles uninstallation + +### macOS Packages + +#### Archive Packages +- **`.tar.gz`**: Portable archive containing the executable and dependencies + - `agent-integrations-finder-macos-x86_64.tar.gz` + - `agent-integrations-finder-macos-aarch64.tar.gz` + +#### System Packages +- **`.pkg`**: macOS Package Installer for easy installation + - `agent-integrations-finder-1.0.0-x86_64.pkg` + - `agent-integrations-finder-1.0.0-aarch64.pkg` + - Installs executable to `/usr/local/bin/agent-integrations-finder` + - Dependencies in `/usr/local/bin/_internal/` + +## Installation Methods + +### Linux + +#### Using .deb package (Debian/Ubuntu) +```bash +sudo dpkg -i agent-integrations-finder_1.0.0_amd64.deb +``` + + + +#### Using .tar.gz archive +```bash +tar -xzf agent-integrations-finder-linux-x86_64.tar.gz +cd agent-integrations-finder +./agent-integrations-finder --help +``` + +### Windows + +#### Using .msi package +1. Double-click the `.msi` file +2. Follow the installation wizard +3. Use from Start Menu or `C:\Program Files\agent-integrations-finder\` + +#### Using .zip archive +1. Extract the `.zip` file +2. Run `agent-integrations-finder.exe` from the extracted folder + +### macOS + +#### Using .pkg package +1. Double-click the `.pkg` file +2. Follow the installation wizard +3. Use from Terminal: `agent-integrations-finder --help` + +#### Using .tar.gz archive +```bash +tar -xzf agent-integrations-finder-macos-x86_64.tar.gz +cd agent-integrations-finder +./agent-integrations-finder --help +``` + +## Build Requirements + +### Local Development (Linux) +- **Python 3.11+** +- **PyInstaller**: `pip install -r build_requirements.txt` +- **dpkg-dev**: For .deb package creation (Ubuntu/Debian) +- **rpm-build**: For .rpm package creation (Red Hat/Fedora) + +### GitHub Actions +- **Linux runners**: Include dpkg-dev and rpm-build tools +- **Windows runners**: Would need WiX tools for .msi creation +- **macOS runners**: Include pkgbuild by default + +## Package Contents + +All packages include: +- **Executable**: `agent-integrations-finder` (or `.exe` on Windows) +- **Dependencies**: All required Python libraries and system dependencies +- **Assets**: Application icons and resources +- **Documentation**: Built-in help and usage information + +## Version Management + +Package versions are managed in the build script: +- **Version**: `1.0.0` (configurable in `build.py`) +- **Architecture**: `x86_64`, `aarch64` (automatically detected) +- **Platform**: `linux`, `win`, `macos` (automatically detected) + +## Customization + +To modify package contents or metadata: +1. Edit the package creation methods in `build.py` +2. Update version numbers in the build script +3. Modify package descriptions and metadata +4. Add additional files or dependencies as needed diff --git a/README.md b/README.md index e3e274a..2f198b7 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,48 @@ A tool to trace from SUSE Observability Agent container tags to the correspondin ### Option 2: Use Pre-built Executables -Download the latest release from the [Releases page](https://github.com/StackVista/stackstate-agent-integrations/releases) and extract the appropriate package for your platform. +Download the latest release from the [Releases page](https://github.com/StackVista/stackstate-agent-integrations/releases) and choose the appropriate package for your platform: + +#### Archive Packages (Portable) +- **Linux**: `agent-integrations-finder-linux-x86_64.tar.gz` or `agent-integrations-finder-linux-aarch64.tar.gz` +- **Windows**: `agent-integrations-finder-win-x86_64.zip` +- **macOS**: `agent-integrations-finder-macos-x86_64.tar.gz` or `agent-integrations-finder-macos-aarch64.tar.gz` + +#### System Packages (Easy Installation) +- **Linux (Debian/Ubuntu)**: `agent-integrations-finder_1.0.0_amd64.deb` +- **Linux (Red Hat/Fedora)**: `agent-integrations-finder-1.0.0-1.x86_64.rpm` +- **Windows**: `agent-integrations-finder-1.0.0-x86_64.msi` +- **macOS**: `agent-integrations-finder-1.0.0-x86_64.pkg` + +For detailed installation instructions, see [PACKAGING.md](PACKAGING.md). ## Installation +### Using Docker (Recommended) +The easiest way to run Agent Integrations Finder is using Docker: + +```bash +# Pull and run the latest version +docker run --rm ghcr.io/stackvista/agent-integrations-finder:latest --help + +# Run with a specific version +docker run --rm ghcr.io/stackvista/agent-integrations-finder:v1.0.0 --help + +# Run GUI (requires X11 forwarding on Linux/macOS) +docker run --rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \ + ghcr.io/stackvista/agent-integrations-finder:latest gui + +# Run CLI with specific SHA +docker run --rm ghcr.io/stackvista/agent-integrations-finder:latest find a1b2c3d4 +``` + +**Available Docker Images:** +- `ghcr.io/stackvista/agent-integrations-finder:latest` - Latest stable version +- `ghcr.io/stackvista/agent-integrations-finder:v1.0.0` - Specific version +- `ghcr.io/stackvista/agent-integrations-finder:` - Specific commit +- `ghcr.io/stackvista/agent-integrations-finder:-amd64` - AMD64 architecture only +- `ghcr.io/stackvista/agent-integrations-finder:-arm64` - ARM64 architecture only + ### From Source ```bash @@ -132,6 +170,33 @@ Icons are automatically detected and used based on platform requirements. The `c 1. **Direct Build**: `python build.py -` 2. **Docker Build**: `./build-docker.sh -` 3. **Makefile**: `make build--` +4. **Docker Images**: `make docker-build` or `./docker-build.sh build` + +### Docker Build System + +The project includes a comprehensive Docker build system for creating multi-architecture container images: + +```bash +# Build Docker images for all architectures (local) +make docker-build + +# Build and push to GitHub Container Registry +make docker-push + +# Build specific architecture +make docker-amd64 +make docker-arm64 + +# Clean up Docker images +make docker-cleanup +``` + +**Docker Build Features:** +- Multi-architecture support (AMD64, ARM64) +- Automatic tagging with git SHA and version tags +- GitHub Container Registry integration +- Multi-architecture manifests +- Build caching for faster builds **Note**: Windows builds use Python's built-in `zipfile` module for packaging, ensuring compatibility across all Windows environments. diff --git a/build.py b/build.py index 2081f15..e416120 100755 --- a/build.py +++ b/build.py @@ -177,6 +177,241 @@ def build(self, target_platform, target_arch): print(f"stderr: {e.stderr}") return False + def create_deb_package(self, target_platform, target_arch, source_dir, output_dir): + """Create .deb package for Debian/Ubuntu""" + print("Creating .deb package...") + + # Create package structure + package_name = "agent-integrations-finder" + package_version = "1.0.0" + + # Convert architecture names to Debian format + deb_arch = target_arch.replace("x86_64", "amd64").replace("aarch64", "arm64") + deb_name = f"{package_name}_{package_version}_{deb_arch}.deb" + deb_path = output_dir / deb_name + + # Create temporary directory structure + temp_dir = self.project_root / "temp_deb" + if temp_dir.exists(): + shutil.rmtree(temp_dir) + temp_dir.mkdir() + + # Create DEBIAN control file + debian_dir = temp_dir / "DEBIAN" + debian_dir.mkdir() + + control_content = f"""Package: {package_name} +Version: {package_version} +Architecture: {deb_arch} +Maintainer: SUSE Observability Team +Description: Agent Integrations Finder + A tool to trace from SUSE Observability Agent container tags to the corresponding integrations source code. + Provides both CLI and GUI interfaces for finding integration source code. + Installs executable to /usr/local/bin/agent-integrations-finder +""" + + with open(debian_dir / "control", "w") as f: + f.write(control_content) + + # Create usr/local/bin directory and copy executable + bin_dir = temp_dir / "usr" / "local" / "bin" + bin_dir.mkdir(parents=True) + + # Copy the executable + executable = source_dir / "agent-integrations-finder" + if executable.exists(): + shutil.copy2(executable, bin_dir / "agent-integrations-finder") + os.chmod(bin_dir / "agent-integrations-finder", 0o755) + + # Copy _internal directory to /usr/local/bin (where PyInstaller expects it) + bin_internal_dir = temp_dir / "usr" / "local" / "bin" / "_internal" + bin_internal_dir.mkdir(parents=True) + + internal_dir = source_dir / "_internal" + if internal_dir.exists(): + shutil.copytree(internal_dir, bin_internal_dir, dirs_exist_ok=True) + + # Create .deb package using dpkg-deb + try: + cmd = ["dpkg-deb", "--build", str(temp_dir), str(deb_path)] + subprocess.run(cmd, check=True) + print(f"Created .deb package: {deb_path}") + except (subprocess.CalledProcessError, FileNotFoundError): + print("Warning: dpkg-deb not available, skipping .deb package creation") + finally: + # Clean up + shutil.rmtree(temp_dir) + + def create_msi_package(self, target_platform, target_arch, source_dir, output_dir): + """Create .msi package for Windows""" + print("Creating .msi package...") + + # Create package structure + package_name = "agent-integrations-finder" + package_version = "1.0.0" + msi_name = f"{package_name}-{package_version}-{target_arch}.msi" + msi_path = output_dir / msi_name + + # Create temporary directory structure + temp_dir = self.project_root / "temp_msi" + if temp_dir.exists(): + shutil.rmtree(temp_dir) + temp_dir.mkdir() + + # Copy files to temp directory + program_files = temp_dir / "Program Files" / package_name + program_files.mkdir(parents=True) + + # Copy the executable + executable = source_dir / "agent-integrations-finder.exe" + if executable.exists(): + shutil.copy2(executable, program_files / "agent-integrations-finder.exe") + + # Copy _internal directory + internal_dir = source_dir / "_internal" + if internal_dir.exists(): + shutil.copytree(internal_dir, program_files / "_internal") + + # Create WiX XML file for MSI + wix_content = f""" + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + wix_file = temp_dir / f"{package_name}.wxs" + with open(wix_file, "w") as f: + f.write(wix_content) + + # Try to build MSI using WiX + try: + cmd = ["candle", str(wix_file), "-out", str(temp_dir / f"{package_name}.wixobj")] + subprocess.run(cmd, check=True) + + cmd = ["light", str(temp_dir / f"{package_name}.wixobj"), "-out", str(msi_path)] + subprocess.run(cmd, check=True) + + print(f"Created .msi package: {msi_path}") + except (subprocess.CalledProcessError, FileNotFoundError): + print("Warning: WiX tools not available, skipping .msi package creation") + finally: + # Clean up + shutil.rmtree(temp_dir) + + def create_pkg_package(self, target_platform, target_arch, source_dir, output_dir): + """Create .pkg package for macOS""" + print("Creating .pkg package...") + + # Create package structure + package_name = "agent-integrations-finder" + package_version = "1.0.0" + pkg_name = f"{package_name}-{package_version}-{target_arch}.pkg" + pkg_path = output_dir / pkg_name + + # Create temporary directory structure + temp_dir = self.project_root / "temp_pkg" + if temp_dir.exists(): + shutil.rmtree(temp_dir) + temp_dir.mkdir() + + # Create package structure + pkg_root = temp_dir / "pkgroot" + pkg_root.mkdir() + + # Copy files to /usr/local/bin and /usr/local/lib + bin_dir = pkg_root / "usr" / "local" / "bin" + bin_dir.mkdir(parents=True) + + executable = source_dir / "agent-integrations-finder" + if executable.exists(): + shutil.copy2(executable, bin_dir / "agent-integrations-finder") + os.chmod(bin_dir / "agent-integrations-finder", 0o755) + + lib_dir = pkg_root / "usr" / "local" / "lib" / package_name + lib_dir.mkdir(parents=True) + + internal_dir = source_dir / "_internal" + if internal_dir.exists(): + shutil.copytree(internal_dir, lib_dir / "_internal") + + # Create package info + info_dir = temp_dir / "package_info" + info_dir.mkdir() + + info_content = f""" + + + + + + + + + + + + +""" + + with open(info_dir / "PackageInfo", "w") as f: + f.write(info_content) + + # Create postinstall script + postinstall_content = """#!/bin/bash +chmod +x /usr/local/bin/agent-integrations-finder +""" + + with open(info_dir / "postinstall", "w") as f: + f.write(postinstall_content) + os.chmod(info_dir / "postinstall", 0o755) + + # Try to build pkg using pkgbuild + try: + cmd = [ + "pkgbuild", + "--root", + str(pkg_root), + "--identifier", + f"com.suse.observability.{package_name}", + "--version", + package_version, + "--install-location", + "/", + str(pkg_path), + ] + subprocess.run(cmd, check=True) + print(f"Created .pkg package: {pkg_path}") + except (subprocess.CalledProcessError, FileNotFoundError): + print("Warning: pkgbuild not available, skipping .pkg package creation") + finally: + # Clean up + shutil.rmtree(temp_dir) + def package(self, target_platform, target_arch): """Package the built executable""" print(f"Packaging for {target_platform}-{target_arch}...") @@ -194,7 +429,15 @@ def package(self, target_platform, target_arch): output_dir = self.project_root / "packages" output_dir.mkdir(exist_ok=True) - # Package based on platform + # Create system packages first + if target_platform == "linux": + self.create_deb_package(target_platform, target_arch, source_dir, output_dir) + elif target_platform == "win": + self.create_msi_package(target_platform, target_arch, source_dir, output_dir) + elif target_platform == "macos": + self.create_pkg_package(target_platform, target_arch, source_dir, output_dir) + + # Package based on platform (original tar.gz/zip packages) if target_platform == "linux": # Create tar.gz archive_name = f"agent-integrations-finder-{target_platform}-{target_arch}.tar.gz" @@ -244,11 +487,13 @@ def package(self, target_platform, target_arch): print(f"Package created: {archive_path}") return True + pass + def main(): """Main build function""" if len(sys.argv) < 2 or sys.argv[1] in ["-h", "--help", "help"]: - print("Usage: python build.py ") + print("Usage: python build.py [options]") print("Targets:") print(" linux-x86_64") print(" linux-aarch64") @@ -256,25 +501,44 @@ def main(): print(" macos-aarch64") print(" win-x86_64") print(" all") + print(" docker-amd64") + print(" docker-arm64") + print(" docker-all") + print("") + print("Options:") + print(" --create-deb-only - Create only .deb package (Linux)") + print(" --create-msi-only - Create only .msi package (Windows)") + print(" --create-pkg-only - Create only .pkg package (macOS)") print("") print("Examples:") print(" python build.py linux-x86_64") + print(" python build.py linux-x86_64 --create-deb-only") + print(" python build.py docker-all") print(" python build.py all") sys.exit(1) target = sys.argv[1] builder = Builder() + # Check for package-only options + create_deb_only = "--create-deb-only" in sys.argv + create_msi_only = "--create-msi-only" in sys.argv + create_pkg_only = "--create-pkg-only" in sys.argv + targets = { "linux-x86_64": ("linux", "x86_64"), "linux-aarch64": ("linux", "aarch64"), "macos-x86_64": ("macos", "x86_64"), "macos-aarch64": ("macos", "aarch64"), "win-x86_64": ("win", "x86_64"), + "docker-amd64": ("docker", "amd64"), + "docker-arm64": ("docker", "arm64"), } if target == "all": build_targets = targets.values() + elif target == "docker-all": + build_targets = [("docker", "amd64"), ("docker", "arm64")] elif target in targets: build_targets = [targets[target]] else: @@ -290,8 +554,41 @@ def main(): print(f"Building {platform_name}-{arch}") print(f"{'=' * 50}") - if builder.build(platform_name, arch): - builder.package(platform_name, arch) + if platform_name == "docker": + # Handle Docker builds + print(f"Building Docker image for {arch}...") + if builder.build("linux", arch.replace("amd64", "x86_64").replace("arm64", "aarch64")): + print(f"Docker build preparation completed for {arch}") + else: + print(f"Failed to prepare Docker build for {arch}") + sys.exit(1) + elif builder.build(platform_name, arch): + # Handle package-only options + if create_deb_only and platform_name == "linux": + builder.create_deb_package( + platform_name, + arch, + builder.get_platform_dist_dir(platform_name, arch) / "agent-integrations-finder", + builder.project_root / "packages", + ) + + elif create_msi_only and platform_name == "win": + builder.create_msi_package( + platform_name, + arch, + builder.get_platform_dist_dir(platform_name, arch) / "agent-integrations-finder", + builder.project_root / "packages", + ) + elif create_pkg_only and platform_name == "macos": + builder.create_pkg_package( + platform_name, + arch, + builder.get_platform_dist_dir(platform_name, arch) / "agent-integrations-finder", + builder.project_root / "packages", + ) + else: + # Normal packaging (all formats) + builder.package(platform_name, arch) else: print(f"Failed to build {platform_name}-{arch}") sys.exit(1) diff --git a/build_requirements.txt b/build_requirements.txt index 4adb79a..d3983c7 100644 --- a/build_requirements.txt +++ b/build_requirements.txt @@ -2,3 +2,4 @@ pyinstaller>=6.0.0 requests>=2.31.0 click>=8.1.0 pillow>=10.0.0 +PyQt6>=6.5.0 diff --git a/docker-build.sh b/docker-build.sh new file mode 100755 index 0000000..ac38937 --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,316 @@ +#!/bin/bash + +# Docker build script for Agent Integrations Finder +# Supports multi-architecture builds and GitHub Container Registry + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$SCRIPT_DIR" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_step() { + echo -e "${BLUE}[STEP]${NC} $1" +} + +# Configuration +REGISTRY="ghcr.io" +REPO="stackvista/agent-integrations-finder" +IMAGE_NAME="${REGISTRY}/${REPO}" + +# Get git information +GIT_SHA=$(git rev-parse --short=8 HEAD) +GIT_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "") + +# Check if Docker is available +check_docker() { + if ! command -v docker &> /dev/null; then + print_error "Docker is not installed or not in PATH" + exit 1 + fi + + if ! docker info &> /dev/null; then + print_error "Docker is not running or you don't have permissions" + exit 1 + fi +} + +# Check if docker buildx is available +check_buildx() { + if ! docker buildx version &> /dev/null; then + print_error "Docker buildx is not available" + exit 1 + fi +} + +# Create and use buildx builder +setup_builder() { + print_step "Setting up Docker buildx builder..." + + # Create a new builder if it doesn't exist + if ! docker buildx inspect multiarch-builder &> /dev/null; then + docker buildx create --name multiarch-builder --use + else + docker buildx use multiarch-builder + fi + + # Bootstrap the builder + docker buildx inspect --bootstrap +} + +# Build for a specific architecture +build_architecture() { + local arch=$1 + local platform="linux/${arch}" + + print_step "Building for ${arch}..." + + # Build the executable first + print_status "Building executable for ${arch}..." + # Convert architecture names for build.py + build_arch=${arch} + if [[ "${arch}" == "amd64" ]]; then + build_arch="x86_64" + elif [[ "${arch}" == "arm64" ]]; then + build_arch="aarch64" + fi + python3 build.py linux-${build_arch} + + # Build Docker image + print_status "Building Docker image for ${arch}..." + docker buildx build \ + --platform ${platform} \ + --tag ${IMAGE_NAME}:${GIT_SHA}-${arch} \ + --file Dockerfile \ + --load \ + . + + # Tag with version if this is a release + if [[ -n "${GIT_TAG}" ]]; then + print_status "Tagging release image for ${arch}..." + docker tag ${IMAGE_NAME}:${GIT_SHA}-${arch} ${IMAGE_NAME}:${GIT_TAG}-${arch} + fi +} + +# Create multi-architecture manifest +create_manifest() { + print_step "Creating multi-architecture manifest..." + + # Build for both architectures + build_architecture "amd64" + build_architecture "arm64" + + # Prepare tags + local tags="--tag ${IMAGE_NAME}:${GIT_SHA}" + + # Add version tag if this is a release + if [[ -n "${GIT_TAG}" ]]; then + print_status "Adding version tag: ${GIT_TAG}" + tags="${tags} --tag ${IMAGE_NAME}:${GIT_TAG}" + fi + + # Create manifest + print_status "Creating manifest with tags: ${GIT_SHA}${GIT_TAG:+ and ${GIT_TAG}}" + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + ${tags} \ + --file Dockerfile \ + --push \ + . +} + +# Push individual architecture images +push_architectures() { + print_step "Pushing individual architecture images..." + + # Push amd64 image + print_status "Pushing amd64 image..." + docker push ${IMAGE_NAME}:${GIT_SHA}-amd64 + + # Push arm64 image + print_status "Pushing arm64 image..." + docker push ${IMAGE_NAME}:${GIT_SHA}-arm64 + + # Push versioned images if this is a release + if [[ -n "${GIT_TAG}" ]]; then + print_status "Pushing versioned images..." + docker push ${IMAGE_NAME}:${GIT_TAG}-amd64 + docker push ${IMAGE_NAME}:${GIT_TAG}-arm64 + fi +} + +# Build and push all images +build_and_push() { + print_step "Building and pushing all images..." + + # Build for both architectures and create manifest + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --tag ${IMAGE_NAME}:${GIT_SHA} \ + --tag ${IMAGE_NAME}:${GIT_SHA}-amd64 \ + --tag ${IMAGE_NAME}:${GIT_SHA}-arm64 \ + --file Dockerfile \ + --push \ + . + + # Add version tags if this is a release + if [[ -n "${GIT_TAG}" ]]; then + print_status "Adding version tags..." + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --tag ${IMAGE_NAME}:${GIT_TAG} \ + --tag ${IMAGE_NAME}:${GIT_TAG}-amd64 \ + --tag ${IMAGE_NAME}:${GIT_TAG}-arm64 \ + --file Dockerfile \ + --push \ + . + fi +} + +# Build only (no push) +build_only() { + print_step "Building images (no push)..." + + # Build for both architectures + build_architecture "amd64" + build_architecture "arm64" + + # Note: Cannot create multi-arch manifest locally with --load + # Individual architecture images are already built and loaded + print_status "Individual architecture images built and loaded locally" + print_status "Available images:" + print_status " ${IMAGE_NAME}:${GIT_SHA}-amd64" + print_status " ${IMAGE_NAME}:${GIT_SHA}-arm64" + + if [[ -n "${GIT_TAG}" ]]; then + print_status " ${IMAGE_NAME}:${GIT_TAG}-amd64" + print_status " ${IMAGE_NAME}:${GIT_TAG}-arm64" + fi +} + +# Clean up Docker images +cleanup() { + print_step "Cleaning up Docker images..." + + # Remove local images + docker rmi ${IMAGE_NAME}:${GIT_SHA}-amd64 2>/dev/null || true + docker rmi ${IMAGE_NAME}:${GIT_SHA}-arm64 2>/dev/null || true + docker rmi ${IMAGE_NAME}:${GIT_SHA} 2>/dev/null || true + + if [[ -n "${GIT_TAG}" ]]; then + docker rmi ${IMAGE_NAME}:${GIT_TAG}-amd64 2>/dev/null || true + docker rmi ${IMAGE_NAME}:${GIT_TAG}-arm64 2>/dev/null || true + docker rmi ${IMAGE_NAME}:${GIT_TAG} 2>/dev/null || true + fi +} + +# Show help +show_help() { + echo "Docker build script for Agent Integrations Finder" + echo "" + echo "Usage: $0 [COMMAND]" + echo "" + echo "Commands:" + echo " build - Build images for all architectures (no push)" + echo " push - Build and push all images to registry" + echo " manifest - Create multi-architecture manifest" + echo " amd64 - Build only for amd64 architecture" + echo " arm64 - Build only for arm64 architecture" + echo " cleanup - Clean up local Docker images" + echo " help - Show this help message" + echo "" + echo "Environment:" + echo " GITHUB_TOKEN - GitHub token for pushing to registry" + echo "" + echo "Examples:" + echo " $0 build" + echo " $0 push" + echo " GITHUB_TOKEN=xxx $0 push" +} + +# Main function +main() { + local command=${1:-help} + + print_status "Starting Docker build for Agent Integrations Finder" + print_status "Git SHA: ${GIT_SHA}" + if [[ -n "${GIT_TAG}" ]]; then + print_status "Git Tag: ${GIT_TAG}" + fi + print_status "Registry: ${REGISTRY}" + print_status "Repository: ${REPO}" + + # Check prerequisites + check_docker + check_buildx + setup_builder + + # Execute command + case "$command" in + build) + build_only + ;; + push) + if [[ -z "${GITHUB_TOKEN}" ]]; then + print_error "GITHUB_TOKEN environment variable is required for pushing" + exit 1 + fi + # Login to GitHub Container Registry + echo "${GITHUB_TOKEN}" | docker login ${REGISTRY} -u USERNAME --password-stdin + build_and_push + ;; + manifest) + create_manifest + push_architectures + ;; + amd64) + build_architecture "amd64" + ;; + arm64) + build_architecture "arm64" + ;; + cleanup) + cleanup + ;; + help|--help|-h) + show_help + ;; + *) + print_error "Unknown command: $command" + show_help + exit 1 + ;; + esac + + print_status "Docker build completed successfully!" +} + +# Parse command line arguments +case "${1:-}" in + -h|--help|help) + show_help + exit 0 + ;; + *) + main "$@" + ;; +esac diff --git a/integrations_finder.py b/integrations_finder.py index 4318e5f..090dd69 100644 --- a/integrations_finder.py +++ b/integrations_finder.py @@ -13,21 +13,28 @@ import click import requests -from PyQt6.QtCore import Qt, QThread, QUrl, pyqtSignal -from PyQt6.QtGui import QDesktopServices, QFont, QPixmap -from PyQt6.QtWidgets import ( - QApplication, - QHBoxLayout, - QLabel, - QLineEdit, - QMainWindow, - QMessageBox, - QProgressBar, - QPushButton, - QTextEdit, - QVBoxLayout, - QWidget, -) + +# Import PyQt6 only when needed for GUI functionality +try: + from PyQt6.QtCore import Qt, QThread, QUrl, pyqtSignal + from PyQt6.QtGui import QDesktopServices, QFont, QPixmap + from PyQt6.QtWidgets import ( + QApplication, + QHBoxLayout, + QLabel, + QLineEdit, + QMainWindow, + QMessageBox, + QProgressBar, + QPushButton, + QTextEdit, + QVBoxLayout, + QWidget, + ) + + PYQT6_AVAILABLE = True +except ImportError: + PYQT6_AVAILABLE = False class IntegrationsFinder: @@ -314,39 +321,56 @@ def find_integrations(self, input_string: str) -> Tuple[bool, str]: return True, success_message, is_branch -class WorkerThread(QThread): - """Worker thread for GUI to prevent blocking.""" +if PYQT6_AVAILABLE: - finished = pyqtSignal(bool, str) + class WorkerThread(QThread): + """Worker thread for GUI to prevent blocking.""" - def __init__(self, finder: IntegrationsFinder, input_string: str): - super().__init__() - self.finder = finder - self.input_string = input_string + finished = pyqtSignal(bool, str) - def run(self): - result = self.finder.find_integrations(self.input_string) - if len(result) == 3: - success, message, is_branch = result - else: - # Backward compatibility - success, message = result - is_branch = False + def __init__(self, finder: IntegrationsFinder, input_string: str): + super().__init__() + self.finder = finder + self.input_string = input_string - # Add branch indicator to message for GUI detection - if is_branch: - message += "\n[BRANCH_VERSION_DETECTED]" + def run(self): + result = self.finder.find_integrations(self.input_string) + if len(result) == 3: + success, message, is_branch = result + else: + # Backward compatibility + success, message = result + is_branch = False - self.finished.emit(success, message) + # Add branch indicator to message for GUI detection + if is_branch: + message += "\n[BRANCH_VERSION_DETECTED]" + self.finished.emit(success, message) -class IntegrationsFinderGUI(QMainWindow): - """GUI for the Agent Integrations Finder tool.""" +else: + # Dummy class for when PyQt6 is not available + class WorkerThread: + def __init__(self, finder: IntegrationsFinder, input_string: str): + self.finder = finder + self.input_string = input_string - def __init__(self): - super().__init__() - self.finder = IntegrationsFinder() - self.init_ui() + +if PYQT6_AVAILABLE: + + class IntegrationsFinderGUI(QMainWindow): + """GUI for the Agent Integrations Finder tool.""" + + def __init__(self): + super().__init__() + self.finder = IntegrationsFinder() + self.init_ui() + +else: + # Dummy class for when PyQt6 is not available + class IntegrationsFinderGUI: + def __init__(self): + pass def init_ui(self): """Initialize the user interface.""" @@ -578,6 +602,11 @@ def find(input_string): @cli.command() def gui(): """Launch the graphical user interface.""" + if not PYQT6_AVAILABLE: + click.echo("Error: PyQt6 is not available. GUI mode requires PyQt6 to be installed.") + click.echo("Please install PyQt6 with: pip install PyQt6") + sys.exit(1) + app = QApplication(sys.argv) window = IntegrationsFinderGUI() window.show()