Hi, I'm trying to write a dynamic resource to hand...
# general
c
Hi, I'm trying to write a dynamic resource to handle Cloudflare DNS records, this is a wip code
when running pulumi up, I get this error message:
Copy code
error: Error serializing '() => provider': index.js(19,37)
    
    '() => provider': index.js(19,37): captured
      variable 'provider' which indirectly referenced
        function '<anonymous>': dns.ts(17,22): which could not be serialized because
          arrow function captured 'this'. Assign 'this' to another name outside function and capture that.
    
    Function code:
      (inputs) => __awaiter(this, void 0, void 0, function* () {
                  yield this.init();
                  const record = {
                      type: inputs.type,
                      name: inputs.name,
      ...
    error: an unhandled error occurred: Program exited with non-zero exit code: 1
as far as I know, the code above is a valid typescript code, don't really understand what is the problem here
s
I can’t answer the typescript question without a bit more investigation, but would it be helpful to you if we bridge the Cloudflare Terraform Provider?
c
yes, it would, I saw there's an issue for that
s
OK, I’ll have a look at doing it, it shouldn’t be too hard.
c
based on this test, it's the expected behavior, but not clear why: https://github.com/pulumi/pulumi/blob/master/sdk/nodejs/tests/runtime/tsClosureCases.ts#L143
thanks Jen, I checked the bridge documentation too, but creating the resources.go file seems complicated, especially because I use only the DNS related functionality, so I'm not familier with the other Cloudflare functions
b
This is a(n unfortunate) limitation of V8's representation of closures (
this
is not available in the same way other captured variables are). We've been eeking by without hitting this very often -- and I keep hoping we will find a long-term solution (our old solution was to maintain a fork of V8 which wasn't sustainable when you factor in all the different versions). I had hoped Node.js 11 would solve this but alas, it hasn't. cc @lemon-spoon-91807
c
so it's not a pulumi serialization issue, but a nodejs one? and pulumi serialize it when sending the data to the engine through grpc?
in the meantime I created an other version
Copy code
// @ts-ignore
import * as cloudflare from 'cloudflare'
import * as pulumi from '@pulumi/pulumi'
import * as dynamic from '@pulumi/pulumi/dynamic'
import { RunError } from '@pulumi/pulumi/errors'

class DNSProvider implements dynamic.ResourceProvider {
  create = async (inputs: any) => {
    let client = new cloudflare({
      email: process.env.CLOUDFLARE_EMAIL as string,
      key: process.env.CLOUDFLARE_TOKEN as string
    })

    let domains = await getDomains(client)

    const record = {
      type: inputs.type,
      name: inputs.name,
      content: inputs.content,
      ttl: inputs.ttl,
      priority: inputs.priority,
      proxied: inputs.proxied
    }

    const zoneID = domains[inputs.domain]

    let res
    try {
      res = await client.dnsRecords.add(zoneID, record)
    } catch (e) {
      throw new RunError(`Unable to add DNS record: ${e}`)
    }

    return {
      id: `dns:${zoneID}:${res.result.id}`
    }
  }

  delete = async (id: pulumi.ID, props: any) => {
    const data = await parseID(id)

    let client = new cloudflare({
      email: process.env.CLOUDFLARE_EMAIL as string,
      key: process.env.CLOUDFLARE_TOKEN as string
    })

    try {
      await client.dnsRecords.del(data.zoneID, data.recordID)
    } catch (e) {
      throw new RunError(`Unable to delete DNS record: ${e}`)
    }
  }
}

export interface DNSRecordArgs {
  domain: pulumi.Input<string>
  type: pulumi.Input<string>
  name: pulumi.Input<string>
  content: pulumi.Input<string>
  ttl?: pulumi.Input<string>
  priority?: pulumi.Input<string>
  proxied?: pulumi.Input<string>
}

export class DNSRecord extends pulumi.dynamic.Resource {
  constructor(name: string, args: DNSRecordArgs, opts?: pulumi.CustomResourceOptions) {
    super(new DNSProvider(), name, args, opts)
  }
}

async function getDomains(client: cloudflare) {
  let domains: {
    [domain: string]: string
  } = {}

  let zones
  try {
    zones = await client.zones.browse()
  } catch (e) {
    throw new RunError(`Exception happened when retrieving zone information: ${e}`)
  }

  for (let zone of zones.result) {
    domains[zone.name] = zone.id
  }

  return domains
}

function parseID(id: string) {
  const parts = id.split(':')

  if (parts.length !== 3) {
    throw new RunError(`Invalid id: ${id}`)
  }

  return {
    zoneID: parts[1],
    recordID: parts[2]
  }
}
it does not look nice, but works, the problem I found with this version that I can't pass a provider, like with the standard pulumi resources, because
dynamic.ResourceProvider
is not a
ProviderResource
and I can't pass the cloudflare auth data as an arg, e.g with
DNSRecordArgs
, because those args will be persisted in the state, getting from the env works, but would be better to provide this data from the outside, without storing in the state