Metadata-Version: 2.4
Name: nexus-pkg-push
Version: 1.2.0
Summary: Publish deb, rpm, and raw packages to a Sonatype Nexus repository, with automatic format detection and index-availability polling.
Author-email: Patrik Dufresne <patrik@ikus-soft.com>
License: MIT License
Project-URL: Homepage, https://gitlab.com/ikus-soft/nexus-pkg-push
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: <4,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests
Provides-Extra: dev
Requires-Dist: black==26.3.0; extra == "dev"
Requires-Dist: flake8-copyright; extra == "dev"
Requires-Dist: flake8==7.3.0; extra == "dev"
Requires-Dist: isort==8.0.1; extra == "dev"
Provides-Extra: test
Dynamic: license-file

<p align="center">
<a href="LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/debbuild"></a>
<a href="https://gitlab.com/ikus-soft/nexus-pkg-push/pipelines"><img alt="Build" src="https://gitlab.com/ikus-soft/nexus-pkg-push/badges/main/pipeline.svg"></a>
<a href="https://sonar.ikus-soft.com/dashboard?id=nexus-pkg-push"><img alt="Quality Gate Minarca Client" src="https://sonar.ikus-soft.com/api/project_badges/measure?project=nexus-pkg-push&metric=alert_status"></a>
<a href="https://sonar.ikus-soft.com/dashboard?id=nexus-pkg-push"><img alt="Coverage" src="https://sonar.ikus-soft.com/api/project_badges/measure?project=nexus-pkg-push&metric=coverage"></a>
</p>

# nexus-pkg-push – README

## Overview

**nexus-pkg-push** is a command-line tool to publish Debian (`.deb`), RPM (`.rpm`), or raw packages to a Sonatype Nexus repository over its REST API. It automatically waits for the repository index to be rebuilt before confirming success, eliminating race conditions in CI/CD pipelines.

Supports:
- **Debian (apt)** repositories with distribution and component parameters
- **RPM (yum)** repositories with sub-path support
- **Raw** repositories with recursive directory uploads
- Automatic retry on transient failures
- Index confirmation polling
- Bearer token or username/password authentication

## Installation

```bash
pip install nexus-pkg-push
```

Or install from source:
```bash
git clone <repo>
cd nexus-pkg-push
pip install .
```

## Requirements

- Python 3.7+
- `requests` library

## Quick Start

### Upload a single .deb file

```bash
nexus-pkg-push \
  -u admin:password \
  --distribution bullseye,bookworm \
  myapp_1.0_amd64.deb \
  https://nexus.example.com/repository/apt-release/
```

### Upload multiple .rpm files

```bash
nexus-pkg-push \
  -u admin:password \
  dist/*.rpm \
  https://nexus.example.com/repository/yum-release/
```

### Upload a directory recursively (raw)

```bash
nexus-pkg-push \
  -u admin:password \
  -R \
  ./dist/docs/ \
  https://nexus.example.com/repository/archive/myapp/1.0/docs/
```

### Rename a file on upload

```bash
nexus-pkg-push \
  -u admin:password \
  myapp.tar.gz \
  https://nexus.example.com/repository/raw-release/myapp-latest.tar.gz
```

## Usage

```
nexus-pkg-push [OPTIONS] FILE [FILE ...] REPOURL
```

### Positional Arguments

| Argument | Description |
|---|---|
| `FILE` | One or more files or directories to upload |
| `REPOURL` | Full URL to the target Nexus repository (e.g., `https://nexus.example.com/repository/apt-release/`) |

### Authentication

| Option | Description |
|---|---|
| `-u, --user USER[:PASS]` | Username, or `user:password` pair (like `curl -U`). Can omit password to prompt. |
| `--password PASSWORD` | Nexus password (alternative to embedding in `-u`) |
| `--token TOKEN` | Bearer token (alternative to username/password) |

**Environment Variables:**
- `NEXUS_USERNAME` / `NEXUS_USR` – Username
- `NEXUS_PASSWORD` / `NEXUS_PWD` – Password
- `NEXUS_TOKEN` – Bearer token

### Upload Options

| Option | Description |
|---|---|
| `-R, --recursive` | Upload directory contents recursively (required for directories) |
| `--distribution DIST[,DIST...]` | **(deb only)** Comma-separated list of target distributions (e.g., `bullseye,bookworm,jammy`). If omitted, auto-discovers available distributions. |
| `--component COMPONENT` | **(deb only)** APT component/section (default: `main`) |

### Retry & Timeout Options

| Option | Description |
|---|---|
| `--retries N` | Number of upload retry attempts on transient failure (default: `3`) |
| `--timeout SECONDS` | HTTP request timeout in seconds (default: `10`) |
| `--wait-timeout SECONDS` | Max time to wait for index confirmation (default: `60`). Use `0` to skip waiting. |
| `--wait-interval SECONDS` | Polling interval while waiting for index (default: `1`) |

### Other Options

| Option | Description |
|---|---|
| `--continue-on-error` | Attempt all uploads and report summary instead of stopping at first failure |
| `-d, --debug` | Increase verbosity (prints debug messages to stderr) |
| `--version` | Show version information and exit |
| `-h, --help` | Show help message and exit |

