Skip to content

Commit a3d6b86

Browse files
feat: configurable OCI registry for DPDY {{inherit}} refs in nightly mode (#104)
* feat: configurable OCI registry for DPDY {{inherit}} refs in nightly mode Decouple the {{inherit}} tag registry from metadata's dynamicArtifact to avoid registry mismatches (e.g. metadata switching from RHEC to ghcr.io while DPDY stays on RHEC). The registry is now resolved via: 1. NIGHTLY_DPDY_OCI_REGISTRY_MAP (per-plugin JSON override) 2. NIGHTLY_DPDY_OCI_REGISTRY (blanket override) 3. Default: registry.access.redhat.com/rhdh Also adds selective config injection in nightly: only non-DPDY OCI plugins get appConfigExamples injected; DPDY plugins inherit config from RHDH, and wrapper plugins get no injection. Assisted-by: Claude Code Co-Authored-By: Claude Code <noreply@anthropic.com> * docs: clarify {{inherit}} resolves both OCI tag and config from DPDY Rewrite confusing references across docs and source comments: - {{inherit}} tells RHDH to resolve both the OCI tag (version) AND default config from its built-in DPDY, not just one or the other - Replace vague "DPDY OCI plugin" with explicit "plugins in default.packages.yaml whose metadata spec.dynamicArtifact is OCI" - Remove redundant tests covered by existing assertions Assisted-by: Claude Code Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 7d0a378 commit a3d6b86

14 files changed

Lines changed: 1272 additions & 259 deletions

docs/api/utils/plugin-metadata.md

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Plugin metadata handling is fully automatic during `rhdh.deploy()`. The function
1313
Extracts the plugin name from a package path or OCI reference.
1414

1515
```typescript
16-
function extractPluginName(packageRef: string): string
16+
function extractPluginName(packageRef: string): string;
1717
```
1818

1919
**Parameters:**
@@ -25,18 +25,20 @@ function extractPluginName(packageRef: string): string
2525

2626
**Supported Formats:**
2727

28-
| Format | Example | Extracted Name |
29-
|--------|---------|----------------|
30-
| Wrapper path | `./dynamic-plugins/dist/my-plugin` | `my-plugin` |
31-
| OCI with tag | `oci://quay.io/rhdh/my-plugin:1.0.0` | `my-plugin` |
32-
| OCI with digest | `oci://quay.io/rhdh/my-plugin@sha256:abc...` | `my-plugin` |
33-
| OCI with alias | `oci://quay.io/rhdh/my-plugin@sha256:abc!alias` | `my-plugin` |
34-
| GHCR | `ghcr.io/org/repo/my-plugin:tag` | `my-plugin` |
28+
| Format | Example | Extracted Name |
29+
| --------------- | ----------------------------------------------- | -------------- |
30+
| Wrapper path | `./dynamic-plugins/dist/my-plugin` | `my-plugin` |
31+
| OCI with tag | `oci://quay.io/rhdh/my-plugin:1.0.0` | `my-plugin` |
32+
| OCI with digest | `oci://quay.io/rhdh/my-plugin@sha256:abc...` | `my-plugin` |
33+
| OCI with alias | `oci://quay.io/rhdh/my-plugin@sha256:abc!alias` | `my-plugin` |
34+
| GHCR | `ghcr.io/org/repo/my-plugin:tag` | `my-plugin` |
3535

3636
**Example:**
3737

3838
```typescript
39-
const name = extractPluginName("oci://quay.io/rhdh/backstage-community-plugin-tech-radar:1.0.0");
39+
const name = extractPluginName(
40+
"oci://quay.io/rhdh/backstage-community-plugin-tech-radar:1.0.0",
41+
);
4042
// Returns: "backstage-community-plugin-tech-radar"
4143
```
4244

@@ -47,7 +49,7 @@ const name = extractPluginName("oci://quay.io/rhdh/backstage-community-plugin-te
4749
Returns a stable merge key for a plugin entry so that OCI and local path for the same logical plugin match when merging dynamic-plugins configs. Strips a trailing `-dynamic` suffix so that e.g. `backstage-community-plugin-catalog-backend-module-keycloak-dynamic` (local) and `backstage-community-plugin-catalog-backend-module-keycloak` (from OCI) map to the same key.
4850

4951
```typescript
50-
function getNormalizedPluginMergeKey(entry: { package?: string }): string
52+
function getNormalizedPluginMergeKey(entry: { package?: string }): string;
5153
```
5254

5355
**Parameters:**
@@ -62,12 +64,14 @@ function getNormalizedPluginMergeKey(entry: { package?: string }): string
6264
```typescript
6365
// OCI and local path for the same plugin yield the same key
6466
getNormalizedPluginMergeKey({
65-
package: "oci://ghcr.io/org/repo/backstage-community-plugin-catalog-backend-module-keycloak:tag!alias",
67+
package:
68+
"oci://ghcr.io/org/repo/backstage-community-plugin-catalog-backend-module-keycloak:tag!alias",
6669
});
6770
// Returns: "backstage-community-plugin-catalog-backend-module-keycloak"
6871

6972
getNormalizedPluginMergeKey({
70-
package: "./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-keycloak-dynamic",
73+
package:
74+
"./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-keycloak-dynamic",
7175
});
7276
// Returns: "backstage-community-plugin-catalog-backend-module-keycloak"
7377
```
@@ -79,12 +83,13 @@ getNormalizedPluginMergeKey({
7983
Determines whether the current execution is a nightly/periodic job. Controls whether metadata config injection is enabled and which OCI resolution strategy is used.
8084

8185
```typescript
82-
function isNightlyJob(): boolean
86+
function isNightlyJob(): boolean;
8387
```
8488

8589
**Returns:** `true` if running in nightly mode, `false` for PR/local mode.
8690

8791
**Priority order:**
92+
8893
1. If `GIT_PR_NUMBER` is set → returns `false` (PR mode takes precedence)
8994
2. If `E2E_NIGHTLY_MODE` is `"true"` or `"1"` → returns `true`
9095
3. If `JOB_NAME` contains `periodic-` → returns `true`
@@ -97,7 +102,7 @@ function isNightlyJob(): boolean
97102
Gets the metadata directory path.
98103

99104
```typescript
100-
function getMetadataDirectory(metadataPath?: string): string | null
105+
function getMetadataDirectory(metadataPath?: string): string | null;
101106
```
102107

103108
**Parameters:**
@@ -115,8 +120,8 @@ Parses all metadata files in a directory and builds a map of plugin name to conf
115120

116121
```typescript
117122
async function parseAllMetadataFiles(
118-
metadataDir: string
119-
): Promise<Map<string, PluginMetadata>>
123+
metadataDir: string,
124+
): Promise<Map<string, PluginMetadata>>;
120125
```
121126

122127
**Parameters:**
@@ -134,8 +139,8 @@ Auto-generates plugin entries from workspace metadata files when no user-provide
134139

135140
```typescript
136141
async function generatePluginsFromMetadata(
137-
metadataPath?: string
138-
): Promise<DynamicPluginsConfig>
142+
metadataPath?: string,
143+
): Promise<DynamicPluginsConfig>;
139144
```
140145

141146
**Parameters:**
@@ -154,22 +159,25 @@ Unified entry point for both PR and nightly plugin resolution flows. Called auto
154159
```typescript
155160
async function processPluginsForDeployment(
156161
config: DynamicPluginsConfig,
157-
metadataPath?: string
158-
): Promise<DynamicPluginsConfig>
162+
metadataPath?: string,
163+
dpdyPackages?: Set<string>,
164+
): Promise<DynamicPluginsConfig>;
159165
```
160166

161167
**Parameters:**
162168
| Parameter | Type | Default | Description |
163169
|-----------|------|---------|-------------|
164170
| `config` | [`DynamicPluginsConfig`](#dynamicpluginsconfig) | - | The plugins config to process |
165171
| `metadataPath` | `string` | `"../metadata"` | Path to metadata directory |
172+
| `dpdyPackages` | `Set<string>` | - | Pre-loaded DPDY package set (for testing; fetched automatically if omitted in nightly) |
166173

167174
**Returns:** Processed configuration with resolved OCI references.
168175

169176
**Behavior:**
177+
170178
- **PR mode** (`!isNightlyJob()`): Injects `appConfigExamples` from metadata as base config, then resolves packages to OCI URLs (PR-specific if `GIT_PR_NUMBER` set, metadata refs otherwise)
171-
- **Nightly mode** (`isNightlyJob()`): Resolves packages to OCI refs from metadata only (no config injection)
172-
- Respects `RHDH_SKIP_PLUGIN_METADATA_INJECTION` to skip config injection
179+
- **Nightly mode** (`isNightlyJob()`): Plugins in `default.packages.yaml` whose metadata `spec.dynamicArtifact` is an OCI ref use `{{inherit}}` with configurable registry (default `registry.access.redhat.com/rhdh`, overridable via `NIGHTLY_DPDY_OCI_REGISTRY` or `NIGHTLY_DPDY_OCI_REGISTRY_MAP`) — RHDH resolves both the OCI tag and default config from its built-in DPDY. Plugins NOT in `default.packages.yaml` with OCI metadata use full metadata refs with config injection.
180+
- Respects `RHDH_SKIP_PLUGIN_METADATA_INJECTION` to skip config injection (local only, ignored in CI)
173181

174182
---
175183

@@ -178,7 +186,7 @@ async function processPluginsForDeployment(
178186
Creates a dynamic plugins config that disables wrapper plugins. Used during PR builds when wrapper plugins would conflict with PR-built OCI images.
179187

180188
```typescript
181-
function disablePluginWrappers(plugins: string[]): DynamicPluginsConfig
189+
function disablePluginWrappers(plugins: string[]): DynamicPluginsConfig;
182190
```
183191

184192
**Parameters:**

docs/changelog.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [1.1.39] - Current
5+
## [1.1.40] - Current
6+
7+
### Changed
8+
9+
- **Nightly `{{inherit}}` resolution**: In nightly mode, plugins listed in [`default.packages.yaml`](https://github.com/redhat-developer/rhdh/blob/main/default.packages.yaml) whose metadata `spec.dynamicArtifact` is an OCI ref now resolve to `{{inherit}}` tags instead of pinned OCI refs. The `{{inherit}}` tag tells RHDH to resolve both the OCI tag (version) and default config from its built-in DPDY (`dynamic-plugins.default.yaml` in the catalog index image), so no config injection is needed from our side. This tests against the exact versions and configuration shipped in the RC. Plugins NOT in `default.packages.yaml` with OCI metadata continue using full metadata refs with config injection (they aren't in RHDH's built-in defaults). The DPDY list is fetched at runtime from the `rhdh` repo using `RELEASE_BRANCH_NAME` (required in CI, defaults to `main` locally). The `{{inherit}}` registry defaults to `registry.access.redhat.com/rhdh` and can be overridden with `NIGHTLY_DPDY_OCI_REGISTRY` (blanket) or `NIGHTLY_DPDY_OCI_REGISTRY_MAP` (per-plugin JSON: `{"registry": ["pkg1", "pkg2"]}`). This decouples the `{{inherit}}` registry from metadata's `spec.dynamicArtifact`, avoiding mismatches when metadata points to `ghcr.io` but the DPDY uses `registry.access.redhat.com`.
10+
- **`RHDH_SKIP_PLUGIN_METADATA_INJECTION` is local-only**: This env var is now ignored in CI (`CI=true`). It was intended for local development opt-out only — in CI, metadata injection should always run to ensure consistent test behavior.
11+
- **`RELEASE_BRANCH_NAME` required in CI for nightly**: When running nightly mode in CI, `RELEASE_BRANCH_NAME` must be set (exported by the OpenShift CI step registry). Locally it defaults to `main`.
12+
13+
## [1.1.39]
614

715
### Changed
816

docs/guide/configuration/config-files.md

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -212,18 +212,24 @@ Your `pluginConfig` in `dynamic-plugins.yaml` overrides values from metadata.
212212
### When Injection is Enabled
213213

214214
Plugin metadata injection is **enabled by default** for:
215+
215216
- Local development
216217
- PR builds in CI
217218

218-
Injection is **disabled** when:
219-
- [`RHDH_SKIP_PLUGIN_METADATA_INJECTION`](/guide/configuration/environment-variables#plugin-metadata-variables) environment variable is set
220-
- `JOB_NAME` contains `periodic-` (nightly/periodic CI builds)
219+
Injection is **disabled locally** when:
220+
221+
- [`RHDH_SKIP_PLUGIN_METADATA_INJECTION`](/guide/configuration/environment-variables#plugin-metadata-variables) is set to `true` (ignored in CI)
222+
223+
In **nightly mode** (`E2E_NIGHTLY_MODE=true` or `JOB_NAME` contains `periodic-`):
224+
225+
- Only plugins NOT in `default.packages.yaml` with OCI metadata get injection; plugins in `default.packages.yaml` use `{{inherit}}` — RHDH resolves both the OCI tag and default config from its built-in DPDY
221226

222227
::: warning
223228
When injection is enabled, deployment will fail if:
229+
224230
- The `metadata/` directory doesn't exist
225231
- No valid metadata files are found in the directory
226-
:::
232+
:::
227233

228234
### OCI URL Replacement for PR Builds
229235

@@ -244,6 +250,7 @@ This allows E2E tests to run against the actual OCI images built for the PR.
244250
If you want to reproduce OCI URL replacement locally, create the required files at the workspace root:
245251
246252
**source.json**
253+
247254
```json
248255
{
249256
"repo": "https://github.com/redhat-developer/rhdh-plugin-export-overlays",
@@ -252,6 +259,7 @@ If you want to reproduce OCI URL replacement locally, create the required files
252259
```
253260

254261
**plugins-list.yaml**
262+
255263
```yaml
256264
plugins/tech-radar:
257265
plugins/my-plugin:
@@ -274,26 +282,27 @@ See [Plugin Metadata - OCI URL Generation](/guide/utilities/plugin-metadata#oci-
274282

275283
The package automatically matches plugins across different reference formats:
276284

277-
| Format | Example |
278-
|--------|---------|
279-
| Wrapper path | `./dynamic-plugins/dist/my-plugin` |
280-
| OCI with tag | `oci://quay.io/rhdh/my-plugin:1.0.0` |
285+
| Format | Example |
286+
| --------------- | -------------------------------------------- |
287+
| Wrapper path | `./dynamic-plugins/dist/my-plugin` |
288+
| OCI with tag | `oci://quay.io/rhdh/my-plugin:1.0.0` |
281289
| OCI with digest | `oci://quay.io/rhdh/my-plugin@sha256:abc...` |
282-
| GHCR | `ghcr.io/org/repo/my-plugin:tag` |
290+
| GHCR | `ghcr.io/org/repo/my-plugin:tag` |
283291

284292
All formats extract the plugin name (`my-plugin`) for matching against metadata.
285293

286294
## Environment Variable Substitution
287295

288296
Use these syntaxes in YAML files:
289297

290-
| Syntax | Description |
291-
|--------|-------------|
292-
| `$VAR` | Simple substitution |
293-
| `${VAR}` | Braced substitution |
294-
| `${VAR:-default}` | Default if unset |
298+
| Syntax | Description |
299+
| ----------------- | ------------------- |
300+
| `$VAR` | Simple substitution |
301+
| `${VAR}` | Braced substitution |
302+
| `${VAR:-default}` | Default if unset |
295303

296304
Example:
305+
297306
```yaml
298307
backend:
299308
baseUrl: https://backstage-${NAMESPACE}.${K8S_CLUSTER_ROUTER_BASE}

0 commit comments

Comments
 (0)