From 2c2981d7916552ab47ee5ca786ff8c2bffba408c Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Wed, 25 Feb 2026 21:12:40 +0100 Subject: [PATCH] refactor: replace requireVariable and requireInput with task-lib equivalents and improve error handling --- shared/src/blob.ts | 14 ++++++++++--- shared/src/devops-helpers.ts | 28 ------------------------- shared/src/index.ts | 1 - shared/src/oidc.ts | 31 ++++++++-------------------- task/AzureFederatedAuth/src/index.ts | 18 ++++++++++------ task/CopyBlob/src/index.ts | 15 +++++++------- task/GetBlob/src/index.ts | 13 ++++++------ task/ListBlobs/src/index.ts | 13 ++++++------ task/PutBlob/src/index.ts | 15 +++++++------- vss-extension.json | 2 +- 10 files changed, 59 insertions(+), 91 deletions(-) delete mode 100644 shared/src/devops-helpers.ts diff --git a/shared/src/blob.ts b/shared/src/blob.ts index 2b9e5b1..2d6a551 100644 --- a/shared/src/blob.ts +++ b/shared/src/blob.ts @@ -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 { - 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); diff --git a/shared/src/devops-helpers.ts b/shared/src/devops-helpers.ts deleted file mode 100644 index 2def4ef..0000000 --- a/shared/src/devops-helpers.ts +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/shared/src/index.ts b/shared/src/index.ts index 120bd15..d415871 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -1,3 +1,2 @@ -export * from './devops-helpers'; export * from './oidc'; export * from './blob'; \ No newline at end of file diff --git a/shared/src/oidc.ts b/shared/src/oidc.ts index 818e1bf..f99c136 100644 --- a/shared/src/oidc.ts +++ b/shared/src/oidc.ts @@ -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.'); } diff --git a/task/AzureFederatedAuth/src/index.ts b/task/AzureFederatedAuth/src/index.ts index d591358..ba86429 100644 --- a/task/AzureFederatedAuth/src/index.ts +++ b/task/AzureFederatedAuth/src/index.ts @@ -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 { 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...'); diff --git a/task/CopyBlob/src/index.ts b/task/CopyBlob/src/index.ts index 03cd945..093df9a 100644 --- a/task/CopyBlob/src/index.ts +++ b/task/CopyBlob/src/index.ts @@ -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 { 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); diff --git a/task/GetBlob/src/index.ts b/task/GetBlob/src/index.ts index 1800e82..233870a 100644 --- a/task/GetBlob/src/index.ts +++ b/task/GetBlob/src/index.ts @@ -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 { @@ -28,11 +27,11 @@ async function downloadBlob(blobUrl: string, bearerToken: string): Promise { 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); diff --git a/task/ListBlobs/src/index.ts b/task/ListBlobs/src/index.ts index c889710..b50b00d 100644 --- a/task/ListBlobs/src/index.ts +++ b/task/ListBlobs/src/index.ts @@ -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 { 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) { diff --git a/task/PutBlob/src/index.ts b/task/PutBlob/src/index.ts index 9f2db66..4ad14e3 100644 --- a/task/PutBlob/src/index.ts +++ b/task/PutBlob/src/index.ts @@ -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 { 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); diff --git a/vss-extension.json b/vss-extension.json index be3427f..dcdceaa 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -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": [ {