## Examples

### Example 1: Upload .deb with auto-discovery of distributions

```bash
nexus-pkg-push \
  -u admin:password \
  myapp_1.0_amd64.deb \
  https://nexus.ikus-soft.com/repository/apt-dev/
```

The tool queries the repository and uploads to all available distributions.

### Example 2: Upload multiple .rpm files with retries

```bash
nexus-pkg-push \
  -u admin:password \
  --retries 5 \
  dist/myapp-1.0.x86_64.rpm \
  dist/myapp-debuginfo-1.0.x86_64.rpm \
  https://nexus.example.com/repository/yum-release/
```

### Example 3: Upload documentation recursively

```bash
nexus-pkg-push \
  -u admin:password \
  -R \
  ./html/ \
  https://nexus.example.com/repository/archive/rdiffweb/1.2.3/doc/
```

Preserves directory structure: `html/index.html` → `archive/rdiffweb/1.2.3/doc/index.html`

### Example 4: Upload with token authentication and skip index wait

```bash
nexus-pkg-push \
  --token my-bearer-token \
  --wait-timeout 0 \
  app.tar.gz \
  https://nexus.example.com/repository/raw-release/builds/
```

### Example 5: Continue on error

```bash
nexus-pkg-push \
  -u admin:password \
  --continue-on-error \
  dist/pkg1.deb \
  dist/pkg2.deb \
  dist/pkg3.deb \
  https://nexus.example.com/repository/apt-dev/
```

Even if `pkg1` fails, will attempt `pkg2` and `pkg3`, then report summary.

## Exit Codes

| Code | Meaning |
|---|---|
| `0` | All files uploaded and indexed successfully |
| `1` | Upload failed (after retries) |
| `2` | Invalid arguments or usage error |
| `3` | Upload succeeded, but index confirmation timed out |

## How It Works

1. **Authenticate**: Uses provided credentials (username/password or token)
2. **Discover Repository**: Queries Nexus REST API to determine repository format (apt/yum/raw)
3. **Collect Files**: Expands file/directory arguments, applying `cp`-like semantics
4. **Upload**: Sends files to Nexus using the appropriate HTTP method:
   - **apt**: `POST` with multipart form data
   - **yum/raw**: `PUT` with raw file data
5. **Retry on Failure**: Retries transient HTTP errors with exponential backoff
6. **Index Confirmation**: Polls repository metadata until the uploaded file is visible:
   - **apt**: Queries `Packages` files for each distribution/architecture
   - **yum**: Decompresses `primary.xml.gz` and searches for the file
   - **raw**: Skips polling (immediate availability)

## Special Behaviors

### cp-like Semantics for Renaming

When uploading a **single file** to a `REPOURL` that includes a filename (no trailing `/`), the file is renamed:

```bash
# Uploads as 'myapp-latest.tar.gz'
nexus-pkg-push -u admin:pass app.tar.gz \
  https://nexus.example.com/repository/raw/myapp-latest.tar.gz
```

With multiple files, this raises an error (cp behavior):

```bash
# ERROR: cannot rename multiple files
nexus-pkg-push -u admin:pass file1.deb file2.deb \
  https://nexus.example.com/repository/apt/renamed.deb
```

### Recursive Directory Upload

For raw repositories, use `-R/--recursive` to upload a directory tree:

```bash
nexus-pkg-push -u admin:pass -R ./docs/ \
  https://nexus.example.com/repository/archive/v1.0/
```

Preserves structure: `docs/api/index.html` → `archive/v1.0/api/index.html`

### Distribution Auto-Discovery (Debian)

If `--distribution` is omitted, the tool queries Nexus to find all available distributions and uploads to each:

```bash
nexus-pkg-push -u admin:pass myapp.deb \
  https://nexus.example.com/repository/apt-dev/
# Discovers: bullseye, bookworm, trixie, jammy, noble, ...
```

## Troubleshooting

### "Repository not found via Nexus API"

- Verify the repository name in the URL
- Check Nexus credentials and permissions
- Confirm Nexus base URL is correct

### "Index confirmation timed out"

- Increase `--wait-timeout` if Nexus is slow to rebuild indexes
- Use `--wait-timeout 0` to skip polling (not recommended for CI)

### "Upload attempt N/3 failed: Connection timeout"

- Increase `--timeout` if network is slow
- Increase `--retries` for flaky networks

### Authentication fails

Ensure at least one of:
- `-u username:password` provided
- `NEXUS_USERNAME` and `NEXUS_PASSWORD` environment variables set
- `--token` provided with `NEXUS_TOKEN` environment variable

## Development

Run tests:

```bash
pip install -e '.[dev]'
python -m pytest test_nexus_pkg_push.py -v
```

Debug mode:

```bash
nexus-pkg-push -d -u admin:pass file.deb https://nexus.example.com/repository/apt-dev/
```

## License

MIT © 2026 Patrik Dufresne

## Contributing

Issues and pull requests welcome at the project repository.
