Skip to content
Draft
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
88 changes: 88 additions & 0 deletions .github/workflows/test-validate-metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,91 @@ jobs:
assert.equal(VALIDATION_PASSED, 'true');
assert.equal(VALIDATION_ERROR_COUNT, '0');
assert.equal(VALIDATION_ERRORS, '[]');

test-validation-fail-wrapper-path:
name: Test Validation (Wrapper Path Rejected)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js 24.x
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24.x

- name: Run Validate Metadata
id: validate
continue-on-error: true
uses: ./validate-metadata
with:
overlay-root: ${{ env.TEST_DIR }}/cases/fail-wrapper-path
plugins-root: ${{ env.TEST_DIR }}/source
target-backstage-version: ${{ env.TARGET_BACKSTAGE_VERSION }}
image-repository-prefix: ${{ env.IMAGE_REPOSITORY_PREFIX }}

- name: Verify validation failed with dynamicArtifact error
env:
STEP_OUTCOME: ${{ steps.validate.outcome }}
VALIDATION_PASSED: ${{ steps.validate.outputs.validation-passed }}
VALIDATION_ERROR_COUNT: ${{ steps.validate.outputs.validation-error-count }}
VALIDATION_ERRORS: ${{ steps.validate.outputs.validation-errors }}
shell: node {0}
run: |
import assert from 'node:assert/strict';
const { STEP_OUTCOME, VALIDATION_PASSED, VALIDATION_ERROR_COUNT, VALIDATION_ERRORS } = process.env;
const errors = JSON.parse(VALIDATION_ERRORS);

assert.equal(STEP_OUTCOME, 'failure');
assert.equal(VALIDATION_PASSED, 'false');
assert.ok(Number(VALIDATION_ERROR_COUNT) >= 1);

const dynamicArtifactError = errors.find(e => e.field === 'dynamicArtifact');
assert.ok(dynamicArtifactError, 'Expected a dynamicArtifact error');
assert.equal(dynamicArtifactError.kind, 'mismatch');
assert.equal(dynamicArtifactError.file, 'test-wrapper-path.yaml');
assert.match(dynamicArtifactError.message, /must be an OCI reference/);

test-validation-fail-missing-dynamic-artifact:
name: Test Validation (Missing dynamicArtifact)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js 24.x
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24.x

- name: Run Validate Metadata
id: validate
continue-on-error: true
uses: ./validate-metadata
with:
overlay-root: ${{ env.TEST_DIR }}/cases/fail-missing-dynamic-artifact
plugins-root: ${{ env.TEST_DIR }}/source
target-backstage-version: ${{ env.TARGET_BACKSTAGE_VERSION }}
image-repository-prefix: ${{ env.IMAGE_REPOSITORY_PREFIX }}

- name: Verify validation failed with missing dynamicArtifact error
env:
STEP_OUTCOME: ${{ steps.validate.outcome }}
VALIDATION_PASSED: ${{ steps.validate.outputs.validation-passed }}
VALIDATION_ERROR_COUNT: ${{ steps.validate.outputs.validation-error-count }}
VALIDATION_ERRORS: ${{ steps.validate.outputs.validation-errors }}
shell: node {0}
run: |
import assert from 'node:assert/strict';
const { STEP_OUTCOME, VALIDATION_PASSED, VALIDATION_ERROR_COUNT, VALIDATION_ERRORS } = process.env;
const errors = JSON.parse(VALIDATION_ERRORS);

assert.equal(STEP_OUTCOME, 'failure');
assert.equal(VALIDATION_PASSED, 'false');
assert.ok(Number(VALIDATION_ERROR_COUNT) >= 1);

const dynamicArtifactError = errors.find(e => e.field === 'dynamicArtifact');
assert.ok(dynamicArtifactError, 'Expected a dynamicArtifact missing-field error');
assert.equal(dynamicArtifactError.kind, 'missing-field');
assert.equal(dynamicArtifactError.file, 'test-missing-dynamic-artifact.yaml');
assert.equal(dynamicArtifactError.message, 'Missing required field: dynamicArtifact');
28 changes: 17 additions & 11 deletions update-overlay/create-pr-if-necessary.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,23 @@ module.exports = async ({github, context, core}) => {
}

