Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ It provides a safe workflow for:
18. [Automation and CI/CD](#automation-and-cicd)
19. [Troubleshooting](#troubleshooting)
20. [Development and release](#development-and-release)
21. [Docker](#docker)

---

Expand Down Expand Up @@ -1386,6 +1387,83 @@ CHANGELOG.md

---

## Docker

A sample containerized deployment lives under `docker/`. It builds an image that:

- builds the `salt-config-cli` wheel from this repo and installs it (`scc`/`salt-config`/`raas` entry points);
- installs the system `git` client that SCC shells out to for repo operations;
- runs a small FastAPI server (`docker/api/app.py`) with a single `POST /commands` endpoint that executes `scc` commands and returns the result.

```text
docker/
├── Dockerfile
└── api/
├── app.py
└── requirements.txt
```

### Build

Build from the **repo root** (the Dockerfile copies `pyproject.toml`, `README.md`, `LICENSE`, and `salt_config_cli/` from the build context):

```bash
docker build \
-f docker/Dockerfile \
-t salt-cli-api:latest \
.
```

Corporate networks that block direct PyPI access can point `pip` at an internal mirror via the `PIP_INDEX_URL` build arg (defaults to Broadcom's internal Artifactory):

```bash
docker build \
-f docker/Dockerfile \
--build-arg PIP_INDEX_URL=https://packages.vcfd.broadcom.net/artifactory/api/pypi/pypi-virtual/simple \
-t salt-cli-api:latest \
.
```

### Run

```bash
docker run --rm -p 8000:8000 salt-cli-api:latest
```

### Use

Health check:

```bash
curl http://localhost:8000/healthz
# {"status":"ok"}
```

Trigger an `scc` command (the `scc`/`salt-config`/`raas` prefix in `command` is optional):

```bash
curl -X POST http://localhost:8000/commands \
-H 'Content-Type: application/json' \
-d '{"command": "repo list --json"}'
```

Response shape:

```json
{
"command": "scc repo list --json",
"returncode": 0,
"stdout": "...",
"stderr": ""
}
```

An optional `timeout` field (seconds, default `60`, max `600`) bounds how long the server waits for the command before returning `504`.

> This API executes `scc` with arguments taken directly from the request body and returns raw stdout/stderr — it is a development/demo convenience, not a hardened production service. Do not expose it on an untrusted network without adding authentication and access controls.

---

## Contributing

Contributions should:
Expand Down
63 changes: 63 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# syntax=docker/dockerfile:1.7
#
# Build (from the raas-cli repo root):
#
# docker build \
# -f docker/Dockerfile \
# -t salt-cli-api:latest \
# .
#
# Run:
#
# docker run --rm -p 8000:8000 salt-cli-api:latest
#
# Trigger a command:
#
# curl -X POST http://localhost:8000/commands \
# -H 'Content-Type: application/json' \
# -d '{"command": "--version"}'

# Broadcom corp networks block direct PyPI access; point pip at the internal
# Artifactory mirror by default (override with --build-arg PIP_INDEX_URL=... elsewhere).
ARG PIP_INDEX_URL=https://packages.vcfd.broadcom.net/artifactory/api/pypi/pypi-virtual/simple

########################
# Stage 1: build the salt-config-cli wheel
########################
FROM python:3.12-slim AS wheel-builder
ARG PIP_INDEX_URL
ENV PIP_INDEX_URL=${PIP_INDEX_URL}

WORKDIR /build

COPY pyproject.toml README.md LICENSE ./
COPY salt_config_cli ./salt_config_cli

RUN pip install --no-cache-dir build && \
python -m build --wheel --outdir /dist

########################
# Stage 2: runtime image
########################
FROM python:3.12-slim AS runtime
ARG PIP_INDEX_URL
ENV PIP_INDEX_URL=${PIP_INDEX_URL}

# scc shells out to the system git client for repo operations
RUN apt-get update && \
apt-get install -y --no-install-recommends git && \
rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 1. Install the salt-config-cli wheel built above
COPY --from=wheel-builder /dist/*.whl /tmp/
RUN pip install --no-cache-dir /tmp/*.whl && rm -rf /tmp/*.whl

# 2. Sample API server that fronts the scc CLI
COPY docker/api/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt
COPY docker/api/app.py /app/app.py

EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
69 changes: 69 additions & 0 deletions docker/api/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import shlex
import subprocess

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="salt-config-cli API", version="1.0.0")

SCC_BINARY = "scc"
DEFAULT_TIMEOUT_SECONDS = 60


class CommandRequest(BaseModel):
command: str = Field(
...,
description='Salt config CLI command to run, e.g. "repo list --json" or "scc repo list --json"',
min_length=1,
)
timeout: int = Field(DEFAULT_TIMEOUT_SECONDS, ge=1, le=600)


class CommandResponse(BaseModel):
command: str
returncode: int
stdout: str
stderr: str


@app.get("/healthz")
def healthz():
return {"status": "ok"}


@app.post("/commands", response_model=CommandResponse)
def run_command(request: CommandRequest) -> CommandResponse:
try:
args = shlex.split(request.command)
except ValueError as exc:
raise HTTPException(status_code=400, detail=f"could not parse command: {exc}") from exc

if not args:
raise HTTPException(status_code=400, detail="command must not be empty")

# Allow the command to optionally be prefixed with the binary name itself.
if args[0] in (SCC_BINARY, "salt-config", "raas"):
args = args[1:]

command = [SCC_BINARY, *args]

try:
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=request.timeout,
)
except subprocess.TimeoutExpired as exc:
raise HTTPException(
status_code=504, detail=f"command timed out after {request.timeout}s"
) from exc
except FileNotFoundError as exc:
raise HTTPException(status_code=500, detail=f"{SCC_BINARY} binary not found") from exc

return CommandResponse(
command=shlex.join(command),
returncode=result.returncode,
stdout=result.stdout,
stderr=result.stderr,
)
2 changes: 2 additions & 0 deletions docker/api/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fastapi>=0.111,<1
uvicorn[standard]>=0.30,<1