Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
# (docker-in-docker) which the autogenerated test image does not provide.
# It is exercised in the test-scenarios job instead.
- atlassian-jira-confluence-cli
- package-source-overrides
baseImage:
- debian:latest
- ubuntu:latest
Expand All @@ -48,6 +49,7 @@ jobs:
- gitlab-ci-local
- copilot-metrics-lgtm
- atlassian-jira-confluence-cli
- package-source-overrides
steps:
- uses: actions/checkout@v4

Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,41 @@ acli jira project list

`acli auth login` opens an OAuth page via `xdg-open`, which doesn't honor `$BROWSER` inside a dev container. With `configureBrowser` enabled (the default), the feature installs a small `xdg-open` shim at `/usr/local/bin/xdg-open` that prefers `$BROWSER` (such as the VS Code browser helper) so the login page opens on your host machine. Set `configureBrowser` to `false` to skip it.

### Package Source Overrides

Points npm, pnpm, pip, and NuGet at internal **override package sources** (a pull-through proxy such as Artifactory, Nexus, or Azure Artifacts) instead of public feeds. It writes system- and user-scoped configuration as early as possible in the container lifecycle so that other Features also resolve packages through the proxy during their own installation.

**Usage:**

```json
{
"features": {
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides:1": {
"npmRegistry": "https://artifactory.example.com/artifactory/api/npm/npm-remote/",
"pipIndexUrl": "https://artifactory.example.com/artifactory/api/pypi/pypi-remote/simple",
"nugetSource": "https://artifactory.example.com/artifactory/api/nuget/v3/index.json"
},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/python:1": {}
},
"overrideFeatureInstallOrder": [
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides"
]
}
```

**Options:**
- `npmRegistry` - registry URL for npm and pnpm (default: "")
- `pipIndexUrl` - pip / PyPI index URL that replaces the default index (default: "")
- `nugetSource` - NuGet v3 source URL that replaces nuget.org (default: "")
- `nugetSourceName` - key/name for the configured NuGet source (default: "override")
- `scope` - where config is written: `system`, `user`, or `both` (default: "both")
- `strictSsl` - set to `false` for self-signed / HTTP internal proxies (default: `true`)

**Lifecycle & Ordering:**

The feature declares no `installsAfter` dependencies so it installs as early as possible, and it writes root-readable config (`/etc/pip.conf`, `/etc/npmrc`, `/root/.npmrc`, root `NuGet.Config`) that other Features' build-time (root) package installs pick up automatically. Because a Feature can't force itself ahead of arbitrary third-party Features, list it first in `overrideFeatureInstallOrder` to guarantee it runs before anything that downloads packages. Providing no URLs makes it a safe no-op.

## Publishing

This repository uses a **GitHub Action** [workflow](.github/workflows/release.yaml) that publishes each Feature to GHCR (GitHub Container Registry).
Expand Down
6 changes: 6 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ build-gitlab-ci-local:
build-atlassian-jira-confluence-cli:
just build-feature atlassian-jira-confluence-cli

# Build Package Source Overrides feature
build-package-source-overrides:
just build-feature package-source-overrides

# Build all features
build-all:
just build-ohmyposh
just build-microsoft-security-devops-cli
just build-gitlab-ci-local
just build-atlassian-jira-confluence-cli
just build-package-source-overrides

