dvApplyInfraPlan function

Future<DVInfraApplyResult> dvApplyInfraPlan({
  1. required DVInfraPlan approved,
  2. required DVInfraDesiredState desired,
  3. required DVInfraHost host,
  4. required DVInfraSecretLookup secret,
  5. bool confirmDestructive = false,
})

Applies approved to host, if it is still the plan desired and the host produce.

Refuses, running nothing, when the plan has unsupported declarations, when the plan computed now differs from the one approved, or when it removes something and confirmDestructive is false. Stops at the first failing step. After the last step it observes the host again, and reports success only when nothing is left to do.

Implementation

Future<DVInfraApplyResult> dvApplyInfraPlan({
  required DVInfraPlan approved,
  required DVInfraDesiredState desired,
  required DVInfraHost host,
  required DVInfraSecretLookup secret,
  bool confirmDestructive = false,
}) async {
  if (approved.unsupported.isNotEmpty) {
    return DVInfraApplyResult(
      status: DVInfraApplyStatus.refused,
      message: 'The manifest declares what this provisioner cannot do, so '
          'nothing was applied:\n  ${approved.unsupported.join('\n  ')}',
    );
  }

  final DVInfraPlan current = dvInfraPlan(
    desired,
    await host.observe(),
    secret: secret,
  );
  if (current.digest != approved.digest) {
    return DVInfraApplyResult(
      status: DVInfraApplyStatus.refused,
      message: 'The host or the manifest changed after this plan was approved '
          '(approved ${approved.digest.substring(0, 12)}, now '
          '${current.digest.substring(0, 12)}), so nothing was applied. Run '
          'the plan again and review what it would do now.',
    );
  }

  final List<DVInfraStep> destructive = current.destructive;
  if (destructive.isNotEmpty && !confirmDestructive) {
    return DVInfraApplyResult(
      status: DVInfraApplyStatus.refused,
      message: 'The plan removes ${destructive.length} thing(s), and nothing '
          'was applied without confirmation: '
          '${destructive.map((DVInfraStep s) => s.id).join(', ')}',
    );
  }

  final List<String> values = <String>[
    for (final DVInfraResource r in desired.resources)
      if (r.kind == DVInfraResourceKind.credential) ?secret(r.id),
  ];
  String redact(String text) {
    String out = text;
    for (final String v in values) {
      if (v.length >= 4) out = out.replaceAll(v, '[redacted]');
    }
    return out;
  }

  final List<DVInfraStep> applied = <DVInfraStep>[];
  for (int i = 0; i < current.steps.length; i++) {
    final DVInfraStep step = current.steps[i];
    DVInfraResource? resource;
    if (step.action != DVInfraAction.remove) {
      for (final DVInfraResource r in desired.resources) {
        if (r.kind == step.kind && r.id == step.id) resource = r;
      }
    }
    try {
      await host.apply(
        step,
        resource,
        secretValue: step.action == DVInfraAction.deliver ? secret(step.id) : null,
      );
    } on Object catch (e) {
      final String error = redact('$e');
      final List<DVInfraStep> pending = current.steps.sublist(i + 1);
      return DVInfraApplyResult(
        status: DVInfraApplyStatus.failed,
        applied: List<DVInfraStep>.unmodifiable(applied),
        failed: step,
        error: error,
        pending: List<DVInfraStep>.unmodifiable(pending),
        message: 'Provisioning ${host.name} stopped at ${step.action.name} '
            '${step.id}: $error. ${applied.length} step(s) ran before it and '
            '${pending.length} did not. Running the provision again plans '
            'only what is still missing.',
      );
    }
    applied.add(step);
  }

  final DVInfraPlan after = dvInfraPlan(
    desired,
    await host.observe(),
    secret: secret,
  );
  if (!after.isEmpty) {
    return DVInfraApplyResult(
      status: DVInfraApplyStatus.unverified,
      applied: List<DVInfraStep>.unmodifiable(applied),
      remaining: after.steps,
      message: 'Every step ran on ${host.name}, and the host still does not '
          'match the manifest: ${after.steps.join('; ')}',
    );
  }
  return DVInfraApplyResult(
    status: DVInfraApplyStatus.applied,
    applied: List<DVInfraStep>.unmodifiable(applied),
    message: applied.isEmpty
        ? '${host.name} already matches the manifest.'
        : '${host.name}: ${applied.length} step(s) applied and verified.',
  );
}