Android container utility integration
Summary: Report commands as executed by the android container utility. Reviewed By: antonk52 Differential Revision: D47340410 fbshipit-source-id: dc2f80572816c8746e603aae2d721da2c47c3c4e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6668420083
commit
0e01fcad44
@@ -17,8 +17,6 @@ import {
|
|||||||
import {ClientQuery} from 'flipper-common';
|
import {ClientQuery} from 'flipper-common';
|
||||||
import {recorder} from '../../recorder';
|
import {recorder} from '../../recorder';
|
||||||
|
|
||||||
const logTag = 'AndroidCertificateProvider';
|
|
||||||
|
|
||||||
export default class AndroidCertificateProvider extends CertificateProvider {
|
export default class AndroidCertificateProvider extends CertificateProvider {
|
||||||
name = 'AndroidCertificateProvider';
|
name = 'AndroidCertificateProvider';
|
||||||
medium = 'FS_ACCESS' as const;
|
medium = 'FS_ACCESS' as const;
|
||||||
@@ -34,31 +32,32 @@ export default class AndroidCertificateProvider extends CertificateProvider {
|
|||||||
csr: string,
|
csr: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
recorder.log(clientQuery, 'Query available devices via adb');
|
recorder.log(clientQuery, 'Query available devices via adb');
|
||||||
const devicesInAdb = await this.adb.listDevices();
|
const devices = await this.adb.listDevices();
|
||||||
if (devicesInAdb.length === 0) {
|
if (devices.length === 0) {
|
||||||
recorder.error(clientQuery, 'No devices found via adb');
|
recorder.error(clientQuery, 'No devices found via adb');
|
||||||
throw new Error('No Android devices found');
|
throw new Error('No Android devices found');
|
||||||
}
|
}
|
||||||
const deviceMatchList = devicesInAdb.map(async (device) => {
|
|
||||||
|
const deviceMatches = devices.map(async (device) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.androidDeviceHasMatchingCSR(
|
const result = await this.androidDeviceHasMatchingCSR(
|
||||||
appDirectory,
|
appDirectory,
|
||||||
device.id,
|
device.id,
|
||||||
appName,
|
appName,
|
||||||
csr,
|
csr,
|
||||||
|
clientQuery,
|
||||||
);
|
);
|
||||||
return {id: device.id, ...result, error: null};
|
return {id: device.id, ...result, error: null};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[conn] Unable to check for matching CSR in ${device.id}:${appName}`,
|
`[conn] Unable to check for matching CSR in ${device.id}:${appName}`,
|
||||||
logTag,
|
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
return {id: device.id, isMatch: false, foundCsr: null, error: e};
|
return {id: device.id, isMatch: false, foundCsr: null, error: e};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const devices = await Promise.all(deviceMatchList);
|
const matches = await Promise.all(deviceMatches);
|
||||||
const matchingIds = devices.filter((m) => m.isMatch).map((m) => m.id);
|
const matchingIds = matches.filter((m) => m.isMatch).map((m) => m.id);
|
||||||
|
|
||||||
if (matchingIds.length == 0) {
|
if (matchingIds.length == 0) {
|
||||||
recorder.error(
|
recorder.error(
|
||||||
@@ -66,11 +65,11 @@ export default class AndroidCertificateProvider extends CertificateProvider {
|
|||||||
'Unable to find a matching device for the incoming request',
|
'Unable to find a matching device for the incoming request',
|
||||||
);
|
);
|
||||||
|
|
||||||
const erroredDevice = devices.find((d) => d.error);
|
const erroredDevice = matches.find((d) => d.error);
|
||||||
if (erroredDevice) {
|
if (erroredDevice) {
|
||||||
throw erroredDevice.error;
|
throw erroredDevice.error;
|
||||||
}
|
}
|
||||||
const foundCsrs = devices
|
const foundCsrs = matches
|
||||||
.filter((d) => d.foundCsr !== null)
|
.filter((d) => d.foundCsr !== null)
|
||||||
.map((d) => (d.foundCsr ? encodeURI(d.foundCsr) : 'null'));
|
.map((d) => (d.foundCsr ? encodeURI(d.foundCsr) : 'null'));
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -112,6 +111,7 @@ export default class AndroidCertificateProvider extends CertificateProvider {
|
|||||||
appName,
|
appName,
|
||||||
destination + filename,
|
destination + filename,
|
||||||
contents,
|
contents,
|
||||||
|
clientQuery,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +120,14 @@ export default class AndroidCertificateProvider extends CertificateProvider {
|
|||||||
deviceId: string,
|
deviceId: string,
|
||||||
processName: string,
|
processName: string,
|
||||||
csr: string,
|
csr: string,
|
||||||
|
clientQuery: ClientQuery,
|
||||||
): Promise<{isMatch: boolean; foundCsr: string}> {
|
): Promise<{isMatch: boolean; foundCsr: string}> {
|
||||||
const deviceCsr = await androidUtil.pull(
|
const deviceCsr = await androidUtil.pull(
|
||||||
this.adb,
|
this.adb,
|
||||||
deviceId,
|
deviceId,
|
||||||
processName,
|
processName,
|
||||||
directory + csrFileName,
|
directory + csrFileName,
|
||||||
|
clientQuery,
|
||||||
);
|
);
|
||||||
// Santitize both of the string before comparation
|
// Santitize both of the string before comparation
|
||||||
// The csr string extraction on client side return string in both way
|
// The csr string extraction on client side return string in both way
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {UnsupportedError} from 'flipper-common';
|
import {ClientQuery, UnsupportedError} from 'flipper-common';
|
||||||
import adbkit, {Client} from 'adbkit';
|
import adbkit, {Client} from 'adbkit';
|
||||||
|
import {recorder} from '../../recorder';
|
||||||
|
|
||||||
const allowedAppNameRegex = /^[\w.-]+$/;
|
const allowedAppNameRegex = /^[\w.-]+$/;
|
||||||
const appNotApplicationRegex = /not an application/;
|
const appNotApplicationRegex = /not an application/;
|
||||||
@@ -23,27 +24,29 @@ export type FilePath = string;
|
|||||||
export type FileContent = string;
|
export type FileContent = string;
|
||||||
|
|
||||||
export async function push(
|
export async function push(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: string,
|
app: string,
|
||||||
filepath: string,
|
filepath: string,
|
||||||
contents: string,
|
contents: string,
|
||||||
|
clientQuery?: ClientQuery,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
validateAppName(app);
|
validateAppName(app);
|
||||||
validateFilePath(filepath);
|
validateFilePath(filepath);
|
||||||
validateFileContent(contents);
|
validateFileContent(contents);
|
||||||
return await _push(client, deviceId, app, filepath, contents);
|
return await _push(adbClient, deviceId, app, filepath, contents, clientQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pull(
|
export async function pull(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: string,
|
app: string,
|
||||||
path: string,
|
path: string,
|
||||||
|
clientQuery?: ClientQuery,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
validateAppName(app);
|
validateAppName(app);
|
||||||
validateFilePath(path);
|
validateFilePath(path);
|
||||||
return await _pull(client, deviceId, app, path);
|
return await _pull(adbClient, deviceId, app, path, clientQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAppName(app: string): void {
|
function validateAppName(app: string): void {
|
||||||
@@ -80,54 +83,129 @@ class RunAsError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _push(
|
async function _push(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: AppName,
|
app: AppName,
|
||||||
filename: FilePath,
|
filename: FilePath,
|
||||||
contents: FileContent,
|
contents: FileContent,
|
||||||
|
clientQuery?: ClientQuery,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.debug(`Deploying ${filename} to ${deviceId}:${app}`, logTag);
|
console.debug(`Deploying ${filename} to ${deviceId}:${app}`, logTag);
|
||||||
// TODO: this is sensitive to escaping issues, can we leverage client.push instead?
|
|
||||||
// https://www.npmjs.com/package/adbkit#pushing-a-file-to-all-connected-devices
|
const cmd = `echo "${contents}" > '${filename}' && chmod 644 '${filename}'`;
|
||||||
const command = `echo "${contents}" > '${filename}' && chmod 644 '${filename}'`;
|
const description = 'Push file to device using adb shell (echo / chmod)';
|
||||||
return executeCommandAsApp(client, deviceId, app, command)
|
const troubleshoot = 'adb may be unresponsive, try `adb kill-server`';
|
||||||
.then((_) => undefined)
|
|
||||||
.catch((error) => {
|
const reportSuccess = () => {
|
||||||
if (error instanceof RunAsError) {
|
recorder.event('cmd', {
|
||||||
// Fall back to running the command directly. This will work if adb is running as root.
|
cmd,
|
||||||
executeCommandWithSu(client, deviceId, app, command, error);
|
description,
|
||||||
return undefined;
|
troubleshoot,
|
||||||
}
|
success: true,
|
||||||
throw error;
|
context: clientQuery,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
const reportFailure = (error: Error) => {
|
||||||
|
recorder.event('cmd', {
|
||||||
|
cmd,
|
||||||
|
description,
|
||||||
|
troubleshoot,
|
||||||
|
stdout: error.message,
|
||||||
|
success: false,
|
||||||
|
context: clientQuery,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executeCommandAsApp(adbClient, deviceId, app, cmd);
|
||||||
|
reportSuccess();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof RunAsError) {
|
||||||
|
// Fall back to running the command directly.
|
||||||
|
// This will work if adb is running as root.
|
||||||
|
try {
|
||||||
|
await executeCommandWithSu(adbClient, deviceId, app, cmd, error);
|
||||||
|
reportSuccess();
|
||||||
|
return;
|
||||||
|
} catch (suError) {
|
||||||
|
reportFailure(suError);
|
||||||
|
throw suError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reportFailure(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _pull(
|
async function _pull(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: AppName,
|
app: AppName,
|
||||||
path: FilePath,
|
path: FilePath,
|
||||||
|
clientQuery?: ClientQuery,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const command = `cat '${path}'`;
|
const cmd = `cat '${path}'`;
|
||||||
return executeCommandAsApp(client, deviceId, app, command).catch((error) => {
|
const description = 'Pull file from device using adb shell (cat)';
|
||||||
|
const troubleshoot = 'adb may be unresponsive, try `adb kill-server`';
|
||||||
|
|
||||||
|
const reportSuccess = () => {
|
||||||
|
recorder.event('cmd', {
|
||||||
|
cmd,
|
||||||
|
description,
|
||||||
|
troubleshoot,
|
||||||
|
success: true,
|
||||||
|
context: clientQuery,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const reportFailure = (error: Error) => {
|
||||||
|
recorder.event('cmd', {
|
||||||
|
cmd,
|
||||||
|
description,
|
||||||
|
troubleshoot,
|
||||||
|
stdout: error.message,
|
||||||
|
success: false,
|
||||||
|
context: clientQuery,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await executeCommandAsApp(adbClient, deviceId, app, cmd);
|
||||||
|
reportSuccess();
|
||||||
|
return content;
|
||||||
|
} catch (error) {
|
||||||
if (error instanceof RunAsError) {
|
if (error instanceof RunAsError) {
|
||||||
// Fall back to running the command directly. This will work if adb is running as root.
|
// Fall back to running the command directly.
|
||||||
return executeCommandWithSu(client, deviceId, app, command, error);
|
// This will work if adb is running as root.
|
||||||
|
try {
|
||||||
|
const content = await executeCommandWithSu(
|
||||||
|
adbClient,
|
||||||
|
deviceId,
|
||||||
|
app,
|
||||||
|
cmd,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
reportSuccess();
|
||||||
|
return content;
|
||||||
|
} catch (suError) {
|
||||||
|
reportFailure(suError);
|
||||||
|
throw suError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reportFailure(error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep this method private since it relies on pre-validated arguments
|
// Keep this method private since it relies on pre-validated arguments
|
||||||
export function executeCommandAsApp(
|
export function executeCommandAsApp(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: string,
|
app: string,
|
||||||
command: string,
|
command: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return _executeCommandWithRunner(
|
return _executeCommandWithRunner(
|
||||||
client,
|
adbClient,
|
||||||
deviceId,
|
deviceId,
|
||||||
app,
|
app,
|
||||||
command,
|
command,
|
||||||
@@ -136,28 +214,27 @@ export function executeCommandAsApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function executeCommandWithSu(
|
async function executeCommandWithSu(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: string,
|
app: string,
|
||||||
command: string,
|
command: string,
|
||||||
originalErrorToThrow: RunAsError,
|
originalErrorToThrow: RunAsError,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return _executeCommandWithRunner(client, deviceId, app, command, 'su');
|
return _executeCommandWithRunner(adbClient, deviceId, app, command, 'su');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.debug(e);
|
|
||||||
throw originalErrorToThrow;
|
throw originalErrorToThrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _executeCommandWithRunner(
|
function _executeCommandWithRunner(
|
||||||
client: Client,
|
adbClient: Client,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
app: string,
|
app: string,
|
||||||
command: string,
|
command: string,
|
||||||
runner: string,
|
runner: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return client
|
return adbClient
|
||||||
.shell(deviceId, `echo '${command}' | ${runner}`)
|
.shell(deviceId, `echo '${command}' | ${runner}`)
|
||||||
.then(adbkit.util.readAll)
|
.then(adbkit.util.readAll)
|
||||||
.then((buffer) => buffer.toString())
|
.then((buffer) => buffer.toString())
|
||||||
|
|||||||
Reference in New Issue
Block a user