@lando/acquia
Version:
A Lando plugin that provides a tight integration with Acquia.
292 lines (271 loc) • 9.8 kB
JavaScript
;
// Modules
const _ = require('lodash');
const API = require('../lib/api');
const path = require('path');
const utils = require('../lib/utils');
// Acquia
const acquiaKeyCache = 'acquia.keys';
const api = new API();
// Singleton just to hold app/env data
let acquiaApps = [];
// Helper to combine host and cached keys
const mergeKeys = (home, keys = []) => utils.sortKeys(utils.getHostKeys(home), keys);
// Helper to suss out our key and secret
const getAuthPair = answers => {
// If we dont even have a key then just return the answers unaltered
if (!answers['acquia-key']) return answers;
// Otherwise break up auth into parts
const authParts = answers['acquia-key'].split(':');
// If we have two parts then we need to separate, otherwise we assume
// secret and key were passed in separately
if (authParts.length === 2) {
answers['acquia-key'] = authParts[0];
answers['acquia-secret'] = authParts[1];
}
// Return
return {key: answers['acquia-key'], secret: answers['acquia-secret']};
};
// Helper to determine whether to show list of pre-used keys or not
const showKeyList = (data, home, keys = []) => data === 'acquia' && !_.isEmpty(mergeKeys(home, keys));
// Helper to determine whether to show key entry or not
const showKeyEntry = (data, answer, home, keys = []) => {
return data === 'acquia' && (_.isEmpty(mergeKeys(home, keys)) || answer === 'more');
};
// Helper to determine whether to show secret entry or not
const showSecretEntry = answers => {
// If there is no acquia key then we should bail on this right good now
if (!answers['acquia-key']) return false;
// If we are manually entering another key/secret pair
if (answers['acquia-needs-secret-entry']) return answers['acquia-needs-secret-entry'];
// Otherwise we only want to show this in a partial options pass in
// eg lando init --source acquia --acquia-key my-key
const authParts = answers['acquia-key'].split(':');
if (authParts.length === 1) return true;
// Otherwise i dont think we need to show this
return false;
};
// Helper to get sites for autocomplete
const getAutoCompleteSites = (answers, lando, input = null) => {
// We already have apps that we can sort
if (!_.isEmpty(acquiaApps)) {
return lando.Promise.resolve(acquiaApps).filter(app => _.startsWith(app.name, input));
}
// Get the key and secret so we can populate the list of apps
// NOTE: we do this here instead of upstream because we do not know exactly
// how the user is going to provide us with the auth creds
const {key, secret} = getAuthPair(answers);
return api.auth(key, secret, true, true)
.then(() => api.getApplications())
.then(apps => _.map(apps, app => (_.merge({}, {name: app.name, value: app.uuid}, app))))
.then(apps => {
acquiaApps = apps;
return lando.Promise.resolve(acquiaApps);
});
};
/*
* Init Acquia
*/
module.exports = {
name: 'acquia',
options: lando => ({
'acquia-key': {
describe: 'An Acquia API token key',
string: true,
interactive: {
type: 'list',
choices: utils.getKeys(mergeKeys(lando.config.home, lando.cache.get(acquiaKeyCache))),
message: 'Select an Acquia Cloud Platform API token',
when: answers => showKeyList(answers.recipe, lando.config.home, lando.cache.get(acquiaKeyCache)),
weight: 510,
},
},
'acquia-key-entry': {
hidden: true,
interactive: {
name: 'acquia-key',
type: 'input',
message: 'Enter an Acquia API token key, visit https://cloud.acquia.com/a/profile/tokens to create one',
when: answers => showKeyEntry(
answers.recipe,
answers['acquia-key'],
lando.config.home,
lando.cache.get(acquiaKeyCache),
),
validate: (input, answers) => {
// If we end up here we likely need to ask for the secret as well
if (answers['acquia-key'] === 'more') answers['acquia-needs-secret-entry'] = true;
// @NOTE: this exists mostly to stealth add acquia-needs-secret-entry
// but @TODO could actually validate the key as well
return true;
},
weight: 520,
},
},
'acquia-secret': {
describe: 'An Acquia API token secret',
string: true,
interactive: {
name: 'acquia-secret',
type: 'password',
message: 'Enter corresponding Acquia API token secret',
when: answers => showSecretEntry(answers),
weight: 530,
},
},
'acquia-app': {
describe: 'An Acquia application uuid',
string: true,
interactive: {
type: 'autocomplete',
message: 'Which site?',
source: (answers, input) => {
return getAutoCompleteSites(answers, lando, input);
},
when: answers => {
// Sneak this in here so we can use auth correctly downstream
getAuthPair(answers);
// If we can infer this based on local file then let us
if (answers.recipe === 'acquia' && answers.source === 'cwd') {
answers['acquia-app'] = utils.getAcliUuid();
return _.isNil(answers['acquia-app']);
} else return answers.recipe === 'acquia';
},
weight: 540,
},
},
'acquia-key-name': {
describe: 'A hidden field mostly for easy testing and key removal',
string: true,
hidden: true,
default: 'Landokey',
when: answers => answers.recipe === 'acquia',
},
}),
overrides: {
name: {
when: answers => {
answers.name = answers['acquia-app'];
return false;
},
},
webroot: {
when: () => false,
},
},
sources: [{
name: 'acquia',
label: 'acquia',
overrides: {
recipe: {
when: answers => {
answers.recipe = 'acquia';
return false;
},
},
},
build: () => ([
{
name: 'wait-for-user',
cmd: '/helpers/acquia-wait-for-user.sh',
},
{
name: 'get-user-account',
func: options => {
return api.auth(options['acquia-key'], options['acquia-secret'], true, true)
.then(() => api.getAccount())
.then(account => {
options['acquia-account'] = account;
options['acquia-keyname'] = `${account.mail}.acquia.lando.id_rsa`;
options['acquia-keycomment'] = `${account.mail}@lando`;
});
},
},
{
name: 'generate-key',
cmd: options => `/helpers/acquia-generate-key.sh ${options['acquia-keyname']} ${options['acquia-keycomment']}`,
},
{
name: 'post-key',
func: (options, lando) => {
const pubKey = path.join(lando.config.userConfRoot, 'keys', `${options['acquia-keyname']}.pub`);
const keyName = options['acquia-key-name'];
return api.auth(options['acquia-key'], options['acquia-secret'], true, true)
.then(() => api.postKey(pubKey, keyName));
},
},
{
name: 'get-git-url',
func: options => {
return api.auth(options['acquia-key'], options['acquia-secret'], true, true)
.then(() => api.getEnvironments(options['acquia-app']))
.then(envs => {
// Match our euuid with acquias
const env = utils.getBestEnv(envs);
// Get GIT URL
options['acquia-git-url'] = env.git;
// And some other things
const parts = env.vcs.split('/');
if (parts[0] === 'tags') {
options['acquia-git-branch'] = parts[1];
} else {
options['acquia-git-branch'] = env.vcs;
}
options['acquia-php-version'] = env.php;
options['acquia-site-group'] = env.group;
});
},
},
{
name: 'reload-keys',
cmd: '/helpers/load-keys.sh --silent',
user: 'root',
},
{
name: 'clone-repo',
cmd: options =>
`/helpers/acquia-clone.sh ${options['acquia-git-url']} "--branch ${options['acquia-git-branch']}" ` +
`${options['acquia-keyname']}`,
remove: 'true',
},
])}],
build: (options, lando) => {
// Get the info we need to build the relevant config
return api.auth(options['acquia-key'], options['acquia-secret'], true, true)
.then(() => Promise.all([
api.getAccount(),
api.getEnvironments(options['acquia-app']),
]))
.then(data => {
const account = data[0];
const env = utils.getBestEnv(data[1]);
// Write the acli-cli.yml file
utils.writeAcliUuid(options['acquia-app']);
// Reset the name to something human readable
options.name = env.group;
// Merge in other lando config
const landofileConfig = {
config: {
acli_version: 'latest',
ah_application_uuid: options['acquia-app'],
ah_site_group: env.group,
php: env.php,
},
};
// This is good auth, lets update our cache
const cache = {
key: options['acquia-key'],
label: account.mail,
secret: options['acquia-secret'],
};
// Update lando's store of acquia creds
const keys = lando.cache.get(acquiaKeyCache) || [];
lando.cache.set(acquiaKeyCache, utils.sortKeys(keys, [cache]), {persist: true});
// Update app metdata
const metaData = lando.cache.get(`${options.name}.meta.cache`);
lando.cache.set(`${options.name}.meta.cache`, _.merge({}, metaData, cache), {persist: true});
// Finish up
return landofileConfig;
});
},
};