Skip to content

Commit

Permalink
feat(cubejs-cli): Save deploy credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
paveltiunov committed Jun 10, 2020
1 parent c523bbb commit af7e930
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 24 deletions.
160 changes: 160 additions & 0 deletions packages/cubejs-cli/Config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
const inquirer = require('inquirer');
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const jwt = require('jsonwebtoken');
const rp = require('request-promise');

class Config {
async loadConfig() {
const { configFile } = this.configFile();
if (await fs.exists(configFile)) {
return await fs.readJson(configFile);
}
return {};
}

async writeConfig(config) {
const { cubeCloudConfigPath, configFile } = this.configFile();
await fs.mkdirp(cubeCloudConfigPath);
await fs.writeJson(configFile, config);
}

configFile() {
const cubeCloudConfigPath = this.cubeCloudConfigPath();
const configFile = path.join(cubeCloudConfigPath, `config.json`);
return { cubeCloudConfigPath, configFile };
}

cubeCloudConfigPath() {
return path.join(os.homedir(), '.cubecloud');
}

async deployAuth(url) {
if (process.env.CUBE_CLOUD_DEPLOY_AUTH) {
const payload = jwt.decode(process.env.CUBE_CLOUD_DEPLOY_AUTH);
if (!payload.url) {
throw new Error(`Malformed token in CUBE_CLOUD_DEPLOY_AUTH`);
}
if (url && payload.url !== url) {
throw new Error(`CUBE_CLOUD_DEPLOY_AUTH token doesn't match url in .cubecloud`);
}
return {
[payload.url]: {
auth: process.env.CUBE_CLOUD_DEPLOY_AUTH
}
};
}
const config = await this.loadConfig();
if (config.auth) {
return config.auth;
} else {
const auth = await inquirer.prompt([{
name: 'auth',
message: `Cube Cloud Auth Token${url ? ` for ${url}` : ''}`
}]);
const authToken = auth.auth;
return (await this.addAuthToken(authToken, config)).auth;
}
}

async addAuthToken(authToken, config) {
if (!config) {
config = await this.loadConfig();
}
const payload = jwt.decode(authToken);
if (!payload || !payload.url) {
throw `Malformed Cube Cloud token`;
}
config.auth = config.auth || {};
config.auth[payload.url] = {
auth: authToken
};
await this.writeConfig(config);
return config;
}

async deployAuthForCurrentDir() {
const dotCubeCloud = await this.loadDotCubeCloud();
if (dotCubeCloud.url && dotCubeCloud.deploymentId) {
const deployAuth = await this.deployAuth(dotCubeCloud.url);
if (!deployAuth[dotCubeCloud.url]) {
throw new Error(`Provided token isn't for ${dotCubeCloud.url}`);
}
return {
...deployAuth[dotCubeCloud.url],
url: dotCubeCloud.url,
deploymentId: dotCubeCloud.deploymentId
}
}
const auth = await this.deployAuth();
let url = Object.keys(auth)[0];
if (Object.keys(auth).length > 1) {
url = (await inquirer.prompt([{
type: 'list',
name: 'url',
message: 'Please select an organization',
choices: Object.keys(auth)
}])).url;
}
const authToken = auth[url];
const deployments = await this.cloudReq({
url: () => `build/deploy/deployments`,
method: 'GET',
auth: { ...authToken, url }
});
const { deployment } = await inquirer.prompt([{
type: 'list',
name: 'deployment',
message: 'Please select a deployment to deploy to',
choices: deployments
}]);
const deploymentId = deployments.find(d => d.name === deployment).id;
await this.writeDotCubeCloud({
url,
deploymentId
});
return {
...authToken,
url,
deploymentId
}
}

async loadDeployAuth() {
this.preLoadDeployAuth = await this.deployAuthForCurrentDir();
}

dotCubeCloudFile() {
return '.cubecloud';
}

async loadDotCubeCloud() {
if (await fs.exists(this.dotCubeCloudFile())) {
return await fs.readJson(this.dotCubeCloudFile());
}
return {};
}

async writeDotCubeCloud(config) {
await fs.writeJson(this.dotCubeCloudFile(), config);
}

async cloudReq(options) {
const { url, auth, ...restOptions } = options;
const authorization = auth || this.preLoadDeployAuth;
if (!authorization) {
throw new Error('Auth isn\'t set');
}
return rp({
headers: {
authorization: authorization.auth
},
...restOptions,
url: `${authorization.url}/${url(authorization.deploymentId)}`,
json: true
});
}
}

module.exports = Config;
16 changes: 16 additions & 0 deletions packages/cubejs-cli/cubejsCli.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const chalk = require('chalk');
const spawn = require('cross-spawn');
const crypto = require('crypto');

