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 existsconst 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 parsingconst 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 URLconst 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 endpointconst 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 responsesif (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.