Repository: serverless/serverless (JavaScript) — Feature

Issue:
Add support for Lambda tagging
# This is a Feature Proposal

## Description

AWS has recently introduced Lambda tagging support which makes it possible to group Lambda functions and e.g. search by groups in the console.

The framework should support Lambda tagging. Here are the corresponding docs: http://docs.aws.amazon.com/lambda/latest/dg/tagging.html

Unfortunately it seems like CloudFormation has no support for it just yet: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html


Discussion:
One thing to keep in mind is that it's AWS specific, although one could argument that lots of function properties are currently AWS specific too.

Personally, I don't see much value there
> One thing to keep in mind is that it's AWS specific

We could prefix the parameter with `aws-tag` (which is proposed strategy to still support provider specific configs):

```yml
functions:
  foo:
    handler: index.handler
    aws-tag: client1
```

> Personally, I don't see much value there

Me neither. It would be maybe helpful if you could then filter your logs by tags. Not sure if this is possible with the current implementation.
We use tags in AWS to be able to have a global view of how much it cost per environment. For example, we tag everything related to production with the tag `Production` (ie: CloudFormation, EC2, S3, Route53, VPC, etc.) and we are able to tell how much the production cost us. Same behavior with the staging / dev / sandbox env.

That's why I'm :+1: for that feature.
+1 for cost tracking
In addition to cost attribution, I find tagging is also important to identify ownership of resources. For example node.js v0.10 for AWS lambda functions is due to become EOL on 30th April and will cease to work after that date. Identifying ownership and ensuring the runtime is upgraded to a supported node version in large accounts is problematic without tags to indicate ownership. 
Since AWS released lambda tagging, we manually tagged all of our functions, mainly for cost analysis enhancement. Would be great if the offered features weeks be supported in the future.
+1 for cost tracking
As of today, AWS allows to set tags on lambda via cloudformation

http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
Nice! Thanks for letting us know @maximede 👍 
@pmuens @eahefnawy providers like Azure also offer tagging. https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-using-tags

Personally, this seems like a pretty generic feature that will also fit well in to the Serverless Platform. I think we should make this feature a first class citizen and then leave it up to the other provider implementations to convert it.

Therefore, I would suggest an implementation like this...
```yaml
functions:
  foo:
    handler: index.handler
    tags:
      key1: value1
      key2: value2
```
@brianneisler I like the proposal. Simple and easy to use.

Great to have it as a first-class property.

Will look into it today!
Personally I updated to serverless 1.11.0 (frameworkVersion: "1.11.0")
In **provider** section I use **stackTags** and all my tags go to each lambda defined in **function** section
```yaml
provider:
  name: aws
  stackTags: 
    Tag1 : MyTag1
    Tag2 : MyTag2
```
But it's true, I can't define a specific tag for a lambda

Resolving patch:
diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md
index 818be4bb781..24c470008bc 100644
--- a/docs/providers/aws/guide/functions.md
+++ b/docs/providers/aws/guide/functions.md
@@ -263,6 +263,26 @@ functions:
       TABLE_NAME: tableName2
 ```
 
+## Tags
+
+Using the `tags` configuration makes it opssible to add `key` / `value` tags to your functions.
+
+Those tags will appear in your AWS console and makes it easier for you to group functions by tag or find functions with a common tag.
+
+```yml
+functions:
+  hello:
+    handler: handler.hello
+    tags:
+      foo: bar
+```
+
+Real-world use cases where tagging your functions is helpful include:
+
+- Cost estimations (tag functions with an environemnt tag: `environment: Production`)
+- Keeping track of legacy code (e.g. tag functions which use outdated runtimes: `runtime: nodejs0.10`)
+- ...
+
 ## Log Group Resources
 
 By default, the framework will create LogGroups for your Lambdas. This makes it easy to clean up your log groups in the case you remove your service, and make the lambda IAM permissions much more specific and secure.
diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md
index 9f7fc342331..cf2ee10a6ec 100644
--- a/docs/providers/aws/guide/serverless.yml.md
+++ b/docs/providers/aws/guide/serverless.yml.md
@@ -74,6 +74,8 @@ functions:
     role: arn:aws:iam::XXXXXX:role/role # IAM role which will be used for this function
     environment: # Function level environment variables
       functionEnvVar: 12345678
+    tags: # Function specific tags
+      foo: bar
     events: # The Events that trigger this Function
       - http: # This creates an API Gateway HTTP endpoint which can be used to trigger this function.  Learn more in "events/apigateway"
           path: users/create # Path for this endpoint
diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js
index 8be6b41a41b..7d87053ecaa 100644
--- a/lib/plugins/aws/package/compile/functions/index.js
+++ b/lib/plugins/aws/package/compile/functions/index.js
@@ -125,6 +125,13 @@ class AwsCompileFunctions {
     newFunction.Properties.Timeout = Timeout;
     newFunction.Properties.Runtime = Runtime;
 
+    if (functionObject.tags && typeof functionObject.tags === 'object') {
+      newFunction.Properties.Tags = [];
+      _.forEach(functionObject.tags, (Value, Key) => {
+        newFunction.Properties.Tags.push({ Key, Value });
+      });
+    }
+
     if (functionObject.description) {
       newFunction.Properties.Description = functionObject.description;
     }


Test patch:
diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js
index 0b60b03e084..f4e88847b4f 100644
--- a/lib/plugins/aws/package/compile/functions/index.test.js
+++ b/lib/plugins/aws/package/compile/functions/index.test.js
@@ -487,6 +487,53 @@ describe('AwsCompileFunctions', () => {
       ).to.deep.equal(compiledFunction);
     });
 
+    it('should create a function resource with tags', () => {
+      const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName;
+      const s3FileName = awsCompileFunctions.serverless.service.package.artifact
+        .split(path.sep).pop();
+      awsCompileFunctions.serverless.service.functions = {
+        func: {
+          handler: 'func.function.handler',
+          name: 'new-service-dev-func',
+          tags: {
+            foo: 'bar',
+            baz: 'qux',
+          },
+        },
+      };
+
+      const compiledFunction = {
+        Type: 'AWS::Lambda::Function',
+        DependsOn: [
+          'FuncLogGroup',
+          'IamRoleLambdaExecution',
+        ],
+        Properties: {
+          Code: {
+            S3Key: `${s3Folder}/${s3FileName}`,
+            S3Bucket: { Ref: 'ServerlessDeploymentBucket' },
+          },
+          FunctionName: 'new-service-dev-func',
+          Handler: 'func.function.handler',
+          MemorySize: 1024,
+          Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] },
+          Runtime: 'nodejs4.3',
+          Timeout: 6,
+          Tags: [
+            { Key: 'foo', Value: 'bar' },
+            { Key: 'baz', Value: 'qux' },
+          ],
+        },
+      };
+
+      awsCompileFunctions.compileFunctions();
+
+      expect(
+        awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources.FuncLambdaFunction
+      ).to.deep.equal(compiledFunction);
+    });
+
     it('should create a function resource with environment config', () => {
       const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName;
       const s3FileName = awsCompileFunctions.serverless.service.package.artifact