diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index ae7c918e7..05b681737 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -10,7 +10,6 @@ "./logs/logs.js", "./login/login.js", "./logout/logout.js", - "./platform/platform.js", "./metrics/metrics.js", "./remove/remove.js", "./rollback/index.js", @@ -40,6 +39,7 @@ "./aws/package/compile/events/cognitoUserPool/index.js", "./aws/deployFunction/index.js", "./aws/deployList/index.js", - "./aws/invokeLocal/index.js" + "./aws/invokeLocal/index.js", + "./platform/platform.js" ] } diff --git a/lib/plugins/platform/platform.js b/lib/plugins/platform/platform.js index 201c67bd7..67b953929 100644 --- a/lib/plugins/platform/platform.js +++ b/lib/plugins/platform/platform.js @@ -4,6 +4,7 @@ const path = require('path'); const fs = require('fs'); const gql = require('graphql-tag'); const jwtDecode = require('jwt-decode'); +const BbPromise = require('bluebird'); const fsExtra = require('../../utils/fs/fse'); const fetch = require('node-fetch'); const configUtils = require('../../utils/config'); @@ -27,23 +28,6 @@ function addReadme(attributes, readmePath) { return attributes; } -function publishService(service, client) { - return client - .mutate({ - mutation: gql` - mutation publishService($service: ServicePublishInputType!) { - publishService(service: $service) { - name - } - } - `, - variables: { - service, - }, - }) - .then(response => response.data); -} - function fetchEndpoint(provider, stage, region) { return provider .request( @@ -72,33 +56,55 @@ class Platform { constructor(serverless, options) { this.serverless = serverless; this.options = options; - - this.hooks = { - 'after:deploy:deploy': this.publishService.bind(this), - }; + this.provider = this.serverless.getProvider('aws'); + // NOTE for the time being we only track services published to AWS + if (this.provider) { + this.hooks = { + 'after:deploy:deploy': this.publishService.bind(this), + }; + } } - publishService() { - this.serverless.cli.log('Publish service to Serverless Platform...'); + + publishServiceRequest(service, client) { + return client + .mutate({ + mutation: gql` + mutation publishService($service: ServicePublishInputType!) { + publishService(service: $service) { + name + } + } + `, + variables: { service }, + }) + .then(response => response.data); + } + + getAuthToken() { const userConfig = configUtils.getConfig(); const currentId = userConfig.userId; const globalConfig = configUtils.getGlobalConfig(); - let authToken; if (globalConfig.users && globalConfig.users[currentId] && globalConfig.users[currentId].auth) { - authToken = globalConfig.users[currentId].auth.id_token; + return globalConfig.users[currentId].auth.id_token; } + return null; + } + + publishService() { + const authToken = this.getAuthToken(); if (!authToken) { // NOTE publishService is an opt-in feature and no warning is needed - return undefined; + return BbPromise.resolve(); } + this.serverless.cli.log('Publish service to Serverless Platform...'); const clientWithAuth = createApolloClient(config.GRAPHQL_ENDPOINT_URL, authToken); - const provider = this.serverless.getProvider('aws'); const region = this.serverless.service.provider.region; const stage = this.serverless.processedInput.options.stage; - return provider.getAccountId().then(accountId => { - fetchEndpoint(provider, stage, region).then(endpoint => { + return this.provider.getAccountId().then(accountId => + fetchEndpoint(this.provider, stage, region).then(endpoint => { const funcs = this.serverless.service.getAllFunctions().map(key => { const arnName = functionInfoUtils.getArnName(key, this.serverless); let funcAttributes = { @@ -121,7 +127,6 @@ class Platform { } return funcAttributes; }); - const serviceData = { name: this.serverless.service.service, stage: this.serverless.processedInput.options.stage, @@ -148,8 +153,7 @@ class Platform { const readmePath = path.join(this.serverless.config.servicePath, 'README.md'); const serviceDataWithReadme = addReadme(serviceData, readmePath); - // publish to platform - publishService(serviceDataWithReadme, clientWithAuth) + return this.publishServiceRequest(serviceDataWithReadme, clientWithAuth) .then(() => { const username = jwtDecode(authToken).nickname; const serviceName = this.serverless.service.service; @@ -162,8 +166,8 @@ class Platform { error ); }); - }); - }); + }) + ); } } diff --git a/lib/plugins/platform/platform.test.js b/lib/plugins/platform/platform.test.js index d6f30daf9..fbc03c384 100644 --- a/lib/plugins/platform/platform.test.js +++ b/lib/plugins/platform/platform.test.js @@ -1,16 +1,19 @@ 'use strict'; const expect = require('chai').expect; +const sinon = require('sinon'); const PlatformPlugin = require('./platform'); const Serverless = require('../../Serverless'); +const AwsProvider = require('../aws/provider/awsProvider'); -describe('platform', () => { +describe.only('platform', () => { let serverless; let plugin; beforeEach(() => { serverless = new Serverless(); serverless.init(); + serverless.setProvider('aws', new AwsProvider(serverless)); plugin = new PlatformPlugin(serverless); }); @@ -22,5 +25,123 @@ describe('platform', () => { it('should have a hook after the deploy', () => { expect(plugin.hooks).to.have.property('after:deploy:deploy'); }); + + it('should set the provider variable to an instance of AwsProvider', () => { + expect(plugin.provider).to.be.instanceof(AwsProvider); + }); + }); + + describe('#publishService()', () => { + let getAuthTokenStub; + let getAccountIdStub; + let endpointsRequestStub; + let publishServiceRequestStub; + + beforeEach(() => { + getAuthTokenStub = sinon.stub(plugin, 'getAuthToken').returns( + // eslint-disable-next-line max-len + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0OTc4ODMwMzMsImV4cCI6MTUyOTQxOTAzMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5pY2tuYW1lIjoiam9obmRvZSJ9.GD6sqQR3qLirnrvLKKrmOc7vgsHpqZ3TPwyG8ZI69ig' + ); + getAccountIdStub = sinon.stub(plugin.provider, 'getAccountId').resolves('acountId123'); + endpointsRequestStub = sinon.stub(plugin.provider, 'request').resolves({ + Stacks: [ + { + Outputs: [{ OutputKey: 'ServiceEndpoint', OutputValue: 'http://service-endpoint' }], + }, + ], + }); + publishServiceRequestStub = sinon.stub(plugin, 'publishServiceRequest').resolves(); + }); + + afterEach(() => { + getAuthTokenStub.restore(); + getAccountIdStub.restore(); + endpointsRequestStub.restore(); + publishServiceRequestStub.restore(); + }); + + it('should send a minimal service request to the platform', () => { + plugin.serverless.service.service = 'new-service-2'; + plugin.serverless.service.serviceObject = { + name: 'new-service-2', + }; + plugin.serverless.config.servicePath = '/path/to/service'; + sinon.spy(plugin.serverless.cli, 'log'); + + return plugin.publishService().then(() => { + expect(getAuthTokenStub.calledOnce).to.be.equal(true); + expect(getAccountIdStub.calledOnce).to.be.equal(true); + expect(endpointsRequestStub.calledOnce).to.be.equal(true); + expect(publishServiceRequestStub.calledOnce).to.be.equal(true); + const expected = { name: 'new-service-2', stage: undefined, functions: [] }; + expect(publishServiceRequestStub.getCall(0).args[0]).to.deep.equal(expected); + const expectedLog = + 'Your service is available at https://platform.serverless.com/services/johndoe/new-service-2'; + expect(plugin.serverless.cli.log.calledWithExactly(expectedLog)).to.be.equal(true); + }); + }); + + it('should send a full service request to the platform', () => { + plugin.serverless.service.service = 'new-service-2'; + plugin.serverless.service.serviceObject = { + name: 'new-service-2', + description: 'test description', + repository: 'https://example.com/repo', + homepage: 'https://example.com', + bugs: 'https://example.com/bugs', + license: 'MIT', + }; + plugin.serverless.config.servicePath = '/path/to/service'; + plugin.serverless.service.provider.name = 'aws'; + plugin.serverless.service.functions = { + hello: { + handler: 'handler.hello', + description: 'test desc', + events: [{ http: { path: 'users/create', method: 'get', integration: 'AWS_PROXY' } }], + name: 'test-service2-dev-hello', + package: {}, + vpc: {}, + }, + }; + + sinon.spy(plugin.serverless.cli, 'log'); + + return plugin.publishService().then(() => { + expect(getAuthTokenStub.calledOnce).to.be.equal(true); + expect(getAccountIdStub.calledOnce).to.be.equal(true); + expect(endpointsRequestStub.calledOnce).to.be.equal(true); + expect(publishServiceRequestStub.calledOnce).to.be.equal(true); + const expected = { + name: 'new-service-2', + stage: undefined, + repository: 'https://example.com/repo', + bugs: 'https://example.com/bugs', + description: 'test description', + functions: [ + { + description: 'test desc', + endpoints: [ + { + method: 'GET', + url: 'http://service-endpoint/users/create', + }, + ], + memory: 1024, + name: 'hello', + originId: 'arn:aws:lambda:us-east-1:acountId123:function:test-service2-dev-hello', + provider: 'aws', + runtime: 'nodejs4.3', + timeout: 6, + }, + ], + homepage: 'https://example.com', + license: 'MIT', + }; + expect(publishServiceRequestStub.getCall(0).args[0]).to.deep.equal(expected); + const expectedLog = + 'Your service is available at https://platform.serverless.com/services/johndoe/new-service-2'; + expect(plugin.serverless.cli.log.calledWithExactly(expectedLog)).to.be.equal(true); + }); + }); }); });