utils/promote: Do not rely on the user to always be there

Change-type: minor
Depends-on: https://github.com/resin-io/resin-sdk/pull/595
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
This commit is contained in:
Thodoris Greasidis 2018-10-20 14:35:34 +02:00 committed by Tim Perry
parent 31628cfdcb
commit f60d857c93

View File

@ -173,6 +173,47 @@ async function getOrSelectLocalDevice(deviceIp?: string): Promise<string> {
return ip;
}
async function getApplicationsWithOptionalUsers(sdk: ResinSdk.ResinSDK,
options: ResinSdk.PineOptionsFor<ResinSdk.Application>) {
const _ = await import('lodash');
let applications = await sdk.models.application.getAll(options);
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app.
if (applications.length !== _.uniqBy(applications, 'app_name').length) {
options = _.merge(_.cloneDeep(options), {
$expand: { user: { $select: ['username'] } }
});
applications = await sdk.models.application.getAll(options);
}
return applications;
}
async function selectAppFromList(applications: ResinSdk.Application[]) {
const _ = await import('lodash');
const { selectFromList } = await import('../utils/patterns');
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app. We
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select.
const hasSameNameApps = applications.length !== _.uniqBy(applications, 'app_name').length
return selectFromList(
hasSameNameApps ? 'Found multiple applications with that name; please select the one to use' : 'Select application',
_.map(applications, app => {
let name = app.app_name;
if (hasSameNameApps) {
const owner = _.get(app, 'user[0].username');
name = `${owner}/${app.app_name}`;
}
return _.merge({ name }, app);
}),
);
}
async function getOrSelectApplication(
sdk: ResinSdk.ResinSDK,
deviceType: string,
@ -180,7 +221,6 @@ async function getOrSelectApplication(
): Promise<ResinSdk.Application> {
const _ = await import('lodash');
const form = await import('resin-cli-form');
const { selectFromList } = await import('../utils/patterns');
const allDeviceTypes = await sdk.models.config.getDeviceTypes();
const deviceTypeManifest = _.find(allDeviceTypes, { slug: deviceType });
@ -192,14 +232,14 @@ async function getOrSelectApplication(
.map(type => type.slug)
.value();
const options: any = {
$expand: { user: { $select: ['username'] } },
if (!appName) {
const options = {
$filter: { device_type: { $in: compatibleDeviceTypes } },
};
if (!appName) {
// No application specified, show a list to select one.
const applications = await sdk.models.application.getAll(options);
const applications = await getApplicationsWithOptionalUsers(sdk, options);
if (applications.length === 0) {
const shouldCreateApp = await form.ask({
message:
@ -213,29 +253,31 @@ async function getOrSelectApplication(
}
process.exit(1);
}
return selectFromList(
'Select application',
_.map(applications, app => _.merge({ name: app.app_name }, app)),
);
return selectAppFromList(applications);
}
// We're given an application; resolve it if it's ambiguous and also validate
// it's of appropriate device type.
options.$filter = { app_name: appName };
const options: ResinSdk.PineOptionsFor<ResinSdk.Application> = {};
// Check for an app of the form `user/application` and update the API query.
const match = appName.split('/');
if (match.length > 1) {
// These will match at most one app, so we'll return early.
options.$expand.user.$filter = { username: match[0] };
options.$filter.app_name = match[1];
// These will match at most one app
options.$expand = {
user: {
$select: ['username'],
$filter: { username: match[0] },
},
};
options.$filter = { app_name: match[1] };
} else {
// We're given an application; resolve it if it's ambiguous and also validate
// it's of appropriate device type.
options.$filter = { app_name: appName };
}
// Fetch all applications with the given name that are accessible to the user
const applications = await sdk.pine.get<ResinSdk.Application>({
resource: 'application',
options,
});
const applications = await getApplicationsWithOptionalUsers(sdk, options);
if (applications.length === 0) {
const shouldCreateApp = await form.ask({
@ -246,7 +288,7 @@ async function getOrSelectApplication(
default: true,
});
if (shouldCreateApp) {
return createApplication(sdk, deviceType, options.$filter.app_name);
return createApplication(sdk, deviceType, options.$filter.app_name as string);
}
process.exit(1);
}
@ -257,21 +299,15 @@ async function getOrSelectApplication(
_.includes(compatibleDeviceTypes, app.device_type),
);
if (validApplications.length === 0) {
throw new Error('No application found with a matching device type')
}
if (validApplications.length === 1) {
return validApplications[0];
}
// If we got more than one application with the same name it means that the
// user has access to a collab app with the same name as a personal app. We
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select
return selectFromList(
'Found multiple applications with that name; please select the one to use',
_.map(validApplications, app => {
const owner = _.get(app, 'user[0].username');
return _.merge({ name: `${owner}/${app.app_name}` }, app);
}),
);
return selectAppFromList(applications);
}
async function createApplication(
@ -284,9 +320,6 @@ async function createApplication(
const patterns = await import('./patterns');
const user = await sdk.auth.getUserId();
const queryOptions = {
$filter: { user },
};
const appName = await new Promise<string>(async (resolve, reject) => {
while (true) {
@ -299,7 +332,9 @@ async function createApplication(
});
try {
await sdk.models.application.get(appName, queryOptions);
await sdk.models.application.get(appName, {
$filter: { user },
});
patterns.printErrorMessage(
'You already have an application with that name; please choose another.',
);