Compare commits

...

8 commits

Author SHA1 Message Date
Jonas Rutishauser
2c9d3660f8
Merge 97dbc95cb8 into 766d0c37cf 2025-01-08 16:28:34 +01:00
ssams
766d0c37cf
refactor(manager/flux): extract helm repo handling to helper functions (#33462) 2025-01-08 15:17:04 +00:00
Borja Domínguez
8683eeb7ad
feat(datasource/azure-pipelines-tasks): Azure DevOps API based datasource (#32966) 2025-01-08 16:04:34 +01:00
Rhys Arkins
97dbc95cb8
Merge branch 'main' into feature/maven-relocation 2024-12-18 06:12:40 +01:00
Rhys Arkins
d12dab528a
Update lib/workers/repository/process/lookup/index.ts
Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
2024-12-18 06:12:31 +01:00
Jonas Rutishauser
bb63588cb1
Merge branch 'main' into feature/maven-relocation 2024-12-17 20:22:30 +01:00
Jonas Rutishauser
7cc4b9ec58
Apply review suggestions 2024-11-21 22:34:54 +01:00
Jonas Rutishauser
b6fb8221e1
feat(worker): Use replacement from datasource
Fixes #5667
2024-11-20 13:13:20 +01:00
8 changed files with 888 additions and 60 deletions

View file

@ -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"
}
}
]
}

View file

@ -1,5 +1,9 @@
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 { AzurePipelinesTask } from './schema';
import { AzurePipelinesTasksDatasource } from '.';
const gitHubHost = 'https://raw.githubusercontent.com';
@ -9,6 +13,11 @@ const marketplaceTasksPath =
'/renovatebot/azure-devops-marketplace/main/azure-pipelines-marketplace-tasks.json';
describe('modules/datasource/azure-pipelines-tasks/index', () => {
beforeEach(() => {
GlobalConfig.reset();
hostRules.clear();
});
it('returns null for unknown task', async () => {
httpMock
.scope(gitHubHost)
@ -64,4 +73,103 @@ 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',
});
hostRules.add({
hostType: AzurePipelinesTasksDatasource.id,
matchHost: 'my.custom.domain',
token: '123test',
});
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',
});
hostRules.add({
hostType: AzurePipelinesTasksDatasource.id,
matchHost: 'my.custom.domain',
token: '123test',
});
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' },
],
});
});
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 AzurePipelinesTask.parse({
name: '',
deprecated: false,
version,
});
});
const azureSortedVersions = azureVersions.sort(
AzurePipelinesTasksDatasource.compareSemanticVersions('version'),
);
expect(
azureSortedVersions.map((x: any) => {
const data = AzurePipelinesTask.parse(x);
return data.version === null
? ''
: `${data.version.major}.${data.version.minor}.${data.version.patch}`;
}),
).toStrictEqual(exp);
});
});
});

View file

