From fde2cec442b35db9d9dcbeb9495ab39666d99f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Thu, 5 Dec 2024 18:19:24 +0100 Subject: [PATCH 01/10] Add support for Azure DevOps API-based datasource --- .../datasource/azure-pipelines-tasks/index.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index db9bf125aff6ef..6aa12150e4e161 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -1,13 +1,10 @@ +import { GlobalConfig } from '../../../config/global'; import { cache } from '../../../util/cache/package/decorator'; +import type { HttpOptions } from '../../../util/http/types'; import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -const TASKS_URL_BASE = - 'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main'; -const BUILT_IN_TASKS_URL = `${TASKS_URL_BASE}/azure-pipelines-builtin-tasks.json`; -const MARKETPLACE_TASKS_URL = `${TASKS_URL_BASE}/azure-pipelines-marketplace-tasks.json`; - export class AzurePipelinesTasksDatasource extends Datasource { static readonly id = 'azure-pipelines-tasks'; @@ -22,9 +19,16 @@ export class AzurePipelinesTasksDatasource extends Datasource { async getReleases({ packageName, }: GetReleasesConfig): Promise { - const versions = - (await this.getTasks(BUILT_IN_TASKS_URL))[packageName.toLowerCase()] ?? - (await this.getTasks(MARKETPLACE_TASKS_URL))[packageName.toLowerCase()]; + const endpoint = GlobalConfig.get('endpoint'); + const auth = Buffer.from(`renovate:${process.env.RENOVATE_TOKEN}`).toString( + 'base64', + ); + const opts: HttpOptions = { + headers: { authorization: `Basic ${auth}` }, + }; + const versions = ( + await this.getTasks(`${endpoint}/_apis/distributedtask/tasks/`, opts) + )[packageName.toLowerCase()]; if (versions) { const releases = versions.map((version) => ({ version })); @@ -39,8 +43,14 @@ export class AzurePipelinesTasksDatasource extends Datasource { key: (url: string) => url, ttlMinutes: 24 * 60, }) - async getTasks(url: string): Promise> { - const { body } = await this.http.getJson>(url); + async getTasks( + url: string, + opts: HttpOptions, + ): Promise> { + const { body } = await this.http.getJson>( + url, + opts, + ); return body; } } From 65d35843dbcd5bd00a91074d1c8e483150fcd5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sat, 7 Dec 2024 13:05:46 +0100 Subject: [PATCH 02/10] Add support for Azure DevOps API-based datasource only in Azure initially --- .../datasource/azure-pipelines-tasks/index.ts | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 6aa12150e4e161..6ea6580e9c65df 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -5,6 +5,11 @@ import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; +const TASKS_URL_BASE = + 'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main'; +const BUILT_IN_TASKS_URL = `${TASKS_URL_BASE}/azure-pipelines-builtin-tasks.json`; +const MARKETPLACE_TASKS_URL = `${TASKS_URL_BASE}/azure-pipelines-marketplace-tasks.json`; + export class AzurePipelinesTasksDatasource extends Datasource { static readonly id = 'azure-pipelines-tasks'; @@ -19,16 +24,26 @@ export class AzurePipelinesTasksDatasource extends Datasource { async getReleases({ packageName, }: GetReleasesConfig): Promise { + const platform = GlobalConfig.get('platform'); const endpoint = GlobalConfig.get('endpoint'); - const auth = Buffer.from(`renovate:${process.env.RENOVATE_TOKEN}`).toString( - 'base64', - ); - const opts: HttpOptions = { - headers: { authorization: `Basic ${auth}` }, - }; - const versions = ( - await this.getTasks(`${endpoint}/_apis/distributedtask/tasks/`, opts) - )[packageName.toLowerCase()]; + + let versions; + + if (platform === 'azure' && endpoint) { + const auth = Buffer.from( + `renovate:${process.env.RENOVATE_TOKEN}`, + ).toString('base64'); + const opts: HttpOptions = { + headers: { authorization: `Basic ${auth}` }, + }; + versions = ( + await this.getTasks(`${endpoint}/_apis/distributedtask/tasks/`, opts) + )[packageName.toLowerCase()]; + } else { + versions = + (await this.getTasks(BUILT_IN_TASKS_URL))[packageName.toLowerCase()] ?? + (await this.getTasks(MARKETPLACE_TASKS_URL))[packageName.toLowerCase()]; + } if (versions) { const releases = versions.map((version) => ({ version })); @@ -45,7 +60,7 @@ export class AzurePipelinesTasksDatasource extends Datasource { }) async getTasks( url: string, - opts: HttpOptions, + opts?: HttpOptions, ): Promise> { const { body } = await this.http.getJson>( url, From c9b3133710a9a7a51b0283a84bab1d3b22f16436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sat, 7 Dec 2024 16:22:32 +0100 Subject: [PATCH 03/10] Add support for Azure DevOps API-based datasource only in Azure initially --- .../__fixtures__/tasks.json | 575 ++++++++++++++++++ .../azure-pipelines-tasks/index.spec.ts | 49 ++ .../datasource/azure-pipelines-tasks/index.ts | 71 ++- .../datasource/azure-pipelines-tasks/types.ts | 15 + 4 files changed, 693 insertions(+), 17 deletions(-) create mode 100644 lib/modules/datasource/azure-pipelines-tasks/__fixtures__/tasks.json create mode 100644 lib/modules/datasource/azure-pipelines-tasks/types.ts diff --git a/lib/modules/datasource/azure-pipelines-tasks/__fixtures__/tasks.json b/lib/modules/datasource/azure-pipelines-tasks/__fixtures__/tasks.json new file mode 100644 index 00000000000000..df4a3ea7a7e5da --- /dev/null +++ b/lib/modules/datasource/azure-pipelines-tasks/__fixtures__/tasks.json @@ -0,0 +1,575 @@ +{ + "count": 3, + "value": [ + { + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "id": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1", + "name": "PowerShell", + "version": { + "major": 2, + "minor": 247, + "patch": 1, + "isTest": false + }, + "serverOwned": true, + "contentsUploaded": true, + "iconUrl": "https://dev.azure.com/test_organization/_apis/distributedtask/tasks/e213ff0f-5d5c-4791-802d-52ea3e7be1f1/2.247.1/icon", + "minimumAgentVersion": "2.115.0", + "friendlyName": "PowerShell", + "description": "Run a PowerShell script on Linux, macOS, or Windows", + "category": "Utility", + "helpMarkDown": "[Learn more about this task](https://go.microsoft.com/fwlink/?LinkID=613736)", + "helpUrl": "https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/powershell", + "releaseNotes": "Script task consistency. Added support for macOS and Linux.", + "definitionType": "task", + "showEnvironmentVariables": true, + "author": "Microsoft Corporation", + "demands": [], + "groups": [ + { + "name": "preferenceVariables", + "displayName": "Preference Variables", + "isExpanded": false + }, + { + "name": "advanced", + "displayName": "Advanced", + "isExpanded": false + } + ], + "inputs": [ + { + "options": { + "filePath": "File Path", + "inline": "Inline" + }, + "name": "targetType", + "label": "Type", + "defaultValue": "filePath", + "type": "radio", + "helpMarkDown": "Target script type: File Path or Inline" + }, + { + "name": "filePath", + "label": "Script Path", + "defaultValue": "", + "required": true, + "type": "filePath", + "helpMarkDown": "Path of the script to execute. Must be a fully qualified path or relative to $(System.DefaultWorkingDirectory).", + "visibleRule": "targetType = filePath" + }, + { + "name": "arguments", + "label": "Arguments", + "defaultValue": "", + "type": "string", + "helpMarkDown": "Arguments passed to the PowerShell script. Either ordinal parameters or named parameters.", + "visibleRule": "targetType = filePath" + }, + { + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "20000" + }, + "name": "script", + "label": "Script", + "defaultValue": "# Write your PowerShell commands here.\n\nWrite-Host \"Hello World\"\n", + "required": true, + "type": "multiLine", + "helpMarkDown": "", + "visibleRule": "targetType = inline" + }, + { + "options": { + "default": "Default", + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "errorActionPreference", + "label": "ErrorActionPreference", + "defaultValue": "stop", + "type": "pickList", + "helpMarkDown": "When not `Default`, prepends the line `$ErrorActionPreference = 'VALUE'` at the top of your script.", + "groupName": "preferenceVariables" + }, + { + "options": { + "default": "Default", + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "warningPreference", + "label": "WarningPreference", + "defaultValue": "default", + "type": "pickList", + "helpMarkDown": "When not `Default`, prepends the line `$WarningPreference = 'VALUE'` at the top of your script.", + "groupName": "preferenceVariables" + }, + { + "options": { + "default": "Default", + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "informationPreference", + "label": "InformationPreference", + "defaultValue": "default", + "type": "pickList", + "helpMarkDown": "When not `Default`, prepends the line `$InformationPreference = 'VALUE'` at the top of your script.", + "groupName": "preferenceVariables" + }, + { + "options": { + "default": "Default", + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "verbosePreference", + "label": "VerbosePreference", + "defaultValue": "default", + "type": "pickList", + "helpMarkDown": "When not `Default`, prepends the line `$VerbosePreference = 'VALUE'` at the top of your script.", + "groupName": "preferenceVariables" + }, + { + "options": { + "default": "Default", + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "debugPreference", + "label": "DebugPreference", + "defaultValue": "default", + "type": "pickList", + "helpMarkDown": "When not `Default`, prepends the line `$DebugPreference = 'VALUE'` at the top of your script.", + "groupName": "preferenceVariables" + }, + { + "options": { + "default": "Default", + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "progressPreference", + "label": "ProgressPreference", + "defaultValue": "silentlyContinue", + "type": "pickList", + "helpMarkDown": "When not `Default`, prepends the line `$ProgressPreference = 'VALUE'` at the top of your script.", + "groupName": "preferenceVariables" + }, + { + "name": "failOnStderr", + "label": "Fail on Standard Error", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is true, this task will fail if any errors are written to the error pipeline, or if any data is written to the Standard Error stream. Otherwise the task will rely on the exit code to determine failure.", + "groupName": "advanced" + }, + { + "name": "showWarnings", + "label": "Show warnings as Azure DevOps warnings", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is true, and your script writes a warnings - they are shown as warnings also in pipeline logs", + "groupName": "advanced" + }, + { + "name": "ignoreLASTEXITCODE", + "label": "Ignore $LASTEXITCODE", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.", + "groupName": "advanced" + }, + { + "name": "pwsh", + "label": "Use PowerShell Core", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is true, then on Windows the task will use pwsh.exe from your PATH instead of powershell.exe.", + "groupName": "advanced" + }, + { + "name": "workingDirectory", + "label": "Working Directory", + "defaultValue": "", + "type": "filePath", + "helpMarkDown": "Working directory where the script is run.", + "groupName": "advanced" + }, + { + "name": "runScriptInSeparateScope", + "label": "Run script in the separate scope", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "This input allows executing PowerShell scripts using '&' operator instead of the default '.'. If this input set to the true script will be executed in separate scope and globally scoped PowerShell variables won't be updated", + "groupName": "advanced" + } + ], + "satisfies": [], + "sourceDefinitions": [], + "dataSourceBindings": [], + "instanceNameFormat": "PowerShell Script", + "preJobExecution": {}, + "execution": { + "PowerShell3": { + "target": "powershell.ps1", + "platforms": [ + "windows" + ] + }, + "Node10": { + "target": "powershell.js", + "argumentFormat": "" + }, + "Node16": { + "target": "powershell.js", + "argumentFormat": "" + }, + "Node20_1": { + "target": "powershell.js", + "argumentFormat": "" + } + }, + "postJobExecution": {}, + "_buildConfigMapping": { + "Default": "2.247.0", + "Node20-225": "2.247.1" + } + }, + { + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "id": "e213ff0f-5d5c-4791-802d-52ea3e7be1f1", + "name": "PowerShell", + "deprecated": true, + "version": { + "major": 1, + "minor": 2, + "patch": 3, + "isTest": false + }, + "serverOwned": true, + "contentsUploaded": true, + "iconUrl": "https://dev.azure.com/test_organization/_apis/distributedtask/tasks/e213ff0f-5d5c-4791-802d-52ea3e7be1f1/1.2.3/icon", + "minimumAgentVersion": "1.102", + "friendlyName": "PowerShell", + "description": "Run a PowerShell script", + "category": "Utility", + "helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613736)", + "definitionType": "task", + "author": "Microsoft Corporation", + "demands": [ + "DotNetFramework" + ], + "groups": [ + { + "name": "advanced", + "displayName": "Advanced", + "isExpanded": false + } + ], + "inputs": [ + { + "options": { + "inlineScript": "Inline Script", + "filePath": "File Path" + }, + "name": "scriptType", + "label": "Type", + "defaultValue": "filePath", + "required": true, + "type": "pickList", + "helpMarkDown": "Type of the script: File Path or Inline Script" + }, + { + "name": "scriptName", + "label": "Script Path", + "defaultValue": "", + "required": true, + "type": "filePath", + "helpMarkDown": "Path of the script to execute. Should be fully qualified path or relative to the default working directory.", + "visibleRule": "scriptType = filePath" + }, + { + "name": "arguments", + "label": "Arguments", + "defaultValue": "", + "type": "string", + "helpMarkDown": "Arguments passed to the PowerShell script. Either ordinal parameters or named parameters" + }, + { + "name": "workingFolder", + "label": "Working folder", + "defaultValue": "", + "type": "filePath", + "helpMarkDown": "Current working directory when script is run. Defaults to the folder where the script is located.", + "groupName": "advanced" + }, + { + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "500" + }, + "name": "inlineScript", + "label": "Inline Script", + "defaultValue": "# You can write your powershell scripts inline here. \n# You can also pass predefined and custom variables to this scripts using arguments\n\n Write-Host \"Hello World\"", + "required": true, + "type": "multiLine", + "helpMarkDown": "", + "visibleRule": "scriptType = inlineScript" + }, + { + "name": "failOnStandardError", + "label": "Fail on Standard Error", + "defaultValue": "true", + "type": "boolean", + "helpMarkDown": "If this is true, this task will fail if any errors are written to the error pipeline, or if any data is written to the Standard Error stream. Otherwise the task will rely solely on $LASTEXITCODE and the exit code to determine failure.", + "groupName": "advanced" + } + ], + "satisfies": [], + "sourceDefinitions": [], + "dataSourceBindings": [], + "instanceNameFormat": "PowerShell Script", + "preJobExecution": {}, + "execution": { + "PowerShellExe": { + "target": "$(scriptName)", + "argumentFormat": "$(arguments)", + "workingDirectory": "$(workingFolder)", + "inlineScript": "$(inlineScript)", + "scriptType": "$(scriptType)", + "failOnStandardError": "$(failOnStandardError)" + } + }, + "postJobExecution": {}, + "_buildConfigMapping": {} + }, + { + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "id": "72a1931b-effb-4d2e-8fd8-f8472a07cb62", + "name": "AzurePowerShell", + "version": { + "major": 5, + "minor": 248, + "patch": 3, + "isTest": false + }, + "serverOwned": true, + "contentsUploaded": true, + "iconUrl": "https://dev.azure.com/test_organization/_apis/distributedtask/tasks/72a1931b-effb-4d2e-8fd8-f8472a07cb62/5.248.3/icon", + "minimumAgentVersion": "2.115.0", + "friendlyName": "Azure PowerShell", + "description": "Run a PowerShell script within an Azure environment", + "category": "Deploy", + "helpMarkDown": "[Learn more about this task](https://go.microsoft.com/fwlink/?LinkID=613749)", + "helpUrl": "https://aka.ms/azurepowershelltroubleshooting", + "releaseNotes": "Added support for Az Module and cross platform agents.", + "definitionType": "task", + "author": "Microsoft Corporation", + "demands": [], + "groups": [ + { + "name": "AzurePowerShellVersionOptions", + "displayName": "Azure PowerShell version options", + "isExpanded": true + }, + { + "name": "advanced", + "displayName": "Advanced", + "isExpanded": false + } + ], + "inputs": [ + { + "aliases": [ + "azureSubscription" + ], + "properties": { + "EndpointFilterRule": "ScopeLevel != AzureMLWorkspace" + }, + "name": "ConnectedServiceNameARM", + "label": "Azure Subscription", + "defaultValue": "", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Azure Resource Manager subscription to configure before running PowerShell" + }, + { + "options": { + "FilePath": "Script File Path", + "InlineScript": "Inline Script" + }, + "name": "ScriptType", + "label": "Script Type", + "defaultValue": "FilePath", + "type": "radio", + "helpMarkDown": "Type of the script: File Path or Inline Script" + }, + { + "name": "ScriptPath", + "label": "Script Path", + "defaultValue": "", + "type": "filePath", + "helpMarkDown": "Path of the script. Should be fully qualified path or relative to the default working directory.", + "visibleRule": "ScriptType = FilePath" + }, + { + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + }, + "name": "Inline", + "label": "Inline Script", + "defaultValue": "# You can write your azure powershell scripts inline here. \n# You can also pass predefined and custom variables to this script using arguments", + "type": "multiLine", + "helpMarkDown": "Enter the script to execute.", + "visibleRule": "ScriptType = InlineScript" + }, + { + "properties": { + "editorExtension": "ms.vss-services-azure.parameters-grid" + }, + "name": "ScriptArguments", + "label": "Script Arguments", + "defaultValue": "", + "type": "string", + "helpMarkDown": "Additional parameters to pass to PowerShell. Can be either ordinal or named parameters.", + "visibleRule": "ScriptType = FilePath" + }, + { + "options": { + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "name": "errorActionPreference", + "label": "ErrorActionPreference", + "defaultValue": "stop", + "type": "pickList", + "helpMarkDown": "Select the value of the ErrorActionPreference variable for executing the script." + }, + { + "name": "FailOnStandardError", + "label": "Fail on Standard Error", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is true, this task will fail if any errors are written to the error pipeline, or if any data is written to the Standard Error stream." + }, + { + "aliases": [ + "azurePowerShellVersion" + ], + "options": { + "LatestVersion": "Latest installed version", + "OtherVersion": "Specify other version" + }, + "name": "TargetAzurePs", + "label": "Azure PowerShell Version", + "defaultValue": "OtherVersion", + "type": "radio", + "helpMarkDown": "In case of hosted agents, the supported Azure PowerShell Version is: 1.0.0, 1.6.0, 2.3.2, 2.6.0, 3.1.0 (Hosted VS2017 Queue).\nTo pick the latest version available on the agent, select \"Latest installed version\".\n\nFor private agents you can specify preferred version of Azure PowerShell using \"Specify version\"", + "groupName": "AzurePowerShellVersionOptions" + }, + { + "aliases": [ + "preferredAzurePowerShellVersion" + ], + "name": "CustomTargetAzurePs", + "label": "Preferred Azure PowerShell Version", + "defaultValue": "", + "required": true, + "type": "string", + "helpMarkDown": "Preferred Azure PowerShell Version needs to be a proper semantic version eg. 1.2.3. Regex like 2.\\*,2.3.\\* is not supported. The Hosted VS2017 Pool currently supports Az module version: 1.0.0, 1.6.0, 2.3.2, 2.6.0, 3.1.0", + "visibleRule": "TargetAzurePs = OtherVersion", + "groupName": "AzurePowerShellVersionOptions" + }, + { + "name": "pwsh", + "label": "Use PowerShell Core", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is true, then on Windows the task will use pwsh.exe from your PATH instead of powershell.exe.", + "groupName": "advanced" + }, + { + "name": "validateScriptSignature", + "label": "Validate script signature", + "defaultValue": "false", + "type": "boolean", + "helpMarkDown": "If this is true, then the task will first check to make sure specified script is signed and valid before executing it.", + "visibleRule": "ScriptType = FilePath", + "groupName": "advanced" + }, + { + "name": "workingDirectory", + "label": "Working Directory", + "defaultValue": "", + "type": "filePath", + "helpMarkDown": "Working directory where the script is run.", + "groupName": "advanced" + } + ], + "satisfies": [], + "sourceDefinitions": [], + "dataSourceBindings": [], + "instanceNameFormat": "Azure PowerShell script: $(ScriptType)", + "preJobExecution": {}, + "execution": { + "PowerShell3": { + "target": "azurepowershell.ps1", + "platforms": [ + "windows" + ] + }, + "Node16": { + "target": "azurepowershell.js", + "argumentFormat": "" + }, + "Node10": { + "target": "azurepowershell.js", + "argumentFormat": "" + }, + "Node20_1": { + "target": "azurepowershell.js", + "argumentFormat": "" + } + }, + "postJobExecution": {}, + "_buildConfigMapping": { + "Default": "5.248.2", + "Node20_229_2": "5.248.3" + } + } + ] +} \ No newline at end of file diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts index 94d15fcec1f583..34abfaf6e4f10d 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts @@ -1,5 +1,7 @@ import { getPkgReleases } from '..'; +import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; +import { GlobalConfig } from '../../../config/global'; import { AzurePipelinesTasksDatasource } from '.'; const gitHubHost = 'https://raw.githubusercontent.com'; @@ -9,6 +11,10 @@ const marketplaceTasksPath = '/renovatebot/azure-devops-marketplace/main/azure-pipelines-marketplace-tasks.json'; describe('modules/datasource/azure-pipelines-tasks/index', () => { + beforeEach(() => { + GlobalConfig.reset(); + }); + it('returns null for unknown task', async () => { httpMock .scope(gitHubHost) @@ -64,4 +70,47 @@ describe('modules/datasource/azure-pipelines-tasks/index', () => { }), ).toEqual({ releases: [{ version: '0.171.0' }, { version: '0.198.0' }] }); }); + + it('returns organization task with single version', async () => { + GlobalConfig.set({ + platform: 'azure', + endpoint: 'https://my.custom.domain', + }); + + httpMock + .scope('https://my.custom.domain') + .get('/_apis/distributedtask/tasks/') + .reply(200, Fixtures.get('tasks.json')); + + expect( + await getPkgReleases({ + datasource: AzurePipelinesTasksDatasource.id, + packageName: 'AzurePowerShell', + }), + ).toEqual({ releases: [{ version: '5.248.3' }] }); + }); + + it('returns organization task with multiple versions', async () => { + GlobalConfig.set({ + platform: 'azure', + endpoint: 'https://my.custom.domain', + }); + + httpMock + .scope('https://my.custom.domain') + .get('/_apis/distributedtask/tasks/') + .reply(200, Fixtures.get('tasks.json')); + + expect( + await getPkgReleases({ + datasource: AzurePipelinesTasksDatasource.id, + packageName: 'PowerShell', + }), + ).toEqual({ + releases: [ + { isDeprecated: true, version: '1.2.3' }, + { isDeprecated: undefined, version: '2.247.1' }, + ], + }); + }); }); diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 6ea6580e9c65df..d21d5935e64d10 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -4,6 +4,7 @@ import type { HttpOptions } from '../../../util/http/types'; import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; +import type { AzurePipelinesJSON, AzurePipelinesTaskVersion } from './types'; const TASKS_URL_BASE = 'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main'; @@ -36,18 +37,60 @@ export class AzurePipelinesTasksDatasource extends Datasource { const opts: HttpOptions = { headers: { authorization: `Basic ${auth}` }, }; - versions = ( - await this.getTasks(`${endpoint}/_apis/distributedtask/tasks/`, opts) - )[packageName.toLowerCase()]; + const results = await this.getTasks( + `${endpoint}/_apis/distributedtask/tasks/`, + opts, + ); + + const compareSemanticVersions = (key: string) => (a: any, b: any) => { + const a1Version: AzurePipelinesTaskVersion = a[key]; + const b1Version: AzurePipelinesTaskVersion = b[key]; + + const a1 = `${a1Version.major}.${a1Version.minor}.${a1Version.patch}`; + const b1 = `${b1Version.major}.${b1Version.minor}.${b1Version.patch}`; + + const len = Math.min(a1.length, b1.length); + + for (let i = 0; i < len; i++) { + const a2 = +a1[i] || 0; + const b2 = +b1[i] || 0; + + if (a2 !== b2) { + return a2 > b2 ? 1 : -1; + } + } + + return b1.length - a1.length; + }; + + if (results.value) { + const result: ReleaseResult = { releases: [] }; + + results.value + .filter((task) => task.name === packageName) + .sort(compareSemanticVersions('version')) + .forEach((task) => { + result.releases.push({ + version: `${task.version.major}.${task.version.minor}.${task.version.patch}`, + isDeprecated: task.deprecated, + }); + }); + + return result; + } } else { versions = - (await this.getTasks(BUILT_IN_TASKS_URL))[packageName.toLowerCase()] ?? - (await this.getTasks(MARKETPLACE_TASKS_URL))[packageName.toLowerCase()]; - } + (await this.getTasks>(BUILT_IN_TASKS_URL))[ + packageName.toLowerCase() + ] ?? + (await this.getTasks>(MARKETPLACE_TASKS_URL))[ + packageName.toLowerCase() + ]; - if (versions) { - const releases = versions.map((version) => ({ version })); - return { releases }; + if (versions) { + const releases = versions.map((version) => ({ version })); + return { releases }; + } } return null; @@ -58,14 +101,8 @@ export class AzurePipelinesTasksDatasource extends Datasource { key: (url: string) => url, ttlMinutes: 24 * 60, }) - async getTasks( - url: string, - opts?: HttpOptions, - ): Promise> { - const { body } = await this.http.getJson>( - url, - opts, - ); + async getTasks(url: string, opts?: HttpOptions): Promise { + const { body } = await this.http.getJson(url, opts); return body; } } diff --git a/lib/modules/datasource/azure-pipelines-tasks/types.ts b/lib/modules/datasource/azure-pipelines-tasks/types.ts new file mode 100644 index 00000000000000..a2bd6c78a60981 --- /dev/null +++ b/lib/modules/datasource/azure-pipelines-tasks/types.ts @@ -0,0 +1,15 @@ +export interface AzurePipelinesJSON { + value?: AzurePipelinesTask[]; +} + +export interface AzurePipelinesTask { + name: string; + deprecated?: boolean; + version: AzurePipelinesTaskVersion; +} + +export interface AzurePipelinesTaskVersion { + major: number; + minor: number; + patch: number; +} From 67ff5eb5d35d71c0d0ae40fc7ee5ac542c13f11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez?= Date: Mon, 9 Dec 2024 09:30:51 +0100 Subject: [PATCH 04/10] Update index.ts --- lib/modules/datasource/azure-pipelines-tasks/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index d21d5935e64d10..13c893d8b8ba6b 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -28,8 +28,6 @@ export class AzurePipelinesTasksDatasource extends Datasource { const platform = GlobalConfig.get('platform'); const endpoint = GlobalConfig.get('endpoint'); - let versions; - if (platform === 'azure' && endpoint) { const auth = Buffer.from( `renovate:${process.env.RENOVATE_TOKEN}`, @@ -79,7 +77,7 @@ export class AzurePipelinesTasksDatasource extends Datasource { return result; } } else { - versions = + const versions = (await this.getTasks>(BUILT_IN_TASKS_URL))[ packageName.toLowerCase() ] ?? From 2cead7d94bcfb792b8bd8aa76c4750a33ea5748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sat, 14 Dec 2024 13:06:35 +0100 Subject: [PATCH 05/10] Add compare semver tests --- .../azure-pipelines-tasks/index.spec.ts | 43 +++++++++++++++ .../datasource/azure-pipelines-tasks/index.ts | 52 +++++++++++-------- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts index 34abfaf6e4f10d..07819e4490455c 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts @@ -2,6 +2,7 @@ import { getPkgReleases } from '..'; import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; import { GlobalConfig } from '../../../config/global'; +import type { AzurePipelinesTask, AzurePipelinesTaskVersion } from './types'; import { AzurePipelinesTasksDatasource } from '.'; const gitHubHost = 'https://raw.githubusercontent.com'; @@ -113,4 +114,46 @@ describe('modules/datasource/azure-pipelines-tasks/index', () => { ], }); }); + + describe('compare semver', () => { + it.each` + a | exp + ${[]} | ${[]} + ${['']} | ${['']} + ${['', '']} | ${['', '']} + ${['1.0.0']} | ${['1.0.0']} + ${['1.0.1', '1.1.0', '1.0.0']} | ${['1.0.0', '1.0.1', '1.1.0']} + `('when versions is $a', ({ a, exp }) => { + const azureVersions = a.map((x: string) => { + const splitted = x.split('.'); + + const version = + splitted.length === 3 + ? { + major: Number(splitted[0]), + minor: Number(splitted[1]), + patch: Number(splitted[2]), + } + : null; + + return { + name: '', + deprecated: false, + version, + } as AzurePipelinesTask; + }); + + const azureSortedVersions = azureVersions.sort( + AzurePipelinesTasksDatasource.compareSemanticVersions('version'), + ); + + expect( + azureSortedVersions.map((x: AzurePipelinesTask) => + x.version === null + ? '' + : `${x.version.major}.${x.version.minor}.${x.version.patch}`, + ), + ).toStrictEqual(exp); + }); + }); }); diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 13c893d8b8ba6b..8ec6483f9bdee0 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -40,33 +40,14 @@ export class AzurePipelinesTasksDatasource extends Datasource { opts, ); - const compareSemanticVersions = (key: string) => (a: any, b: any) => { - const a1Version: AzurePipelinesTaskVersion = a[key]; - const b1Version: AzurePipelinesTaskVersion = b[key]; - - const a1 = `${a1Version.major}.${a1Version.minor}.${a1Version.patch}`; - const b1 = `${b1Version.major}.${b1Version.minor}.${b1Version.patch}`; - - const len = Math.min(a1.length, b1.length); - - for (let i = 0; i < len; i++) { - const a2 = +a1[i] || 0; - const b2 = +b1[i] || 0; - - if (a2 !== b2) { - return a2 > b2 ? 1 : -1; - } - } - - return b1.length - a1.length; - }; - if (results.value) { const result: ReleaseResult = { releases: [] }; results.value .filter((task) => task.name === packageName) - .sort(compareSemanticVersions('version')) + .sort( + AzurePipelinesTasksDatasource.compareSemanticVersions('version'), + ) .forEach((task) => { result.releases.push({ version: `${task.version.major}.${task.version.minor}.${task.version.patch}`, @@ -103,4 +84,31 @@ export class AzurePipelinesTasksDatasource extends Datasource { const { body } = await this.http.getJson(url, opts); return body; } + + static compareSemanticVersions = (key: string) => (a: any, b: any) => { + const a1Version = a[key] as AzurePipelinesTaskVersion; + const b1Version = b[key] as AzurePipelinesTaskVersion; + + const a1 = + a1Version === null + ? '' + : `${a1Version.major}.${a1Version.minor}.${a1Version.patch}`; + const b1 = + b1Version === null + ? '' + : `${b1Version.major}.${b1Version.minor}.${b1Version.patch}`; + + const len = Math.min(a1.length, b1.length); + + for (let i = 0; i < len; i++) { + const a2 = +a1[i] || 0; + const b2 = +b1[i] || 0; + + if (a2 !== b2) { + return a2 > b2 ? 1 : -1; + } + } + + return b1.length - a1.length; + }; } From 68da3b09d8e49e94f5bea46e10d670748f19cbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sat, 14 Dec 2024 13:07:57 +0100 Subject: [PATCH 06/10] Remove unneeded type --- lib/modules/datasource/azure-pipelines-tasks/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts index 07819e4490455c..148c70b1a57a12 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts @@ -2,7 +2,7 @@ import { getPkgReleases } from '..'; import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; import { GlobalConfig } from '../../../config/global'; -import type { AzurePipelinesTask, AzurePipelinesTaskVersion } from './types'; +import type { AzurePipelinesTask } from './types'; import { AzurePipelinesTasksDatasource } from '.'; const gitHubHost = 'https://raw.githubusercontent.com'; From 620fa90803e995e404e29c478b1cfcd32ad028f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sat, 14 Dec 2024 13:32:31 +0100 Subject: [PATCH 07/10] Use host rules --- .../datasource/azure-pipelines-tasks/index.spec.ts | 14 ++++++++++++++ .../datasource/azure-pipelines-tasks/index.ts | 11 +++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts index 148c70b1a57a12..3c9913d40fa2c4 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts @@ -2,6 +2,7 @@ import { getPkgReleases } from '..'; import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; import { GlobalConfig } from '../../../config/global'; +import * as hostRules from '../../../util/host-rules'; import type { AzurePipelinesTask } from './types'; import { AzurePipelinesTasksDatasource } from '.'; @@ -14,6 +15,7 @@ const marketplaceTasksPath = describe('modules/datasource/azure-pipelines-tasks/index', () => { beforeEach(() => { GlobalConfig.reset(); + hostRules.clear(); }); it('returns null for unknown task', async () => { @@ -78,6 +80,12 @@ describe('modules/datasource/azure-pipelines-tasks/index', () => { endpoint: 'https://my.custom.domain', }); + hostRules.add({ + hostType: AzurePipelinesTasksDatasource.id, + matchHost: 'my.custom.domain', + token: '123test', + }); + httpMock .scope('https://my.custom.domain') .get('/_apis/distributedtask/tasks/') @@ -97,6 +105,12 @@ describe('modules/datasource/azure-pipelines-tasks/index', () => { endpoint: 'https://my.custom.domain', }); + hostRules.add({ + hostType: AzurePipelinesTasksDatasource.id, + matchHost: 'my.custom.domain', + token: '123test', + }); + httpMock .scope('https://my.custom.domain') .get('/_apis/distributedtask/tasks/') diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 8ec6483f9bdee0..29b437fb641fc2 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -1,5 +1,6 @@ import { GlobalConfig } from '../../../config/global'; import { cache } from '../../../util/cache/package/decorator'; +import * as hostRules from '../../../util/host-rules'; import type { HttpOptions } from '../../../util/http/types'; import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; @@ -27,11 +28,13 @@ export class AzurePipelinesTasksDatasource extends Datasource { }: GetReleasesConfig): Promise { const platform = GlobalConfig.get('platform'); const endpoint = GlobalConfig.get('endpoint'); + const { token } = hostRules.find({ + hostType: AzurePipelinesTasksDatasource.id, + url: endpoint, + }); - if (platform === 'azure' && endpoint) { - const auth = Buffer.from( - `renovate:${process.env.RENOVATE_TOKEN}`, - ).toString('base64'); + if (platform === 'azure' && endpoint && token) { + const auth = Buffer.from(`renovate:${token}`).toString('base64'); const opts: HttpOptions = { headers: { authorization: `Basic ${auth}` }, }; From 60639693babcc040c946fdbc5253d037da3fbb6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sun, 22 Dec 2024 21:48:32 +0100 Subject: [PATCH 08/10] Use zod schema --- .../azure-pipelines-tasks/index.spec.ts | 16 +++-- .../datasource/azure-pipelines-tasks/index.ts | 72 +++++++++++-------- .../datasource/azure-pipelines-tasks/types.ts | 28 ++++---- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts index 3c9913d40fa2c4..cee551cc82a31a 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts @@ -3,7 +3,7 @@ import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; import { GlobalConfig } from '../../../config/global'; import * as hostRules from '../../../util/host-rules'; -import type { AzurePipelinesTask } from './types'; +import { AzurePipelinesTask } from './types'; import { AzurePipelinesTasksDatasource } from '.'; const gitHubHost = 'https://raw.githubusercontent.com'; @@ -150,11 +150,11 @@ describe('modules/datasource/azure-pipelines-tasks/index', () => { } : null; - return { + return AzurePipelinesTask.parse({ name: '', deprecated: false, version, - } as AzurePipelinesTask; + }); }); const azureSortedVersions = azureVersions.sort( @@ -162,11 +162,13 @@ describe('modules/datasource/azure-pipelines-tasks/index', () => { ); expect( - azureSortedVersions.map((x: AzurePipelinesTask) => - x.version === null + azureSortedVersions.map((x: any) => { + const data = AzurePipelinesTask.parse(x); + + return data.version === null ? '' - : `${x.version.major}.${x.version.minor}.${x.version.patch}`, - ), + : `${data.version.major}.${data.version.minor}.${data.version.patch}`; + }), ).toStrictEqual(exp); }); }); diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 29b437fb641fc2..db2e42bb20d4a9 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -1,3 +1,4 @@ +import type { TypeOf, ZodType } from 'zod'; import { GlobalConfig } from '../../../config/global'; import { cache } from '../../../util/cache/package/decorator'; import * as hostRules from '../../../util/host-rules'; @@ -5,7 +6,7 @@ import type { HttpOptions } from '../../../util/http/types'; import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -import type { AzurePipelinesJSON, AzurePipelinesTaskVersion } from './types'; +import { AzurePipelinesJSON, AzurePipelinesTaskVersion } from './types'; const TASKS_URL_BASE = 'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main'; @@ -38,36 +39,37 @@ export class AzurePipelinesTasksDatasource extends Datasource { const opts: HttpOptions = { headers: { authorization: `Basic ${auth}` }, }; - const results = await this.getTasks( + const results = await this.getTasks( `${endpoint}/_apis/distributedtask/tasks/`, opts, + AzurePipelinesJSON, ); - if (results.value) { - const result: ReleaseResult = { releases: [] }; + const result: ReleaseResult = { releases: [] }; - results.value - .filter((task) => task.name === packageName) - .sort( - AzurePipelinesTasksDatasource.compareSemanticVersions('version'), - ) - .forEach((task) => { - result.releases.push({ - version: `${task.version.major}.${task.version.minor}.${task.version.patch}`, - isDeprecated: task.deprecated, - }); + results.value + .filter((task) => task.name === packageName) + .sort(AzurePipelinesTasksDatasource.compareSemanticVersions('version')) + .forEach((task) => { + result.releases.push({ + version: `${task.version.major}.${task.version.minor}.${task.version.patch}`, + isDeprecated: task.deprecated, }); + }); - return result; - } + return result; } else { const versions = - (await this.getTasks>(BUILT_IN_TASKS_URL))[ - packageName.toLowerCase() - ] ?? - (await this.getTasks>(MARKETPLACE_TASKS_URL))[ - packageName.toLowerCase() - ]; + ( + await this.getFallbackTasks>( + BUILT_IN_TASKS_URL, + ) + )[packageName.toLowerCase()] ?? + ( + await this.getFallbackTasks>( + MARKETPLACE_TASKS_URL, + ) + )[packageName.toLowerCase()]; if (versions) { const releases = versions.map((version) => ({ version })); @@ -83,21 +85,35 @@ export class AzurePipelinesTasksDatasource extends Datasource { key: (url: string) => url, ttlMinutes: 24 * 60, }) - async getTasks(url: string, opts?: HttpOptions): Promise { - const { body } = await this.http.getJson(url, opts); + async getTasks = ZodType>( + url: string, + opts: HttpOptions, + schema: Schema, + ): Promise> { + const { body } = await this.http.getJson(url, opts, schema); + return body; + } + + @cache({ + namespace: `datasource-${AzurePipelinesTasksDatasource.id}`, + key: (url: string) => url, + ttlMinutes: 24 * 60, + }) + async getFallbackTasks(url: string): Promise { + const { body } = await this.http.getJson(url); return body; } static compareSemanticVersions = (key: string) => (a: any, b: any) => { - const a1Version = a[key] as AzurePipelinesTaskVersion; - const b1Version = b[key] as AzurePipelinesTaskVersion; + const a1Version = AzurePipelinesTaskVersion.safeParse(a[key]).data; + const b1Version = AzurePipelinesTaskVersion.safeParse(b[key]).data; const a1 = - a1Version === null + a1Version === undefined ? '' : `${a1Version.major}.${a1Version.minor}.${a1Version.patch}`; const b1 = - b1Version === null + b1Version === undefined ? '' : `${b1Version.major}.${b1Version.minor}.${b1Version.patch}`; diff --git a/lib/modules/datasource/azure-pipelines-tasks/types.ts b/lib/modules/datasource/azure-pipelines-tasks/types.ts index a2bd6c78a60981..197ebf40c92146 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/types.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/types.ts @@ -1,15 +1,17 @@ -export interface AzurePipelinesJSON { - value?: AzurePipelinesTask[]; -} +import { z } from 'zod'; -export interface AzurePipelinesTask { - name: string; - deprecated?: boolean; - version: AzurePipelinesTaskVersion; -} +export const AzurePipelinesTaskVersion = z.object({ + major: z.number(), + minor: z.number(), + patch: z.number(), +}); -export interface AzurePipelinesTaskVersion { - major: number; - minor: number; - patch: number; -} +export const AzurePipelinesTask = z.object({ + name: z.string(), + deprecated: z.boolean().optional(), + version: AzurePipelinesTaskVersion.nullable(), +}); + +export const AzurePipelinesJSON = z.object({ + value: AzurePipelinesTask.array(), +}); From ebe7e198df5f61bb434f0cc07fce3c2cd0b9683d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sun, 22 Dec 2024 21:49:25 +0100 Subject: [PATCH 09/10] Rename file --- lib/modules/datasource/azure-pipelines-tasks/index.spec.ts | 2 +- lib/modules/datasource/azure-pipelines-tasks/index.ts | 2 +- .../datasource/azure-pipelines-tasks/{types.ts => schema.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename lib/modules/datasource/azure-pipelines-tasks/{types.ts => schema.ts} (100%) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts index cee551cc82a31a..94d73d131bfe46 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.spec.ts @@ -3,7 +3,7 @@ import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; import { GlobalConfig } from '../../../config/global'; import * as hostRules from '../../../util/host-rules'; -import { AzurePipelinesTask } from './types'; +import { AzurePipelinesTask } from './schema'; import { AzurePipelinesTasksDatasource } from '.'; const gitHubHost = 'https://raw.githubusercontent.com'; diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index db2e42bb20d4a9..9315b59dc932f6 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -6,7 +6,7 @@ import type { HttpOptions } from '../../../util/http/types'; import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -import { AzurePipelinesJSON, AzurePipelinesTaskVersion } from './types'; +import { AzurePipelinesJSON, AzurePipelinesTaskVersion } from './schema'; const TASKS_URL_BASE = 'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main'; diff --git a/lib/modules/datasource/azure-pipelines-tasks/types.ts b/lib/modules/datasource/azure-pipelines-tasks/schema.ts similarity index 100% rename from lib/modules/datasource/azure-pipelines-tasks/types.ts rename to lib/modules/datasource/azure-pipelines-tasks/schema.ts From 3c435cb7ffd6786e002c81c3e68ce9ae96fc2937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez=20V=C3=A1zquez?= Date: Sun, 22 Dec 2024 22:37:58 +0100 Subject: [PATCH 10/10] Use schema for fallback tasks --- .../datasource/azure-pipelines-tasks/index.ts | 26 +++++++++---------- .../azure-pipelines-tasks/schema.ts | 2 ++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/modules/datasource/azure-pipelines-tasks/index.ts b/lib/modules/datasource/azure-pipelines-tasks/index.ts index 9315b59dc932f6..2605cd91a78845 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/index.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/index.ts @@ -6,7 +6,11 @@ import type { HttpOptions } from '../../../util/http/types'; import { id as versioning } from '../../versioning/loose'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -import { AzurePipelinesJSON, AzurePipelinesTaskVersion } from './schema'; +import { + AzurePipelinesFallbackTasks, + AzurePipelinesJSON, + AzurePipelinesTaskVersion, +} from './schema'; const TASKS_URL_BASE = 'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main'; @@ -52,7 +56,7 @@ export class AzurePipelinesTasksDatasource extends Datasource { .sort(AzurePipelinesTasksDatasource.compareSemanticVersions('version')) .forEach((task) => { result.releases.push({ - version: `${task.version.major}.${task.version.minor}.${task.version.patch}`, + version: `${task.version!.major}.${task.version!.minor}.${task.version!.patch}`, isDeprecated: task.deprecated, }); }); @@ -61,13 +65,17 @@ export class AzurePipelinesTasksDatasource extends Datasource { } else { const versions = ( - await this.getFallbackTasks>( + await this.getTasks( BUILT_IN_TASKS_URL, + {}, + AzurePipelinesFallbackTasks, ) )[packageName.toLowerCase()] ?? ( - await this.getFallbackTasks>( + await this.getTasks( MARKETPLACE_TASKS_URL, + {}, + AzurePipelinesFallbackTasks, ) )[packageName.toLowerCase()]; @@ -94,16 +102,6 @@ export class AzurePipelinesTasksDatasource extends Datasource { return body; } - @cache({ - namespace: `datasource-${AzurePipelinesTasksDatasource.id}`, - key: (url: string) => url, - ttlMinutes: 24 * 60, - }) - async getFallbackTasks(url: string): Promise { - const { body } = await this.http.getJson(url); - return body; - } - static compareSemanticVersions = (key: string) => (a: any, b: any) => { const a1Version = AzurePipelinesTaskVersion.safeParse(a[key]).data; const b1Version = AzurePipelinesTaskVersion.safeParse(b[key]).data; diff --git a/lib/modules/datasource/azure-pipelines-tasks/schema.ts b/lib/modules/datasource/azure-pipelines-tasks/schema.ts index 197ebf40c92146..eb20bc97dd0a06 100644 --- a/lib/modules/datasource/azure-pipelines-tasks/schema.ts +++ b/lib/modules/datasource/azure-pipelines-tasks/schema.ts @@ -15,3 +15,5 @@ export const AzurePipelinesTask = z.object({ export const AzurePipelinesJSON = z.object({ value: AzurePipelinesTask.array(), }); + +export const AzurePipelinesFallbackTasks = z.record(z.string().array());