# Generate feature README.md files from devcontainer-feature.json and NOTES.md
generate-docs:
Expand All @@ -41,5 +46,6 @@ clean:
rm -rf .devcontainer/ohmyposh
rm -rf .devcontainer/gitlab-ci-local
rm -rf .devcontainer/atlassian-jira-confluence-cli
rm -rf .devcontainer/package-source-overrides
rm -f .devcontainer/*.tgz
echo "✓ Cleaned local features"
78 changes: 78 additions & 0 deletions src/package-source-overrides/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
## About

In locked-down environments you often cannot reach public package feeds
(`registry.npmjs.org`, `pypi.org`, `api.nuget.org`, …) and must go through an
internal pull-through proxy / mirror such as **JFrog Artifactory**, **Sonatype
Nexus**, or **Azure Artifacts**.

This Feature points **npm**, **pnpm**, **pip**, and **NuGet** at those override
sources by writing their configuration files as early as possible in the
container lifecycle, so that **other Features installed afterward also resolve
their packages through the proxy** during their own install scripts.

It only writes configuration — no network calls, no `apt`, and no assumption that
the package managers are already installed. Providing no URLs makes the Feature a
safe no-op.

## How It Works

For each ecosystem you give a URL, the Feature writes standard config files at
both **system** and **per-user** scope (`scope` option, default `both`). Per-user
config is written for **root** (so package installs run by other Features at build
time use the proxy) and for the **remote user** (so it also applies at runtime).

| Ecosystem | System scope | User scope (root + remote user) |
|-----------|--------------|---------------------------------|
| npm / pnpm | `/etc/npmrc` (read via the `NPM_CONFIG_GLOBALCONFIG` env var this Feature sets) | `~/.npmrc` |
| pip / PyPI | `/etc/pip.conf` | `~/.config/pip/pip.conf` |
| NuGet | `/etc/opt/NuGet/Config/NuGet.Config` | `~/.nuget/NuGet/NuGet.Config` |

- **npm / pnpm** share the same `npmRegistry` (pnpm reads `.npmrc`). The
`registry=` key replaces the default registry.
- **pip** sets `index-url`, which replaces the default PyPI index.
- **NuGet** writes `<clear />` before the override source, so `nuget.org` is
removed and only your source remains.

The `.npmrc` files are updated inside a managed marker block so any unrelated npm
settings you already have are preserved; `pip.conf` and `NuGet.Config` are fully
managed by this Feature.

## Getting It First in the Lifecycle

A Feature cannot *force* itself to install before arbitrary third-party Features.
To maximize how early this runs, it declares **no `installsAfter` dependencies**,
so it sorts as early as the resolver allows, and it writes config to root-readable
default locations that later Features' package managers pick up automatically.

For a **hard guarantee** that it runs before Features that download packages, list
it first in `overrideFeatureInstallOrder`:

```jsonc
{
"features": {
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides:1": {
"npmRegistry": "https://artifactory.example.com/artifactory/api/npm/npm-remote/",
"pipIndexUrl": "https://artifactory.example.com/artifactory/api/pypi/pypi-remote/simple",
"nugetSource": "https://artifactory.example.com/artifactory/api/nuget/v3/index.json"
},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/python:1": {}
},
// Ensure the override sources are configured before anything installs packages
"overrideFeatureInstallOrder": [
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides"
]
}
```

## Notes

- **No authentication** is configured — the override sources are expected to allow
anonymous access. Do not put credentials in the URL options (they would be baked
into image layers).
- **Replace policy:** public feeds are replaced, not augmented. Only the override
sources are consulted.
- **Self-signed / HTTP proxies:** set `strictSsl` to `false` to emit
`strict-ssl=false` (npm), `trusted-host` (pip), and `allowInsecureConnections`
(NuGet). Leave it `true` whenever your proxy presents a trusted certificate.
- Leave an ecosystem's URL empty to skip configuring it.
107 changes: 107 additions & 0 deletions src/package-source-overrides/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@

# Package Source Overrides (package-source-overrides)

Configure npm, pnpm, pip, and NuGet to use internal override package sources instead of public feeds, early in the container lifecycle so other Features resolve packages through them too

## Example Usage

```json
"features": {
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides:1": {}
}
```

## Options

| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| npmRegistry | Registry URL for npm and pnpm (they share .npmrc). Leave empty to skip npm/pnpm. Example: https://artifactory.example.com/artifactory/api/npm/npm-remote/ | string | - |
| pipIndexUrl | pip / PyPI index URL that replaces the default index. Leave empty to skip pip. Example: https://artifactory.example.com/artifactory/api/pypi/pypi-remote/simple | string | - |
| nugetSource | NuGet v3 package source URL that replaces nuget.org. Leave empty to skip NuGet. Example: https://artifactory.example.com/artifactory/api/nuget/v3/nuget-remote/index.json | string | - |
| nugetSourceName | Friendly key/name for the configured NuGet package source | string | override |
| scope | Where configuration is written: 'system' (system-wide files), 'user' (root + remote user home), or 'both' | string | both |
| strictSsl | When false, allow self-signed/insecure internal proxies (npm strict-ssl=false, pip trusted-host, NuGet allowInsecureConnections) | boolean | true |

## About

In locked-down environments you often cannot reach public package feeds
(`registry.npmjs.org`, `pypi.org`, `api.nuget.org`, …) and must go through an
internal pull-through proxy / mirror such as **JFrog Artifactory**, **Sonatype
Nexus**, or **Azure Artifacts**.

This Feature points **npm**, **pnpm**, **pip**, and **NuGet** at those override
sources by writing their configuration files as early as possible in the
container lifecycle, so that **other Features installed afterward also resolve
their packages through the proxy** during their own install scripts.

It only writes configuration — no network calls, no `apt`, and no assumption that
the package managers are already installed. Providing no URLs makes the Feature a
safe no-op.

## How It Works

For each ecosystem you give a URL, the Feature writes standard config files at
both **system** and **per-user** scope (`scope` option, default `both`). Per-user
config is written for **root** (so package installs run by other Features at build
time use the proxy) and for the **remote user** (so it also applies at runtime).

| Ecosystem | System scope | User scope (root + remote user) |
|-----------|--------------|---------------------------------|
| npm / pnpm | `/etc/npmrc` (read via the `NPM_CONFIG_GLOBALCONFIG` env var this Feature sets) | `~/.npmrc` |
| pip / PyPI | `/etc/pip.conf` | `~/.config/pip/pip.conf` |
| NuGet | `/etc/opt/NuGet/Config/NuGet.Config` | `~/.nuget/NuGet/NuGet.Config` |

- **npm / pnpm** share the same `npmRegistry` (pnpm reads `.npmrc`). The
`registry=` key replaces the default registry.
- **pip** sets `index-url`, which replaces the default PyPI index.
- **NuGet** writes `<clear />` before the override source, so `nuget.org` is
removed and only your source remains.

The `.npmrc` files are updated inside a managed marker block so any unrelated npm
settings you already have are preserved; `pip.conf` and `NuGet.Config` are fully
managed by this Feature.

## Getting It First in the Lifecycle

A Feature cannot *force* itself to install before arbitrary third-party Features.
To maximize how early this runs, it declares **no `installsAfter` dependencies**,
so it sorts as early as the resolver allows, and it writes config to root-readable
default locations that later Features' package managers pick up automatically.

For a **hard guarantee** that it runs before Features that download packages, list
it first in `overrideFeatureInstallOrder`:

```jsonc
{
"features": {
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides:1": {
"npmRegistry": "https://artifactory.example.com/artifactory/api/npm/npm-remote/",
"pipIndexUrl": "https://artifactory.example.com/artifactory/api/pypi/pypi-remote/simple",
"nugetSource": "https://artifactory.example.com/artifactory/api/nuget/v3/index.json"
},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/python:1": {}
},
// Ensure the override sources are configured before anything installs packages
"overrideFeatureInstallOrder": [
"ghcr.io/rosstaco/devcontainer-features/package-source-overrides"
]
}
```

## Notes

- **No authentication** is configured — the override sources are expected to allow
anonymous access. Do not put credentials in the URL options (they would be baked
into image layers).
- **Replace policy:** public feeds are replaced, not augmented. Only the override
sources are consulted.
- **Self-signed / HTTP proxies:** set `strictSsl` to `false` to emit
`strict-ssl=false` (npm), `trusted-host` (pip), and `allowInsecureConnections`
(NuGet). Leave it `true` whenever your proxy presents a trusted certificate.
- Leave an ecosystem's URL empty to skip configuring it.


---

_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/rosstaco/devcontainer-features/blob/main/src/package-source-overrides/devcontainer-feature.json). Add additional notes to a `NOTES.md`._
58 changes: 58 additions & 0 deletions src/package-source-overrides/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"id": "package-source-overrides",
"version": "1.0.0",
"name": "Package Source Overrides",
"description": "Configure npm, pnpm, pip, and NuGet to use internal override package sources instead of public feeds, early in the container lifecycle so other Features resolve packages through them too",
"documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/package-source-overrides",
"options": {
"npmRegistry": {
"type": "string",
"default": "",
"description": "Registry URL for npm and pnpm (they share .npmrc). Leave empty to skip npm/pnpm. Example: https://artifactory.example.com/artifactory/api/npm/npm-remote/"
},
"pipIndexUrl": {
"type": "string",
"default": "",
"description": "pip / PyPI index URL that replaces the default index. Leave empty to skip pip. Example: https://artifactory.example.com/artifactory/api/pypi/pypi-remote/simple"
},
"nugetSource": {
"type": "string",
"default": "",
"description": "NuGet v3 package source URL that replaces nuget.org. Leave empty to skip NuGet. Example: https://artifactory.example.com/artifactory/api/nuget/v3/nuget-remote/index.json"
},
"nugetSourceName": {
"type": "string",
"default": "override",
"description": "Friendly key/name for the configured NuGet package source"
},
"scope": {
"type": "string",
"enum": [
"system",
"user",
"both"
],
"default": "both",
"description": "Where configuration is written: 'system' (system-wide files), 'user' (root + remote user home), or 'both'"
},
"strictSsl": {
"type": "boolean",
"default": true,
"description": "When false, allow self-signed/insecure internal proxies (npm strict-ssl=false, pip trusted-host, NuGet allowInsecureConnections)"
}
},
"containerEnv": {
"NPM_CONFIG_GLOBALCONFIG": "/etc/npmrc"
},
"customizations": {
"vscode": {
"settings": {
"github.copilot.chat.codeGeneration.instructions": [
{
"text": "This dev container is configured to resolve packages through internal override sources (npm, pnpm, pip, NuGet) instead of public feeds. Do not reconfigure package managers back to public registries such as registry.npmjs.org, pypi.org, or api.nuget.org."
}
]
}
}
}
}
Loading
Loading