@ -1,7 +1,16 @@
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';
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 {
AzurePipelinesFallbackTasks,
AzurePipelinesJSON,
AzurePipelinesTaskVersion,
} from './schema';
const TASKS_URL_BASE =
'https://raw.githubusercontent.com/renovatebot/azure-devops-marketplace/main';
@ -22,13 +31,58 @@ export class AzurePipelinesTasksDatasource extends Datasource {
async getReleases({
packageName,
}: GetReleasesConfig): Promise<ReleaseResult | null> {
const versions =
(await this.getTasks(BUILT_IN_TASKS_URL))[packageName.toLowerCase()] ??
(await this.getTasks(MARKETPLACE_TASKS_URL))[packageName.toLowerCase()];
const platform = GlobalConfig.get('platform');
const endpoint = GlobalConfig.get('endpoint');
const { token } = hostRules.find({
hostType: AzurePipelinesTasksDatasource.id,
url: endpoint,
});
if (versions) {
const releases = versions.map((version) => ({ version }));
return { releases };
if (platform === 'azure' && endpoint && token) {
const auth = Buffer.from(`renovate:${token}`).toString('base64');
const opts: HttpOptions = {
headers: { authorization: `Basic ${auth}` },
};
const results = await this.getTasks(
`${endpoint}/_apis/distributedtask/tasks/`,
opts,
AzurePipelinesJSON,
);
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,
});
});
return result;
} else {
const versions =
(
await this.getTasks(
BUILT_IN_TASKS_URL,
{},
AzurePipelinesFallbackTasks,
)
)[packageName.toLowerCase()] ??
(
await this.getTasks(
MARKETPLACE_TASKS_URL,
{},
AzurePipelinesFallbackTasks,
)
)[packageName.toLowerCase()];
if (versions) {
const releases = versions.map((version) => ({ version }));
return { releases };
}
}
return null;
@ -39,8 +93,39 @@ export class AzurePipelinesTasksDatasource extends Datasource {
key: (url: string) => url,
ttlMinutes: 24 * 60,
})
async getTasks(url: string): Promise<Record<string, string[]>> {
const { body } = await this.http.getJson<Record<string, string[]>>(url);
async getTasks<ResT, Schema extends ZodType<ResT> = ZodType<ResT>>(
url: string,
opts: HttpOptions,
schema: Schema,
): Promise<TypeOf<Schema>> {
const { body } = await this.http.getJson(url, opts, schema);
return body;
}
static compareSemanticVersions = (key: string) => (a: any, b: any) => {
const a1Version = AzurePipelinesTaskVersion.safeParse(a[key]).data;
const b1Version = AzurePipelinesTaskVersion.safeParse(b[key]).data;
const a1 =
a1Version === undefined
? ''
: `${a1Version.major}.${a1Version.minor}.${a1Version.patch}`;
const b1 =
b1Version === undefined
? ''
: `${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;
};
}

View file

@ -0,0 +1,19 @@
import { z } from 'zod';
export const AzurePipelinesTaskVersion = z.object({
major: z.number(),
minor: z.number(),
patch: z.number(),
});
export const AzurePipelinesTask = z.object({
name: z.string(),
deprecated: z.boolean().optional(),
version: AzurePipelinesTaskVersion.nullable(),
});
export const AzurePipelinesJSON = z.object({
value: AzurePipelinesTask.array(),
});
export const AzurePipelinesFallbackTasks = z.record(z.string().array());

View file

@ -1,4 +1,6 @@
import { regEx } from '../../../util/regex';
import type { HelmRepository } from './schema';
import type { FluxManifest } from './types';
export const systemManifestFileNameRegex = '(?:^|/)gotk-components\\.ya?ml$';
@ -8,3 +10,19 @@ export const systemManifestHeaderRegex =
export function isSystemManifest(file: string): boolean {
return regEx(systemManifestFileNameRegex).test(file);
}
export function collectHelmRepos(manifests: FluxManifest[]): HelmRepository[] {
const helmRepositories: HelmRepository[] = [];
for (const manifest of manifests) {
if (manifest.kind === 'resource') {
for (const resource of manifest.resources) {
if (resource.kind === 'HelmRepository') {
helmRepositories.push(resource);
}
}
}
}
return helmRepositories;
}

View file

@ -21,7 +21,11 @@ import type {
PackageFile,
PackageFileContent,
} from '../types';
import { isSystemManifest, systemManifestHeaderRegex } from './common';
import {
collectHelmRepos,
isSystemManifest,
systemManifestHeaderRegex,
} from './common';
import { FluxResource, type HelmRepository } from './schema';
import type {
FluxManagerData,
@ -102,6 +106,39 @@ function resolveGitRepositoryPerSourceTag(
}
}
function resolveHelmRepository(
dep: PackageDependency,
matchingRepositories: HelmRepository[],
registryAliases: Record<string, string> | undefined,
): void {
if (matchingRepositories.length) {
dep.registryUrls = matchingRepositories
.map((repo) => {
if (repo.spec.type === 'oci' || isOCIRegistry(repo.spec.url)) {
// Change datasource to Docker
dep.datasource = DockerDatasource.id;
// Ensure the URL is a valid OCI path
dep.packageName = getDep(
`${removeOCIPrefix(repo.spec.url)}/${dep.depName}`,
false,
registryAliases,
).depName;
return null;
} else {
return repo.spec.url;
}
})
.filter(is.string);
// if registryUrls is empty, delete it from dep
if (!dep.registryUrls?.length) {
delete dep.registryUrls;
}
} else {
dep.skipReason = 'unknown-registry';
}
}
function resolveSystemManifest(
manifest: SystemFluxManifest,
): PackageDependency<FluxManagerData>[] {
@ -126,7 +163,8 @@ function resolveResourceManifest(
for (const resource of manifest.resources) {
switch (resource.kind) {
case 'HelmRelease': {
const depName = resource.spec.chart.spec.chart;
const chartSpec = resource.spec.chart.spec;
const depName = chartSpec.chart;
const dep: PackageDependency = {
depName,
currentValue: resource.spec.chart.spec.version,
@ -142,40 +180,12 @@ function resolveResourceManifest(
const matchingRepositories = helmRepositories.filter(
(rep) =>
rep.kind === resource.spec.chart.spec.sourceRef?.kind &&
rep.metadata.name === resource.spec.chart.spec.sourceRef.name &&
rep.kind === chartSpec.sourceRef?.kind &&
rep.metadata.name === chartSpec.sourceRef.name &&
rep.metadata.namespace ===
(resource.spec.chart.spec.sourceRef.namespace ??
resource.metadata?.namespace),
(chartSpec.sourceRef.namespace ?? resource.metadata?.namespace),
);
if (matchingRepositories.length) {
dep.registryUrls = matchingRepositories
.map((repo) => {
if (repo.spec.type === 'oci' || isOCIRegistry(repo.spec.url)) {
// Change datasource to Docker
dep.datasource = DockerDatasource.id;
// Ensure the URL is a valid OCI path
dep.packageName = getDep(
`${removeOCIPrefix(repo.spec.url)}/${
resource.spec.chart.spec.chart
}`,
false,
registryAliases,
).depName;
return null;
} else {
return repo.spec.url;
}
})
.filter(is.string);
// if registryUrls is empty, delete it from dep
if (!dep.registryUrls?.length) {
delete dep.registryUrls;
}
} else {
dep.skipReason = 'unknown-registry';
}
resolveHelmRepository(dep, matchingRepositories, registryAliases);
deps.push(dep);
break;
}
@ -252,14 +262,7 @@ export function extractPackageFile(
if (!manifest) {
return null;
}
const helmRepositories: HelmRepository[] = [];
if (manifest.kind === 'resource') {
for (const resource of manifest.resources) {
if (resource.kind === 'HelmRepository') {
helmRepositories.push(resource);
}
}
}
const helmRepositories = collectHelmRepos([manifest]);
let deps: PackageDependency[] | null = null;
switch (manifest.kind) {
case 'system':
@ -293,16 +296,7 @@ export async function extractAllPackageFiles(
}
}
const helmRepositories: HelmRepository[] = [];
for (const manifest of manifests) {
if (manifest.kind === 'resource') {
for (const resource of manifest.resources) {
if (resource.kind === 'HelmRepository') {
helmRepositories.push(resource);
}
}
}
}
const helmRepositories = collectHelmRepos(manifests);
for (const manifest of manifests) {
let deps: PackageDependency[] | null = null;

View file

@ -4947,6 +4947,29 @@ describe('workers/repository/process/lookup/index', () => {
]);
});
it('handles replacements - from datasource', async () => {
config.currentValue = '2.0.0';
config.packageName = 'org.example:foo';
config.datasource = MavenDatasource.id;
getMavenReleases.mockResolvedValueOnce({
releases: [{ version: '2.0.0' }],
replacementName: 'foo:bar',
replacementVersion: '2.0.0',
});
const { updates } = await Result.wrap(
lookup.lookupUpdates(config),
).unwrapOrThrow();
expect(updates).toEqual([
{
updateType: 'replacement',
newName: 'foo:bar',
newValue: '2.0.0',
},
]);
});
it('rollback for invalid version to last stable version', async () => {
config.currentValue = '2.5.17';
config.packageName = 'vue';

View file

@ -571,6 +571,12 @@ export async function lookupUpdates(
if (isReplacementRulesConfigured(config)) {
addReplacementUpdateIfValid(res.updates, config);
} else if (dependency?.replacementName && dependency.replacementVersion) {
res.updates.push({
updateType: 'replacement',
newName: dependency.replacementName,
newValue: dependency.replacementVersion,
});
}
// Record if the dep is fixed to a version