mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
commit
bb7700dd0d
@ -62,6 +62,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
|
||||
<li><a href="./cli-reference/invoke.md">Invoke</a></li>
|
||||
<li><a href="./cli-reference/invoke-local.md">Invoke Local</a></li>
|
||||
<li><a href="./cli-reference/logs.md">Logs</a></li>
|
||||
<li><a href="./cli-reference/login.md">Login</a></li>
|
||||
<li><a href="./cli-reference/metrics.md">Metrics</a></li>
|
||||
<li><a href="./cli-reference/info.md">Info</a></li>
|
||||
<li><a href="./cli-reference/rollback.md">Rollback</a></li>
|
||||
|
||||
24
docs/providers/aws/cli-reference/login.md
Normal file
24
docs/providers/aws/cli-reference/login.md
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
title: Serverless Framework Commands - Login
|
||||
menuText: login
|
||||
menuOrder: 11
|
||||
description: Login to the serverless platform
|
||||
layout: Doc
|
||||
-->
|
||||
|
||||
<!-- DOCS-SITE-LINK:START automatically generated -->
|
||||
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/login)
|
||||
<!-- DOCS-SITE-LINK:END -->
|
||||
|
||||
# Login
|
||||
|
||||
The `login` command logs users into the serverless platform.
|
||||
|
||||
It will create a new serverless platform account if one doesn't already exist.
|
||||
|
||||
```bash
|
||||
serverless login
|
||||
|
||||
# Shorthand
|
||||
sls login
|
||||
```
|
||||
@ -54,6 +54,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
|
||||
<li><a href="./cli-reference/deploy.md">Deploy</a></li>
|
||||
<li><a href="./cli-reference/deploy-function.md">Deploy Function</a></li>
|
||||
<li><a href="./cli-reference/invoke.md">Invoke</a></li>
|
||||
<li><a href="./cli-reference/login.md">Login</a></li>
|
||||
<li><a href="./cli-reference/logs.md">Logs</a></li>
|
||||
<li><a href="./cli-reference/remove.md">Remove</a></li>
|
||||
</ul>
|
||||
|
||||
24
docs/providers/azure/cli-reference/login.md
Normal file
24
docs/providers/azure/cli-reference/login.md
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
title: Serverless Framework Commands - Login
|
||||
menuText: login
|
||||
menuOrder: 11
|
||||
description: Login to the serverless platform
|
||||
layout: Doc
|
||||
-->
|
||||
|
||||
<!-- DOCS-SITE-LINK:START automatically generated -->
|
||||
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/login)
|
||||
<!-- DOCS-SITE-LINK:END -->
|
||||
|
||||
# Login
|
||||
|
||||
The `login` command logs users into the serverless platform.
|
||||
|
||||
It will create a new serverless platform account if one doesn't already exist.
|
||||
|
||||
```bash
|
||||
serverless login
|
||||
|
||||
# Shorthand
|
||||
sls login
|
||||
```
|
||||
@ -54,6 +54,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
|
||||
<li><a href="./cli-reference/deploy.md">Deploy</a></li>
|
||||
<li><a href="./cli-reference/info.md">Info</a></li>
|
||||
<li><a href="./cli-reference/invoke.md">Invoke</a></li>
|
||||
<li><a href="./cli-reference/login.md">Login</a></li>
|
||||
<li><a href="./cli-reference/logs.md">Logs</a></li>
|
||||
<li><a href="./cli-reference/remove.md">Remove</a></li>
|
||||
</ul>
|
||||
|
||||
24
docs/providers/google/cli-reference/login.md
Normal file
24
docs/providers/google/cli-reference/login.md
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
title: Serverless Framework Commands - Login
|
||||
menuText: login
|
||||
menuOrder: 11
|
||||
description: Login to the serverless platform
|
||||
layout: Doc
|
||||
-->
|
||||
|
||||
<!-- DOCS-SITE-LINK:START automatically generated -->
|
||||
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/login)
|
||||
<!-- DOCS-SITE-LINK:END -->
|
||||
|
||||
# Login
|
||||
|
||||
The `login` command logs users into the serverless platform.
|
||||
|
||||
It will create a new serverless platform account if one doesn't already exist.
|
||||
|
||||
```bash
|
||||
serverless login
|
||||
|
||||
# Shorthand
|
||||
sls login
|
||||
```
|
||||
@ -59,6 +59,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
|
||||
<li><a href="./cli-reference/deploy-function.md">Deploy Function</a></li>
|
||||
<li><a href="./cli-reference/invoke.md">Invoke</a></li>
|
||||
<li><a href="./cli-reference/invoke-local.md">Invoke Local</a></li>
|
||||
<li><a href="./cli-reference/login.md">Login</a></li>
|
||||
<li><a href="./cli-reference/logs.md">Logs</a></li>
|
||||
<li><a href="./cli-reference/info.md">Info</a></li>
|
||||
<li><a href="./cli-reference/remove.md">Remove</a></li>
|
||||
|
||||
24
docs/providers/openwhisk/cli-reference/login.md
Normal file
24
docs/providers/openwhisk/cli-reference/login.md
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
title: Serverless Framework Commands - Login
|
||||
menuText: login
|
||||
menuOrder: 11
|
||||
description: Login to the serverless platform
|
||||
layout: Doc
|
||||
-->
|
||||
|
||||
<!-- DOCS-SITE-LINK:START automatically generated -->
|
||||
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/login)
|
||||
<!-- DOCS-SITE-LINK:END -->
|
||||
|
||||
# Login
|
||||
|
||||
The `login` command logs users into the serverless platform.
|
||||
|
||||
It will create a new serverless platform account if one doesn't already exist.
|
||||
|
||||
```bash
|
||||
serverless login
|
||||
|
||||
# Shorthand
|
||||
sls login
|
||||
```
|
||||
@ -2,15 +2,19 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const YAML = require('js-yaml');
|
||||
const ci = require('ci-info');
|
||||
const BbPromise = require('bluebird');
|
||||
const fse = BbPromise.promisifyAll(require('fs-extra'));
|
||||
const _ = require('lodash');
|
||||
const fetch = require('node-fetch');
|
||||
const uuid = require('uuid');
|
||||
const os = require('os');
|
||||
const fileExistsSync = require('../utils/fs/fileExistsSync');
|
||||
const writeFileSync = require('../utils/fs/writeFileSync');
|
||||
const readFileSync = require('../utils/fs/readFileSync');
|
||||
const isDockerContainer = require('../utils/isDockerContainer');
|
||||
const version = require('../../package.json').version;
|
||||
const segment = require('../utils/segment');
|
||||
const configUtils = require('../utils/config');
|
||||
|
||||
class Utils {
|
||||
constructor(serverless) {
|
||||
@ -31,35 +35,15 @@ class Utils {
|
||||
}
|
||||
|
||||
fileExistsSync(filePath) {
|
||||
try {
|
||||
const stats = fse.lstatSync(filePath);
|
||||
return stats.isFile();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return fileExistsSync(filePath);
|
||||
}
|
||||
|
||||
writeFileDir(filePath) {
|
||||
return fse.mkdirsSync(path.dirname(filePath));
|
||||
}
|
||||
|
||||
writeFileSync(filePath, conts) {
|
||||
let contents = conts || '';
|
||||
|
||||
fse.mkdirsSync(path.dirname(filePath));
|
||||
|
||||
if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') {
|
||||
contents = JSON.stringify(contents, null, 2);
|
||||
}
|
||||
|
||||
const yamlFileExists = (filePath.indexOf('.yaml') !== -1);
|
||||
const ymlFileExists = (filePath.indexOf('.yml') !== -1);
|
||||
|
||||
if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') {
|
||||
contents = YAML.dump(contents);
|
||||
}
|
||||
|
||||
return fse.writeFileSync(filePath, contents);
|
||||
writeFileSync(filePath, contents) {
|
||||
return writeFileSync(filePath, contents);
|
||||
}
|
||||
|
||||
writeFile(filePath, contents) {
|
||||
@ -88,21 +72,7 @@ class Utils {
|
||||
}
|
||||
|
||||
readFileSync(filePath) {
|
||||
let contents;
|
||||
|
||||
// Read file
|
||||
contents = fse.readFileSync(filePath);
|
||||
|
||||
// Auto-parse JSON
|
||||
if (filePath.endsWith('.json')) {
|
||||
contents = JSON.parse(contents);
|
||||
} else if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
|
||||
contents = YAML.load(contents.toString(), { filename: filePath });
|
||||
} else {
|
||||
contents = contents.toString().trim();
|
||||
}
|
||||
|
||||
return contents;
|
||||
return readFileSync(filePath);
|
||||
}
|
||||
|
||||
readFile(filePath) {
|
||||
@ -164,23 +134,15 @@ class Utils {
|
||||
// the context in which serverless was executed (e.g. "install", "usage", "uninstall", ...)
|
||||
context = context || 'usage'; //eslint-disable-line
|
||||
|
||||
const log = (data) => {
|
||||
const writeKey = 'XXXX'; // TODO: Replace me before release
|
||||
const auth = `${writeKey}:`;
|
||||
// Service values
|
||||
const service = serverless.service;
|
||||
const resources = service.resources;
|
||||
const provider = service.provider;
|
||||
const functions = service.functions;
|
||||
|
||||
return fetch('https://api.segment.io/v1/track', {
|
||||
headers: {
|
||||
Authorization: `Basic ${new Buffer(auth).toString('base64')}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
timeout: '1000',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => BbPromise.resolve())
|
||||
.catch(() => BbPromise.resolve());
|
||||
};
|
||||
// CLI inputs
|
||||
const options = serverless.processedInput.options;
|
||||
const commands = serverless.processedInput.commands;
|
||||
|
||||
return new BbPromise((resolve) => {
|
||||
const serverlessDirPath = path.join(os.homedir(), '.serverless');
|
||||
@ -190,7 +152,7 @@ class Utils {
|
||||
if (this.fileExistsSync(statsDisabledFilePath)) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// Todo update with new user id
|
||||
let userId = uuid.v1();
|
||||
|
||||
if (!this.fileExistsSync(statsEnabledFilePath)) {
|
||||
@ -200,7 +162,6 @@ class Utils {
|
||||
}
|
||||
|
||||
// filter out the whitelisted options
|
||||
const options = serverless.processedInput.options;
|
||||
const whitelistedOptionKeys = ['help', 'disable', 'enable'];
|
||||
const optionKeys = Object.keys(options);
|
||||
|
||||
@ -214,11 +175,11 @@ class Utils {
|
||||
});
|
||||
|
||||
// function related information retrieval
|
||||
const numberOfFunctions = _.size(serverless.service.functions);
|
||||
const numberOfFunctions = _.size(functions);
|
||||
|
||||
const memorySizeAndTimeoutPerFunction = [];
|
||||
if (numberOfFunctions) {
|
||||
_.forEach(serverless.service.functions, (func) => {
|
||||
_.forEach(functions, (func) => {
|
||||
const memorySize = Number(func.memorySize)
|
||||
|| Number(this.serverless.service.provider.memorySize)
|
||||
|| 1024;
|
||||
@ -239,7 +200,7 @@ class Utils {
|
||||
const numberOfEventsPerType = [];
|
||||
const eventNamesPerFunction = [];
|
||||
if (numberOfFunctions) {
|
||||
_.forEach(serverless.service.functions, (func) => {
|
||||
_.forEach(functions, (func) => {
|
||||
if (func.events) {
|
||||
const funcEventsArray = [];
|
||||
|
||||
@ -264,18 +225,12 @@ class Utils {
|
||||
}
|
||||
|
||||
let hasCustomResourcesDefined = false;
|
||||
|
||||
// check if configuration in resources.Resources is defined
|
||||
if ((serverless.service.resources &&
|
||||
serverless.service.resources.Resources &&
|
||||
Object.keys(serverless.service.resources.Resources).length)) {
|
||||
if ((resources && resources.Resources && Object.keys(resources.Resources).length)) {
|
||||
hasCustomResourcesDefined = true;
|
||||
}
|
||||
|
||||
// check if configuration in resources.Outputs is defined
|
||||
if ((serverless.service.resources &&
|
||||
serverless.service.resources.Outputs &&
|
||||
Object.keys(serverless.service.resources.Outputs).length)) {
|
||||
if ((resources && resources.Outputs && Object.keys(resources.Outputs).length)) {
|
||||
hasCustomResourcesDefined = true;
|
||||
}
|
||||
|
||||
@ -283,43 +238,32 @@ class Utils {
|
||||
const defaultVariableSyntax = '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}';
|
||||
|
||||
// check if the variableSyntax in the provider section is defined
|
||||
if (serverless.service.provider &&
|
||||
serverless.service.provider.variableSyntax &&
|
||||
serverless.service.provider.variableSyntax !== defaultVariableSyntax) {
|
||||
if (provider && provider.variableSyntax
|
||||
&& provider.variableSyntax !== defaultVariableSyntax) {
|
||||
hasCustomVariableSyntaxDefined = true;
|
||||
}
|
||||
|
||||
// wrap in try catch to make sure that missing permissions won't break anything
|
||||
let isDockerContainer = false;
|
||||
try {
|
||||
const cgroupFilePath = path.join('/', 'proc', '1', 'cgroup');
|
||||
const cgroupFileContent = fs.readFileSync(cgroupFilePath).toString();
|
||||
isDockerContainer = !!cgroupFileContent.match(/docker/);
|
||||
} catch (exception) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
const data = {
|
||||
userId,
|
||||
event: 'framework_stat',
|
||||
properties: {
|
||||
version: 2,
|
||||
command: {
|
||||
name: serverless.processedInput.commands.join(' '),
|
||||
name: commands.join(' '),
|
||||
filteredOptions,
|
||||
isRunInService: (!!serverless.config.servicePath),
|
||||
},
|
||||
service: {
|
||||
numberOfCustomPlugins: _.size(serverless.service.plugins),
|
||||
numberOfCustomPlugins: _.size(service.plugins),
|
||||
hasCustomResourcesDefined,
|
||||
hasVariablesInCustomSectionDefined: (!!serverless.service.custom),
|
||||
hasVariablesInCustomSectionDefined: (!!service.custom),
|
||||
hasCustomVariableSyntaxDefined,
|
||||
},
|
||||
provider: {
|
||||
name: serverless.service.provider.name,
|
||||
runtime: serverless.service.provider.runtime,
|
||||
stage: serverless.service.provider.stage,
|
||||
region: serverless.service.provider.region,
|
||||
name: provider.name,
|
||||
runtime: provider.runtime,
|
||||
stage: provider.stage,
|
||||
region: provider.region,
|
||||
},
|
||||
functions: {
|
||||
numberOfFunctions,
|
||||
@ -339,17 +283,24 @@ class Utils {
|
||||
userAgent: (process.env.SERVERLESS_DASHBOARD) ? 'dashboard' : 'cli',
|
||||
serverlessVersion: serverless.version,
|
||||
nodeJsVersion: process.version,
|
||||
isDockerContainer,
|
||||
isDockerContainer: isDockerContainer(),
|
||||
isCISystem: ci.isCI,
|
||||
ciSystem: ci.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const config = configUtils.getConfig();
|
||||
if (config.userId && data.properties && data.properties.general) {
|
||||
data.properties.general.platformId = config.userId;
|
||||
}
|
||||
|
||||
return resolve(data);
|
||||
}).then((data) => {
|
||||
// only log the data if it's there
|
||||
if (data) log(data);
|
||||
if (data) {
|
||||
segment.track(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,21 +6,22 @@ const expect = require('chai').expect;
|
||||
const fse = require('fs-extra');
|
||||
const fs = require('fs');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire');
|
||||
const Serverless = require('../../lib/Serverless');
|
||||
const testUtils = require('../../tests/utils');
|
||||
const serverlessVersion = require('../../package.json').version;
|
||||
const segment = require('../utils/segment');
|
||||
const proxyquire = require('proxyquire');
|
||||
|
||||
describe('Utils', () => {
|
||||
let utils;
|
||||
let serverless;
|
||||
let fetchStub;
|
||||
let isDockerContainerStub;
|
||||
let Utils;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchStub = sinon.stub().resolves();
|
||||
isDockerContainerStub = sinon.stub().returns(true);
|
||||
Utils = proxyquire('../../lib/classes/Utils.js', {
|
||||
'node-fetch': fetchStub,
|
||||
'../utils/isDockerContainer': isDockerContainerStub,
|
||||
});
|
||||
serverless = new Serverless();
|
||||
utils = new Utils(serverless);
|
||||
@ -294,6 +295,7 @@ describe('Utils', () => {
|
||||
describe('#logStat()', () => {
|
||||
let serverlessDirPath;
|
||||
let homeDir;
|
||||
let trackStub;
|
||||
|
||||
beforeEach(() => {
|
||||
serverless.init();
|
||||
@ -313,6 +315,12 @@ describe('Utils', () => {
|
||||
// set the properties for the processed inputs
|
||||
serverless.processedInput.commands = [];
|
||||
serverless.processedInput.options = {};
|
||||
|
||||
trackStub = sinon.stub(segment, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
segment.track.restore();
|
||||
});
|
||||
|
||||
it('should resolve if a file called stats-disabled is present', () => {
|
||||
@ -323,7 +331,7 @@ describe('Utils', () => {
|
||||
);
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
expect(fetchStub.calledOnce).to.equal(false);
|
||||
expect(trackStub.calledOnce).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -358,36 +366,23 @@ describe('Utils', () => {
|
||||
serverless.processedInput.options = options;
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
expect(fetchStub.calledOnce).to.equal(true);
|
||||
expect(fetchStub.args[0][0]).to.equal('https://api.segment.io/v1/track');
|
||||
expect(fetchStub.args[0][1].method).to.equal('POST');
|
||||
expect(fetchStub.args[0][1].timeout).to.equal('1000');
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
const parsedBody = JSON.parse(fetchStub.args[0][1].body);
|
||||
const data = trackStub.args[0][0];
|
||||
|
||||
expect(parsedBody.properties.command.filteredOptions)
|
||||
.to.deep.equal({ help: true });
|
||||
expect(data.properties.command.filteredOptions).to.deep.equal({ help: true });
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to detect Docker containers', () => {
|
||||
const cgroupFileContent = '6:devices:/docker/3601745b3bd54d9780436faa5f0e4f72';
|
||||
const cgroupFilePath = path.join('/', 'proc', '1', 'cgroup');
|
||||
const cgroupFileContentStub = sinon.stub(fs, 'readFileSync')
|
||||
.withArgs(cgroupFilePath)
|
||||
.returns(cgroupFileContent);
|
||||
it('should be able to detect Docker containers', () => utils
|
||||
.logStat(serverless).then(() => {
|
||||
expect(isDockerContainerStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
utils.logStat(serverless).then(() => {
|
||||
expect(cgroupFileContentStub.calledOnce).to.equal(true);
|
||||
|
||||
const parsedBody = JSON.parse(fetchStub.args[0][1].body);
|
||||
|
||||
expect(parsedBody.properties.general.isDockerContainer)
|
||||
.to.equal(true);
|
||||
|
||||
fs.readFileSync.restore();
|
||||
});
|
||||
});
|
||||
const data = trackStub.args[0][0];
|
||||
expect(data.properties.general.isDockerContainer).to.equal(true);
|
||||
})
|
||||
);
|
||||
|
||||
it('should send the gathered information', () => {
|
||||
serverless.service = {
|
||||
@ -436,55 +431,52 @@ describe('Utils', () => {
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
expect(fetchStub.calledOnce).to.equal(true);
|
||||
expect(fetchStub.args[0][0]).to.equal('https://api.segment.io/v1/track');
|
||||
expect(fetchStub.args[0][1].method).to.equal('POST');
|
||||
expect(fetchStub.args[0][1].timeout).to.equal('1000');
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
const parsedBody = JSON.parse(fetchStub.args[0][1].body);
|
||||
const data = trackStub.args[0][0];
|
||||
|
||||
expect(parsedBody.userId.length).to.be.at.least(1);
|
||||
expect(data.userId.length).to.be.at.least(1);
|
||||
// command property
|
||||
expect(parsedBody.properties.command.name)
|
||||
expect(data.properties.command.name)
|
||||
.to.equal('');
|
||||
expect(parsedBody.properties.command
|
||||
expect(data.properties.command
|
||||
.isRunInService).to.equal(false); // false because CWD is not a service
|
||||
expect(parsedBody.properties.command.filteredOptions)
|
||||
expect(data.properties.command.filteredOptions)
|
||||
.to.deep.equal({});
|
||||
// service property
|
||||
expect(parsedBody.properties.service.numberOfCustomPlugins).to.equal(0);
|
||||
expect(parsedBody.properties.service.hasCustomResourcesDefined).to.equal(true);
|
||||
expect(parsedBody.properties.service.hasVariablesInCustomSectionDefined).to.equal(false);
|
||||
expect(parsedBody.properties.service.hasCustomVariableSyntaxDefined).to.equal(true);
|
||||
expect(data.properties.service.numberOfCustomPlugins).to.equal(0);
|
||||
expect(data.properties.service.hasCustomResourcesDefined).to.equal(true);
|
||||
expect(data.properties.service.hasVariablesInCustomSectionDefined).to.equal(false);
|
||||
expect(data.properties.service.hasCustomVariableSyntaxDefined).to.equal(true);
|
||||
// functions property
|
||||
expect(parsedBody.properties.functions.numberOfFunctions).to.equal(2);
|
||||
expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[0]
|
||||
expect(data.properties.functions.numberOfFunctions).to.equal(2);
|
||||
expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0]
|
||||
.memorySize).to.equal(1024);
|
||||
expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[0]
|
||||
expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0]
|
||||
.timeout).to.equal(6);
|
||||
expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[1]
|
||||
expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1]
|
||||
.memorySize).to.equal(16);
|
||||
expect(parsedBody.properties.functions.memorySizeAndTimeoutPerFunction[1]
|
||||
expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1]
|
||||
.timeout).to.equal(200);
|
||||
// events property
|
||||
expect(parsedBody.properties.events.numberOfEvents).to.equal(3);
|
||||
expect(parsedBody.properties.events.numberOfEventsPerType[0].name).to.equal('http');
|
||||
expect(parsedBody.properties.events.numberOfEventsPerType[0].count).to.equal(2);
|
||||
expect(parsedBody.properties.events.numberOfEventsPerType[1].name).to.equal('s3');
|
||||
expect(parsedBody.properties.events.numberOfEventsPerType[1].count).to.equal(1);
|
||||
expect(parsedBody.properties.events.numberOfEventsPerType[2].name).to.equal('sns');
|
||||
expect(parsedBody.properties.events.numberOfEventsPerType[2].count).to.equal(1);
|
||||
expect(parsedBody.properties.events.eventNamesPerFunction[0][0]).to.equal('http');
|
||||
expect(parsedBody.properties.events.eventNamesPerFunction[0][1]).to.equal('s3');
|
||||
expect(parsedBody.properties.events.eventNamesPerFunction[1][0]).to.equal('http');
|
||||
expect(parsedBody.properties.events.eventNamesPerFunction[1][1]).to.equal('sns');
|
||||
expect(data.properties.events.numberOfEvents).to.equal(3);
|
||||
expect(data.properties.events.numberOfEventsPerType[0].name).to.equal('http');
|
||||
expect(data.properties.events.numberOfEventsPerType[0].count).to.equal(2);
|
||||
expect(data.properties.events.numberOfEventsPerType[1].name).to.equal('s3');
|
||||
expect(data.properties.events.numberOfEventsPerType[1].count).to.equal(1);
|
||||
expect(data.properties.events.numberOfEventsPerType[2].name).to.equal('sns');
|
||||
expect(data.properties.events.numberOfEventsPerType[2].count).to.equal(1);
|
||||
expect(data.properties.events.eventNamesPerFunction[0][0]).to.equal('http');
|
||||
expect(data.properties.events.eventNamesPerFunction[0][1]).to.equal('s3');
|
||||
expect(data.properties.events.eventNamesPerFunction[1][0]).to.equal('http');
|
||||
expect(data.properties.events.eventNamesPerFunction[1][1]).to.equal('sns');
|
||||
// general property
|
||||
expect(parsedBody.properties.general.userId.length).to.be.at.least(1);
|
||||
expect(parsedBody.properties.general.timestamp).to.match(/[0-9]+/);
|
||||
expect(parsedBody.properties.general.timezone.length).to.be.at.least(1);
|
||||
expect(parsedBody.properties.general.operatingSystem.length).to.be.at.least(1);
|
||||
expect(parsedBody.properties.general.serverlessVersion).to.equal(serverlessVersion);
|
||||
expect(parsedBody.properties.general.nodeJsVersion.length).to.be.at.least(1);
|
||||
expect(data.properties.general.userId.length).to.be.at.least(1);
|
||||
expect(data.properties.general.timestamp).to.match(/[0-9]+/);
|
||||
expect(data.properties.general.timezone.length).to.be.at.least(1);
|
||||
expect(data.properties.general.operatingSystem.length).to.be.at.least(1);
|
||||
expect(data.properties.general.serverlessVersion).to.equal(serverlessVersion);
|
||||
expect(data.properties.general.nodeJsVersion.length).to.be.at.least(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
"./invoke/invoke.js",
|
||||
"./info/info.js",
|
||||
"./logs/logs.js",
|
||||
"./login/login.js",
|
||||
"./logout/logout.js",
|
||||
"./metrics/metrics.js",
|
||||
"./remove/remove.js",
|
||||
"./rollback/index.js",
|
||||
|
||||
@ -4,6 +4,7 @@ const BbPromise = require('bluebird');
|
||||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
const _ = require('lodash');
|
||||
const userStats = require('../../utils/userStats');
|
||||
|
||||
// class wide constants
|
||||
const validTemplates = [
|
||||
@ -117,7 +118,9 @@ class Create {
|
||||
});
|
||||
}
|
||||
|
||||
if (notPlugin) this.serverless.config.update({ servicePath: process.cwd() });
|
||||
if (notPlugin) {
|
||||
this.serverless.config.update({ servicePath: process.cwd() });
|
||||
}
|
||||
|
||||
// copy template files recursively to cwd
|
||||
// while keeping template file tree
|
||||
@ -152,6 +155,11 @@ class Create {
|
||||
fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent);
|
||||
}
|
||||
|
||||
userStats.track('service_created', {
|
||||
template: this.options.template,
|
||||
serviceName,
|
||||
});
|
||||
|
||||
this.serverless.cli.asciiGreeting();
|
||||
this.serverless.cli
|
||||
.log(`Successfully generated boilerplate for template: "${this.options.template}"`);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const userStats = require('../../utils/userStats');
|
||||
|
||||
class Deploy {
|
||||
constructor(serverless, options) {
|
||||
@ -88,8 +89,13 @@ class Deploy {
|
||||
}
|
||||
return BbPromise.resolve();
|
||||
}),
|
||||
'after:deploy:deploy': () => BbPromise.bind(this)
|
||||
.then(this.track),
|
||||
};
|
||||
}
|
||||
track() {
|
||||
userStats.track('service_deployed');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Deploy;
|
||||
|
||||
159
lib/plugins/login/login.js
Normal file
159
lib/plugins/login/login.js
Normal file
@ -0,0 +1,159 @@
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const crypto = require('crypto');
|
||||
const readline = require('readline');
|
||||
const fetch = require('node-fetch');
|
||||
const jwtDecode = require('jwt-decode');
|
||||
const chalk = require('chalk');
|
||||
const openBrowser = require('../../utils/openBrowser');
|
||||
const getFrameworkId = require('../../utils/getFrameworkId');
|
||||
const clearConsole = require('../../utils/clearConsole');
|
||||
const userStats = require('../../utils/userStats');
|
||||
const setConfig = require('../../utils/config').set;
|
||||
|
||||
const config = {
|
||||
AUTH0_CLIENT_ID: 'iiEYK0KB30gj94mjB8HP9lhhTgae0Rg3',
|
||||
AUTH0_URL: 'https://serverlessinc.auth0.com',
|
||||
AUTH0_CALLBACK_URL: 'https://serverless.com/auth',
|
||||
};
|
||||
|
||||
function base64url(url) {
|
||||
return url.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
}
|
||||
|
||||
class Login {
|
||||
constructor(serverless, options) {
|
||||
this.serverless = serverless;
|
||||
this.options = options;
|
||||
|
||||
this.commands = {
|
||||
login: {
|
||||
usage: 'Login or sign up for the Serverless Platform',
|
||||
lifecycleEvents: [
|
||||
'login',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
this.hooks = {
|
||||
'login:login': () => BbPromise.bind(this).then(this.login),
|
||||
};
|
||||
}
|
||||
login() {
|
||||
clearConsole();
|
||||
this.serverless.cli.log('The Serverless login will open in your default browser...');
|
||||
// Generate the verifier, and the corresponding challenge
|
||||
const verifier = base64url(crypto.randomBytes(32));
|
||||
const verifierChallenge = base64url(crypto.createHash('sha256').update(verifier).digest());
|
||||
const frameworkId = getFrameworkId();
|
||||
// eslint-disable-next-line prefer-template
|
||||
const version = this.serverless.version;
|
||||
const state = `id%3D${frameworkId}%26version%3D${version}%26platform%3D${process.platform}`;
|
||||
// refresh token docs https://auth0.com/docs/tokens/preview/refresh-token#get-a-refresh-token
|
||||
const authorizeUrl =
|
||||
`${config.AUTH0_URL}/authorize?response_type=code&scope=openid%20profile%20offline_access` +
|
||||
`&client_id=${config.AUTH0_CLIENT_ID}&redirect_uri=${config.AUTH0_CALLBACK_URL}` +
|
||||
`&code_challenge=${verifierChallenge}&code_challenge_method=S256&state=${state}`;
|
||||
|
||||
setTimeout(() => {
|
||||
this.serverless.cli.log('Opening browser...');
|
||||
}, 300);
|
||||
|
||||
setTimeout(() => {
|
||||
// pop open default browser
|
||||
openBrowser(authorizeUrl);
|
||||
|
||||
// wait for token
|
||||
const readlineInterface = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
// o get an access token and a refresh token from that code you need to call the oauth/token endpoint. Here's more info - https://auth0.com/docs/protocols#3-getting-the-access-token
|
||||
readlineInterface.question('Please enter the verification code here: ', (code) => {
|
||||
const authorizationData = {
|
||||
code,
|
||||
code_verifier: verifier,
|
||||
client_id: config.AUTH0_CLIENT_ID,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: config.AUTH0_CALLBACK_URL,
|
||||
};
|
||||
// verify login
|
||||
fetch(`${config.AUTH0_URL}/oauth/token`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(authorizationData),
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((platformResponse) => {
|
||||
const decoded = jwtDecode(platformResponse.id_token);
|
||||
this.serverless.cli.log('You are now logged in');
|
||||
|
||||
// because platform only support github
|
||||
const id = decoded.original_user_id || decoded.sub;
|
||||
|
||||
/* For future use
|
||||
segment.identify({
|
||||
userId: id,
|
||||
traits: {
|
||||
email: profile.email,
|
||||
},
|
||||
}) */
|
||||
|
||||
const userConfig = {
|
||||
userId: id,
|
||||
frameworkId,
|
||||
users: {},
|
||||
};
|
||||
// set user auth in global .serverlessrc file
|
||||
userConfig.users[id] = {
|
||||
userId: id,
|
||||
name: decoded.name,
|
||||
email: decoded.email,
|
||||
auth: platformResponse,
|
||||
};
|
||||
|
||||
// update .serverlessrc
|
||||
setConfig(userConfig);
|
||||
|
||||
const userID = new Buffer(id).toString('base64');
|
||||
const email = new Buffer(decoded.email).toString('base64');
|
||||
const name = new Buffer(decoded.name).toString('base64');
|
||||
const loginCount = decoded.login_count;
|
||||
const createdAt = decoded.created_at;
|
||||
const successUrl = `https://serverless.com/success?u=${userID}&e=${email}&n=${name}&c=${loginCount}&v=${version}&d=${createdAt}&id=${frameworkId}`; // eslint-disable-line
|
||||
|
||||
openBrowser(successUrl);
|
||||
// identify user for better onboarding
|
||||
userStats.identify({
|
||||
id,
|
||||
frameworkId,
|
||||
email: decoded.email,
|
||||
// unix timestamp
|
||||
created_at: Math.round(+new Date(createdAt) / 1000),
|
||||
force: true,
|
||||
}).then(() => {
|
||||
userStats.track('user_loggedIn', {
|
||||
id,
|
||||
email: decoded.email,
|
||||
force: true,
|
||||
}).then(() => {
|
||||
// then exit process
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.serverless.cli.consoleLog(
|
||||
chalk.red('Incorrect token value supplied. Please run "serverless login" again'));
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Login;
|
||||
19
lib/plugins/login/login.test.js
Normal file
19
lib/plugins/login/login.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const Login = require('./login');
|
||||
const Serverless = require('../../Serverless');
|
||||
|
||||
describe('Login', () => {
|
||||
let login;
|
||||
let serverless;
|
||||
|
||||
beforeEach(() => {
|
||||
serverless = new Serverless();
|
||||
login = new Login(serverless);
|
||||
});
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should have commands', () => expect(login.commands).to.be.not.empty);
|
||||
});
|
||||
});
|
||||
53
lib/plugins/logout/logout.js
Normal file
53
lib/plugins/logout/logout.js
Normal file
@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const userStats = require('../../utils/userStats');
|
||||
const configUtils = require('../../utils/config');
|
||||
|
||||
class Login {
|
||||
constructor(serverless, options) {
|
||||
this.serverless = serverless;
|
||||
this.options = options;
|
||||
|
||||
this.commands = {
|
||||
logout: {
|
||||
usage: 'Logout from the Serverless Platform',
|
||||
lifecycleEvents: ['logout'],
|
||||
},
|
||||
};
|
||||
|
||||
this.hooks = {
|
||||
'logout:logout': this.logout.bind(this),
|
||||
};
|
||||
}
|
||||
logout() {
|
||||
const config = configUtils.getConfig();
|
||||
const currentId = config.userId;
|
||||
const globalConfig = configUtils.getGlobalConfig();
|
||||
|
||||
try {
|
||||
// TODO Once we start using refresh tokens we also need to implement an API endpoint
|
||||
// that invalidate a refresh token in Auth0 (using the Auth0 Management API).
|
||||
|
||||
if (globalConfig && globalConfig.users && globalConfig.users[currentId]) {
|
||||
if (globalConfig.users[currentId].auth) {
|
||||
// remove auth tokens from user
|
||||
configUtils.set(`users.${currentId}.auth`, null);
|
||||
// log stat
|
||||
userStats.track('user_loggedOut').then(() => {
|
||||
this.serverless.cli.consoleLog('Successfully logged out.');
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
this.serverless.cli.consoleLog('You are already logged out');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.serverless.cli.consoleLog(
|
||||
'Failed to logout. Please report bug in https://github.com/serverless/serverless/issues');
|
||||
// Note no need to wait for any connections e.g. segment to close
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Login;
|
||||
19
lib/plugins/logout/logout.test.js
Normal file
19
lib/plugins/logout/logout.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const Logout = require('./logout');
|
||||
const Serverless = require('../../Serverless');
|
||||
|
||||
describe('Logout', () => {
|
||||
let logout;
|
||||
let serverless;
|
||||
|
||||
beforeEach(() => {
|
||||
serverless = new Serverless();
|
||||
logout = new Logout(serverless);
|
||||
});
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should have commands', () => expect(logout.commands).to.be.not.empty);
|
||||
});
|
||||
});
|
||||
@ -3,6 +3,8 @@
|
||||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
const os = require('os');
|
||||
const userStats = require('../../utils/userStats');
|
||||
const setConfig = require('../../utils/config').set;
|
||||
|
||||
class SlStats {
|
||||
constructor(serverless, options) {
|
||||
@ -53,10 +55,20 @@ class SlStats {
|
||||
const isStatsEnabled = this.options.enable && !this.options.disable;
|
||||
const isStatsDisabled = this.options.disable && !this.options.enable;
|
||||
if (isStatsEnabled) {
|
||||
// stats file to be depricated in future release
|
||||
this.createStatsFile(statsDisabledFilePath, statsEnabledFilePath);
|
||||
// set new `trackingDisabled` key in .serverlessrc config
|
||||
userStats.track('user_enabledTracking', { force: true }).then(() => {
|
||||
setConfig('trackingDisabled', false);
|
||||
});
|
||||
this.serverless.cli.log('Stats successfully enabled');
|
||||
} else if (isStatsDisabled) {
|
||||
// stats file to be depricated in future release
|
||||
this.createStatsFile(statsEnabledFilePath, statsDisabledFilePath);
|
||||
// set new `trackingDisabled` key in .serverlessrc config
|
||||
userStats.track('user_disabledTracking', { force: true }).then(() => {
|
||||
setConfig('trackingDisabled', true);
|
||||
});
|
||||
this.serverless.cli.log('Stats successfully disabled');
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
8
lib/utils/clearConsole.js
Normal file
8
lib/utils/clearConsole.js
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/* Clear terminal output */
|
||||
const clearConsole = function () {
|
||||
process.stdout.write(process.platform !== 'win32' ? '\x1B[2J\x1B[3J\x1B[H' : '\x1Bc');
|
||||
};
|
||||
|
||||
module.exports = clearConsole;
|
||||
54
lib/utils/config/config.test.js
Normal file
54
lib/utils/config/config.test.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const config = require('./index');
|
||||
|
||||
describe('Config', () => {
|
||||
it('should have CONFIG_FILE_PATH', () => {
|
||||
const configPath = config.CONFIG_FILE_PATH;
|
||||
expect(configPath).to.exist; // eslint-disable-line
|
||||
});
|
||||
|
||||
describe('When using config.getConfig', () => {
|
||||
it('should have userId key', () => {
|
||||
const conf = config.getConfig();
|
||||
expect(conf).to.have.deep.property('userId');
|
||||
});
|
||||
|
||||
it('should have frameworkId key', () => {
|
||||
const conf = config.getConfig();
|
||||
expect(conf).to.have.deep.property('frameworkId');
|
||||
});
|
||||
|
||||
it('should have trackingDisabled key', () => {
|
||||
const conf = config.getConfig();
|
||||
expect(conf).to.have.deep.property('trackingDisabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When using config.get', () => {
|
||||
it('should have frameworkId', () => {
|
||||
const frameworkId = config.get('frameworkId');
|
||||
expect(frameworkId).to.exist; // eslint-disable-line
|
||||
});
|
||||
it('should have not have a value that doesnt exist', () => {
|
||||
const doesntExist = config.get('frameworkIdzzzz');
|
||||
expect(doesntExist).to.not.exist; // eslint-disable-line
|
||||
});
|
||||
});
|
||||
|
||||
describe('When using config.set', () => {
|
||||
it('should add new properties with "set"', () => {
|
||||
config.set('foo', true);
|
||||
const foo = config.get('foo');
|
||||
expect(foo).to.equal(true);
|
||||
});
|
||||
|
||||
it('should delete properties with "delete"', () => {
|
||||
// cleanup foo
|
||||
config.delete('foo');
|
||||
const zaz = config.get('foo');
|
||||
expect(zaz).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
96
lib/utils/config/index.js
Normal file
96
lib/utils/config/index.js
Normal file
@ -0,0 +1,96 @@
|
||||
'use strict';
|
||||
|
||||
/* Config util */
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const _ = require('lodash');
|
||||
const writeFileAtomic = require('write-file-atomic');
|
||||
const getFrameworkId = require('../getFrameworkId');
|
||||
const fileExistsSync = require('../fs/fileExistsSync');
|
||||
const readFileSync = require('../fs/readFileSync');
|
||||
const isTrackingDisabled = require('../isTrackingDisabled');
|
||||
|
||||
const globalConfigPath = path.join(os.homedir(), '.serverlessrc');
|
||||
|
||||
function createConfig() {
|
||||
// set default config options
|
||||
const config = {
|
||||
userId: null, // currentUserId
|
||||
frameworkId: getFrameworkId(),
|
||||
trackingDisabled: isTrackingDisabled(),
|
||||
meta: {
|
||||
created_at: Math.round(+new Date() / 1000),
|
||||
updated_at: null,
|
||||
},
|
||||
};
|
||||
writeFileAtomic.sync(globalConfigPath, JSON.stringify(config, null, 2));
|
||||
return JSON.parse(readFileSync(globalConfigPath));
|
||||
}
|
||||
|
||||
function getGlobalConfig() {
|
||||
if (!fileExistsSync(globalConfigPath)) {
|
||||
return createConfig();
|
||||
}
|
||||
const config = readFileSync(globalConfigPath);
|
||||
// if global config empty add defaults
|
||||
if (!config) {
|
||||
return createConfig();
|
||||
}
|
||||
return JSON.parse(config);
|
||||
}
|
||||
|
||||
// get .serverlessrc config + local config if exists
|
||||
function getConfig() {
|
||||
if (!fileExistsSync(globalConfigPath)) {
|
||||
return createConfig();
|
||||
}
|
||||
return require('rc')('serverless'); // eslint-disable-line
|
||||
}
|
||||
|
||||
// set global .serverlessrc config value.
|
||||
function setConfigValue(key, value) {
|
||||
let config = getGlobalConfig();
|
||||
if (key && typeof key === 'string' && typeof value !== 'undefined') {
|
||||
config = _.set(config, key, value);
|
||||
} else if (_.isObject(key)) {
|
||||
config = _.merge(config, key);
|
||||
} else if (typeof value !== 'undefined') {
|
||||
config = _.merge(config, value);
|
||||
}
|
||||
// update config meta
|
||||
config.meta = config.meta || {};
|
||||
config.meta.updated_at = Math.round(+new Date() / 1000);
|
||||
// write file
|
||||
writeFileAtomic.sync(globalConfigPath, JSON.stringify(config, null, 2));
|
||||
return config;
|
||||
}
|
||||
|
||||
function deleteConfigValue(key) {
|
||||
let config = getGlobalConfig();
|
||||
if (key && typeof key === 'string') {
|
||||
config = _.omit(config, [key]);
|
||||
} else if (key && _.isArray(key)) {
|
||||
config = _.omit(config, key);
|
||||
}
|
||||
// write file
|
||||
writeFileAtomic.sync(globalConfigPath, JSON.stringify(config, null, 2));
|
||||
return config;
|
||||
}
|
||||
|
||||
// set .serverlessrc config value
|
||||
function getConfigValue(objectPath) {
|
||||
const config = getConfig();
|
||||
if (objectPath && typeof objectPath === 'string') {
|
||||
return _.get(config, objectPath);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
set: setConfigValue,
|
||||
get: getConfigValue,
|
||||
delete: deleteConfigValue,
|
||||
getConfig,
|
||||
getGlobalConfig,
|
||||
CONFIG_FILE_PATH: globalConfigPath,
|
||||
};
|
||||
14
lib/utils/fs/fileExistsSync.js
Normal file
14
lib/utils/fs/fileExistsSync.js
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const fse = require('./fse');
|
||||
|
||||
function fileExistsSync(filePath) {
|
||||
try {
|
||||
const stats = fse.lstatSync(filePath);
|
||||
return stats.isFile();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fileExistsSync;
|
||||
19
lib/utils/fs/fileExistsSync.test.js
Normal file
19
lib/utils/fs/fileExistsSync.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const expect = require('chai').expect;
|
||||
const fileExistsSync = require('./fileExistsSync');
|
||||
|
||||
describe('#fileExistsSync()', () => {
|
||||
describe('When reading a file', () => {
|
||||
it('should detect if a file exists', () => {
|
||||
const file = fileExistsSync(__filename);
|
||||
expect(file).to.equal(true);
|
||||
});
|
||||
|
||||
it('should detect if a file doesn\'t exist', () => {
|
||||
const noFile = fileExistsSync(path.join(__dirname, 'XYZ.json'));
|
||||
expect(noFile).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
9
lib/utils/fs/fse.js
Normal file
9
lib/utils/fs/fse.js
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Promisified FSE
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const fse = BbPromise.promisifyAll(require('fs-extra'));
|
||||
|
||||
module.exports = fse;
|
||||
24
lib/utils/fs/readFileSync.js
Normal file
24
lib/utils/fs/readFileSync.js
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const YAML = require('js-yaml');
|
||||
const fse = require('./fse');
|
||||
|
||||
function readFileSync(filePath) {
|
||||
let contents;
|
||||
|
||||
// Read file
|
||||
contents = fse.readFileSync(filePath);
|
||||
|
||||
// Auto-parse JSON
|
||||
if (filePath.endsWith('.json')) {
|
||||
contents = JSON.parse(contents);
|
||||
} else if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
|
||||
contents = YAML.load(contents.toString(), { filename: filePath });
|
||||
} else {
|
||||
contents = contents.toString().trim();
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
module.exports = readFileSync;
|
||||
45
lib/utils/fs/readFileSync.test.js
Normal file
45
lib/utils/fs/readFileSync.test.js
Normal file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const testUtils = require('../../../tests/utils');
|
||||
const expect = require('chai').expect;
|
||||
const writeFileSync = require('./writeFileSync');
|
||||
const readFileSync = require('./readFileSync');
|
||||
|
||||
describe('#readFileSync()', () => {
|
||||
it('should read a file synchronously', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.json');
|
||||
|
||||
writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
const obj = readFileSync(tmpFilePath);
|
||||
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
|
||||
it('should read a filename extension .yml', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.yml');
|
||||
|
||||
writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
const obj = readFileSync(tmpFilePath);
|
||||
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
|
||||
it('should read a filename extension .yaml', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.yaml');
|
||||
|
||||
writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
const obj = readFileSync(tmpFilePath);
|
||||
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
|
||||
it('should throw YAMLException with filename if yml file is invalid format', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('invalid.yml');
|
||||
|
||||
writeFileSync(tmpFilePath, ': a');
|
||||
|
||||
expect(() => {
|
||||
readFileSync(tmpFilePath);
|
||||
}).to.throw(new RegExp('YAMLException:.*invalid.yml'));
|
||||
});
|
||||
});
|
||||
26
lib/utils/fs/writeFileSync.js
Normal file
26
lib/utils/fs/writeFileSync.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const YAML = require('js-yaml');
|
||||
const fse = require('./fse');
|
||||
|
||||
function writeFileSync(filePath, conts) {
|
||||
let contents = conts || '';
|
||||
|
||||
fse.mkdirsSync(path.dirname(filePath));
|
||||
|
||||
if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') {
|
||||
contents = JSON.stringify(contents, null, 2);
|
||||
}
|
||||
|
||||
const yamlFileExists = (filePath.indexOf('.yaml') !== -1);
|
||||
const ymlFileExists = (filePath.indexOf('.yml') !== -1);
|
||||
|
||||
if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') {
|
||||
contents = YAML.dump(contents);
|
||||
}
|
||||
|
||||
return fse.writeFileSync(filePath, contents);
|
||||
}
|
||||
|
||||
module.exports = writeFileSync;
|
||||
49
lib/utils/fs/writeFileSync.test.js
Normal file
49
lib/utils/fs/writeFileSync.test.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const testUtils = require('../../../tests/utils');
|
||||
const Serverless = require('../../../lib/Serverless');
|
||||
const expect = require('chai').expect;
|
||||
const writeFileSync = require('./writeFileSync');
|
||||
const readFileSync = require('./readFileSync');
|
||||
|
||||
describe('#writeFileSync()', () => {
|
||||
let serverless;
|
||||
|
||||
beforeEach(() => {
|
||||
serverless = new Serverless();
|
||||
serverless.init();
|
||||
});
|
||||
|
||||
it('should write a .json file synchronously', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.json');
|
||||
|
||||
writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
const obj = readFileSync(tmpFilePath);
|
||||
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
|
||||
it('should write a .yml file synchronously', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.yml');
|
||||
|
||||
writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
|
||||
return serverless.yamlParser.parse(tmpFilePath).then((obj) => {
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
});
|
||||
|
||||
it('should write a .yaml file synchronously', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.yaml');
|
||||
|
||||
writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
|
||||
return serverless.yamlParser.parse(tmpFilePath).then((obj) => {
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if invalid path is provided', () => {
|
||||
expect(() => { writeFileSync(null); }).to.throw(Error);
|
||||
});
|
||||
});
|
||||
29
lib/utils/getFrameworkId.js
Normal file
29
lib/utils/getFrameworkId.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const getServerlessDir = require('./getServerlessDir');
|
||||
const readFileSync = require('./fs/readFileSync');
|
||||
const fileExistsSync = require('./fs/fileExistsSync');
|
||||
|
||||
function getFrameworkId() {
|
||||
const serverlessHomePath = getServerlessDir();
|
||||
const statsEnabledFilePath = path.join(serverlessHomePath, 'stats-enabled');
|
||||
const statsDisabledFilePath = path.join(serverlessHomePath, 'stats-disabled');
|
||||
const serverlessRCFilePath = path.join(serverlessHomePath, '.serverlessrc');
|
||||
|
||||
if (fileExistsSync(statsEnabledFilePath)) {
|
||||
return readFileSync(statsEnabledFilePath).toString();
|
||||
}
|
||||
if (fileExistsSync(statsDisabledFilePath)) {
|
||||
return readFileSync(statsDisabledFilePath).toString();
|
||||
}
|
||||
if (fileExistsSync(serverlessRCFilePath)) {
|
||||
const config = JSON.parse(readFileSync(serverlessRCFilePath));
|
||||
if (config && config.frameworkId) {
|
||||
return config.frameworkId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = getFrameworkId;
|
||||
11
lib/utils/getServerlessDir.js
Normal file
11
lib/utils/getServerlessDir.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// get .serverless home path
|
||||
function getServerlessDir() {
|
||||
return path.join(os.homedir(), '.serverless');
|
||||
}
|
||||
|
||||
module.exports = getServerlessDir;
|
||||
20
lib/utils/isDockerContainer.js
Normal file
20
lib/utils/isDockerContainer.js
Normal file
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const readFileSync = require('./fs/readFileSync');
|
||||
const fileExistsSync = require('./fs/fileExistsSync');
|
||||
|
||||
/* Check if is inside docker container */
|
||||
module.exports = function isDockerContainer() {
|
||||
// wrap in try catch to make sure that missing permissions won't break anything
|
||||
try {
|
||||
const cgroupFilePath = path.join('/', 'proc', '1', 'cgroup');
|
||||
if (fileExistsSync(cgroupFilePath)) {
|
||||
const cgroupFileContent = readFileSync(cgroupFilePath).toString();
|
||||
return !!cgroupFileContent.match(/docker/);
|
||||
}
|
||||
} catch (exception) {
|
||||
// do nothing
|
||||
}
|
||||
return false;
|
||||
};
|
||||
13
lib/utils/isTrackingDisabled.js
Normal file
13
lib/utils/isTrackingDisabled.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fileExistsSync = require('./fs/fileExistsSync');
|
||||
const getServerlessDir = require('./getServerlessDir');
|
||||
|
||||
module.exports = function isTrackingDisabled() {
|
||||
// to be updated to .serverlessrc
|
||||
if (fileExistsSync(path.join(getServerlessDir(), 'stats-disabled'))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
34
lib/utils/openBrowser.js
Normal file
34
lib/utils/openBrowser.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const opn = require('opn');
|
||||
const chalk = require('chalk');
|
||||
const isDockerContainer = require('./isDockerContainer');
|
||||
|
||||
function displayManualOpenMessage(url) {
|
||||
// https://github.com/sindresorhus/log-symbols
|
||||
console.log('---------------------------');
|
||||
console.log(`🙈 ${chalk.red('Unable to open browser automatically')}`);
|
||||
console.log(chalk.green('Please open your browser & open the URL below to login:'));
|
||||
console.log(chalk.yellow(url));
|
||||
console.log('---------------------------');
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = function openBrowser(url) {
|
||||
let browser = process.env.BROWSER;
|
||||
if (browser === 'none' || isDockerContainer()) {
|
||||
return displayManualOpenMessage(url);
|
||||
}
|
||||
if (process.platform === 'darwin' && browser === 'open') {
|
||||
browser = undefined;
|
||||
}
|
||||
try {
|
||||
const options = { app: browser };
|
||||
opn(url, options).catch(() => {});
|
||||
return true;
|
||||
} catch (err) {
|
||||
return displayManualOpenMessage(url);
|
||||
}
|
||||
};
|
||||
37
lib/utils/segment.js
Normal file
37
lib/utils/segment.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const fetch = require('node-fetch');
|
||||
const isTrackingDisabled = require('./isTrackingDisabled');
|
||||
// TODO: Replace me before release
|
||||
const writeKey = 'XXXX';
|
||||
const auth = `${writeKey}:`;
|
||||
const TRACKING_IS_DISABLED = isTrackingDisabled();
|
||||
|
||||
/* note segment call swallows errors */
|
||||
function request(url, payload) {
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Basic ${new Buffer(auth).toString('base64')}`,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
timeout: '1000',
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => BbPromise.resolve())
|
||||
.catch(() => BbPromise.resolve());
|
||||
}
|
||||
|
||||
function track(payload) {
|
||||
// exit early is tracking disabled
|
||||
if (TRACKING_IS_DISABLED) {
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
return request('https://api.segment.io/v1/track', payload);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
track,
|
||||
};
|
||||
111
lib/utils/userStats.js
Normal file
111
lib/utils/userStats.js
Normal file
@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const _ = require('lodash');
|
||||
const fetch = require('node-fetch');
|
||||
const configUtils = require('./config');
|
||||
const isTrackingDisabled = require('./isTrackingDisabled');
|
||||
const isValidEventName = require('./userStatsValidation');
|
||||
|
||||
const TRACKING_IS_DISABLED = isTrackingDisabled();
|
||||
const TRACK_URL = 'https://serverless.com/api/framework/track';
|
||||
const IDENTIFY_URL = 'https://serverless.com/api/framework/identify';
|
||||
const DEBUG = false;
|
||||
|
||||
function debug() {
|
||||
if (DEBUG) console.log(arguments);
|
||||
}
|
||||
|
||||
/* note tracking swallows errors */
|
||||
function request(url, payload) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
// set to 1000 b/c no response needed
|
||||
timeout: '1000',
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 404) {
|
||||
return BbPromise.reject('404 api not found');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((res) => BbPromise.resolve(res))
|
||||
.catch((e) => BbPromise.resolve(e));
|
||||
}
|
||||
|
||||
function track(eventName, payload) {
|
||||
const data = payload || {};
|
||||
let userId = data.id;
|
||||
let userEmail = data.email;
|
||||
|
||||
// exit early if tracking disabled
|
||||
if (TRACKING_IS_DISABLED && !data.force) {
|
||||
debug('abort .track call TRACKING_IS_DISABLED');
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
|
||||
// getConfig for values if not provided from .track call
|
||||
if (!userId || !userEmail) {
|
||||
const config = configUtils.getConfig();
|
||||
userId = config.userId;
|
||||
if (config.users && config.users[userId] && config.users[userId].email) {
|
||||
userEmail = config.users[userId].email;
|
||||
} else {
|
||||
debug('exit early if no user data or email found');
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// automatically add `framework:` prefix
|
||||
if (eventName.indexOf('framework:') === -1) {
|
||||
eventName = `framework:${eventName}`; // eslint-disable-line
|
||||
}
|
||||
|
||||
// to ensure clean data, validate event name
|
||||
if (!isValidEventName(eventName)) {
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
|
||||
const defaultData = {
|
||||
event: eventName,
|
||||
id: userId,
|
||||
email: userEmail,
|
||||
data: {
|
||||
id: userId,
|
||||
timestamp: Math.round(+new Date() / 1000),
|
||||
},
|
||||
};
|
||||
|
||||
delete data.force;
|
||||
const eventData = _.merge(defaultData, data);
|
||||
if (DEBUG) {
|
||||
debug('.track call', eventData);
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
return request(TRACK_URL, eventData);
|
||||
}
|
||||
|
||||
function identify(payload) {
|
||||
const data = payload || {};
|
||||
if (TRACKING_IS_DISABLED && !data.force) {
|
||||
if (DEBUG) {
|
||||
console.log('abort .identify call');
|
||||
}
|
||||
// exit early is tracking disabled
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
delete data.force;
|
||||
if (DEBUG) {
|
||||
console.log('.identify call', data);
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
return request(IDENTIFY_URL, data);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
track,
|
||||
identify,
|
||||
};
|
||||
85
lib/utils/userStatsValidation.js
Normal file
85
lib/utils/userStatsValidation.js
Normal file
@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
/**
|
||||
* Validate Event names to keep data clean
|
||||
*/
|
||||
const chalk = require('chalk');
|
||||
|
||||
const VALID_TRACKING_PROJECTS = ['framework'];
|
||||
const VALID_TRACKING_OBJECTS = ['user', 'service'];
|
||||
|
||||
function containsSeparators(eventName) {
|
||||
const underscores = (eventName.match(/_/g) || []).length;
|
||||
const colons = (eventName.match(/:/g) || []).length;
|
||||
if (underscores !== 1) {
|
||||
console.log(chalk.red('Tracking Error:'));
|
||||
console.log(
|
||||
chalk.red(`Event name must have single underscore. "${eventName}" contains ${underscores}`));
|
||||
return false;
|
||||
}
|
||||
if (colons !== 1) {
|
||||
console.log(chalk.red('Tracking Error:'));
|
||||
console.log(chalk.red(`Event name must have single colon. "${eventName}" contains ${colons}`));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate tracking project for clean events
|
||||
function isValidProject(project) {
|
||||
const isValid = VALID_TRACKING_PROJECTS.indexOf(project) !== -1;
|
||||
if (!isValid) {
|
||||
console.log(chalk.red('Tracking Error:'));
|
||||
console.log(`"${project}" is invalid project. Must be one of`, VALID_TRACKING_PROJECTS);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Validate tracking objects for clean events
|
||||
function isValidObject(key) {
|
||||
const isValid = VALID_TRACKING_OBJECTS.indexOf(key) !== -1;
|
||||
if (!isValid) {
|
||||
console.log(chalk.red('Tracking Error:'));
|
||||
console.log(`"${key}" is invalid tracking object. Must be one of`, VALID_TRACKING_OBJECTS);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function formattingWarning(eventName) {
|
||||
console.log(chalk.red(`Incorrect tracking event format: "${eventName}"`));
|
||||
console.log(`Tracking event must match ${chalk.yellow('product:objectName_actionName')}`);
|
||||
console.log(`It must be all camelCase: ${chalk.yellow('camelCase:camelCase_camelCase')}`);
|
||||
console.log(`Here is an Example ${chalk.green('framework:user_loggedIn')}`);
|
||||
console.log('Note: "framework:" is automatically prepended');
|
||||
console.log(`Usage: ${chalk.yellow('track("user_loggedIn", { ..extraData });')}`);
|
||||
console.log('-----------------------------');
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate events to naming conventions. clean data FTW!
|
||||
module.exports = function isValidEventName(eventName) {
|
||||
// match framework:objectName_actionName
|
||||
const matches = eventName.match(/([a-zA-Z]*):([a-zA-Z]*)_(.*)/);
|
||||
if (!containsSeparators(eventName) || !matches) {
|
||||
return formattingWarning(eventName);
|
||||
}
|
||||
const project = matches[1];
|
||||
const object = matches[2];
|
||||
const action = matches[3];
|
||||
|
||||
// if missing any parts of event, exit;
|
||||
if (!project || !object || !action) {
|
||||
return formattingWarning(eventName);
|
||||
}
|
||||
// validate project name
|
||||
if (!isValidProject(project)) {
|
||||
return formattingWarning(eventName);
|
||||
}
|
||||
// validate object name
|
||||
if (!isValidObject(object)) {
|
||||
return formattingWarning(eventName);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
613
npm-shrinkwrap.json
generated
613
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@ -98,15 +98,19 @@
|
||||
"https-proxy-agent": "^1.0.0",
|
||||
"js-yaml": "^3.6.1",
|
||||
"json-refs": "^2.1.5",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.13.1",
|
||||
"minimist": "^1.2.0",
|
||||
"moment": "^2.13.0",
|
||||
"node-fetch": "^1.6.0",
|
||||
"opn": "^5.0.0",
|
||||
"rc": "^1.1.6",
|
||||
"replaceall": "^0.1.6",
|
||||
"resolve-from": "^2.0.0",
|
||||
"semver": "^5.0.3",
|
||||
"semver-regex": "^1.0.0",
|
||||
"shelljs": "^0.6.0",
|
||||
"uuid": "^2.0.2"
|
||||
"uuid": "^2.0.2",
|
||||
"write-file-atomic": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user