Repository: serverless/serverless (JavaScript)
Date: 2016-10-21 06:32:53+00:00
Task type: Feature

Issue:
Support Resource only Services
# Feature Proposal
## Description

There are use cases where developers might want to deploy a service that only contains Cloud Formation (CF) resources. Use cases for this include:
- A VPC service to be used by multiple services
- Many-Many SNS topics that cannot be owned by one particular service
- IoT services that trigger lambda's in other services rather than itself

Developers could deploy that via a custom CF template themselves. But they would have to write scripts to manage that deployment and support stages.

This should be a really easy feature to implement. Simply replace this block in `Service.js` line 61

``` js
if (!serverlessFile.functions) {
    throw new SError('"functions" property is missing in serverless.yml');
}
```

with

``` js
if (!serverlessFile.functions) {
    serverlessFile.function = [];
}
```
## Additional Data
- **_Serverless Framework Version you're using**_: 1.0.0-rc.2
- **_Operating System**_: Mac OSX



Issue discussion:
This is an interesting take on the idea, because you could then have an 'infrastructure' service. You could even go as far as to have one service depend on another, which would maybe make the outputs of the one service available to the dependent service... I like it.

I like it too

In first instance before I tried serverless I had thought that this was actually it's main purpose and could split my long CF templates into readable pieces.

@flomotlik @pmuens @eahefnawy what do you think? I'm happy to implement this if you think it's a good idea.

@johncmckim yup sounds good to me

Thanks @flomotlik I'll create a PR 🔜 


Solution patch:
diff --git a/lib/classes/Service.js b/lib/classes/Service.js
index 69136a9bf5b..952d3a345c9 100644
--- a/lib/classes/Service.js
+++ b/lib/classes/Service.js
@@ -58,9 +58,6 @@ class Service {
         if (!serverlessFile.provider) {
           throw new SError('"provider" property is missing in serverless.yml');
         }
-        if (!serverlessFile.functions) {
-          throw new SError('"functions" property is missing in serverless.yml');
-        }
 
         if (typeof serverlessFile.provider !== 'object') {
           const providerName = serverlessFile.provider;
@@ -84,7 +81,7 @@ class Service {
         that.custom = serverlessFile.custom;
         that.plugins = serverlessFile.plugins;
         that.resources = serverlessFile.resources;
-        that.functions = serverlessFile.functions;
+        that.functions = serverlessFile.functions || {};
 
         if (serverlessFile.package) {
           that.package.individually = serverlessFile.package.individually;
diff --git a/lib/plugins/aws/deploy/lib/mergeIamTemplates.js b/lib/plugins/aws/deploy/lib/mergeIamTemplates.js
index 97a8e19a35b..cb288637045 100644
--- a/lib/plugins/aws/deploy/lib/mergeIamTemplates.js
+++ b/lib/plugins/aws/deploy/lib/mergeIamTemplates.js
@@ -6,6 +6,10 @@ const path = require('path');
 
 module.exports = {
   mergeIamTemplates() {
+    if (!this.serverless.service.getAllFunctions().length) {
+      return BbPromise.resolve();
+    }
+
     if (typeof this.serverless.service.provider.iamRoleARN !== 'string') {
       // merge in the iamRoleLambdaTemplate
       const iamRoleLambdaExecutionTemplate = this.serverless.utils.readFileSync(


Test patch:
diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js
index 0c3d9fc203c..088aad7ef48 100644
--- a/lib/classes/Service.test.js
+++ b/lib/classes/Service.test.js
@@ -334,7 +334,7 @@ describe('Service', () => {
       });
     });
 
-    it('should throw error if functions property is missing', () => {
+    it('should not throw error if functions property is missing', () => {
       const SUtils = new Utils();
       const serverlessYml = {
         service: 'service-name',
@@ -347,11 +347,7 @@ describe('Service', () => {
       serviceInstance = new Service(serverless);
 
       return serviceInstance.load().then(() => {
-        // if we reach this, then no error was thrown as expected
-        // so make assertion fail intentionally to let us know something is wrong
-        expect(1).to.equal(2);
-      }).catch(e => {
-        expect(e.name).to.be.equal('ServerlessError');
+        expect(serverless.service.functions).to.deep.equal({});
       });
     });
 
diff --git a/lib/plugins/aws/deploy/tests/mergeIamTemplates.js b/lib/plugins/aws/deploy/tests/mergeIamTemplates.js
index f35ef1fe2e3..76b86c2d05a 100644
--- a/lib/plugins/aws/deploy/tests/mergeIamTemplates.js
+++ b/lib/plugins/aws/deploy/tests/mergeIamTemplates.js
@@ -32,6 +32,18 @@ describe('#mergeIamTemplates()', () => {
     };
   });
 
+  it('should not merge there are no functions', () => {
+    awsDeploy.serverless.service.functions = {};
+
+    return awsDeploy.mergeIamTemplates()
+      .then(() => {
+        const resources = awsDeploy.serverless.service.provider
+                            .compiledCloudFormationTemplate.Resources;
+
+        expect(resources.IamRoleLambdaExecution).to.equal(undefined);
+        expect(resources.IamPolicyLambdaExecution).to.equal(undefined);
+      });
+  });
 
   it('should merge the IamRoleLambdaExecution template into the CloudFormation template', () => {
     const IamRoleLambdaExecutionTemplate = awsDeploy.serverless.utils.readFileSync(