0

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!

2
  • Move Azure DevOps API logic outside the dynamic provider by using an external script or a Pulumi Component Resource to avoid serialization issues. Commented Mar 18 at 13:10
  • @SirraSneha thanks for your answer! Could you please provide me a code snippet? I have tried moving the getWebApiClient to a seperate script, which i then import into my main script from above, but still getting same error. Commented Mar 20 at 15:13

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.