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
14 changes: 9 additions & 5 deletions .github/scripts/src/ext-registry-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
// simple changes (simple version bump, no changes to important fields) can go by with just a simple approval from any developer.
const { isDeepStrictEqual } = require('node:util');

// The registries this check governs. The ext-registry-check workflow keeps an inline
// copy of this list (it runs its detection step before the checkout, so it can't
// require this file); a test asserts the two stay in sync.
const REGISTRY_JSON_PATHS = new Set([
'cli/azd/extensions/registry.json',
'cli/azd/extensions/registry.dev.json',
]);

// GitHub Actions entry point.
module.exports = run;

Expand All @@ -14,13 +22,9 @@ module.exports.forTests = {
isAllowedRegistryJsonUpdate,
isCreatedByCoreTeam,
diffRegistry,
REGISTRY_JSON_PATHS,
}

const REGISTRY_JSON_PATHS = new Set([
'cli/azd/extensions/registry.json',
'cli/azd/extensions/registry.dev.json',
]);

// We only allow URLs that point to our GitHub releases page.
const ALLOWED_ARTIFACT_URL_ORIGIN = 'https://github.com';
const ALLOWED_ARTIFACT_URL_PATH_PREFIX = '/Azure/azure-dev/releases/download/';
Expand Down
22 changes: 22 additions & 0 deletions .github/scripts/test/ext-registry-check.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, it, expect, vi } from 'vitest';
import run from '../src/ext-registry-check.js';

Expand Down Expand Up @@ -35,6 +37,7 @@ const {
diffRegistry,
getCoreReviewers,
isAllowedRegistryJsonUpdate,
REGISTRY_JSON_PATHS,
} = run.forTests;

const PROD_REGISTRY_PATH = 'cli/azd/extensions/registry.json';
Expand Down Expand Up @@ -1024,6 +1027,25 @@ describe('getCoreReviewers', () => {
});
});

describe('REGISTRY_JSON_PATHS', () => {
// The ext-registry-check workflow detects registry changes before it checks out the
// repo, so it can't require this script and keeps its own copy of the path list.
// A path added here but missed there fails open: the gated steps skip and the
// required check reports green without the policy ever running.
it('matches the inline registryPaths list in ext-registry-check.yml', () => {
const workflowPath = join(__dirname, '..', '..', 'workflows', 'ext-registry-check.yml');
const workflow = readFileSync(workflowPath, 'utf8');

const setLiteral = /const registryPaths = new Set\(\[([^\]]*)\]\)/.exec(workflow);
// Fail loudly rather than silently stop guarding if the literal is renamed or restructured.
expect(setLiteral, 'could not find the `registryPaths` Set literal in ext-registry-check.yml').not.toBeNull();

const workflowPaths = [...(setLiteral?.[1] ?? '').matchAll(/'([^']+)'/g)].map((match) => String(match[1]));
expect(workflowPaths.length).toBeGreaterThan(0);
expect(workflowPaths.sort()).toEqual([...REGISTRY_JSON_PATHS].sort());
});
});

/**
* @param {{
* base?: RegistryJson,
Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/ext-registry-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request:
paths:
- "cli/azd/extensions/registry.json"
- "cli/azd/extensions/registry.dev.json"
- ".github/workflows/ext-registry-ci.yml"
branches: [main]

Expand Down Expand Up @@ -34,16 +35,16 @@ jobs:
- name: Build azd
run: go build .

# Validates registry.json and fails on any hard validation error. The most
# important case is a declared dependency constraint that no published version
# Validates the extension registries and fails on any hard validation error. The
# most important case is a declared dependency constraint that no published version
# satisfies, which is the inconsistency a coordinated multi-extension bump can
# introduce, but schema, id, and artifact errors also fail here. Warnings (e.g.
# an absent dependency id) do not fail this step, so routine extension releases
# are not blocked. Runs on the same trigger as registry.json.
# are not blocked. Runs on the same trigger as the registry files.
- name: Validate registry dependency consistency
run: |
if ! go test ./pkg/extensions -run '^TestRegistryFileIsValid$' -v; then
echo "::error::registry.json failed validation. See the test output above for the specific error(s)."
if ! go test ./pkg/extensions -run '^TestRegistryFileIsValid$|^TestDevRegistryFileIsValid$' -v; then
echo "::error::An extension registry failed validation. See the test output above for the specific error(s)."
echo "Suggestion: a common cause is a dependency pinned to a version that is not yet published in the registry; ensure that version is published before updating the dependent extension."
exit 1
fi
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/scripts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
paths:
- ".github/scripts/**"
- ".github/workflows/scripts-ci.yml"
# The script tests assert that the ext-registry-check workflow's inline registry
# path list matches the script's, so changes to that workflow run them too.
- ".github/workflows/ext-registry-check.yml"

# If two events are triggered within a short time in the same PR, cancel the run of the oldest event
concurrency:
Expand Down
9 changes: 8 additions & 1 deletion cli/azd/pkg/extensions/registry_files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func collectValidationErrors(result *RegistryValidationResult) []string {
return errs
}

// TestDevRegistryFileIsValid gates the development registry on the same
// ext-registry-ci trigger as TestRegistryFileIsValid, and applies the same rule:
// only validation errors fail, never warnings.
func TestDevRegistryFileIsValid(t *testing.T) {
registryPath := filepath.Join("..", "..", "extensions", "registry.dev.json")
data, err := os.ReadFile(registryPath)
Expand All @@ -45,7 +48,11 @@ func TestDevRegistryFileIsValid(t *testing.T) {
require.Equal(t, CurrentRegistrySchemaVersion, registry.SchemaVersion)

result := ValidateRegistry(&registry, false)
require.True(t, result.Valid, "registry.dev.json failed validation: %+v", result)
if !result.Valid {
errs := collectValidationErrors(result)
t.Fatalf("registry.dev.json failed validation with %d error(s):\n - %s",
len(errs), strings.Join(errs, "\n - "))
}
}

// TestRegistryFileIsValid gates the production registry on every pull request that
Expand Down
Loading