Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging a Azure DevOps Task Extension (TypeScript)

I develop all our task extensions in PowerShell, now I start to translate my first extension into TypeScript. The extension is a small task which should run in build or release pipelines. The task should get deployed to a Azure DevOps Server 2020.1 (on prem).


Preparation

Tutorials

  • I follow the tutorial create a custom pipelines task and build a sample app with it
  • I clone the ansible task extension and checkout the programming style

System Setup

- Visual Studio Code
- Node (v14.15.4)
- TypeScript (Version 4.1.3)
- ts-node (v9.1.1)
- mocha (8.2.0)
- ts-mocha (8.0.0)
- azure-pipelines-task-lib (2.12.0)

Launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "args": ["task/index.ts", "--Template", "Custom"],
      "internalConsoleOptions": "openOnSessionStart",
      "name": "Run TypeScript",
      "request": "launch",
      "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
      "skipFiles": ["<node_internals>/**"],
      "type": "pwa-node"
    }
  ]
}

Start up command: node.exe --nolazy -r ts-node/register/transpile-only task/index.ts --Template Custom


The Issue

At runtime, when the tl.getInput function with required true get executed, the debugging stop immediately without any response (no error, no output).

App.ts:

import tl = require("azure-pipelines-task-lib/task");
export const App = {
  Param: {
      Test: "Here",
      Template: tl.getInput("Template", true),
  }
}

Index.ts (entry point):

import { App } from "./app";

function run() {
  console.log("Hello");
  console.log(App.Param.Test);
}

run();

Output (just nothing):

Index.ts (modified):

import { App } from "./app";

function run() {
  console.log("Hello");
  // console.log(App.Param.Test);
}

run();

Output (modified):

Hello

obviously it stops because the required variable Template get not passed to the application.


The Question

  • is there a way to debug an azure devops task extension?
  • is it possible to pass parameter and load them via tl.getInput?
  • is there a state of the art or a complete guideline how to develop azure devops task extension?

It is totally clear that running azure-pipelines-task-lib without a Azure DevOps environment run into issues. But I was hoping that it is possible to mockup the required pipeline variables and run this library locally. If using azure-pipelines-task-lib means that you have to deploy the extension and run it in a pipeline to test, it get kind of komplex to develop tasks with it, or?


Edit 1:

I found the deprecated repository about vsts-task-lib. In azure-pipelines-tasks/docs/debugging.md is manual to debug that library. The author of Debugging TypeScript Tasks in VS Code describe an example launch.json configuration and I modify it for my usecase:

{
      "name": "Launch tar.gz",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/dist/task/index.js",
      "stopOnEntry": false,
      "args": [],
      "cwd": "${workspaceRoot}/task",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "runtimeExecutable": null,
      "runtimeArgs": ["--nolazy"],
      "env": {
        "NODE_ENV": "development",
        "INPUT_Separator": ";",
        "BUILD_SOURCESDIRECTORY": "C:\\agents\\latest\\_work\\21\\s"
      },
      "sourceMaps": true,
      "outFiles": ["${workspaceRoot}/dist"]
    }

I can confirm that it is possible to start up debugging and the tl.getInput("Separator") will return ;.

like image 486
Mar Tin Avatar asked Jul 26 '26 14:07

Mar Tin


1 Answers

With the help of Debugging TypeScript Tasks in VS Code I was able to do the following things:

  • read input parameter with tl.getInput from import tl = require("azure-pipelines-task-lib/task")
  • read environment variable with tl.getVariable from import tl = require("azure-pipelines-task-lib/task")
  • connect to Azure DevOps Server with new azdev.WebApi from import * as azdev from "azure-devops-node-api"
  • make a build api request with getBuildApi from import * as ba from "azure-devops-node-api/BuildApi"
  • run and debug application directly with TypeScript without JavaScript translation

launch.json

{
  "name": "Run TypeScript",
  "type": "pwa-node",
  "request": "launch",
  "internalConsoleOptions": "openOnSessionStart",
  "stopOnEntry": false,
  // path to your ts file
  "args": ["index.ts"],
  "cwd": "${workspaceRoot}/task",
  "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
  "env": {
    "NODE_ENV": "development",
    // param (enter your input params here!)
    "INPUT_WebhookUrl": "MyVariables",
    "INPUT_Template": "Empty",
    "INPUT_Json": "{\"text\":\"I am a test message\",\"attachments\":[{\"text\":\"And here’s an attachment!\"}]}",
    "INPUT_Separator": ";",
    // env
    "AGENT_JOBSTATUS": "Succeeded",
    "AGENT_NAME": "MyAgent",
    "BUILD_BUILDID": "5",
    "BUILD_BUILDNUMBER": "20210108.1",
    "BUILD_REASON": "Scheduled",
    "BUILD_REPOSITORY_NAME": "MyRepo",
    "BUILD_SOURCEBRANCHNAME": "master",
    "BUILD_SOURCEVERSION": "122a24f",
    "BUILDCONFIGURATION": "Debug",
    "BUILDPLATFORM": "Any CPU",
    "SYSTEM_ACCESSTOKEN": "",
    "SYSTEM_DEFINITIONNAME": "MyDefinitionName",
    "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://myurl.de/mycollection/",
    "SYSTEM_TEAMPROJECT": "PSItraffic",
    // debug
    "DEBUG_PAT": "my debug pat"
  },
  "skipFiles": ["<node_internals>/**"]
}

Usecases

Read param & env: app.ts

import tl = require("azure-pipelines-task-lib/task");

export const App = {
  // ------------------------------------------------------------ param
  Param: {
    WebhookUrl: tl.getDelimitedInput("WebhookUrl", "\n", true),
    Template: tl.getInput("Template", true)
  },
  // ------------------------------------------------------------ env
  Env: {
    Agent: {
      Jobstatus: getVariable("AGENT_JOBSTATUS"),
      Name: getVariable("AGENT_NAME"),
    },

    ...

    System: {
      AccessToken: getVariable("SYSTEM_ACCESSTOKEN"),
      DefinitionName: getVariable("SYSTEM_DEFINITIONNAME"),
      TeamFoundationServerUri: getVariable("SYSTEM_TEAMFOUNDATIONSERVERURI"),
      TeamProject: getVariable("SYSTEM_TEAMPROJECT"),
    },
  // ------------------------------------------------------------ debug
  Debug: {
    Pat: getVariable("DEBUG_PAT"),
  },
}

function getVariable(name: string): string {
  // get variable
  let v = tl.getVariable(name);
  if (v === undefined) return "";
  return v;
}

connect to azure devops server: rest.ts

import { App } from "./app";
import * as azdev from "azure-devops-node-api";
import * as ba from "azure-devops-node-api/BuildApi";


export class Rest {
  static AuthHanlder: IRequestHandler = Rest.Auth();
  static Connection: azdev.WebApi = new azdev.WebApi(App.Env.System.TeamFoundationServerUri, Rest.AuthHanlder);

  static Auth(): IRequestHandler {
    // auth
    if (App.Env.System.AccessToken === "") return azdev.getPersonalAccessTokenHandler(App.Debug.Pat);
    // no sure if this works on production
    return azdev.getBearerHandler(App.Env.System.AccessToken);
  }
}
like image 121
Mar Tin Avatar answered Jul 30 '26 09:07

Mar Tin