Skip to content

Commit

Permalink
Validate referencing and configuration of registries
Browse files Browse the repository at this point in the history
  • Loading branch information
mburumaxwell committed Sep 11, 2023
1 parent 7d077fd commit 6314e14
Show file tree
Hide file tree
Showing 19 changed files with 322 additions and 194 deletions.
16 changes: 10 additions & 6 deletions extension/task/IDependabotConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ export interface IDependabotConfig {
/**
* Optional. Specify authentication details to access private package registries.
*/
registries?: IDependabotRegistry[];
registries?: Record<string, IDependabotRegistry>;
}

export interface IDependabotUpdate {
/**
* Location of package manifests.
* */
directory: string;
/**
* Package manager to use.
* */
packageEcosystem: string;
/**
* Location of package manifests.
* */
directory: string;
schedule?: IDependabotUpdateSchedule;
/**
* Customize which updates are allowed.
Expand Down Expand Up @@ -56,11 +56,15 @@ export interface IDependabotUpdate {
/**
* Whether to reject external code
*/
rejectExternalCode: boolean;
insecureExternalCodeExecution?: string;
/**
* Limit number of open pull requests for version updates.
*/
openPullRequestsLimit?: number;
/**
* Registries configured for this update.
*/
registries: string[];
/**
* Branch to create pull requests against.
*/
Expand Down
12 changes: 8 additions & 4 deletions extension/task/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as tl from "azure-pipelines-task-lib/task"
import { ToolRunner } from "azure-pipelines-task-lib/toolrunner"
import { IDependabotConfig, IDependabotUpdate } from "./IDependabotConfig";
import { IDependabotConfig, IDependabotRegistry, IDependabotUpdate } from "./IDependabotConfig";
import getSharedVariables from "./utils/getSharedVariables";
import { parseConfigFile } from "./utils/parseConfigFile";

Expand Down Expand Up @@ -88,7 +88,7 @@ async function run() {
}

// Set exception behaviour if true
if (update.rejectExternalCode === true) {
if (update.insecureExternalCodeExecution === "deny") {
dockerRunner.arg(["-e", 'DEPENDABOT_REJECT_EXTERNAL_CODE=true']);
}

Expand Down Expand Up @@ -130,8 +130,12 @@ async function run() {
}

// Set the extra credentials
if (config.registries != undefined && config.registries.length > 0) {
let extraCredentials = JSON.stringify(config.registries, (k, v) => v === null ? undefined : v);
if (config.registries != undefined && Object.keys(config.registries).length > 0) {
let selectedRegistries: IDependabotRegistry[] = [];
for (const reg of update.registries) {
selectedRegistries.push(config.registries[reg]);
}
let extraCredentials = JSON.stringify(selectedRegistries, (k, v) => v === null ? undefined : v);
dockerRunner.arg(["-e", `DEPENDABOT_EXTRA_CREDENTIALS=${extraCredentials}`]);
}

Expand Down
2 changes: 1 addition & 1 deletion extension/task/utils/getAzureDevOpsAccessToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function getAzureDevOpsAccessToken() {
debug(`Loading authorization for service connection ${serviceConnectionName}`);
return getEndpointAuthorizationParameter(serviceConnectionName, "AccessToken", false);
}

debug("No custom token provided. The SystemVssConnection's AccessToken shall be used.");
return getEndpointAuthorizationParameter(
"SystemVssConnection",
Expand Down
46 changes: 36 additions & 10 deletions extension/task/utils/parseConfigFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,15 @@ async function parseConfigFile(variables: ISharedVariables): Promise<IDependabot
);
}

var dependabotConfig: IDependabotConfig = {
const updates = parseUpdates(config);
const registries = parseRegistries(config);
validateConfiguration(updates, registries);

return {
version: version,
updates: parseUpdates(config),
registries: parseRegistries(config),
updates: updates,
registries: registries,
};

return dependabotConfig;
}

function parseUpdates(config: any): IDependabotUpdate[] {
Expand All @@ -162,6 +164,7 @@ function parseUpdates(config: any): IDependabotUpdate[] {
directory: update["directory"],

openPullRequestsLimit: update["open-pull-requests-limit"],
registries: update["registries"] || [],

targetBranch: update["target-branch"],
vendor: update["vendor"] ? JSON.parse(update["vendor"]) : null,
Expand All @@ -170,7 +173,7 @@ function parseUpdates(config: any): IDependabotUpdate[] {
branchNameSeparator: update["pull-request-branch-name"]
? update["pull-request-branch-name"]["separator"]
: undefined,
rejectExternalCode: update["insecure-external-code-execution"] === "deny",
insecureExternalCodeExecution: update["insecure-external-code-execution"],

// We are well aware that ignore is not parsed here. It is intentional.
// The ruby script in the docker container does it automatically.
Expand Down Expand Up @@ -214,8 +217,8 @@ function parseUpdates(config: any): IDependabotUpdate[] {
return updates;
}

function parseRegistries(config: any): IDependabotRegistry[] {
var registries: IDependabotRegistry[] = [];
function parseRegistries(config: any): Record<string, IDependabotRegistry> {
var registries: Record<string, IDependabotRegistry> = {};

var rawRegistries = config["registries"];

Expand Down Expand Up @@ -243,7 +246,7 @@ function parseRegistries(config: any): IDependabotRegistry[] {
var type = rawType?.replace("-", "_");

var parsed: IDependabotRegistry = { type: type, };
registries.push(parsed);
registries[registryConfigKey] = parsed;

// handle special fields for 'hex-organization' types
if (type === 'hex_organization') {
Expand Down Expand Up @@ -311,6 +314,29 @@ function parseRegistries(config: any): IDependabotRegistry[] {
return registries;
}

function validateConfiguration(updates: IDependabotUpdate[], registries: Record<string, IDependabotRegistry>) {
const configured = Object.keys(registries);
const referenced: string[] = [];
for (const u of updates) referenced.push(...u.registries);

// ensure there are no configured registries that have not been referenced
const missingConfiguration = referenced.filter((el) => !configured.includes(el));
if (missingConfiguration.length > 0) {
throw new Error(
`Referenced registries: '${missingConfiguration.join(',')}' have not been configured in the root of dependabot.yml`
);
}

// ensure there are no registries referenced but not configured
const missingReferences = configured.filter((el) => !referenced.includes(el));
if (missingReferences.length > 0)
{
throw new Error(
`Registries: '${missingReferences.join(',')}' have not been referenced by any update`
);
}
}

const KnownRegistryTypes = [
"composer-repository",
"docker-registry",
Expand All @@ -325,4 +351,4 @@ const KnownRegistryTypes = [
"terraform-registry",
];

export { parseConfigFile, parseUpdates, parseRegistries, };
export { parseConfigFile, parseUpdates, parseRegistries, validateConfiguration, };
42 changes: 42 additions & 0 deletions extension/tests/utils/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: 'docker' # See documentation for possible values
directory: '/' # Location of package manifests
schedule:
interval: 'weekly'
time: '03:00'
day: 'sunday'
open-pull-requests-limit: 10
- package-ecosystem: 'npm' # See documentation for possible values
directory: '/client' # Location of package manifests
schedule:
interval: 'daily'
time: '03:15'
open-pull-requests-limit: 10
registries:
- reg1
- reg2
insecure-external-code-execution: 'deny'
ignore:
- dependency-name: 'react'
update-types: ['version-update:semver-major']
- dependency-name: 'react-dom'
update-types: ['version-update:semver-major']
- dependency-name: '@types/react'
update-types: ['version-update:semver-major']
- dependency-name: '@types/react-dom'
update-types: ['version-update:semver-major']
registries:
reg1:
type: nuget-feed
url: 'https://pkgs.dev.azure.com/dependabot/_packaging/dependabot/nuget/v3/index.json'
token: ':${{DEFAULT_TOKEN}}'
reg2:
type: npm-registry
url: 'https://pkgs.dev.azure.com/dependabot/_packaging/dependabot-npm/npm/registry/'
token: 'tingle-npm:${{DEFAULT_TOKEN}}'
91 changes: 78 additions & 13 deletions extension/tests/utils/parseConfigFile.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import { load } from "js-yaml";
import * as fs from "fs";
import * as path from "path";
import { parseRegistries } from "../../task/utils/parseConfigFile";
import { parseRegistries, parseUpdates, validateConfiguration } from "../../task/utils/parseConfigFile";
import { IDependabotRegistry, IDependabotUpdate } from "../../task/IDependabotConfig";

describe("Parse configuration file", () => {
it("Parsing works as expected", () => {
let config: any = load(fs.readFileSync('tests/utils/dependabot.yml', "utf-8"));
let updates = parseUpdates(config);
expect(updates.length).toBe(2);

// first
const first = updates[0];
expect(first.directory).toBe('/');
expect(first.packageEcosystem).toBe('docker');
expect(first.insecureExternalCodeExecution).toBe(undefined);
expect(first.registries).toEqual([]);

// second
const second = updates[1];
expect(second.directory).toBe('/client');
expect(second.packageEcosystem).toBe('npm');
expect(second.insecureExternalCodeExecution).toBe('deny');
expect(second.registries).toEqual(['reg1', 'reg2']);
});
});

describe("Parse registries", () => {
it("Parsing works as expected", () => {
let config: any = load(fs.readFileSync('tests/utils/sample-registries.yml', "utf-8"));
let registries = parseRegistries(config);
expect(registries.length).toBe(11);
expect(Object.keys(registries).length).toBe(11);

// composer-repository
var registry = registries[0];
var registry = registries['composer'];
expect(registry.type).toBe('composer_repository');
expect(registry.url).toBe('https://repo.packagist.com/example-company/');
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -27,7 +50,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(undefined);

// docker-registry
registry = registries[1];
registry = registries['dockerhub'];
expect(registry.type).toBe('docker_registry');
expect(registry.url).toBe(undefined);
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -44,7 +67,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(true);

// git
registry = registries[2];
registry = registries['github-octocat'];
expect(registry.type).toBe('git');
expect(registry.url).toBe('https://github.com');
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -61,7 +84,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(undefined);

// hex-organization
registry = registries[3];
registry = registries['github-hex-org'];
expect(registry.type).toBe('hex_organization');
expect(registry.url).toBe(undefined);
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -78,7 +101,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(undefined);

// hex-repository
registry = registries[4];
registry = registries['github-hex-repository'];
expect(registry.type).toBe('hex_repository');
expect(registry.url).toBe('https://private-repo.example.com');
expect(registry.registry).toBe(undefined);
Expand All @@ -94,7 +117,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(undefined);

// maven-repository
registry = registries[5];
registry = registries['maven-artifactory'];
expect(registry.type).toBe('maven_repository');
expect(registry.url).toBe('https://artifactory.example.com');
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -111,7 +134,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(true);

// npm-registry
registry = registries[6];
registry = registries['npm-github'];
expect(registry.type).toBe('npm_registry');
expect(registry.url).toBe(undefined);
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -128,7 +151,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(true);

// nuget-feed
registry = registries[7];
registry = registries['nuget-azure-devops'];
expect(registry.type).toBe('nuget_feed');
expect(registry.url).toBe('https://pkgs.dev.azure.com/contoso/_packaging/My_Feed/nuget/v3/index.json');
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -145,7 +168,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(undefined);

// python-index
registry = registries[8];
registry = registries['python-azure'];
expect(registry.type).toBe('python_index');
expect(registry.url).toBe(undefined);
expect(registry["index-url"]).toBe('https://pkgs.dev.azure.com/octocat/_packaging/my-feed/pypi/example');
Expand All @@ -162,7 +185,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(true);

// rubygems-server
registry = registries[9];
registry = registries['ruby-github'];
expect(registry.type).toBe('rubygems_server');
expect(registry.url).toBe('https://rubygems.pkg.github.com/octocat/github_api');
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -179,7 +202,7 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(false);

// terraform-registry
registry = registries[10];
registry = registries['terraform-example'];
expect(registry.type).toBe('terraform_registry');
expect(registry.url).toBe(undefined);
expect(registry["index-url"]).toBe(undefined);
Expand All @@ -196,3 +219,45 @@ describe("Parse registries", () => {
expect(registry["replaces-base"]).toBe(undefined);
});
});

describe("Validate registries", () => {
it("Validation works as expected", () => {
// let config: any = load(fs.readFileSync('tests/utils/dependabot.yml', "utf-8"));
// let updates = parseUpdates(config);
// expect(updates.length).toBe(2);

var updates: IDependabotUpdate[] = [
{
packageEcosystem: "npm",
directory: "/",
registries: ["dummy1", "dummy2"],
},
];

var registries: Record<string, IDependabotRegistry> = {
'dummy1': {
type: 'nuget',
url: "https://pkgs.dev.azure.com/contoso/_packaging/My_Feed/nuget/v3/index.json",
token: "pwd_1234567890",
},
'dummy2': {
type: "python-index",
url: "https://pkgs.dev.azure.com/octocat/_packaging/my-feed/pypi/example",
username: "[email protected]",
password: "pwd_1234567890",
"replaces-base": true,
},
};

// works as expected
validateConfiguration(updates, registries);

// fails: registry not referenced
updates[0].registries = [];
expect(() => validateConfiguration(updates, registries)).toThrow(`Registries: 'dummy1,dummy2' have not been referenced by any update`);

// fails: registrynot configured
updates[0].registries = ["dummy1", "dummy2", "dummy3",];
expect(() => validateConfiguration(updates, registries)).toThrow(`Referenced registries: 'dummy3' have not been configured in the root of dependabot.yml`);
});
});
Loading

0 comments on commit 6314e14

Please sign in to comment.