start method
Pulls the image if it is missing, creates the container, copies the files in, starts it and waits until it is ready.
Does nothing when it is started already. When it does not become ready the container is removed and a ContainerLaunchException tells why, with the last lines the container printed. Throws a DockerUnavailableException without a daemon and a DockerException with the daemon's message for its refusals (a missing image, a port in use).
Implementation
Future<void> start() async {
if (_id != null) {
return;
}
final runtime = ContainerRuntime.instance;
final docker = await runtime.client();
_docker = docker;
final files = [for (final copy in _copies) copy.file()];
final reusable = _reuse && !_runsOnCi;
final hash = reusable ? _configurationHash(files) : null;
String? id;
if (hash != null) {
final found = await docker.listContainers(
labels: ['${ContainerRuntime.reuseLabel}=$hash'],
);
id = found.where((c) => c.state == 'running').firstOrNull?.id;
}
var protectedByReaper = false;
try {
if (id == null) {
if (!await docker.imageExists(image)) {
await docker.pullImage(image);
}
if (!reusable) {
await runtime.protect();
protectedByReaper = true;
}
id = await docker.createContainer(
DockerContainerConfig(
image: image,
environment: _environment,
command: _command,
entrypoint: _entrypoint,
exposedPorts: _ports,
labels:
{
...runtime.labels,
..._labels,
ContainerRuntime.reuseLabel: ?hash,
}..removeWhere(
// A reusable container carries no run id: the reaper must
// not remove it.
(name, _) =>
reusable && name == ContainerRuntime.sessionLabel,
),
publishIp: docker.endpoint.publishIp,
healthCheck: _healthCheck,
),
);
_id = id;
if (files.isNotEmpty) {
await docker.putFiles(id, files);
}
await docker.startContainer(id);
}
_id = id;
_clock = Stopwatch()..start();
await _refreshPorts();
await _wait.waitUntilReady(this);
} on Object catch (error) {
final logs = await _lastLines();
await _discard(releaseReaper: protectedByReaper);
if (error is ContainerLaunchException && logs.isNotEmpty) {
throw ContainerLaunchException(
'${error.message}\nLast output of the container:\n$logs',
);
}
rethrow;
} finally {
_clock?.stop();
}
}