Creating a custom service endpoint

Overview

“Service endpoints - Customization” blog details how a custom endpoint can be registered with TFS/VSTS. This blog explains how a new endpoint for the custom endpoint type can be created taking Azure Classic endpoint type as an example.

Azure Classic endpoint is an endpoint type contribution that is part of the Azure extension.

Once an endpoint type is installed on a TFS collection or VSTS account, the endpoint type can be queried from the collection using a REST API. Here’s how Azure Classic endpoint type can be queried:

https://<account-name>.visualstudio.com/_apis/distributedtask/serviceendpointtypes?type=azure

The response for the query is captured here.

Creating custom endpoint through UI

The endpoint creation UI is driven out of the service endpoint type response got by querying the above REST API. For e.g. when “Azure Classic” endpoint type needs to be created, the following UI is presented upon clicking “New Service Endpoint” -> “Azure Classic” from the endpoint UI menu:

UI elements:

Authentication schemes

  1. The radio button group on the top indicates the authentication schemes supported by the endpoint type. Since Azure Classic endpoint supports Credentials & Azure Certificate authentication schemes, we see two options. UI uses the display names of the authentication scheme specified in the contribution:


"authenticationSchemes": [
{

"scheme": "UsernamePassword",
"displayName": "Credentials",
},
{

"scheme": "Certificate",
"displayName": "Certificate Based",
}

Service endpoints – Authentication schemes blog details the list of authentication schemes that are currently supported.

Connection name

Connection name is a mandatory input required for any endpoint type.

 

Endpoint data

Below are the additional data that are specific to Azure Classic endpoint:

  1. Environment is defined as an input with inputMode=combo & with a given set of values.

{
"id": "environment",
"name": "Environment",
"description": "Microsoft Azure Environment for the subscription",
"type": null,
"properties": null,
"inputMode": "combo",
"isConfidential": false,
"useInDefaultDescription": false,
"groupName": null,
"valueHint": null,
"validation": {
"dataType": "string",
"maxLength": 300
},
"values": {
"inputId": "environmentValues",
"defaultValue": "AzureCloud",
"possibleValues": [
{
"value": "AzureCloud",
"displayValue": "Azure Cloud"
},
{
"value": "AzureChinaCloud",
"displayValue": "Azure China Cloud"
},
{
"value": "AzureUSGovernment",
"displayValue": "Azure US Government"
},
{
"value": "AzureGermanCloud",
"displayValue": "Azure German Cloud"
}
]
}

The combo control shows the display value for each item in the values list.

2. Subscription Id is defined as input with inputMode=textbox with validation that it should be a GUID:

{
"id": "subscriptionId",
"name": "Subscription Id",
"description": "Subscription Id from the <a href=\"https://go.microsoft.com/fwlink/?LinkID=312990\" target=_blank>publish settings file</a>",
"type": null,
"properties": null,
"inputMode": "textBox",
"isConfidential": false,
"useInDefaultDescription": false,
"groupName": null,
"valueHint": null,
"validation": {
"dataType": "guid",
"isRequired": true,
"maxLength": 38
}
},

Description of the input is used for showing help information on the input. The description can be a HTML mark-up.

3. Subscription name is a string with validation that it can be of maximum 255 characters length.

"id": "subscriptionName",
"name": "Subscription Name",
"description": "Subscription Name from the <a href=\"https://go.microsoft.com/fwlink/?LinkID=312990\" target=_blank>publish settings file</a>",
"type": null,
"properties": null,
"inputMode": "textBox",
"isConfidential": false,
"useInDefaultDescription": false,
"groupName": null,
"valueHint": null,
"validation": {
"dataType": "string",
"isRequired": true,
"maxLength": 255
}

Authentication parameters

For “Credentials” authentication scheme, below are the parameters used:

  1. Username input corresponds to input defined as part of the authentication scheme.                    "inputDescriptors": [
    {
    "id": "username",
    "name": "Username",
    "description": "Specify a work or school account (for example <b>@fabrikam.com</b>). Microsoft accounts (for example <b>@live</b> or <b>@hotmail</b>) are not supported. Not recommended if Multi-Factored Authentication is enabled.",
    "type": null,
    "properties": null,
    "inputMode": "textBox",
    "isConfidential": false,
    "useInDefaultDescription": false,
    "groupName": "AuthenticationParameter",
    "valueHint": null,
    "validation": {
    "dataType": "string",
    "isRequired": true,
    "maxLength": 300
    }
    },

Since “isConfidential” is set to “false”, the value of this input does not get masked in the UI.

2. Password input also corresponds to the input defined as part of the authentication scheme.

{
"id": "password",
"name": "Password",
"description": "Password for connecting to the endpoint",
"type": null,
"properties": null,
"inputMode": "passwordBox",
"isConfidential": true,
"useInDefaultDescription": false,
"groupName": "AuthenticationParameter",
"valueHint": null,
"validation": {
"dataType": "string",
"isRequired": true,
"maxLength": 300
}
}

Since “isConfidential” is set to “true” the value of this field gets masked in the UI.

In case “Certificate” authentication scheme is chosen, below are the inputs seen in the UI:

 

For “Certificate” authentication scheme, the only input required is the “Management Certificate”:

{
"id": "certificate",
"name": "Management Certificate",
"description": "Management Certificate from the <a href=\"https://go.microsoft.com/fwlink/?LinkID=312990\" target=_blank>publish settings file</a>",
"type": null,
"properties": null,
"inputMode": "textArea",
"isConfidential": true,
"useInDefaultDescription": false,
"groupName": "AuthenticationParameter",
"valueHint": null,
"validation": {
"dataType": "string",
"isRequired": true
}
}

For the certificate, the inputMode is set to textArea to accommodate larger text to be entered.

The markdown specified in the endpoint type is used to populate the help link in the endpoint UI:

"helpMarkDown": "For certificate: download <a href=\"https://go.microsoft.com/fwlink/?LinkID=312990\" target=_blank><b>publish settings file</b></a>. <a href=\"https://msdn.microsoft.com/Library/vs/alm/Release/author-release-definition/understanding-tasks#serviceconnections\" target=_blank><b>Learn More</b></a>",

Icon

The icon for the service endpoint type specified in the contribution shows up next to the created endpoint in the list view on the left pane of endpoint UI.

"iconUrl": https://<account>.visualstudio.com/_apis/public/Extensions/ms.vss-services-azure/16.125.0.938080564/Assets/icons/azure-endpoint-icon.png

Verify connection

Once the details for the endpoint are entered, we allow verifying the details if the endpoint type supports a data source named “TestConnection”. In case of Azure RM, here is how the data source looks like:

"dataSources": [
{
"name": "TestConnection",
"endpointUrl": "$(endpoint.url)/$(endpoint.subscriptionId)/services/WebSpaces?properties=georegions",
"resourceUrl": "",
"resultSelector": "xpath://Name",
"headers": []
},

Verify connection will succeed if the details entered are valid. In case any error is encountered when validating, the error will be displayed. But failure in verifying connection will not block creation of the endpoint.

Creating endpoint through REST API

In case there is a need to create endpoint of a type directly using API, it can be done by constructing the endpoint request body corresponding to the inputs specified by the endpoint type.

For example, here is the request/response for creating Azure Classic service endpoint type using Certificate authentication scheme:

REST API: https://<your-vsts-account-name>/<projectid>/_apis/distributedtask/serviceendpoints

Method: POST

Request body:

{
"id": “<<any GUID – this is ignored>>”,
"description": "",
"administratorsGroup": null,
"authorization": {
"parameters": {
"certificate": “<<management certificate>>”
},
"scheme": "Certificate"
},
"createdBy": null,
"data": {
"environment": "<<one of the valid Azure environments>>",
"subscriptionId": "<<valid subscription id>>",
"subscriptionName": "<<subscription name>>"
},
"name": "<<endpoint name>>",
"type": "azure",
"url": "https://management.core.windows.net/",
"readersGroup": null,
"groupScopeId": null,
"isReady": false,
"operationStatus": null
}

Response body:

{
"data": {
"environment": "<<environment name given above>>",
"subscriptionId": "<<subscription id given above>>",
"subscriptionName": "<<subscription name given above>>"
},
"id": "c0407398-23ea-465b-9857-a7647f924699",
"name": "<<endpoint name given above>>",
"type": "azure",
"url": "https://management.core.windows.net/",
"createdBy": {
"id": "<<identifier for identity>>",
"displayName": "<<display name for identity>>",
"uniqueName": "<<unique name for identity>>",
"url": "<<identity’s url>>",
"imageUrl": "<<identity’s image url>>"},
"description": "",
"authorization": {
"parameters": {
"certificate": null
},
"scheme": "Certificate"
},
"isReady": true
}