By integrating this REST API in, not only does it show how you can leverage AirTable and Azure, but how you can integrate just about any REST API out there into Azure DevOps.
Azure DevOps, the Microsoft solution providing a comprehensive development environment, has the ability to create custom pipeline tasks. These tasks are built using NodeJS and developed using Typescript, which is a superset language of JavaScript originally developed by Microsoft. Keep in mind that this article, and some code examples within, assume you are using PowerShell for your command line interface.
Here we are going to build a solution to read from a given database within AirTable. We can then take that returned data and pass the information to the next steps in the pipeline. By integrating this REST API in, not only does it show how you can leverage these two tools together, but how you can integrate just about any REST API out there into Azure DevOps.
Download NodeJS for Windows, which is a JavaScript run-time environment. My personal recommendation is to install the LTS (long-term support) release which is 10.15.3 at time of this writing (though any recent version should work fine). Installing with the defaults will work perfectly fine for this.
https://nodejs.org/en/download/
This package will additionally install NPM (Node Package Manager), which we use to install the other prerequisite packages to develop the task.
After you run through the install, you can verify that it installed properly by running the following at the command line:
node -v
v10.15.3
npm -v
6.9.0
Note: You may need to upgrade NPM if you have it previously installed an older version or would like to run with the latest version.
npm install -g npm@latest
Install Typescript, this works only if you have NodeJS and NPM installed already, this will install Typescript globally (via the -g command).
npm install -g typescript
This is a command line tool for interacting with Azure DevOps and Microsoft Team Foundation Server.
npm install -g tfx-cli
Create Build and Package Directory
# Create Package Directory
New-Item -Name "buildRelease" -Type Directory
Set-Location buildRelease
Install Packages
# Initialize NPM (node_modules) and Install Prerequisites
npm init -y
npm install azure-pipelines-task-lib --save
npm install typed-rest-client --save
# Both of the items below are for definitions for Node and Q (helper package)
npm install @types/node --save-dev
npm install @types/q --save-dev
Note: We are installing the typed-rest-client because it gives us an easy HTTP client to perform REST API calls within our task.
Setup Typescript
# Make sure we don't commit the node_modules and it's many thousands of files
echo node_modules > .gitignore
# Initialize Typescript
tsc --init
# Change Typescript Target Language to ES6 from ES5. ES6 is the newest iteration of the Javascript language syntax and functionality and has many benefits for ease of use
((Get-Content -Path '.\tsconfig.json' -Raw) -replace '"es5"','"es6"') | Set-Content -Path '.\tsconfig.json'
2. Create the task .json file in our buildRelease working folder via the below script:
This defines our task, the entry point and also what inputs we require. In our case, we are going to define an AirTable task that we capture the API key and the base URL for a REST call.
# Only required to have a unqiue GUID
$GUID = (New-Guid).Guid
$Name = "get-airtable-data"
$FriendlyName = "Retrieve AirTable Data"
$Description = "This is a task to retrieve Airtable Data."
$Author = "Adam Bertram"
$JSON = @"
{
"id": "${GUID}",
"name": "${Name}",
"friendlyName": "${FriendlyName}",
"description": "${Description}",
"helpMarkDown": "",
"category": "Utility",
"author": "${Author}",
"version": {
"Major": 0,
"Minor": 1,
"Patch": 0
},
"visibility": [
"Build",
"Release"
],
"instanceNameFormat": "Retrieving $(baseurl) from AirTable",
"inputs": [
{
"name": "baseurl",
"type": "string",
"label": "Base URL",
"defaultValue": "",
"required": true,
"helpMarkDown": "The base URL for the REST API call within AirTable"
},
{
"name": "apikey",
"type": "string",
"label": "API Key",
"inputMode": "passwordbox",
"isConfidential": true,
"defaultValue": "",
"required": true,
"helpMarkDown": "AirTable API Key"
}
],
"execution": {
"Node": {
"target": "index.js"
}
}
}
"@
$JSON | Set-Content -Path "./task.json"
3. Create the ts file that actually contains the logic of our new task. For this AirTable task, we will save the JSON output of the results to a variable that can be passed on to the next task.
$boilerplate = @"
import tl = require('azure-pipelines-task-lib/task');
import httpc = require('typed-rest-client/HttpClient');
async function run() {
try {
const baseURL: string = tl.getInput('baseurl', true);
const APIKey: string = tl.getInput('apikey', true);
if (baseURL == 'bad') {
setResult(tl.TaskResult.Failed, 'Bad input was given');
return;
}
if (APIKey == 'bad') {
tl.setResult(tl.TaskResult.Failed, 'Bad input was given');
return;
}
console.log(baseURL);
console.log(APIKey);
let httpClient: httpc.HttpClient = new httpc.HttpClient('Test', []);
let result = await httpClient.get(baseURL, {
'Accept': 'application/json',
'Authorization': 'Bearer ' + APIKey
});
let body: string = await result.readBody();
let obj:any = JSON.parse(body);
console.log(obj.records);
tl.setVariable("AIRTABLERESULT", body, false);
}
catch (err) {
tl.setResult(tl.TaskResult.Failed, err.message);
}
}
run();
"@
$boilerplate | Set-Content -Path "./index.ts"
4. Compile the Typescript file in JavaScript for deployment. This command takes our ts file and compiles it to js. This will also output any obvious errors upon compiling.
tsc
We set environmental variables as that is how data is passed around within the build environments. Upon running our compiled index.js file, it will read in the environmental variables and output the results.
$env:INPUT_APIKEY="MyAPIKey"
$env:INPUT_BASEURL="https://api.airtable.com/v0/asd8f88ff8ads/Test"
node index.js
##vso[task.debug]agent.TempDirectory=undefined
##vso[task.debug]agent.workFolder=undefined
##vso[task.debug]loading inputs and endpoints
##vso[task.debug]loading INPUT_APIKEY
##vso[task.debug]loading INPUT_BASEURL
##vso[task.debug]loaded 2
##vso[task.debug]Agent.ProxyUrl=undefined
##vso[task.debug]Agent.CAInfo=undefined
##vso[task.debug]Agent.ClientCert=undefined
##vso[task.debug]Agent.SkipCertValidation=undefined
##vso[task.debug]baseurl=https://api.airtable.com/v0/asd8f88ff8ads/Test
##vso[task.debug]apikey=key343434343
https://api.airtable.com/v0/asd8f88ff8ads/Test
key343434343
[ { id: 'recLpzf2VZ6jyGIbm',
fields: { Name: 'Record 2', Value: 'Value 2' },
createdTime: '2019-04-25T04:16:25.000Z' },
{ id: 'recP6d01urYlVQOS5',
fields: { Name: 'Record 1', Value: 'Value 1' },
createdTime: '2019-04-25T04:16:25.000Z' },
{ id: 'recPyPXYbvTScUFn1',
fields: { Name: 'Record 3', Value: 'Value 3' },
createdTime: '2019-04-25T04:16:25.000Z' } ]
##vso[task.debug]set AIRTABLERESULT={"records":[{"id":"recLpzf2VZ6jyGIbm","fields":{"Name":"Record 2","Value":"Value 2"},"createdTime":"2019-04-25T04:16:25.000Z"},{"id":"recP6d01urYlVQOS5","fields":{"Name":"Record 1","Value":"Value 1"},"createdTime":"2019-04-25T04:16:25.000Z"},{"id":"recPyPXYbvTScUFn1","fields":{"Name":"Record 3","Value":"Value 3"},"createdTime":"2019-04-25T04:16:25.000Z"}]}
##vso[task.setvariable variable=AIRTABLERESULT;issecret=false;]{"records":[{"id":"recLpzf2VZ6jyGIbm","fields":{"Name":"Record 2","Value":"Value 2"},"createdTime":"2019-04-25T04:16:25.000Z"},{"id":"recP6d01urYlVQOS5","fields":{"Name":"Record 1","Value":"Value 1"},"createdTime":"2019-04-25T04:16:25.000Z"},{"id":"recPyPXYbvTScUFn1","fields":{"Name":"Record 3","Value":"Value 3"},"createdTime":"2019-04-25T04:16:25.000Z"}]}
To publish this extension we need to define our manifest file taht helps to define where and in what cases our extension will show up.
Manifest Documentation
https://docs.microsoft.com/en-us/azure/devops/extend/develop/manifest?view=azure-devops
Key Notes
$Publisher = "adam-bertram" $PublisherFriendly = "Adam Bertram" $Description = "Build and Release Tools"
$Manifest = @" { "manifestVersion": 1, "id": "build-release-task", "name": "${PublisherFriendly} Build and Release
Tools", "version": "0.0.1", "publisher": "$Publisher", "targets": [ { "id": "Microsoft.VisualStudio.Services" } ],
"description": "${Description}", "categories": [ "Azure Pipelines", "Build and release" ], "tags": [ "release", "build" ],
"files": [ { "path": "buildRelease" } ], "contributions": [ { "id": "custom-build-release-task", "type": "ms.vss-distributed-
task.task", "targets": [ "ms.vss-distributed-task.tasks" ], "properties": { "name": "buildRelease" } } ] } "@
Set-Location .. $Manifest | Set-Content -Path "./vss-extension.json"
Package the Extension
This will package up the extension into a .vsix file that we can use to upload to the Marketplace.
Note: Every time we need to package a version, it's required we rev the version. It's easiest to add the --rev-version parameter so that we automatically do so every time.
tfx extension create --manifest-globs vss-extension.json --rev-version
It's required that extensions are identified from a provider, Microsoft's included. If you haven't created one yet, do so with the following steps.
Once a publisher has been created, you can now upload your packaged extension. To do so, click on New extension → Azure DevOps.
You will be prompted to either Drag and Drop or upload the .vsix file that was created earlier by packaging our new extension.
To allow an organization to use your new extension, share it with one or more so that you may install and test your extension.
After the extension is installed, you will be able to find this task within the Build and Release tasks list. You can add one, test the inputs and see the output within the log files.
Adam Bertram is a 25+ year IT veteran and an experienced online business professional. He’s a successful blogger, consultant, 6x Microsoft MVP, trainer, published author and freelance writer for dozens of publications. For how-to tech tutorials, catch up with Adam at adamtheautomator.com, connect on LinkedIn or follow him on X at @adbertram.
Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.
Learn MoreSubscribe to get all the news, info and tutorials you need to build better business apps and sites