refactor: replace requireVariable and requireInput with task-lib equivalents and improve error handling

This commit is contained in:
2026-02-25 21:12:40 +01:00
parent 8e9224cff9
commit 2c2981d791
10 changed files with 59 additions and 91 deletions

View File

@@ -1,18 +1,26 @@
import * as tl from 'azure-pipelines-task-lib/task';
import {
buildOidcUrl,
exchangeOidcForScopedToken,
getServiceConnectionMetadata,
requestOidcToken
} from './oidc';
import { requireVariable } from './devops-helpers';
export const STORAGE_SCOPE = 'https://storage.azure.com/.default';
export async function requestStorageAccessToken(
endpointId: string
): Promise<string> {
const oidcBaseUrl = requireVariable('System.OidcRequestUri');
const systemAccessToken = requireVariable('System.AccessToken');
const oidcBaseUrl = tl.getVariable('System.OidcRequestUri');
const systemAccessToken = tl.getVariable('System.AccessToken');
if (oidcBaseUrl === undefined) {
throw new Error('Missing required pipeline variable: System.OidcRequestUri.');
}
if (systemAccessToken === undefined) {
throw new Error('Missing required pipeline variable: System.AccessToken.');
}
const metadata = getServiceConnectionMetadata(endpointId);

View File

@@ -1,28 +0,0 @@
type TaskLibBridge = {
getInput: (name: string, required?: boolean) => string | undefined;
getVariable: (name: string) => string | undefined;
};
function getTaskLibBridge(): TaskLibBridge {
return require('azure-pipelines-task-lib/task') as TaskLibBridge;
}
export function requireInput(name: string): string {
const taskLib = getTaskLibBridge();
const value = taskLib.getInput(name, true);
if (!value) {
throw new Error(`Task input ${name} is required.`);
}
return value.trim();
}
export function requireVariable(name: string): string {
const taskLib = getTaskLibBridge();
const value = taskLib.getVariable(name);
if (!value) {
throw new Error(`Missing required pipeline variable: ${name}.`);
}
return value.trim();
}

View File

@@ -1,3 +1,2 @@
export * from './devops-helpers';
export * from './oidc';
export * from './blob';

View File

@@ -1,3 +1,5 @@
import * as tl from 'azure-pipelines-task-lib/task';
export type ServiceConnectionMetadata = {
tenantId: string;
clientId: string;
@@ -11,40 +13,25 @@ export type TokenResponse = {
export const CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
type TaskLibEndpointBridge = {
getEndpointAuthorizationParameter: (
endpointId: string,
key: string,
optional: boolean
) => string | undefined;
getEndpointDataParameter: (endpointId: string, key: string, optional: boolean) => string | undefined;
};
type OidcResponse = {
oidcToken?: string;
};
function getTaskLibEndpointBridge(): TaskLibEndpointBridge {
return require('azure-pipelines-task-lib/task') as TaskLibEndpointBridge;
}
export function getServiceConnectionMetadata(endpointId: string): ServiceConnectionMetadata {
const taskLib = getTaskLibEndpointBridge();
const tenantId =
taskLib.getEndpointAuthorizationParameter(endpointId, 'tenantid', true) ||
taskLib.getEndpointDataParameter(endpointId, 'tenantid', true);
tl.getEndpointAuthorizationParameter(endpointId, 'tenantid', true) ||
tl.getEndpointDataParameter(endpointId, 'tenantid', true);
const clientId =
taskLib.getEndpointAuthorizationParameter(endpointId, 'serviceprincipalid', true) ||
taskLib.getEndpointAuthorizationParameter(endpointId, 'clientid', true) ||
taskLib.getEndpointDataParameter(endpointId, 'serviceprincipalid', true);
tl.getEndpointAuthorizationParameter(endpointId, 'serviceprincipalid', true) ||
tl.getEndpointAuthorizationParameter(endpointId, 'clientid', true) ||
tl.getEndpointDataParameter(endpointId, 'serviceprincipalid', true);
if (!tenantId) {
if (tenantId === undefined) {
throw new Error('Could not resolve tenant ID from the selected AzureRM service connection.');
}
if (!clientId) {
if (clientId === undefined) {
throw new Error('Could not resolve client ID from the selected AzureRM service connection.');
}

View File

@@ -4,21 +4,27 @@ import {
buildOidcUrl,
exchangeOidcForScopedToken,
getServiceConnectionMetadata,
requestOidcToken,
requireInput,
requireVariable
requestOidcToken
} from '@skoszewski/ado-sk-toolkit-shared';
const AZDO_APP_SCOPE = '499b84ac-1321-427f-aa17-267ca6975798/.default';
async function run(): Promise<void> {
try {
const endpointId = requireInput('serviceConnectionARM');
const endpointId = tl.getInputRequired('serviceConnectionARM');
const setGitAccessToken = tl.getBoolInput('setGitAccessToken', false);
const printTokenHashes = tl.getBoolInput('printTokenHashes', false);
const oidcBaseUrl = requireVariable('System.OidcRequestUri');
const accessToken = requireVariable('System.AccessToken');
const oidcBaseUrl = tl.getVariable('System.OidcRequestUri');
const accessToken = tl.getVariable('System.AccessToken');
if (oidcBaseUrl === undefined) {
throw new Error('Missing required pipeline variable: System.OidcRequestUri.');
}
if (accessToken === undefined) {
throw new Error('Missing required pipeline variable: System.AccessToken.');
}
console.log('Requesting OIDC token for ARM authentication...');

View File

@@ -1,8 +1,7 @@
import * as tl from 'azure-pipelines-task-lib/task';
import {
buildBlobUrl,
requestStorageAccessToken,
requireInput
requestStorageAccessToken
} from '@skoszewski/ado-sk-toolkit-shared';
async function copyBlob(
@@ -38,12 +37,12 @@ async function copyBlob(
async function run(): Promise<void> {
try {
const endpointId = requireInput('serviceConnectionARM');
const srcStorageAccountName = requireInput('srcStorageAccountName');
const dstStorageAccountName = requireInput('dstStorageAccountName');
const srcContainerName = requireInput('srcContainerName');
const dstContainerNameInput = tl.getInput('dstContainerName', false)?.trim() || '';
const blobName = requireInput('blobName');
const endpointId = tl.getInputRequired('serviceConnectionARM');
const srcStorageAccountName = tl.getInputRequired('srcStorageAccountName');
const dstStorageAccountName = tl.getInputRequired('dstStorageAccountName');
const srcContainerName = tl.getInputRequired('srcContainerName');
const dstContainerNameInput = tl.getInput('dstContainerName', false) || '';
const blobName = tl.getInputRequired('blobName');
console.log('Requesting storage access token from Microsoft Entra ID...');
const accessToken = await requestStorageAccessToken(endpointId);

View File

@@ -3,8 +3,7 @@ import * as path from 'node:path';
import * as tl from 'azure-pipelines-task-lib/task';
import {
buildBlobUrl,
requestStorageAccessToken,
requireInput
requestStorageAccessToken
} from '@skoszewski/ado-sk-toolkit-shared';
async function downloadBlob(blobUrl: string, bearerToken: string): Promise<Buffer> {
@@ -28,11 +27,11 @@ async function downloadBlob(blobUrl: string, bearerToken: string): Promise<Buffe
async function run(): Promise<void> {
try {
const endpointId = requireInput('serviceConnectionARM');
const storageAccountName = requireInput('storageAccountName');
const containerName = requireInput('containerName');
const blobName = requireInput('blobName');
const destinationPath = requireInput('destinationPath');
const endpointId = tl.getInputRequired('serviceConnectionARM');
const storageAccountName = tl.getInputRequired('storageAccountName');
const containerName = tl.getInputRequired('containerName');
const blobName = tl.getInputRequired('blobName');
const destinationPath = tl.getInputRequired('destinationPath');
console.log('Requesting storage access token from Microsoft Entra ID...');
const accessToken = await requestStorageAccessToken(endpointId);

View File

@@ -1,7 +1,6 @@
import * as tl from 'azure-pipelines-task-lib/task';
import {
requestStorageAccessToken,
requireInput
requestStorageAccessToken
} from '@skoszewski/ado-sk-toolkit-shared';
function decodeXmlValue(value: string): string {
@@ -60,11 +59,11 @@ async function listBlobs(listUrl: string, bearerToken: string): Promise<string[]
async function run(): Promise<void> {
try {
const endpointId = requireInput('serviceConnectionARM');
const storageAccountName = requireInput('storageAccountName');
const containerName = requireInput('containerName');
const prefix = tl.getInput('prefix', false)?.trim() || '';
const maxResultsRaw = tl.getInput('maxResults', false)?.trim() || '1000';
const endpointId = tl.getInputRequired('serviceConnectionARM');
const storageAccountName = tl.getInputRequired('storageAccountName');
const containerName = tl.getInputRequired('containerName');
const prefix = tl.getInput('prefix', false) || '';
const maxResultsRaw = tl.getInput('maxResults', false) || '1000';
const maxResults = Number.parseInt(maxResultsRaw, 10);
if (!Number.isInteger(maxResults) || maxResults <= 0) {

View File

@@ -2,8 +2,7 @@ import * as fs from 'node:fs/promises';
import * as tl from 'azure-pipelines-task-lib/task';
import {
buildBlobUrl,
requestStorageAccessToken,
requireInput
requestStorageAccessToken
} from '@skoszewski/ado-sk-toolkit-shared';
async function uploadBlob(
@@ -35,12 +34,12 @@ async function uploadBlob(
async function run(): Promise<void> {
try {
const endpointId = requireInput('serviceConnectionARM');
const storageAccountName = requireInput('storageAccountName');
const containerName = requireInput('containerName');
const blobName = requireInput('blobName');
const sourcePath = requireInput('sourcePath');
const contentType = tl.getInput('contentType', false)?.trim() || 'application/octet-stream';
const endpointId = tl.getInputRequired('serviceConnectionARM');
const storageAccountName = tl.getInputRequired('storageAccountName');
const containerName = tl.getInputRequired('containerName');
const blobName = tl.getInputRequired('blobName');
const sourcePath = tl.getInputRequired('sourcePath');
const contentType = tl.getInput('contentType', false) || 'application/octet-stream';
console.log('Requesting storage access token from Microsoft Entra ID...');
const accessToken = await requestStorageAccessToken(endpointId);

View File

@@ -2,7 +2,7 @@
"manifestVersion": 1,
"id": "sk-azure-devops-toolkit",
"name": "SK Azure DevOps Toolkit",
"version": "1.0.5",
"version": "1.1.0",
"publisher": "skoszewski-lab",
"targets": [
{