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
5 changes: 5 additions & 0 deletions deactivate-reactivate/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"python.languageServer": "Pylance",
"snyk.advanced.organization": "70158a6b-3a6d-4bff-aace-9699582f5950",
"snyk.advanced.autoSelectOrganization": true
}
216 changes: 146 additions & 70 deletions deactivate-reactivate/README.md
Original file line number Diff line number Diff line change
@@ -1,134 +1,210 @@
# deactivate-reactivate-all-projects

The purpose of this script is to run through Snyk organizations, deactivate every selected project, then activate it again. That cycle helps rebuild webhooks for SCM integrations.
Cycle Snyk projects **deactivate → activate** so SCM **webhooks** can be rebuilt. You pick one or more organizations; the script walks the projects you care about and calls the Snyk API for each.

The script uses the Snyk API with **retry behavior for rate limits**: HTTP **429** responses are retried using the **`Retry-After`** header (seconds or HTTP-date), and transient **5xx** responses use exponential backoff via the `pysnyk` client.
**Optional:** **`--activate-inactive-only`** skips deactivate and only **activates** projects that are already inactive (not monitored)—useful when you only want to turn monitoring back on.

# Requirements
**Reliability:** HTTP **429** (rate limit) responses are retried using **`Retry-After`**; transient **5xx** errors use backoff from the underlying `pysnyk` client.

- Python 3.9+ recommended
- Dependencies in `requirements.txt` (`pysnyk`, `yaspin`, and their transitive packages)
---

# Usage
## Quick start (most customers)

1. Clone this repository locally.
1. **Clone** this repo and install:

2. **Authenticate.** The script picks credentials in this order (first match wins):
```bash
pip install -r requirements.txt
```

| Priority | Variables | Notes |
| --- | --- | --- |
| 1 | `SNYK_OAUTH_CLIENT_ID` and `SNYK_OAUTH_CLIENT_SECRET` | OAuth 2.0 **client credentials** (recommended for automation). Short-lived access tokens are obtained from the OAuth token endpoint and refreshed automatically before expiry. |
| 2 | `SNYK_OAUTH_TOKEN` | A short-lived OAuth **access token** (same usage as the Snyk CLI). You must refresh it yourself when it expires; use client credentials instead if you need long runs. |
| 3 | `SNYK_TOKEN` | Classic API token (`Authorization: token …`). **Not available in Snyk for Government (FedRAMP)**; see below. |
2. **Set a token** (same idea as the Snyk CLI—use a token that can manage the target orgs):

If none of these are set and you are **not** targeting Gov, the script prompts for `SNYK_TOKEN` interactively.
```bash
export SNYK_TOKEN="your-snyk-api-token"
```

3. Install dependencies:
3. **Preview**, then run:

```bash
pip install -r requirements.txt
# See what would change (no API writes)
python3 main.py --orgs your-org-slug --dry-run

# Then run for real
python3 main.py --orgs your-org-slug
```

4. Run `main.py` with the options below.
If your Snyk data is **not** on the default US instance, set region first (see **Region** below)—wrong region usually means “wrong orgs or empty project list.”

## Snyk for Government (FedRAMP / `SNYK-GOV-01`)
**Large orgs (many thousands of projects):** add **`--workers 12`** (or 8–16) so deactivate/activate calls run in parallel. Requires `SNYK_TOKEN` (or OAuth env vars) in the environment—not an interactive prompt.

