blob: 7129b48e0e8da86f9f8ca099354989e1cc0a4aaa [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'package:uuid/uuid.dart';
import 'operation_result.dart';
import 'ssh_key_manager.dart';
import 'tar.dart';
/// Paves a prebuilt system image to a Fuchsia device.
///
/// The Fuchsia device must be in zedboot mode.
@immutable
class ImagePaver {
/// Creates a new image paver.
///
/// All properties must not be null.
const ImagePaver({
this.processManager = const LocalProcessManager(),
this.fs = const LocalFileSystem(),
this.tar = const SystemTar(),
this.sshKeyManagerProvider = SystemSshKeyManager.defaultProvider,
}) : assert(processManager != null),
assert(fs != null),
assert(tar != null);
/// The [ProcessManager] used to launch the boot server, `tar`,
/// and `ssh-keygen`.
final ProcessManager processManager;
/// The default pave timeout as [Duration] in milliseconds.
static const Duration defaultPaveTimeoutMs =
Duration(milliseconds: 5 * 60 * 1000);
/// The [FileSystem] implementation used to
final FileSystem fs;
/// The implementation to use for untarring system images.
final Tar tar;
/// The implementation to use for creating SSH keys.
final SshKeyManagerProvider sshKeyManagerProvider;
/// Paves an image (in .tgz format) to the specified device.
///
/// The `imageTgzPath` must not be null. If `deviceName` is null, the
/// first discoverable device will be used.
Future<OperationResult> pave(
String imageTgzPath,
String deviceName, {
String publicKeyPath,
bool verbose = true,
Duration timeoutMs = defaultPaveTimeoutMs,
}) async {
assert(imageTgzPath != null);
if (deviceName == null) {
stderr.writeln('Warning: No device name specified. '
'If multiple devices are attached, this may result in paving '
'an unexpected device.');
}
final SshKeyManager sshKeyManager = sshKeyManagerProvider(
processManager: processManager,
publicKeyPath: publicKeyPath,
fs: fs,
);
final String uuid = const Uuid().v4();
final Directory imageDirectory = fs.directory('image_$uuid');
if (verbose) {
stdout.writeln('Using ${imageDirectory.path} as temp path.');
}
await imageDirectory.create();
final OperationResult untarResult = await tar.untar(
imageTgzPath,
imageDirectory.path,
);
if (!untarResult.success) {
if (verbose) {
stderr.writeln('Unpacking image $imageTgzPath failed.');
}
imageDirectory.deleteSync(recursive: true);
return untarResult;
}
final OperationResult sshResult = await sshKeyManager.createKeys();
if (!sshResult.success) {
if (verbose) {
stderr.writeln('Creating SSH Keys failed.');
}
imageDirectory.deleteSync(recursive: true);
return sshResult;
}
final Process paveProcess = await processManager.start(
<String>[
'${imageDirectory.path}/pave.sh',
'--fail-fast',
'-1', // pave once and exit
'--allow-zedboot-version-mismatch',
if (deviceName != null) ...<String>['-n', deviceName],
'--authorized-keys', '.ssh/authorized_keys',
],
).timeout(timeoutMs);
final StringBuffer paveStdout = StringBuffer();
final StringBuffer paveStderr = StringBuffer();
paveProcess.stdout.transform(utf8.decoder).forEach((String s) {
if (verbose) {
stdout.write(s);
}
paveStdout.write(s);
});
paveProcess.stderr.transform(utf8.decoder).forEach((String s) {
if (verbose) {
stderr.write(s);
}
paveStderr.write(s);
});
final int exitCode = await paveProcess.exitCode;
await stdout.flush();
await stderr.flush();
imageDirectory.deleteSync(recursive: true);
return OperationResult.fromProcessResult(
ProcessResult(
paveProcess.pid,
exitCode,
paveStdout.toString(),
paveStderr.toString(),
),
);
}
}