const Config = require('./Config');
const templates = require('./templates');
const { deploy } = require('./deploy');
const { token, defaultExpiry, collect } = require('./token');
Expand Down Expand Up @@ -274,6 +275,21 @@ program
console.log(' $ cubejs deploy');
});

program
.command('authenticate <token>')
.description('Authenticate access to Cube Cloud')
.action(
(token) => new Config().addAuthToken(token)
.catch(e => displayError(e.stack || e))
)
.on('--help', () => {
console.log('');
console.log('Examples:');
console.log('');
console.log(' $ cubejs authenticate eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXBsb3ltZW50SWQiOiIxIiwidXJsIjoiaHR0cHM6Ly9leGFtcGxlcy5jdWJlY2xvdWQuZGV2IiwiaWF0IjoxNTE2MjM5MDIyfQ.La3MiuqfGigfzADl1wpxZ7jlb6dY60caezgqIOoHt-c');
console.log(' $ cubejs deploy');
});

if (!process.argv.slice(2).length) {
program.help();
}
Expand Down
31 changes: 7 additions & 24 deletions packages/cubejs-cli/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,11 @@ const path = require('path');
const cliProgress = require('cli-progress');
const DeployDir = require('./DeployDir');
const { logStage } = require('./utils');

const cloudReq = (options) => {
const { url, auth, ...restOptions } = options;
const authorization = auth || process.env.CUBE_CLOUD_DEPLOY_AUTH;
if (!authorization) {
throw new Error('CUBE_CLOUD_DEPLOY_AUTH isn\'t set');
}
const payload = jwt.decode(authorization);
if (!payload.url || !payload.deploymentId) {
throw new Error(`Malformed token: ${authorization}`);
}
return rp({
headers: {
authorization
},
...restOptions,
url: `${payload.url}/${url(payload.deploymentId)}`,
json: true
});
};
const Config = require('./Config');

exports.deploy = async ({ directory, auth }) => {
const config = new Config();
await config.loadDeployAuth();
const bar = new cliProgress.SingleBar({
format: '- Uploading files | {bar} | {percentage}% || {value} / {total} | {file}',
barCompleteChar: '\u2588',
Expand All @@ -36,12 +19,12 @@ exports.deploy = async ({ directory, auth }) => {

const deployDir = new DeployDir({ directory });
const fileHashes = await deployDir.fileHashes();
const upstreamHashes = await cloudReq({
const upstreamHashes = await config.cloudReq({
url: (deploymentId) => `build/deploy/${deploymentId}/files`,
method: 'GET',
auth
});
const { transaction, deploymentName } = await cloudReq({
const { transaction, deploymentName } = await config.cloudReq({
url: (deploymentId) => `build/deploy/${deploymentId}/start-upload`,
method: 'POST',
auth
Expand All @@ -59,7 +42,7 @@ exports.deploy = async ({ directory, auth }) => {
const file = files[i];
bar.update(i, { file });
if (!upstreamHashes[file] || upstreamHashes[file].hash !== fileHashes[file].hash) {
await cloudReq({
await config.cloudReq({
url: (deploymentId) => `build/deploy/${deploymentId}/upload-file`,
method: 'POST',
formData: {
Expand All @@ -78,7 +61,7 @@ exports.deploy = async ({ directory, auth }) => {
}
}
bar.update(files.length, { file: 'Post processing...' });
await cloudReq({
await config.cloudReq({
url: (deploymentId) => `build/deploy/${deploymentId}/finish-upload`,
method: 'POST',
body: {
Expand Down
2 changes: 2 additions & 0 deletions packages/cubejs-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"templates.js",
"deploy.js",
"DeployDir.js",
"Config.js",
"yarn.lock",
"README.md",
"LICENSE"
Expand All @@ -37,6 +38,7 @@
"commander": "^2.19.0",
"cross-spawn": "^7.0.1",
"fs-extra": "^8.1.0",
"inquirer": "^7.1.0",
"jsonwebtoken": "^8.5.1",
"node-fetch": "^2.6.0",
"node-machine-id": "^1.1.10",
Expand Down
19 changes: 19 additions & 0 deletions packages/cubejs-cli/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1941,6 +1941,25 @@ inquirer@^7.0.0:
strip-ansi "^6.0.0"
through "^2.3.6"

inquirer@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
dependencies:
ansi-escapes "^4.2.1"
chalk "^3.0.0"
cli-cursor "^3.1.0"
cli-width "^2.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
lodash "^4.17.15"
mute-stream "0.0.8"
run-async "^2.4.0"
rxjs "^6.5.3"
string-width "^4.1.0"
strip-ansi "^6.0.0"
through "^2.3.6"

invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
Expand Down

0 comments on commit af7e930

Please sign in to comment.