[Snyk for Government (US)](https://docs.snyk.io/snyk-data-and-governance/snyk-for-government-us) does **not** allow static API tokens. You must use **OAuth 2.0**—typically a service account with the **client credentials** grant. Access tokens are used like API keys but with `Authorization: Bearer` semantics and a short TTL; this script refreshes them when you use `SNYK_OAUTH_CLIENT_ID` / `SNYK_OAUTH_CLIENT_SECRET`.
---

1. Create an OAuth 2.0 service account (UI or API) and store **client ID** and **client secret** securely.
2. Point the script at the Gov API endpoints using **`--environment SNYK-GOV-01`** (or `export SNYK_ENVIRONMENT=SNYK-GOV-01`). That selects `https://api.snykgov.io` for API v1, REST, and `/oauth2/token`, consistent with [regional URL documentation](https://docs.snyk.io/snyk-data-and-governance/regional-hosting-and-data-residency).
3. Export credentials and run:
## What to pass (cheat sheet)

```bash
export SNYK_ENVIRONMENT=SNYK-GOV-01
export SNYK_OAUTH_CLIENT_ID="your-client-id"
export SNYK_OAUTH_CLIENT_SECRET="your-client-secret"
pip install -r requirements.txt
python3 main.py --orgs your-org-slug
```
| You want to… | Use |
| --- | --- |
| Pick org(s) | **`--orgs slug-or-uuid`** (repeat or space-separate multiple). Omit to be prompted. |
| Preview only | **`--dry-run`** |
| Speed up huge runs | **`--workers N`** (try **8–16**; max **64**) |
| Limit to GitHub / GitLab / etc. | **`--origin github`** (repeat) or **`--origins github gitlab`** |
| Only turn on inactive projects | **`--activate-inactive-only`** |
| EU / AU / second US shard | **`--environment SNYK-EU-01`** (etc.) or **`export SNYK_ENVIRONMENT=...`** |
| More 429 retries per request | **`--rate-limit-attempts 12`** (default **8**) |
| Debug API / org visibility | **`-v`** / **`--verbose`** |

Use **`--dry-run`** until the org names, region, and optional origin filter look right.

Optional: override URLs with `--api-url`, `--rest-api-url`, or `--oauth-token-url`, or the `SNYK_API_URL`, `SNYK_REST_API_URL`, and `SNYK_OAUTH_TOKEN_URL` environment variables. For security, URLs must use **HTTPS** and hostnames under **`*.snyk.io`** or **`*.snykgov.io`**. If your tenant uses another hostname, set `SNYK_ALLOW_UNVERIFIED_API_URL=1` (HTTPS is still required).
---

## Region / environment (`--environment`)
## Authentication

Use the same region names as `snyk config environment` (for example `SNYK-US-02`, `SNYK-EU-01`, `SNYK-GOV-01`). This sets default API and OAuth token URLs. You can override individual URLs as needed.
The script uses the **first** of these that is set:

## Selecting organizations (`--orgs`)
| Order | Environment variables | When to use |
| --- | --- | --- |
| 1 | **`SNYK_OAUTH_CLIENT_ID`** + **`SNYK_OAUTH_CLIENT_SECRET`** | Automation / long runs; tokens refresh automatically. |
| 2 | **`SNYK_OAUTH_TOKEN`** | Short-lived access token (you refresh it yourself). |
| 3 | **`SNYK_TOKEN`** | Classic Snyk API token. |

- Pass one or more **organization slugs** and/or **organization IDs** (UUID from the Snyk UI or API). Slug matching is **case-insensitive**; the ID must match exactly.
- Omit `--orgs` to be prompted for orgs at runtime.
If nothing is set and you are **not** on Snyk for Government, the script may **prompt** for `SNYK_TOKEN`. **Parallel mode** (`--workers` > 1) **always** needs credentials in the environment (no prompt).

Examples:
**Snyk for Government (FedRAMP):** static **`SNYK_TOKEN` is not supported**—use OAuth client credentials and **`--environment SNYK-GOV-01`**. Short instructions: **[Snyk for Government](#optional-snyk-for-government-fedramp)** at the end of this file.

---

## Region / environment

Snyk is hosted in [regions](https://docs.snyk.io/snyk-data-and-governance/regional-hosting-and-data-residency). The script must match **your** org’s region or listing will look wrong.

- **Default:** **`SNYK-US-01`** if you set nothing (same idea as `snyk config environment`).
- **Set once:** `export SNYK_ENVIRONMENT=SNYK-EU-01` **or** `python3 main.py --environment SNYK-EU-01 --orgs my-org`.

| `SNYK_ENVIRONMENT` / `--environment` | Typical use |
| --- | --- |
| `SNYK-US-01` | Default US |
| `SNYK-US-02` | US (`api.us.snyk.io`) |
| `SNYK-EU-01` | Europe |
| `SNYK-AU-01` | Australia |
| `SNYK-GOV-01` | US Government / FedRAMP (OAuth only—see appendix) |

**Advanced:** Override URLs with **`--api-url`**, **`--rest-api-url`**, **`--oauth-token-url`** or **`SNYK_API_URL`**, **`SNYK_REST_API_URL`**, **`SNYK_OAUTH_TOKEN_URL`**. URLs must be **HTTPS**; by default hostnames must be under **`*.snyk.io`** or **`*.snykgov.io`**. Other hosts: set **`SNYK_ALLOW_UNVERIFIED_API_URL=1`** (HTTPS still required).

---

## Organizations (`--orgs`)

Pass **slug** and/or **org id (UUID)**. Slug match is case-insensitive; UUID must match exactly.

```bash
python3 main.py --orgs my-org-slug
python3 main.py --orgs org-one org-two
python3 main.py --orgs 70158a6b-3a6d-4bff-aace-9699582f5950
```

## Filtering by project origin (`--origin` / `--origins`)
---

**Either form works**—pick one style, or mix them. Both apply the same filter (and are merged if you use both).
## Filtering by SCM origin (`--origin` / `--origins`)

Snyk stores an **`origin`** on each project (SCM / import source, for example `github`, `gitlab`). By default the script processes **all** projects in the chosen orgs.
By default **all** project origins in the org are processed. To limit to certain import sources, use either style (you can mix them):

To limit work to specific origins:
- **`--origin github --origin gitlab`**
- **`--origins github gitlab`**

- **`--origin ORIGIN`** — pass once per value (repeat the flag): `--origin github --origin gitlab`.
- **`--origins ORIGIN [ORIGIN ...]`** — pass several values in one go: `--origins github gitlab` (same effect as two `--origin` flags).
Values are **case-insensitive** and must match what the API returns. Many GitHub App imports show as **`github-cloud-app`**, not `github`—run **`--dry-run`** once and copy the **`Origin:`** line if you are unsure.

Matching is **case-insensitive**. If you do not pass `--origin` or `--origins`, every project origin is included.
```bash
python3 main.py --orgs my-org --origin github-cloud-app
python3 main.py --orgs my-org --origins github gitlab
```

Examples:
---

## Activate only inactive projects (`--activate-inactive-only`)

Does **not** deactivate. Only **activates** projects that are already inactive (`isMonitored` false). Same **`--origin` / `--origins`** filters apply.

```bash
# Only GitHub.com projects
python3 main.py --orgs my-org --origin github
python3 main.py --orgs my-org --activate-inactive-only --dry-run
python3 main.py --orgs my-org --activate-inactive-only --origin github
```

# GitHub.com and GitHub Enterprise
python3 main.py --orgs my-org --origin github --origin github-enterprise
---

# Equivalent using --origins
python3 main.py --orgs my-org --origins github github-enterprise
## Parallel workers (`--workers`)

# Only GitLab
python3 main.py --orgs my-org --origin gitlab
```
Each project needs **two** API calls for a full cycle (deactivate, then activate). On large tenants that is mostly **waiting on the network**, so **`--workers N`** runs up to **N** of those calls at the same time (default **1** = same as always).

- Try **8–16** first; raise if 429s stay rare, lower if runs stall on rate limits.
- Max **64**.
- **`--workers` > 1:** set **`SNYK_TOKEN`** or OAuth variables in the environment (no interactive token).
- With **N > 1**, spinners are replaced by a one-line note per org.

Common `origin` values include `github`, `github-enterprise`, `gitlab`, `bitbucket-cloud`, and `azure-repos` (exact strings depend on how projects were imported in Snyk).
```bash
export SNYK_TOKEN="your-api-token"
python3 main.py --orgs my-org --workers 12
```

## Rate limit retries (`--rate-limit-attempts`)
---

- **`--rate-limit-attempts N`** — maximum number of **consecutive HTTP 429** responses to retry **per API request** before failing (default: **8**). Waits honor `Retry-After` when present.
## Rate limits (`--rate-limit-attempts`)

Example:
Max **consecutive HTTP 429** retries **per request** before failing (default **8**). Waits follow **`Retry-After`** when the API sends it.

```bash
python3 main.py --orgs my-org --rate-limit-attempts 12
```

# Full examples
---

## Dry run and verbose

- **`--dry-run`** — List what would happen; **no** deactivate/activate.
- **`-v` / `--verbose`** — Print API bases, orgs visible to the token, and per-project ids/results.

**UI looks unchanged after a full cycle:** projects end **active** again; the goal is often **webhooks / integration**, not a lasting “off” state. If nothing processed, check **region**, **org slug/UUID**, and try without **`--origin`** once.

**Commercial (API token):**
**`RequestsDependencyWarning`:** Usually a **`chardet`** version mismatch in your environment. Use **`pip install -U -r requirements.txt`** in a venv; see `requirements.txt` for the pinned range.

---

## Requirements

- Python **3.9+** recommended
- **`pip install -r requirements.txt`**

---

## Copy-paste examples

**API token, commercial (typical):**

```bash
export SNYK_TOKEN="your-api-token"
pip install -r requirements.txt
python3 main.py \
--orgs my-org-slug \
--origin github --origin github-enterprise \
--rate-limit-attempts 8
python3 main.py --orgs my-org-slug --dry-run
python3 main.py --orgs my-org-slug --workers 12
```

Same run using the other flag: `python3 main.py --orgs my-org-slug --origins github github-enterprise --rate-limit-attempts 8`.

**OAuth client credentials (Enterprise / Gov-compatible):**
**OAuth client credentials (good for automation):**

```bash
export SNYK_OAUTH_CLIENT_ID="…"
export SNYK_OAUTH_CLIENT_SECRET="…"
# For Gov:
# export SNYK_ENVIRONMENT=SNYK-GOV-01
pip install -r requirements.txt
python3 main.py --orgs my-org-slug
```
python3 main.py --orgs my-org-slug --workers 12
```

---

## Optional: Snyk for Government (FedRAMP)

[Snyk for Government](https://docs.snyk.io/snyk-data-and-governance/snyk-for-government-us) does **not** allow static API tokens. Use **OAuth 2.0** (service account **client credentials**). This script refreshes access tokens when **`SNYK_OAUTH_CLIENT_ID`** and **`SNYK_OAUTH_CLIENT_SECRET`** are set.

```bash
export SNYK_ENVIRONMENT=SNYK-GOV-01
export SNYK_OAUTH_CLIENT_ID="your-client-id"
export SNYK_OAUTH_CLIENT_SECRET="your-client-secret"
pip install -r requirements.txt
python3 main.py --orgs your-org-slug
```

Same **HTTPS / hostname** rules and optional **`SNYK_ALLOW_UNVERIFIED_API_URL=1`** as in **Region / environment** above.
Loading