const dynamicArtifact = doc.getIn(['spec', 'dynamicArtifact']);
if (typeof dynamicArtifact === 'string') {
const ociMatch = dynamicArtifact.match(ociGhcrTagPattern);
if (ociMatch) {
const newTag = `bs_${targetBackstageVersion}__${newVersion}`;
const newDynamicArtifact = `${ociMatch[1]}${newTag}${ociMatch[2] ?? ''}`;
if (newDynamicArtifact !== dynamicArtifact) {
doc.setIn(['spec', 'dynamicArtifact'], newDynamicArtifact);
core.info(` Updated dynamicArtifact tag in ${entry.name}`);
modified = true;
}
}
if (typeof dynamicArtifact !== 'string') {
core.setFailed(`${entry.name}: missing required field "dynamicArtifact"`);
return null;
}
const ociMatch = dynamicArtifact.match(ociGhcrTagPattern);
if (!ociMatch) {
core.setFailed(
`${entry.name}: dynamicArtifact must be an OCI reference starting with "oci://ghcr.io", got "${dynamicArtifact}"`
);
return null;
}
const newTag = `bs_${targetBackstageVersion}__${newVersion}`;
const newDynamicArtifact = `${ociMatch[1]}${newTag}${ociMatch[2] ?? ''}`;
if (newDynamicArtifact !== dynamicArtifact) {
doc.setIn(['spec', 'dynamicArtifact'], newDynamicArtifact);
core.info(` Updated dynamicArtifact tag in ${entry.name}`);
modified = true;
}

const currentSupportedVersions = doc.getIn(['spec', 'backstage', 'supportedVersions']);
Expand Down
6 changes: 4 additions & 2 deletions validate-metadata/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ For each YAML file in the `metadata/` folder of the overlay workspace, the follo

2. **Version Match**: The `version` field in the metadata matches the `version` field in the corresponding plugin's `package.json`

3. **OCI Reference Validation** (if `dynamicArtifact` starts with `oci://ghcr.io`):
3. **dynamicArtifact Required**: The `dynamicArtifact` field must be present and must be an OCI reference starting with `oci://ghcr.io`.

4. **OCI Reference Validation**: For `dynamicArtifact` values starting with `oci://ghcr.io`:
- **Tag Format**: The image tag should be `bs_<target backstage version>__<plugin version>`
- **Reference Format**: The image reference (without tag) should be `<image repository prefix>/<package name with @ and / replaced by ->`

4. **Backstage Supported Versions Match**: The `backstage.supportedVersions` field in the metadata matches the major.minor version of `supportedVersions` in the plugin's `dist-dynamic/package.json`
5. **Backstage Supported Versions Match**: The `backstage.supportedVersions` field in the metadata matches the major.minor version of `supportedVersions` in the plugin's `dist-dynamic/package.json`

## Usage

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: extensions.backstage.io/v1alpha1
kind: Package
metadata:
name: test-missing-dynamic-artifact
namespace: rhdh
spec:
packageName: '@test-org/plugin-test'
version: 1.0.0
backstage:
role: frontend-plugin
supportedVersions: 1.42.5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/test-plugin:
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: extensions.backstage.io/v1alpha1
kind: Package
metadata:
name: test-wrapper-path
namespace: rhdh
spec:
packageName: '@test-org/plugin-test'
dynamicArtifact: ./dynamic-plugins/dist/test-org-plugin-test
version: 1.0.0
backstage:
role: frontend-plugin
supportedVersions: 1.42.5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugins/test-plugin:
12 changes: 0 additions & 12 deletions validate-metadata/test/cases/pass/metadata/test-backend.yaml

This file was deleted.

1 change: 0 additions & 1 deletion validate-metadata/test/cases/pass/plugins-list.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
plugins/test-plugin:
plugins/test-backend:
19 changes: 17 additions & 2 deletions validate-metadata/validate-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,23 @@ function validateMetadataFile(metadataFilePath: string, pluginsMapping: Map<stri
});
}

// Validate dynamicArtifact if it's an OCI reference
if (dynamicArtifact?.startsWith('oci://ghcr.io')) {
if (!dynamicArtifact) {
errors.push({
kind: 'missing-field',
file: metadataFilePath,
field: 'dynamicArtifact',
message: 'Missing required field: dynamicArtifact'
});
} else if (!dynamicArtifact.startsWith('oci://ghcr.io')) {
errors.push({
kind: 'mismatch',
file: metadataFilePath,
field: 'dynamicArtifact',
expected: 'oci://ghcr.io/...',
actual: dynamicArtifact,
message: `Invalid dynamicArtifact: must be an OCI reference starting with "oci://ghcr.io", got "${dynamicArtifact}"`
});
} else {
validateOciReference(errors, metadataFilePath, dynamicArtifact, pluginVersion, packageName);
}

Expand Down