diff --git a/docs/updater.md b/docs/updater.md
index 738b807a..03dc43b4 100644
--- a/docs/updater.md
+++ b/docs/updater.md
@@ -89,13 +89,12 @@ To run the script, some environment variables are required.
|DEPENDABOT_TARGET_BRANCH|Update,
vNext|**_Optional_**. The branch to be targeted when creating a pull request. When not specified, Dependabot will resolve the default branch of the repository.|
|DEPENDABOT_VERSIONING_STRATEGY|Update,
vNext|**_Optional_**. The versioning strategy to use. See [official docs](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates#versioning-strategy) for the allowed values|
|DEPENDABOT_OPEN_PULL_REQUESTS_LIMIT|Update,
vNext|**_Optional_**. The maximum number of open pull requests to have at any one time. Defaults to 5. Setting to 0 implies security only updates.|
-|DEPENDABOT_SECURITY_UPDATES_ONLY|vNext|**_Optional_**. If `true`, only security updates will be processed. Can be used in combination `DEPENDABOT_OPEN_PULL_REQUESTS_LIMIT` to exclusively perform security updates whilst also limiting the total number of security PRs opened at once.|
|DEPENDABOT_SECURITY_ADVISORIES_FILE|Update,
vNext|**_Optional_**. The absolute file path containing additional user-defined security advisories in JSON format. For example: `/mnt/security_advisories/nuget-2022-12-13.json`|
|DEPENDABOT_EXTRA_CREDENTIALS|Update,
vNext|**_Optional_**. The extra credentials in JSON format. Extra credentials can be used to access private NuGet feeds, docker registries, maven repositories, etc. For example a private registry authentication (For example FontAwesome Pro: `[{"type":"npm_registry","token":"","registry":"npm.fontawesome.com"}]`)|
|DEPENDABOT_ALLOW_CONDITIONS|Update,
vNext|**_Optional_**. The dependencies whose updates are allowed, in JSON format. This can be used to control which packages can be updated. For example: `[{\"dependency-name\":"django*",\"dependency-type\":\"direct\"}]`. See [official docs](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates#allow) for more.|
|DEPENDABOT_IGNORE_CONDITIONS|Update,
vNext|**_Optional_**. The dependencies to be ignored, in JSON format. This can be used to control which packages can be updated. For example: `[{\"dependency-name\":\"express\",\"versions\":[\"4.x\",\"5.x\"]}]`. See [official docs](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates#ignore) for more.|
|DEPENDABOT_EXCLUDE_REQUIREMENTS_TO_UNLOCK|Update|**_Optional_**. Exclude certain dependency updates requirements. See list of allowed values [here](https://github.com/dependabot/dependabot-core/issues/600#issuecomment-407808103). Useful if you have lots of dependencies and the update script too slow. The values provided are space-separated. Example: `own all` to only use the `none` version requirement.|
-|DEPENDABOT_VENDOR_DEPENDENCIES|vNext|**_Optional_** Determines if dependencies are vendored when updating them. Don't use this option if you're using `gomod` as Dependabot automatically detects vendoring. See [official docs](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#vendor) for more.|
+|DEPENDABOT_VENDOR|vNext|**_Optional_** Determines if dependencies are vendored when updating them. Don't use this option if you're using `gomod` as Dependabot automatically detects vendoring. See [official docs](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#vendor) for more.|
|DEPENDABOT_DEPENDENCY_GROUPS|vNext|**_Optional_**. The dependency group rule mappings, in JSON format. For example: `{"microsoft":{"applies-to":"version-updates","dependency-type":"production","patterns":["microsoft*"],"exclude-patterns":["*azure*"],"update-types":["minor","patch"]}}`. See [official docs](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups) for more. |
|DEPENDABOT_UPDATER_OPTIONS|Update,
vNext|**_Optional_**. Comma separated list of updater options (i.e. experiments); available options depend on `PACKAGE_MANAGER`. Example: `goprivate=true,kubernetes_updates=true`.|
|DEPENDABOT_REJECT_EXTERNAL_CODE|Update,
vNext|**_Optional_**. Determines if the execution external code is allowed when cloning source repositories. Defaults to `false`.|
diff --git a/extension/task/IDependabotConfig.ts b/extension/task/IDependabotConfig.ts
index eafa50e1..3951b9a8 100644
--- a/extension/task/IDependabotConfig.ts
+++ b/extension/task/IDependabotConfig.ts
@@ -26,8 +26,19 @@ export interface IDependabotUpdate {
packageEcosystem: string;
/**
* Location of package manifests.
+ * https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#directory
* */
directory: string;
+ /**
+ * Locations of package manifests.
+ * https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#directories
+ * */
+ directories?: string[];
+ /**
+ * Dependency group rules
+ * https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
+ * */
+ groups?: string;
/**
* Customize which updates are allowed.
*/
diff --git a/extension/task/index.ts b/extension/task/index.ts
index eed439ff..b0038cb8 100644
--- a/extension/task/index.ts
+++ b/extension/task/index.ts
@@ -58,10 +58,13 @@ async function run() {
dockerRunner.arg(["-e", `DEPENDABOT_PACKAGE_MANAGER=${update.packageEcosystem}`]);
dockerRunner.arg(["-e", `DEPENDABOT_OPEN_PULL_REQUESTS_LIMIT=${update.openPullRequestsLimit}`]); // always has a value
- // Set the directory
+ // Set the directory or directories
if (update.directory) {
dockerRunner.arg(["-e", `DEPENDABOT_DIRECTORY=${update.directory}`]);
}
+ if (update.directories && update.directories.length > 0) {
+ dockerRunner.arg(["-e", `DEPENDABOT_DIRECTORIES=${JSON.stringify(update.directories)}`]);
+ }
// Set the target branch
if (update.targetBranch) {
@@ -111,6 +114,12 @@ async function run() {
dockerRunner.arg(["-e", `DEPENDABOT_IGNORE_CONDITIONS=${ignore}`]);
}
+ // Set the dependency groups
+ let groups = update.groups;
+ if (groups) {
+ dockerRunner.arg(["-e", `DEPENDABOT_DEPENDENCY_GROUPS=${groups}`]);
+ }
+
// Set the commit message options
let commitMessage = update.commitMessage;
if (commitMessage) {
@@ -164,6 +173,11 @@ async function run() {
dockerRunner.arg(["-e", 'DEPENDABOT_SKIP_PULL_REQUESTS=true']);
}
+ // Set skip pull requests if true
+ if (variables.commentPullRequests === true) {
+ dockerRunner.arg(["-e", 'DEPENDABOT_COMMENT_PULL_REQUESTS=true']);
+ }
+
// Set abandon Unwanted pull requests if true
if (variables.abandonUnwantedPullRequests === true) {
dockerRunner.arg(["-e", 'DEPENDABOT_CLOSE_PULL_REQUESTS=true']);
@@ -230,6 +244,11 @@ async function run() {
}
}
+ // Set debug
+ if (variables.debug === true) {
+ dockerRunner.arg(["-e", 'DEPENDABOT_DEBUG=true']);
+ }
+
// Add in extra environment variables
variables.extraEnvironmentVariables.forEach(extraEnvVar => {
dockerRunner.arg(["-e", extraEnvVar]);
@@ -247,7 +266,7 @@ async function run() {
dockerRunner.arg(dockerImage);
// set the script to be run
- dockerRunner.arg('update_script');
+ dockerRunner.arg(variables.command);
// Now execute using docker
await dockerRunner.exec();
diff --git a/extension/task/task.json b/extension/task/task.json
index bb9a553f..d744bbe5 100644
--- a/extension/task/task.json
+++ b/extension/task/task.json
@@ -12,7 +12,7 @@
"demands": ["docker"],
"version": {
"Major": 1,
- "Minor": 5,
+ "Minor": 6,
"Patch": 0
},
"instanceNameFormat": "Dependabot",
@@ -20,12 +20,17 @@
"groups": [
{
"name": "security_updates",
- "displayName": "Security advisories, vulnerabilities, and updates.",
+ "displayName": "Security advisories and vulnerabilities",
"isExpanded": false
},
{
- "name": "approval_completion",
- "displayName": "Auto Approval and Auto Completion or PRs",
+ "name": "pull_requests",
+ "displayName": "Pull request options",
+ "isExpanded": false
+ },
+ {
+ "name": "devops",
+ "displayName": "Azure DevOps authentication",
"isExpanded": false
},
{
@@ -40,6 +45,16 @@
}
],
"inputs": [
+ {
+ "name": "useUpdateScriptvNext",
+ "type": "boolean",
+ "groupName": "advanced",
+ "label": "Use latest update script (vNext) (Experimental)",
+ "defaultValue": "false",
+ "required": false,
+ "helpMarkDown": "Determines if the task will use the newest 'vNext' update script instead of the default update script. This Defaults to `false`. See the [vNext update script documentation](https://github.com/tinglesoftware/dependabot-azure-devops/pull/1186) for more information."
+ },
+
{
"name": "useConfigFile",
"type": "boolean",
@@ -52,35 +67,45 @@
{
"name": "failOnException",
"type": "boolean",
- "label": "Determines if the execution should fail when an exception occurs. Defaults to `true`",
+ "groupName": "advanced",
+ "label": "Fail task when an update exception occurs.",
"defaultValue": true,
"required": false,
- "helpMarkDown": "When set to true, a failure in updating a single dependency will cause the container execution to fail thereby causing the task to fail. This is important when you want a single failure to prevent trying to update other dependencies."
+ "helpMarkDown": "When set to `true`, a failure in updating a single dependency will cause the container execution to fail thereby causing the task to fail. This is important when you want a single failure to prevent trying to update other dependencies."
},
+
{
"name": "skipPullRequests",
"type": "boolean",
- "groupName": "advanced",
- "label": "Whether to skip creation and updating of pull requests.",
+ "groupName": "pull_requests",
+ "label": "Skip creation and updating of pull requests.",
"defaultValue": false,
"required": false,
"helpMarkDown": "When set to `true` the logic to update the dependencies is executed but the actual Pull Requests are not created/updated. Defaults to `false`."
},
+ {
+ "name": "commentPullRequests",
+ "type": "boolean",
+ "groupName": "pull_requests",
+ "label": "Comment on abandoned pull requests with close reason.",
+ "defaultValue": false,
+ "required": false,
+ "helpMarkDown": "When set to `true` a comment will be added to abandoned pull requests explanating why it was closed. Defaults to `false`."
+ },
{
"name": "abandonUnwantedPullRequests",
"type": "boolean",
- "groupName": "advanced",
- "label": "Whether to abandon unwanted pull requests.",
+ "groupName": "pull_requests",
+ "label": "Abandon unwanted pull requests.",
"defaultValue": false,
"required": false,
"helpMarkDown": "When set to `true` pull requests that are no longer needed are closed at the tail end of the execution. Defaults to `false`."
},
-
{
"name": "setAutoComplete",
"type": "boolean",
- "groupName": "approval_completion",
- "label": "Determines if the pull requests that dependabot creates should have auto complete set.",
+ "groupName": "pull_requests",
+ "label": "Auto-complete pull requests when all policies pass",
"defaultValue": false,
"required": false,
"helpMarkDown": "When set to `true`, pull requests that pass all policies will be merged automatically. Defaults to `false`."
@@ -88,7 +113,7 @@
{
"name": "mergeStrategy",
"type": "pickList",
- "groupName": "approval_completion",
+ "groupName": "pull_requests",
"label": "Merge Strategy",
"defaultValue": "squash",
"required": true,
@@ -104,17 +129,18 @@
{
"name": "autoCompleteIgnoreConfigIds",
"type": "string",
- "groupName": "approval_completion",
+ "groupName": "pull_requests",
"label": "Semicolon delimited list of any policy configuration IDs which auto-complete should not wait for.",
"defaultValue": "",
"required": false,
- "helpMarkDown": "A semicolon (`;`) delimited list of any policy configuration IDs which auto-complete should not wait for. Only applies to optional policies (isBlocking == false). Auto-complete always waits for required policies (isBlocking == true)."
+ "helpMarkDown": "A semicolon (`;`) delimited list of any policy configuration IDs which auto-complete should not wait for. Only applies to optional policies (isBlocking == false). Auto-complete always waits for required policies (isBlocking == true).",
+ "visibleRule": "setAutoComplete=true"
},
{
"name": "autoApprove",
"type": "boolean",
- "groupName": "approval_completion",
- "label": "Determines if the pull requests that dependabot creates should be automatically approved.",
+ "groupName": "pull_requests",
+ "label": "Auto-approve pull requests",
"defaultValue": false,
"required": false,
"helpMarkDown": "When set to `true`, pull requests will automatically be approved by the specified user. Defaults to `false`."
@@ -122,7 +148,7 @@
{
"name": "autoApproveUserToken",
"type": "string",
- "groupName": "approval_completion",
+ "groupName": "pull_requests",
"label": "A personal access token of the user that should approve the PR.",
"defaultValue": "",
"required": false,
@@ -161,7 +187,7 @@
{
"name": "azureDevOpsServiceConnection",
"type": "connectedService:Externaltfs",
- "groupName": "advanced",
+ "groupName": "devops",
"label": "Azure DevOps Service Connection to use.",
"required": false,
"helpMarkDown": "Specify a service connection to use, if you want to use a different service principal than the default to create your PRs."
@@ -169,7 +195,7 @@
{
"name": "azureDevOpsAccessToken",
"type": "string",
- "groupName": "advanced",
+ "groupName": "devops",
"label": "Azure DevOps Personal Access Token.",
"required": false,
"helpMarkDown": "The Personal Access Token for accessing Azure DevOps repositories. Supply a value here to avoid using permissions for the Build Service either because you cannot change its permissions or because you prefer that the Pull Requests be done by a different user. Use this in place of `azureDevOpsServiceConnection` such as when it is not possible to create a service connection."
@@ -195,9 +221,9 @@
"name": "updaterOptions",
"type": "string",
"groupName": "advanced",
- "label": "Comma separated list of updater options.",
+ "label": "Comma separated list of Dependabot experiments (updater options).",
"required": false,
- "helpMarkDown": "Set a list of updater options in CSV format. Available options depend on the ecosystem. Example: `goprivate=true,kubernetes_updates=true`."
+ "helpMarkDown": "Set a list of Dependabot experiments (updater options) in CSV format. Available options depend on the ecosystem. Example: `goprivate=true,kubernetes_updates=true`."
},
{
"name": "excludeRequirementsToUnlock",
diff --git a/extension/task/utils/getSharedVariables.ts b/extension/task/utils/getSharedVariables.ts
index 5dab8d86..996c95ed 100644
--- a/extension/task/utils/getSharedVariables.ts
+++ b/extension/task/utils/getSharedVariables.ts
@@ -51,21 +51,32 @@ export interface ISharedVariables {
excludeRequirementsToUnlock: string;
updaterOptions: string;
+ /** Determines if verbose log messages are logged */
+ debug: boolean;
+
/** List of update identifiers to run */
targetUpdateIds: number[];
securityAdvisoriesFile: string | undefined;
+
/** Determines whether to skip creating/updating pull requests */
skipPullRequests: boolean;
+ /** Determines whether to comment on pull requests which an explanation of the reason for closing */
+ commentPullRequests: boolean;
/** Determines whether to abandon unwanted pull requests */
abandonUnwantedPullRequests: boolean;
+
/** List of extra environment variables */
extraEnvironmentVariables: string[];
+
/** Flag used to forward the host ssh socket */
forwardHostSshSocket: boolean;
/** Tag of the docker image to be pulled */
dockerImageTag: string;
+
+ /** Dependabot command to run */
+ command: string;
}
/**
@@ -74,7 +85,6 @@ export interface ISharedVariables {
* @returns shared variables
*/
export default function getSharedVariables(): ISharedVariables {
- // Prepare shared variables
let organizationUrl = tl.getVariable("System.TeamFoundationCollectionUri");
//convert url string into a valid JS URL object
let formattedOrganizationUrl = new URL(organizationUrl);
@@ -119,6 +129,8 @@ export default function getSharedVariables(): ISharedVariables {
tl.getInput("excludeRequirementsToUnlock") || "";
let updaterOptions = tl.getInput("updaterOptions");
+ let debug: boolean = tl.getVariable("System.Debug")?.localeCompare("true") === 0;
+
// Get the target identifiers
let targetUpdateIds = tl
.getDelimitedInput("targetUpdateIds", ";", false)
@@ -129,12 +141,15 @@ export default function getSharedVariables(): ISharedVariables {
"securityAdvisoriesFile"
);
let skipPullRequests: boolean = tl.getBoolInput("skipPullRequests", false);
+ let commentPullRequests: boolean = tl.getBoolInput("commentPullRequests", false);
let abandonUnwantedPullRequests: boolean = tl.getBoolInput("abandonUnwantedPullRequests", true);
+
let extraEnvironmentVariables = tl.getDelimitedInput(
"extraEnvironmentVariables",
";",
false
);
+
let forwardHostSshSocket: boolean = tl.getBoolInput(
"forwardHostSshSocket",
false
@@ -143,9 +158,12 @@ export default function getSharedVariables(): ISharedVariables {
// Prepare variables for the docker image to use
let dockerImageTag: string = getDockerImageTag();
+ let command: string = tl.getBoolInput("useUpdateScriptvNext", false)
+ ? "update_script_vnext"
+ : "update_script";
+
return {
organizationUrl: formattedOrganizationUrl,
-
protocol,
hostname,
port,
@@ -169,14 +187,22 @@ export default function getSharedVariables(): ISharedVariables {
failOnException,
excludeRequirementsToUnlock,
updaterOptions,
+
+ debug,
targetUpdateIds,
securityAdvisoriesFile,
+
skipPullRequests,
+ commentPullRequests,
abandonUnwantedPullRequests,
+
extraEnvironmentVariables,
+
forwardHostSshSocket,
dockerImageTag,
+
+ command
};
}
diff --git a/extension/task/utils/parseConfigFile.ts b/extension/task/utils/parseConfigFile.ts
index 8fca4117..71c6917a 100644
--- a/extension/task/utils/parseConfigFile.ts
+++ b/extension/task/utils/parseConfigFile.ts
@@ -165,6 +165,7 @@ function parseUpdates(config: any): IDependabotUpdate[] {
var dependabotUpdate: IDependabotUpdate = {
packageEcosystem: update["package-ecosystem"],
directory: update["directory"],
+ directories: update["directories"] || [],
openPullRequestsLimit: update["open-pull-requests-limit"],
registries: update["registries"] || [],
@@ -197,6 +198,9 @@ function parseUpdates(config: any): IDependabotUpdate[] {
commitMessage: update["commit-message"]
? JSON.stringify(update["commit-message"])
: undefined,
+ groups: update["groups"]
+ ? JSON.stringify(update["groups"])
+ : undefined,
};
if (!dependabotUpdate.packageEcosystem) {
@@ -213,9 +217,9 @@ function parseUpdates(config: any): IDependabotUpdate[] {
dependabotUpdate.openPullRequestsLimit = 5;
}
- if (!dependabotUpdate.directory) {
+ if (!dependabotUpdate.directory && dependabotUpdate.directories.length === 0) {
throw new Error(
- "The value 'directory' in dependency update config is missing"
+ "The values 'directory' and 'directories' in dependency update config is missing, you must specify at least one"
);
}
diff --git a/extension/tests/utils/dependabot.yml b/extension/tests/utils/dependabot.yml
index 9376eeb7..35277f63 100644
--- a/extension/tests/utils/dependabot.yml
+++ b/extension/tests/utils/dependabot.yml
@@ -24,6 +24,17 @@ updates:
update-types: ['version-update:semver-major']
- dependency-name: '@types/react-dom'
update-types: ['version-update:semver-major']
+ - package-ecosystem: 'nuget'
+ directories:
+ - '/src/client'
+ - '/src/server'
+ groups:
+ microsoft:
+ patterns:
+ - "microsoft*"
+ update-types:
+ - "minor"
+ - "patch"
registries:
reg1:
type: nuget-feed
diff --git a/extension/tests/utils/parseConfigFile.test.ts b/extension/tests/utils/parseConfigFile.test.ts
index 64e7448e..4e039cc4 100644
--- a/extension/tests/utils/parseConfigFile.test.ts
+++ b/extension/tests/utils/parseConfigFile.test.ts
@@ -8,11 +8,12 @@ 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);
+ expect(updates.length).toBe(3);
// first
const first = updates[0];
expect(first.directory).toBe('/');
+ expect(first.directories).toEqual([]);
expect(first.packageEcosystem).toBe('docker');
expect(first.insecureExternalCodeExecution).toBe(undefined);
expect(first.registries).toEqual([]);
@@ -20,9 +21,17 @@ describe("Parse configuration file", () => {
// second
const second = updates[1];
expect(second.directory).toBe('/client');
+ expect(second.directories).toEqual([]);
expect(second.packageEcosystem).toBe('npm');
expect(second.insecureExternalCodeExecution).toBe('deny');
expect(second.registries).toEqual(['reg1', 'reg2']);
+
+ // third
+ const third = updates[2];
+ expect(third.directory).toBe(undefined);
+ expect(third.directories).toEqual(['/src/client', '/src/server']);
+ expect(third.packageEcosystem).toBe('nuget');
+ expect(third.groups).toBe('{\"microsoft\":{\"patterns\":[\"microsoft*\"],\"update-types\":[\"minor\",\"patch\"]}}');
});
});
diff --git a/updater/lib/tinglesoftware/dependabot/job.rb b/updater/lib/tinglesoftware/dependabot/job.rb
index 3296c4d2..f6fd6bbc 100644
--- a/updater/lib/tinglesoftware/dependabot/job.rb
+++ b/updater/lib/tinglesoftware/dependabot/job.rb
@@ -226,7 +226,7 @@ def _lockfile_only
end
def _vendor_dependencies
- ENV.fetch("DEPENDABOT_VENDOR_DEPENDENCIES", nil) == "true"
+ ENV.fetch("DEPENDABOT_VENDOR", nil) == "true"
end
def _dependency_groups
@@ -402,9 +402,10 @@ def open_pull_request_limit_reached?
def security_updates_only
# If the pull request limit is set to zero, we assume that the user just wants security updates
+ # https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates#overriding-the-default-behavior-with-a-configuration-file
return true if open_pull_requests_limit.zero?
- ENV.fetch("DEPENDABOT_SECURITY_UPDATES_ONLY", nil) == "true"
+ false
end
def security_advisories