Metadata-Version: 2.5
Name: af-cicd
Version: 0.6.1
Summary: shared cicd tooling
Author-email: hello <hello@allfly.io>
License-Expression: MIT
Requires-Python: <4.0,>=3.13
Requires-Dist: boto3>=1.35.0
Requires-Dist: inquirer<4.0.0,>=3.3.0
Requires-Dist: packaging>=24.1
Requires-Dist: rich<16.0.0,>=15.0.0
Requires-Dist: slack-sdk<4.0.0,>=3.34.0
Requires-Dist: typer[slim]<0.21.0,>=0.20.0
Description-Content-Type: text/markdown

# tools-cicd
share cicd scripts

# Running tool locally

## Example:
```shell
uv sync
AWS_PROFILE=dev-admin-terraform uv run cicd --help
uv run cicd --version      # or `cicd version`
uv run pytest
```

# Release Procedures
- This project uses "trunk" based development off of `master`.
- PRs are allowed but always folded back to `master`.
- Releasing is manual through the `publish.yml` workflow.
- **To release**:
  - Ensure all your features are ready on `master`
  - Trigger the `publish.yml` workflow with `workflow_dispatch` and choose `patch`, `minor`, or `major` for `bump`.
    - The workflow runs `uv version --bump` to auto-rev the version, commits `pyproject.toml` + `uv.lock`,
      tags `vX.Y.Z`, then builds with `uv build` and publishes to PyPI.


# Multi-Repository Release Coordination

The `quest-v2` project includes a coordinated release script that allows you to trigger deployments across all related Quest v2 repositories simultaneously, rather than having to manually trigger each repository's release workflow individually.

### Prerequisites

1. **GitHub CLI**: Install the GitHub CLI tool
   ```bash
   # macOS
   brew install gh
   
   # Ubuntu/Debian
   sudo apt install gh
   
   # Windows
   winget install GitHub.cli
   ```

2. **Authentication**: Authenticate with GitHub
   ```bash
   gh auth login
   ```

3. **Script Setup**: Navigate to the quest-v2 directory and make the script executable
   ```bash
   cd quest-v2
   chmod +x release-all.sh
   ```

### Usage

From the `quest-v2` directory, the script supports promoting from `develop` to `qa`:

```bash
# Promote to QA
./release-all.sh

# Show help and options
./release-all.sh --help
```

### What it does

The script will:
1. Trigger the "Release" workflow on all three Quest v2 repositories:
   - `allfly-quest-ui`
   - `allfly-quest-core-api`
   - `gds-api`
2. Promote code from `develop` branch to `qa`
3. Provide real-time status updates and links to monitor progress
4. Display a summary of successful and failed releases

### Monitoring

After triggering the releases, you can monitor progress through:
- The provided GitHub Actions URLs in the script output
- GitHub's Actions tab in each repository
- Your configured Slack notifications (if enabled)

> **Note**: The script requires appropriate permissions to trigger workflows on all three Quest v2 repositories. Ensure your GitHub account has the necessary access rights.

# Vulnerability scans (`cicd vscan`)

Weekly dependency audit that also fixes what it can, as **one PR per repo**:

```shell
cicd vscan python                      # scan only (pip-audit against uv.lock)
cicd vscan python --fix --dry-run      # show the upgrade plan, touch nothing
cicd vscan python --fix                # upgrade uv.lock locally and re-scan
cicd vscan python --open-pr --notify   # CI: commit, open/update `chore/vscan-python` PR, post to Slack
```

`cicd vscan npm` takes the same flags. It runs `npm audit fix --package-lock-only` (never `--force`) and
diffs `package-lock.json` to report what moved; fixes that need a breaking upgrade are listed under
"Needs attention". A dry run fixes and re-audits a throwaway copy, so its report matches a real run.

- Each vulnerable package is pinned to the **smallest version that fixes all of its advisories**
  (`uv lock --upgrade-package pkg==x`). Upgrades that `uv` can't resolve (e.g. another dependency
  caps the version) or that exceed `max_bump` are listed under "Needs attention" instead.
- The PR branch is stable, so an unmerged PR is updated in place the next week instead of piling up.
- The PR must be pushed with `ACTIONS_PAT` (not `GITHUB_TOKEN`) so PR checks run on it.
- Per-repo overrides (display name, base branch, `max_bump`, expiring ignores) live in
  `.github/vscan.toml`; see `src/allfly/vscan/config.py`.
- Repos call the reusable workflow `.github/workflows/vscan.yml` (usage at the top of that file).
- Slack: posts when there are findings or the scan fails. Add `--notify-clean` (or `notify_clean = true`
  in `vscan.toml`) to also post on clean runs. The reusable workflow posts its own alert if the job dies
  before `cicd` can report (e.g. checkout/token errors).

# Ad-hoc notifications (`cicd notify`)

