Repository: serverless/serverless (JavaScript)
Date: 2018-05-04 02:49:10+00:00
Task type: Feature

Issue:
Add option to overwrite stack name
# This is a Feature Proposal

## Description

We have option to overwrite function name, APIG name etc. however there is no option to overwrite stack name. As we have already options for `stackTags`, `stackPolicy` in the `provider` we could add `stackName` option that would overwrite default stack name define by serverless framework. The `stackName` option would be optional for people that want to overwrite default stack name.

```yaml
provider:
  name: aws
  runtime: nodejs4.3
  stackName: "custom-stack-name"
  stackTags:
    key: value
  stackPolicy:
   - {Effect: Allow, Principal: "*", Action: "Update:*", Resource: "*"}
```


Issue discussion:
This would be useful to maintain consistent naming conventions throughout a stack. The project I am working on adheres to a convention that roughly equates to: `${stage}-${service}-${resource}`.

Being able to customize the stack name (e.g. `stackName: ${opt:stage}-${self:service}`) allows us to view stacks in the console by stage and reinforces the consistency of the convention.

PR #2999 is a basic implementation that seems to satisfy the goal.
@pmuens @HyperBrain Let's continue on the topic within this issue.

As mentioned earlier, we would like to be able to calculate our own stack names.

```yml
service:
  name: foo
  stackName: ${self:service.name}-${opt:stage}-r

# OR

provider:
  stackName: ${self:service.name}-${opt:stage}-r
```

Let's figure which part are currently blocking this. I'm willing to do a PR but don't want to spend time on something which might not get merged or is only useful within our organization.
@nicka Our company also would benefit from the feature, so at least we're two ;-) Here are my 2 cents on the possible (imo feasible) integrations:

From the pure specification perspective I would go for case 1:
```
service:
  name: foo
  stackName: xxxxx
```
as the PR for service object support #3521 already has been merged and expresses the stackName explicitly as service property.

From an integration perspective the stack name generation in `provider.naming` should be changed to set the stackname either to `service.stackName` if that exists, or fall back to the current name generation.
Additionally all sources should be checked for hard-coded stack names.

For the transition from package to deploy phase it has to be checked, that the stack name is persisted correctly in the service state file.

For now I think the implementation could be done right away without problems, but as soon as #3528 has been implemented, the on-deploy generation of the stack name also has to be verified in a real-world scenario.
@nicka @HyperBrain thanks for following up on this again!

🤔 that's a tough question. My gut feeling says that it should be in the recently introduced `service` config:

```
service:
  name: foo
  stackName: ${self:service.name}-${opt:stage}-r
```

However I'm not sure if having this "stack abstraction" is something we call a "standard". E.g. other providers might not use a `stack` at all which makes this property provider specific (AFAIK e.g. OpenWhisk doesn't use a stack behind the scenes).

The implementation proposal @HyperBrain wrote makes sense! The only important thing to keep in mind is that the stack name is used in a bunch of different places so it would be nice to have a central place for the naming (like @HyperBrain already discussed).

And it would be really nice to have this non-breaking 😬 

/ccing @eahefnawy and @brianneisler here for more feedback.
I'd love to control the stack name in detail, too.
This REALLY needs to be implemented.

We have a strict convention internally of naming stacks with 

`<project name>-<environment>-<tier>`

So for example:

```
myproject-dev-api
myproject-dev-ui
myproject-dev-s3
etc
```

Having no ability to name our serverless stacks according to our standards is really hurting the use case for its continued usage.
For us this feature is a deal breaker if we are going to use serverless.com or not.

We have our own tooling that does virtually the exact same thing as serverless.com. It started out for our serverbased stack but now as we move over towards serverless we feel that we might as well integrate serverless.com for that part of our tooling but it can only happen if we can control the stack names.

Everything we create in AWS is based on AWS stacks and all of the follow the same naming convention so we have our tools built to visualize our environments at runtime based on stack names.

Our convention is ${zone}-${region}-${solution}-${environment}-${component}.

Which would stage would map to ${solution}-${environment}, region would be region and zone is a construct which you can think of as a classifier. For the purpose of Resource Name prefixing I would want stage to be ${solution}-${environment} and region to be region but be able to set the entire construct as stack name.

I wonder if this doesnt get implemented would it be possible to build this as a plugin?
Thanks for the very detailed feedback @triha74 👍

