alwaysai
Version:
The alwaysAI command-line interface (CLI)
322 lines (294 loc) • 7.92 kB
text/typescript
import { join } from 'path';
import Ajv, { JSONSchemaType } from 'ajv';
import * as chalk from 'chalk';
import { CliTerseError, CLI_TERSE_ERROR } from '@alwaysai/alwayscli';
import { TargetProtocol } from './target-protocol';
import { ALWAYSAI_CLI_EXECUTABLE_NAME } from '../../constants';
import {
DockerSpawner,
SshSpawner,
JsSpawner,
Spawner,
SshDockerSpawner,
logger,
stringifyError
} from '../../util';
import {
TargetHardware,
TARGET_HARDWARE_OPTIONS
} from './get-target-hardware-type';
import {
ConfigFileSchema,
ConfigFileSchemaReturnType
} from '@alwaysai/config-nodejs';
import { TARGET_JSON_FILE_NAME } from '../../paths';
export interface SshDockerTargetConfig {
targetProtocol: 'ssh+docker:';
targetHostname: string;
targetPath: string;
dockerImageId: string;
targetHardware: TargetHardware;
deviceId: string;
}
export interface DockerTargetConfig {
targetProtocol: 'docker:';
dockerImageId: string;
targetHardware: TargetHardware;
}
export interface NativeTargetConfig {
targetProtocol: 'native:';
}
export type TargetConfig =
| SshDockerTargetConfig
| DockerTargetConfig
| NativeTargetConfig;
export const targetConfigSchema: JSONSchemaType<TargetConfig> = {
anyOf: [
{
type: 'object',
properties: {
targetProtocol: {
type: 'string',
const: 'ssh+docker:'
},
targetHostname: {
type: 'string'
},
targetPath: {
type: 'string'
},
dockerImageId: {
type: 'string'
},
targetHardware: {
type: 'string',
enum: TARGET_HARDWARE_OPTIONS
},
deviceId: {
type: 'string'
}
},
required: [
'targetProtocol',
'targetHostname',
'targetPath',
'dockerImageId',
'targetHardware',
'deviceId'
]
},
{
type: 'object',
properties: {
targetProtocol: {
type: 'string',
const: 'docker:'
},
dockerImageId: {
type: 'string'
},
targetHardware: {
type: 'string',
enum: TARGET_HARDWARE_OPTIONS
}
},
required: ['targetProtocol', 'dockerImageId', 'targetHardware']
},
{
type: 'object',
properties: {
targetProtocol: {
type: 'string',
const: 'native:'
}
},
required: ['targetProtocol']
}
]
};
const ajv = new Ajv();
const validateFunction = ajv.compile(targetConfigSchema);
const DID_YOU_RUN_APP_CONFIGURE = `Did you run "${ALWAYSAI_CLI_EXECUTABLE_NAME} app configure"?`;
const ENOENT = {
message: `${TARGET_JSON_FILE_NAME} not found. ${DID_YOU_RUN_APP_CONFIGURE}`,
code: CLI_TERSE_ERROR
};
export interface TargetJsonFileReturnType
extends ConfigFileSchemaReturnType<TargetConfig> {
name: string;
readContainerSpawner: (opts?: {
ignoreTargetHardware?: boolean;
volumes?: string[];
env_vars?: string[];
}) => Spawner;
readHostSpawner: () => Spawner;
readTargetProtocolSafe: () =>
| 'ssh+docker:'
| 'docker:'
| 'native:'
| undefined;
readFieldSafe: (props: { name: string }) => string | undefined;
describe: () => string | undefined;
}
export function TargetJsonFile(cwd = process.cwd()): TargetJsonFileReturnType {
const filePath = join(cwd, TARGET_JSON_FILE_NAME);
const configFile = ConfigFileSchema({
path: filePath,
validateFunction,
ENOENT
});
return {
...configFile,
name: TARGET_JSON_FILE_NAME,
readContainerSpawner,
readHostSpawner,
readTargetProtocolSafe,
readFieldSafe,
describe
};
function describe() {
const config = configFile.readIfExists();
if (!config) {
return `Target configuration file "${TARGET_JSON_FILE_NAME}" not found`;
}
const docker = chalk.bold('docker');
switch (config.targetProtocol) {
case 'native:': {
return `Target: Native alwaysAI Python on this host`;
}
case 'docker:': {
return `Target: ${docker} container on this host`;
}
case 'ssh+docker:': {
const hostname = chalk.bold(config.targetHostname);
const path = chalk.bold(config.targetPath);
return `Target: ${docker} container on ${hostname}, path ${path}`;
}
default:
throw new Error('Unsupported protocol');
}
}
function readContainerSpawner(opts?: {
ignoreTargetHardware?: boolean;
volumes?: string[];
env_vars?: string[];
}) {
const targetJson = configFile.read();
const volumes = opts?.volumes;
const env_vars = opts?.env_vars;
switch (targetJson.targetProtocol) {
case 'ssh+docker:': {
const { targetHostname, targetPath, dockerImageId, targetHardware } =
targetJson;
return SshDockerSpawner({
dockerImageId,
targetPath,
targetHostname,
targetHardware,
volumes,
env_vars
});
}
case 'docker:': {
const { dockerImageId } = targetJson;
const targetHardware =
opts && opts.ignoreTargetHardware
? undefined
: targetJson.targetHardware;
return DockerSpawner({
dockerImageId,
targetHardware,
volumes,
env_vars
});
}
case 'native:':
default:
throw new CliTerseError('Unsupported protocol');
}
}
function readHostSpawner() {
const targetJson = configFile.read();
switch (targetJson.targetProtocol) {
case 'ssh+docker:': {
const { targetPath, targetHostname } = targetJson;
return SshSpawner({
targetHostname,
targetPath
});
}
case 'docker:':
case 'native:': {
return JsSpawner();
}
default:
throw new CliTerseError('Unsupported protocol');
}
}
function readTargetProtocolSafe() {
try {
const targetJson = configFile.readIfExists();
if (!targetJson) {
return undefined;
}
return targetJson.targetProtocol;
} catch (err) {
configFile.remove();
return undefined;
}
}
function readFieldSafe(props: { name: string }) {
try {
const targetJson = configFile.readIfExists();
if (!targetJson) {
return undefined;
}
switch (targetJson.targetProtocol) {
case 'ssh+docker:': {
switch (props.name) {
case 'targetProtocol':
return TargetProtocol[targetJson.targetProtocol];
case 'targetHostname':
return targetJson.targetHostname;
case 'targetPath':
return targetJson.targetPath;
case 'dockerImageId':
return targetJson.dockerImageId;
case 'targetHardware':
return targetJson.targetHardware;
case 'deviceId':
return targetJson.deviceId;
default:
return undefined;
}
}
case 'docker:': {
switch (props.name) {
case 'targetProtocol':
return TargetProtocol[targetJson.targetProtocol];
case 'dockerImageId':
return targetJson.dockerImageId;
case 'targetHardware':
return targetJson.targetHardware;
default:
return undefined;
}
}
case 'native:': {
switch (props.name) {
case 'targetProtocol':
return TargetProtocol[targetJson.targetProtocol];
default:
return undefined;
}
}
default:
throw new CliTerseError('Unsupported protocol');
}
} catch (err) {
logger.error(stringifyError(err));
configFile.remove();
return undefined;
}
}
}