Post to Slack from any workflow step or laptop, without a purpose-built command. Needs `SLACK_BOT_TOKEN`,
and the bot must be invited to the target channel. `--channel` takes an ID or `#name`; pass env vars yourself
(e.g. `--channel "$SLACK_RELEASES_CHANNEL"`).

```shell
# plain mrkdwn
cicd notify slack --channel "#releases" --text ":rocket: Desktop v1.4.0-dev.3 is out"

# card: header + body + fields + links + footer, no Block Kit needed
cicd notify slack --channel "$SLACK_RELEASES_CHANNEL" \
  --title ":computer: Desktop app dev release" \
  --text "New build available for internal testing" \
  --field Version=1.4.0-dev.3 --field Platform=macOS \
  --link "https://example.com/download|Download" --link "https://example.com/notes|Release notes" \
  --context "Triggered by $GITHUB_ACTOR"

# full Block Kit (inline JSON, @file, or - for stdin; Block Kit Builder exports work as-is)
cicd notify slack --channel C0123ABC --text "fallback for notifications" --blocks @release.json

# raw chat.postMessage args for anything without a flag (explicit flags win)
cicd notify slack --channel C0123ABC --text hi --body '{"metadata": {"event_type": "release", "event_payload": {}}}'

cicd notify slack ... --dry-run   # print the payload, send nothing (no token needed)
```

On success it prints `{"channel": "<ID>", "ts": "<ts>"}` and writes `channel`/`ts` to `$GITHUB_OUTPUT`, so a
later step can reply in the thread (`--thread-ts`, `--reply-broadcast`) or edit the message
(`--update <ts>` with the channel ID). Also: `--attachments` (legacy, e.g. color bars), `--username`,
`--icon-emoji`/`--icon-url` (need `chat:write.customize`), `--[no-]unfurl-links`, `--[no-]unfurl-media`.
Slack errors exit 1 with a hint and do not trigger the build-failure alert.

# Virtual desktops (`cicd desktop`)

Self-serve Windows/Linux desktops on EC2 with Amazon DCV, for QA of the Cozmo desktop app. The backing infra
(VPCs, launch templates, AMIs, SSM documents, idle-stop Lambda) lives in the `allfly-cozmo` repo; see its
`docs/VIRTUAL_DESKTOPS.md`. Each engineer runs the CLI with their own AWS SSO profile for cozmo-dev.

**Prerequisites** (`cicd desktop init` checks them):
- AWS CLI v2 and an SSO profile for the cozmo-dev account with the CozmoDesktopUser permission set
- [session-manager-plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html)

```shell
uv tool install af-cicd==0.5.0
cicd desktop init                                  # profile + fastest region -> ~/.config/af-cicd/desktop.json
cicd desktop flavors                               # flavors and app versions available in your region
cicd desktop create qa-win --flavor windows-x64    # prints + saves the password, offers to connect
cicd desktop connect qa-win                        # SSM tunnel + DCV in your browser; q/Ctrl-C to close
```

| Command | What it does |
|---------|--------------|
| `init` | Pick your profile and default region (by latency), check prerequisites |
| `ping` | TCP latency to each desktop region, fastest first (works without credentials) |
| `flavors [--region R]` | Flavors, and the app versions (AMIs) available per flavor; `*` marks the current default |
| `create NAME --flavor F [--region R] [--app-version X] [--instance-type T] [--connect/--no-connect]` | Launch, wait for SSM, set and print a password, then offer to connect (asks only on a terminal) |
| `list [--all]` | Your desktops in every region (`--all`: everyone's) |
| `describe NAME` | Details, plus when the idle auto-stop will kick in |
| `start NAME` / `stop NAME` | Start or stop and wait for the new state (stopped desktops keep their disk) |
| `connect NAME [--no-open]` | Start if needed, tunnel DCV to a free local port, open DCV's web client in your browser (`--no-open`: print the URL only). Shows the login user and saved password; while open `p` copies the password, `q` closes |
| `password NAME` | Set a new random password and print it |
| `keep-awake NAME --hours H` / `--off` | Skip the idle auto-stop for up to the configured maximum hours |
| `reset NAME [--app-version X] [--yes]` | Replace the disk with a fresh image (current, or a given app version) and print a new password |
| `delete NAME [--yes]` | Terminate the desktop and its disk |

Every AWS command takes `--profile` (default `$AWS_PROFILE`, then the saved profile) and re-runs
`aws sso login` when the SSO session has expired. Desktops are stopped after the configured idle time
(60 min by default) without a DCV connection; `connect` starts them again. Every password the CLI sets
(`create`, `password`, `reset`) is printed and saved to `~/.config/af-cicd/desktop.json` (mode 0600, keyed by
instance ID) so `connect` can show it; `delete` removes it. Exit codes: 0 ok, 1 AWS/runtime error, 2 bad arguments.
