I am writing a Pulumi dynamic resource provider to control Azure DevOps project pipeline settings using the azure-devops-node-api client. Here's my provider code:
import * as pulumi from '@pulumi/pulumi';
import * as azdev from 'azure-devops-node-api';
export interface ProjectPipelineSettingsResourceInputs {
organization: pulumi.Input<string>;
orgServiceUrl: pulumi.Input<string>;
project: pulumi.Input<string>;
auditEnforceSettableVar: pulumi.Input<boolean>;
}
interface ProjectPipelineSettingsInputs {
organization: string;
orgServiceUrl: string;
project: string;
auditEnforceSettableVar: boolean;
}
interface ProjectPipelineSettingsOutputs extends ProjectPipelineSettingsInputs {
id: string;
}
class ProjectPipelineSettingsProvider implements pulumi.dynamic.ResourceProvider {
private async getWebApiClient(orgServiceUrl: string): Promise<azdev.WebApi> {
const token = process.env.AZDO_PERSONAL_ACCESS_TOKEN;
if (!token) {
throw new Error('AZDO_PERSONAL_ACCESS_TOKEN is not set');
}
const authHandler = azdev.getPersonalAccessTokenHandler(token);
return new azdev.WebApi(orgServiceUrl, authHandler);
}
async create(
inputs: ProjectPipelineSettingsInputs
): Promise<pulumi.dynamic.CreateResult<ProjectPipelineSettingsOutputs>> {
const connection = await this.getWebApiClient(inputs.orgServiceUrl);
const buildApiClient = await connection.getBuildApi();
const result = await buildApiClient.updateBuildGeneralSettings(
{ auditEnforceSettableVar: inputs.auditEnforceSettableVar },
"project"
);
let generatedId = `${inputs.organization}-${inputs.project}`.replace(/\s/g, '-').toLowerCase();
return {
id: generatedId,
outs: { id: generatedId, ...inputs, ...result }
};
}
}
export class ProjectPipelineSettings extends pulumi.dynamic.Resource {
readonly organization!: pulumi.Output<string>;
readonly orgServiceUrl!: pulumi.Output<string>;
readonly project!: pulumi.Output<string>;
readonly auditEnforceSettableVar!: pulumi.Output<boolean>;
constructor(
name: string,
args: ProjectPipelineSettingsResourceInputs,
opts?: pulumi.CustomResourceOptions
) {
super(new ProjectPipelineSettingsProvider(), name, args, opts);
}
}
I call this resource in index.ts
like this:
new ProjectPipelineSettings('project-pipeline-settings', {
organization: azdoConfig.require('organization'),
orgServiceUrl: azdoConfig.require('orgServiceUrl'),
project: azdoConfig.require('project'),
auditEnforceSettableVar: true
});
However, when I run pulumi up, I get the following error:
Diagnostics:
pulumi:pulumi:Stack (pulumi-test-dev):
error: Error serializing '() => provider': index.js(50,43)
'() => provider': index.js(50,43): captured
variable 'provider' which indirectly referenced
function 'ProjectPipelineSettingsProvider': ProjectPipelineSettings.ts(1,196): which referenced
function 'getWebApiClient': ProjectPipelineSettings.ts(1,309): which captured
variable 'azdev' which indirectly referenced
function 'WebApi': WebApi.js(97,15): which could not be serialized because
Unexpected missing variable in closure environment: window
It seems like azure-devops-node-api
references window
, which causes problems during serialization. How can I modify my Pulumi dynamic provider to avoid this issue?
Any help would be greatly appreciated!
Unexpected missing variable in closure environment: window
The error you're encountering is due to Pulumi tries to serialize the dynamic provider
, which includes the Azure Devops API client.
To resolve the issue,
Instead of using a Dynamic Provider
which Pulumi serializes, use a ComponentResource
.
As component resources run locally, so Pulumi does not try to serialize azure-devops-node-api
.
Here's the sample Provider code:
import * as pulumi from "@pulumi/pulumi";
import * as azdev from "azure-devops-node-api";
export interface ProjectPipelineSettingsArgs {
organization: pulumi.Input<string>;
orgServiceUrl: pulumi.Input<string>;
project: pulumi.Input<string>;
auditEnforceSettableVar: pulumi.Input<boolean>;
}
export class ProjectPipelineSettings extends pulumi.ComponentResource {
constructor(name: string, args: ProjectPipelineSettingsArgs, opts?: pulumi.ResourceOptions) {
super("custom:devops:ProjectPipelineSettings", name, {}, opts);
const token = process.env.AZDO_PERSONAL_ACCESS_TOKEN;
if (!token) {
throw new Error("AZDO_PERSONAL_ACCESS_TOKEN is not set");
}
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(args.orgServiceUrl, authHandler);
pulumi.output(connection.getBuildApi()).apply(async (buildApi) => {
await buildApi.updateBuildGeneralSettings(
{ auditEnforceSettableVar: args.auditEnforceSettableVar },
args.project
);
});
this.registerOutputs({});
}
}
Please refer this Doc for better understanding of component resources.