> I wonder if this doesnt get implemented would it be possible to build this as a plugin?

Yes, this functionality is definitely a feature we'd love to have in core. We already had a PR which covered this --> https://github.com/serverless/serverless/pull/2999

Would be super nice if someone could pick this up and finish it.
@pmuens 

I have a slightly different suggestion how to implement this.

```
 var es6template = require('es6-template')
 getStackName() {
 if (process.env.STACK_NAME_PATTERN){
      var locals = {
        provider: this.provider,
        env: process.env
      }
      var compileTemplateFn = es6template.compile(process.env.stackNamePattern)
      return compileTemplateFn(locals);
    }
    return `${this.provider.serverless.service.service}-${this.provider.getStage()}`;
  }
```
Personally I like to have my build environment provide environment variables that describes the target environment and my dev environment to provide environment variables that describes it self. 

I dont like having to provide --stack-name or having to set stack-ame in the template. Reason is that we have 100+ micro services which means 100+ templates having the stackname in each requires it be written correctly in each by the developers. Way too error prone. Providing it through the --stack-name is almost equaly bad, CD Env can definatly do that but devs launching ito dev accounts will not want to add --stack-name on each launch.

I also want to disconnect stackname from stage. For us we have a stackname standard that we need to follow even if we switch tool and we have resource prefixes wich are equal to stage in serverless. We need to be able to disconnec these.

Our pattern would look something like this.
```
 export STACK_NAME_PATTERN="\${env.zone}-\${env.site}-\${env.solution}-\${env.solution}-\${provider.serverless.service.service}"
```
Tomas
@triha74 I like the idea of having the stackname configurable. However, letting Serverless directly depend on environment variables instead of configuration options is hard to document and error prone.

What about, having a service configuration like
```yaml
# serverless.yml
provider:
  name: 'aws'
  stackNamePattern: ${env:STACK_NAME_PATTERN}
```

This is much more flexible and with the `env` prefix you can easily reference any defined environment variable. On the other hand, it is much easier and general in the Serverless implementation, as you can reference anything inside (even reference output variables of other stacks, like a central configuration stack).
@HyperBrain 

To be explicit or not. :) Well I do agree with you that its harder to document, more error prone due to beeing "magically picked up".

But on the ohter hand I dont like having 

```
provider:
  name: 'aws'
  stackNamePattern: ${env:STACK_NAME_PATTERN}
```

in 100+ files. If its missed in one place then stack gets launched with wrong name which in our case is a big problem.

So I still want a way for our CD system & Build Environment to set it.
@triha74 I see your point regarding the 100+ YML files. However, you would have the same problem when writing your own plugin (since it also needs to be defined in each YML). But I agree with @HyperBrain the serverless.yml or CLI should be the place for customing defaults.
We too follow convention for our CF templates: ${stage}-${service}-${resource}.

If stackName: ${opt:stage}-${self:service} is supported, it will allow us to maintain stacks consistently and the resources too will follow the convention. This would be very useful for us.
So is this still something we can't do?  I looked at #2999 which linked me to #3513 which linked me to #3521.  None of these seem to add the ability to specify a custom CFN stack name.

Why is this sometimes being called a breaking change?  If you give us a "stackName" parameter that defaults to the service name, nothing breaks and we get the flexibility we need.
Tangentially related question, I see people mentioning `${self:service.name}`. Is it actually possible to do that? When I try that, I get:

```
Serverless Error ---------------------------------------                                                                                    
                                                                                                                                            
Trying to populate non string value into a string for variable ${self:service.name}. Please make sure the value of the property is a string.
```

Would really like some progress on this.  We've had to hack the naming.js in a custom feed for ServerLess v 1.10.0 with the following:

  // Stack
  getStackName() {
    //Nara 9/15/2017: to support custom stackName on CloudFormation stack name
    if(this.provider.serverless.service.provider.customStackName != undefined ){
      return `${this.provider.serverless.service.provider.customStackName}`;
     }
     else{
       return `${this.provider.serverless.service.service}-${this.provider.getStage()}`;
     }
  },

We've been living with this hack for quite some time already and think it should be easier for folks to have a path forward of upgrading in-place old ServerLess stacks built on 0.x ServerLess technology.  This custom hack let us do in-place upgrades of our old stacks without painful data migration.

AWS Cloudformation is pretty limiting in terms of moving resources around or renaming stacks so it would be a huge help if ServerLess can make it easier to support complex stack naming requirements for an organization.


