Skip to content

How Lab Deployments Work Part 2 - API Endpoints

Website API endpoints

This web application uses the Astro framework. It is a static site generator that supports server-side generation. It is essentially perfect for this website which is document heavy but requires some small “islands” of dynamic content. It also has an amazing and easy to use API endpoint architecture based on files - for example, src/pages/data.json.ts will generate a /data.json endpoint. The endpoints can also be static, as in generated at build time, or dynamic - which is essential to the functionality of this website as the endpoints handle dynamic data. The theme I use is called Starlight which is Astro’s own documentation website theme. It is a pretty basic theme looks-wise, but comes with a world of extensibility as well as some great built-in components.

The Lab Deployment Endpoint

This endpoint initiates Azure lab deployments by retrieving configurations from Cosmos DB and calling Azure Durable Starter Function. This endpoint is activated when the user of the website clicks the button component on the lab’s respective webpage. That component provides the endpoint the ID of the relevant lab.

1. Initialise CosmosDB

The first thing this endpoint does is initialise a Cosmos DB client with connection validation:

// Initialize Cosmos DB client only if connection string exists
const connectionString = process.env.connectionstring;
if (!connectionString) {
console.error('connection string is not defined!');
throw new Error('connection string environment variable is required');
}

I had a good bit of trouble when setting this up due to the firewall configuration as well with the format of the CosmosDB connection string. Rookie mistake on my part, but, needless to say, the input validation was very useful here.

2. Request parsing and lab config retrieval

The endpoint extracts the lab identifier from the request body it received which contains the lab ID:

//request parsing
const body = await request.json();
const labId = body.labId as string;

and retrieves the lab config from CosmosDB using both the item ID and partition key (which I’ve left generic):

const { resource: config } = await container.item(<itemID>, <partitionKey>).read();

3. Azure Function Authentication

The endpoint uses function key authentication for secure Azure Function calls:

const functionKey = process.env.AZURE_FUNCTION_KEY;
if (!functionKey) {
console.error('AZURE_FUNCTION_KEY environment variable is not set');
throw new Error('Function key is required for authentication');
}

4. Azure Function Invocation

The deployment is triggered via HTTP POST to the Azure Function:

const response = await fetch(config.functionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-functions-key': functionKey // Add function key for authentication
},
body: JSON.stringify(config.payload)
});

The Deployment Status Polling Endpoint

Whereas a standard Azure Function performs its function and return a response before exiting, Durable Functions are a different beast due to their asynchronous, stateful nature. I will go further into this in part 2 of this blog series. Essentially, though, Durable Functions return a status URL which your application can use to programmatically monitor the orchestration and execution status of activity functions. The purpose of this endpoint is to monitor Azure’s built-in deployment status endpoints for the Durable Function and return lab credentials when complete.

1. Receiving The Status URL

The status URL is received from the initial call to the Azure Durable Starter.

const body = await request.json();
const { statusUrl } = body;
// Parse and prepare the status URL
const url = new URL(statusUrl);
console.log('Calling status URL:', url.toString());

2. Poll Until Response Indicates Success

The polling endpoint is triggered periodically to make a request to the Azure Functions endpoint.

// Make the request to Azure Functions status endpoint
const response = await fetch(url.toString());

3. Processes Successful Response

A successful response is forwarded back to the website component (the client) that initiates the API endpoint.

// Process successful responses
if (response.ok) {
const data = await response.json();
return new Response(JSON.stringify(data), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}

The success response will contain a huge amount of information. The client component that triggers the endpoint sifts through this JSON data to provide the relevant details to the website user so they can access their lab environment. Errors in the deployment are also captured by the polling endpoint and provided to the front-end to be displayed to help with debugging. I haven’t included the error handling as that will be a larger topic later in the series. The client component’s code needs to sift through a tremendous amount of JSON data to find the relevant details - if that’s something you’re interested, let me know. Otherwise, I’ll see you in the next post where we focus on the Durable Functions themselves.