https://pulumi.com logo
#general
Title
# general
c

cold-coat-35200

10/11/2019, 12:48 PM
hi, is it possible to define a deletion order for kubernetes resources, created from a helm chart? I'm facing a problem with the etcd-operator when using periodic backups. In this case, after the EtcdBackup CR created by pulumi, based on the helm chart, the etcd backup operator adds a finalizer to the EtcdBackup CR (there is no finalizer in the helm chart template). When trying to destroy the stack with pulumi, it's unable to delete the EtcdBackup CR, because the etcd backup operator already deleted(pulumi deletes the resources parallel), so the finalizer never be removed by the operator, therefore kubernetes unable to delete the resource If I destroy the EtcdBackup first with the new
--target
option, then the whole stack, it works fine
c

calm-table-93204

10/11/2019, 2:50 PM
you should be able to use depends on to explicitly define that dependency so that it gets deleted in the proper order. See: https://www.pulumi.com/docs/intro/concepts/programming-model/#dependson
c

cold-coat-35200

10/11/2019, 2:54 PM
I don't think you can use dependsOn between helm chart resources, because the dependent resources created by pulumi runtime. I only have 1 resource in my code, which is the helm chart. Maybe with some transformation magic, but I'm not sure
b

busy-umbrella-36067

10/11/2019, 3:33 PM
you could do a weird hack where you instantiate the helm chart twice and use transformations to split out the resources that are dependent upon one-another
c

cold-coat-35200

10/11/2019, 3:45 PM
that's what I wanted to avoid...
g

gorgeous-egg-16927

10/11/2019, 3:46 PM
Yeah, unfortunately the monolithic nature of helm charts makes it difficult to do the right thing in cases like this. Pulumi can’t automatically infer the dependencies since they’re all part of the same chart. You should be able to use a transformation to add the
dependsOn
. I think something like this should do the trick:
Copy code
transformations: [
    (obj: any, opts: pulumi.CustomResourceOptions) => {
        if (obj.kind === "EtcdBackup" && obj.metadata.name == "foo") {
            opts.dependsOn = [resource1, resource2],
        }
    },
],
c

cold-coat-35200

10/11/2019, 3:56 PM
I will check but need to know the reference of the resource on which I want to depend, so if the callback call order not deterministic, that makes it more trickier(calling with the EtcdBackup object first, then just later with the backup operator object)
resource lifecycle support would be the best solution, but that issue is forgotten for a while 🙂
g

gorgeous-egg-16927

10/11/2019, 3:58 PM
This may just be an ugly case to work with since it sounds like something other than Pulumi is creating that resource. You may be able to create a Job to delete those resources that you apply prior to destroying the chart.
c

cold-coat-35200

10/13/2019, 9:29 AM
@gorgeous-egg-16927 for future reference, transformation on chart does not work, because the callback called with an object which is not a resource https://github.com/pulumi/pulumi-kubernetes/blob/1b8e780ff8a9d2f4c6b87274e04044621db83cb2/sdk/nodejs/yaml/yaml.ts#L2454 resource created later: https://github.com/pulumi/pulumi-kubernetes/blob/1b8e780ff8a9d2f4c6b87274e04044621db83cb2/sdk/nodejs/yaml/yaml.ts#L2587 I had to use aComponentResourceOptions based transformation, my final solution is:
Copy code
let backupOperator: any
  const namespace = env
  new k8s.helm.v2.Chart(
    `${env}-etcd`,
    {
      repo: 'stable',
      chart: 'etcd-operator',
      version: '0.10.0',
      namespace: namespace,
      values: values,
      transformations: [transformNamespace(namespace)]
    },
    {
      providers: {
        kubernetes: k8sProvider
      },
      transformations: [
        args => {
          if (args.props.kind === 'Deployment' && args.props.metadata.name.includes('etcd-backup-operator')) {
            backupOperator = args.resource
          } else if (args.props.kind === 'EtcdBackup') {
            args.opts.dependsOn = new Promise<pulumi.Resource>(async resolve => {
              const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))

              while (backupOperator === undefined) {
                await sleep(100)
              }

              resolve(backupOperator)
            })
          }

          return {
            props: args.props,
            opts: args.opts
          }
        }
      ]
    }
  )
👍 1