Completely agree with @dreamspider42, we ended up recreating most of out v0.x stacks, but some aren’t worth the effort and time to recreate.
I have created a feature branch for this and made the updates. However, Im unable to push my feature branch. Can someone help me with the permissions to be able to push my feature branch and create a PR?

This only allows for custom stack names and api gateway API names. This may not do all that @dreamspider42 is referring to, Im not sure. But it is pretty much what he did to his local package `naming.js` file. So not sure if it is sufficient. But i know i would like to have this also int he main code base instead of manually having team members change the source code in the package itself.
@rts-cwalker You should fork the repo and then you can open a PR from your fork against the main repo.
Ah yes. thats right. Its been a while since ive contributed to an open source project. Gotten so used to how things work in our repos at work. 😄 Thanks @HyperBrain 

Solution patch:
diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md
index 3660e307ca7..e092089091c 100644
--- a/docs/providers/aws/guide/serverless.yml.md
+++ b/docs/providers/aws/guide/serverless.yml.md
@@ -28,6 +28,8 @@ provider:
   runtime: nodejs6.10
   stage: dev # Set the default stage used. Default is dev
   region: us-east-1 # Overwrite the default region used. Default is us-east-1
+  stackName: custom-stack-name # Use a custom name for the CloudFormation stack
+  apiName: custom-api-name # Use a custom name for the API Gateway API
   profile: production # The default profile to use with this service
   memorySize: 512 # Overwrite the default memory size. Default is 1024
   timeout: 10 # The default is 6 seconds. Note: API Gateway current maximum is 30 seconds
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index efb408408ce..c6e03c18035 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -46,6 +46,10 @@ module.exports = {
 
   // Stack
   getStackName() {
+    if (this.provider.serverless.service.provider.stackName &&
+        _.isString(this.provider.serverless.service.provider.stackName)) {
+      return `${this.provider.serverless.service.provider.stackName}`;
+    }
     return `${this.provider.serverless.service.service}-${this.provider.getStage()}`;
   },
 
@@ -145,6 +149,10 @@ module.exports = {
 
   // API Gateway
   getApiGatewayName() {
+    if (this.provider.serverless.service.provider.apiName &&
+        _.isString(this.provider.serverless.service.provider.apiName)) {
+      return `${this.provider.serverless.service.provider.apiName}`;
+    }
     return `${this.provider.getStage()}-${this.provider.serverless.service.service}`;
   },
   generateApiGatewayDeploymentLogicalId() {


Test patch:
diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js
index 530d0621e56..1ed4b095420 100644
--- a/lib/plugins/aws/lib/naming.test.js
+++ b/lib/plugins/aws/lib/naming.test.js
@@ -94,11 +94,18 @@ describe('#naming()', () => {
   });
 
   describe('#getStackName()', () => {
-    it('should use the service name and stage from the service and config', () => {
+    it('should use the service name & stage if custom stack name not provided', () => {
       serverless.service.service = 'myService';
       expect(sdk.naming.getStackName()).to.equal(`${serverless.service.service}-${
         sdk.naming.provider.getStage()}`);
     });
+
+    it('should use the custom stack name if provided', () => {
+      serverless.service.provider.stackName = 'app-dev-testApp';
+      serverless.service.service = 'myService';
+      serverless.service.provider.stage = sdk.naming.provider.getStage();
+      expect(sdk.naming.getStackName()).to.equal('app-dev-testApp');
+    });
   });
 
   describe('#getRolePath()', () => {
@@ -214,11 +221,18 @@ describe('#naming()', () => {
   });
 
   describe('#getApiGatewayName()', () => {
-    it('should return the composition of stage and service name', () => {
+    it('should return the composition of stage & service name if custom name not provided', () => {
       serverless.service.service = 'myService';
       expect(sdk.naming.getApiGatewayName())
         .to.equal(`${sdk.naming.provider.getStage()}-${serverless.service.service}`);
     });
+
+    it('should return the custom api name if provided', () => {
+      serverless.service.provider.apiName = 'app-dev-testApi';
+      serverless.service.service = 'myService';
+      serverless.service.provider.stage = sdk.naming.provider.getStage();
+      expect(sdk.naming.getApiGatewayName()).to.equal('app-dev-testApi');
+    });
   });
 
   describe('#generateApiGatewayDeploymentLogicalId()', () => {