dvApplyInfraPlan function
Future<DVInfraApplyResult>
dvApplyInfraPlan({
- required DVInfraPlan approved,
- required DVInfraDesiredState desired,
- required DVInfraHost host,
- required DVInfraSecretLookup secret,
- 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.',
);
}