├── .gitignore
├── src
├── lambda
│ ├── function-generate
│ │ ├── requirements.txt
│ │ ├── app.py
│ │ └── template.html
│ ├── function-generate-selected
│ │ ├── requirements.txt
│ │ ├── app.py
│ │ └── template.html
│ ├── function-fetch-esms
│ │ └── app.py
│ └── function-selected
│ │ └── app.py
└── sfn
│ ├── state-machine-selected.asl.json
│ └── state-machine.asl.json
├── imgs
└── architecture.png
├── CODE_OF_CONDUCT.md
├── LICENSE.txt
├── VERSIONS.md
├── CONTRIBUTING.md
├── README.md
└── template.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | samconfig.toml
2 | **/.aws-sam/**
3 |
--------------------------------------------------------------------------------
/src/lambda/function-generate/requirements.txt:
--------------------------------------------------------------------------------
1 | jinja2
2 | requests
3 | bs4
--------------------------------------------------------------------------------
/src/lambda/function-generate-selected/requirements.txt:
--------------------------------------------------------------------------------
1 | jinja2
2 | requests
3 | bs4
--------------------------------------------------------------------------------
/imgs/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/serverless-ops-review/HEAD/imgs/architecture.png
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/src/lambda/function-fetch-esms/app.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | import json, os, copy
5 | import boto3
6 |
7 |
8 | lbd = boto3.client('lambda')
9 | s3 = boto3.client('s3')
10 |
11 | #env var load
12 | S3_BUCKET = os.environ['S3_BUCKET']
13 |
14 |
15 |
16 | def esms_fetch():
17 | response = lbd.list_event_source_mappings()
18 | response = response['EventSourceMappings']
19 | return response
20 |
21 |
22 | def handler(event, context):
23 |
24 | esms = esms_fetch()
25 |
26 | key = event['FolderKey']
27 | esms_to_s3 = s3.put_object(Bucket=S3_BUCKET, Key=key+'/esms.json', Body=json.dumps(esms, default=str))
28 |
29 |
30 | return {
31 | "statusCode": 200,
32 | "FolderKey": key
33 | }
--------------------------------------------------------------------------------
/src/lambda/function-selected/app.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | import boto3
5 | import os
6 | import json
7 |
8 | STATE_MACHINE = os.environ['SFN_SELECTED']
9 | sfn = boto3.client('stepfunctions')
10 | cfn = boto3.client('cloudformation')
11 |
12 | def handler(event, context):
13 | # Fetch stack name from event:
14 | stack_name = event['resources']
15 |
16 | #Fetch stack parameters:
17 | functions = cfn.describe_stacks(
18 | StackName=stack_name[0]
19 | )
20 | functions = functions['Stacks'][0]['Parameters'][0]['ParameterValue'].split(',')
21 |
22 | #Start state machine execution:
23 | sfn_input = json.dumps({"Functions": functions})
24 |
25 | sfn_selected = sfn.start_execution(
26 | stateMachineArn=STATE_MACHINE,
27 | input=sfn_input
28 | )
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this
4 | software and associated documentation files (the "Software"), to deal in the Software
5 | without restriction, including without limitation the rights to use, copy, modify,
6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7 | permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/VERSIONS.md:
--------------------------------------------------------------------------------
1 | # Serverless Operational Review Tool Versions
2 |
3 | - **Version 1.3.0**
4 | - Added evaluation of deprecated runtimes and runtimes that are scheduled to deprecate, including blocked update and creation dates.
5 | - Functions with deprecated runtime versions allocated to RED Danger Zone.
6 | - Functions with runtime versions scheduled to deprecate allocated to YELLOW Warning Zone.
7 |
8 | - **Version 1.2.1**
9 | - Added runtimes : Python 12, Node.js 20, Java 21, OS-only Runtime Amazon Linux 2023
10 | - Removed runtimes: Java 11, Node.js 16, .NET 6
11 |
12 | - **Version 1.2.0**
13 | - SAM CLI version 1.94 or higher is required to deploy this tool as all backend compute is using Python 3.11!
14 | - Reporting for all Lambda functions agregates all data into single report file.
15 | - Direct passing from JSON to HTML
16 | - Event Source Mappings have its own subsection inside Reviewed function configurations and provide almost all information depending on the type of the event source.
17 |
18 | - **Version 1.1.1**
19 | - Reporting on all or selected Lambda functions in the region the tool is deployed to.
20 | - Generating 1 report per up to 50 Lambda functions due to CLI limitations.
21 | - Backend Lambda functions using Python 3.10.
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Serverless Operational Review Tool
2 |
3 | This tool has been created to help evaluate **AWS Serverless services** in bulk and to automatically generate a full review report which aims to assist in customer resource analysis.
4 |
5 | **Tool versions with changelog are present [here](VERSIONS.md).**
6 |
7 | ## Currently supported features
8 |
9 | - **AWS Lambda**
10 | - RED Danger Zone - Risks :
11 | - Multi-AZ checks evaluate if a function is VPC enabled and if the linked subnets follow multi-AZ approach.
12 | - Runtime checks evaluate if a function runtime has been deprecated (ZIP type only). This check separates functions based on runtime and provides details on all affected function versions.
13 | - **AWS Trusted Advisor checks** (the following checks are included as part of the report if the AWS Account has [AWS Trusted Advisor](https://docs.aws.amazon.com/awssupport/latest/user/trusted-advisor.html) enabled):
14 | - Functions with high error rates
15 | - Functions with excessive timeouts
16 |
17 | - YELLOW Warnings Zone :
18 | - Runtime checks that evaluate if a function runtime versions is scheduled to be deprecated in the near future.
19 |
20 | - BLUE Optimization Zone - Recommendations :
21 | - Provides optimization recommendations focused on function memory and benefits of optimizing (duration and cost).
22 | - This information is pulled from AWS Compute Optimizer so there has to be historical usage available.
23 | - Lists functions using x86 architecture type and recommends converting to arm64 (Graviton2).
24 |
25 | - Reviewed Functions - Configurations :
26 | - Provides the list of reviewed functions with their configurations.
27 | - Provides a list of function event source mappings configurations.
28 |
29 | ## Architecture
30 |
31 | 
32 |
33 | This tool is using an event-driven approach to automatically trigger resource review right after successful deployment. Secondary review can be run using a manual trigger of the AWS Step Functions state machine.
34 |
35 | ## User guide
36 |
37 | ### Step 1 - Deployment
38 |
39 | - Install [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
40 |
41 | - Clone this repository to your local machine, which can access your AWS account containing the resources you would like to review (you may need to configure [AWS CLI credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html))
42 |
43 | - Execute `sam build` command from the root folder - this will build and package a deployable bundle.
44 |
45 | - Execute `sam deploy --guided` command and pass the following parameters in the terminal:
46 |
47 | - Stack Name [sam-app]: ops-review
48 | - AWS Region [region]: **Select the region where your resources are located**.
49 | - Parameter IsTrustedAdvisorEnabled [True]: If the target AWS Account has AWS Trusted Advisor **enabled**, set this parameter set to `true` for additional checks. In case the AWS Trusted Advisor is **not enabled** for the target AWS Account, please set this parameter to `false`.
50 | - Parameter Functions [All]: Leave this parameter as defaul `All` to run a review of all functions in the region **OR** pass a **commadelimited list of function names you would like to review** (up to 40 supported).
51 | - The rest of the configuration can be left with defaults.
52 |
53 | - This process will trigger AWS CloudFormation stack deployment within your selected account. ONce the deployment is complete, the review and the report generation process will begin automatically so there is no need to trigger it.
54 |
55 | ### Step 2 - Review reports
56 |
57 | - Open the [AWS Step Functions console](https://console.aws.amazon.com/states/home) and locate the deployed state machine - its name will be provided in the terminal outputs after SAM CLI completes deployment or it can be found within Outputs tab of the deployed CloudFormation stack in CloudFormation console.
58 | - Locate the latest execution withing the **Executions** section and click on it to open it.
59 | - Navigate to the list of events and locate the **last** execution event called **ExecutionSucceeded** or you can click on the last execution stap in the workflow diagram. The body of this event will contain an S3 presigned URL (valid for an hour), that can be used to access the generated reports.
60 | - Copy the presigned URL and paste it into a browser to download the report file.
61 | - ALternatively you can access the report.html file from every run in past 14 days from the deployed S3 bucket (can be located through the CloudFormation Stack resources or Outputs). Every run has its own timestamped folder.
62 |
63 | #### NOTE
64 |
65 | - **Viewing historical reports**
66 | - All reports and related information is saved on the ReportBucket S3 bucket within a time stamped folder (UTC time stamps).
67 | - These files can be accessed even if the presigned URL has expired.
68 | - The report is saved within report.html file within corresponding report folder.
69 |
70 |
71 | ### Triggering a report manually
72 |
73 | - **All functions** - execute the state machine from [Step Functions console](https://console.aws.amazon.com/states/home) with default or empty input. New set of presigned URLs will be created pointing to new set of reports.
74 |
75 | - **Selected functions**:
76 | - Check the Operational Review CloudFormation stack Parameters tab in the console, to see if a list of functions has been provided (if the value for Functions parameter is **All**, you will need to run the deployment again and pass a commadelimited list of function names into to the parameter)
77 | - To re-run the report for specific set of functions, the state machine input should follow the format of:
78 |
79 | ```
80 | {
81 | "Functions": ["function1", "function2", "function3"]
82 | }
83 | ```
84 | - **The list provided to the state machine doe not need to match the list provided to the deployment.**
85 |
86 | ## Resource Cleanup
87 |
88 | - Remove all files from the deployed S3 bucket (name can be located in the CloudFormation stack Outputs)
89 | - Delete the deployed CloudFormation stack using `sam delete` command from terminal from the deployment folder or delete the CloudFormation stack from console.
90 |
91 |
--------------------------------------------------------------------------------
/src/sfn/state-machine-selected.asl.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "This StateMachine fetches information related to Lambda functions for the purpose of AWS provided Operational Review.",
3 | "StartAt": "MapSelectedFunctions",
4 | "States": {
5 | "ErrorCatchSelected": {
6 | "End": true,
7 | "Parameters": {
8 | "Body.$": "$",
9 | "Bucket": "reports-ap-southeast-1-808632258563",
10 | "Key.$": "States.Format('error-selected-{}.json', $$.State.EnteredTime)"
11 | },
12 | "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
13 | "Type": "Task"
14 | },
15 | "MapSelectedFunctions": {
16 | "Catch": [
17 | {
18 | "Comment": "Function not found error.",
19 | "ErrorEquals": [
20 | "States.TaskFailed"
21 | ],
22 | "Next": "ErrorCatchSelected"
23 | }
24 | ],
25 | "ItemProcessor": {
26 | "ProcessorConfig": {
27 | "Mode": "INLINE"
28 | },
29 | "StartAt": "GetFunction",
30 | "States": {
31 | "GetFunction": {
32 | "InputPath": "$",
33 | "Next": "SelectedGetFunctionConcurrency",
34 | "Parameters": {
35 | "FunctionName.$": "$"
36 | },
37 | "Resource": "arn:aws:states:::aws-sdk:lambda:getFunction",
38 | "Type": "Task"
39 | },
40 | "IsVpcEnabledChoiceSelected": {
41 | "Choices": [
42 | {
43 | "And": [
44 | {
45 | "Variable": "$.Configuration.VpcConfig.VpcId",
46 | "IsPresent": true
47 | },
48 | {
49 | "Not": {
50 | "Variable": "$.Configuration.VpcConfig.VpcId",
51 | "StringMatches": ""
52 | }
53 | }
54 | ],
55 | "Next": "IsRuntimePresentVpcEnabledSelected"
56 | }
57 | ],
58 | "Default": "IsRuntimePresentNoVpcEnabledSelected",
59 | "Type": "Choice"
60 | },
61 | "IsRuntimePresentNoVpcEnabledSelected": {
62 | "Type": "Choice",
63 | "Choices": [
64 | {
65 | "Not": {
66 | "Variable": "$.Configuration.Runtime",
67 | "IsPresent": true
68 | },
69 | "Next": "NoVpcEnabledSelectedAddRuntime"
70 | }
71 | ],
72 | "Default": "NoVpcEnabledSelected"
73 | },
74 | "NoVpcEnabledSelectedAddRuntime": {
75 | "Type": "Pass",
76 | "End": true,
77 | "Parameters": {
78 | "Architectures.$": "States.ArrayGetItem($.Configuration.Architectures, 0)",
79 | "CodeSize.$": "$.Configuration.CodeSize",
80 | "EphemeralStorage.$": "$.Configuration.EphemeralStorage.Size",
81 | "FunctionArn.$": "$.Configuration.FunctionArn",
82 | "FunctionName.$": "$.Configuration.FunctionName",
83 | "MemorySize.$": "$.Configuration.MemorySize",
84 | "PackageType.$": "$.Configuration.PackageType",
85 | "ProvisionedConcurrencyConfigs.$": "$.Configuration.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
86 | "ReservedConcurrency.$": "$.Configuration.FunctionReservedConcurrency",
87 | "Role.$": "$.Configuration.Role",
88 | "Runtime": "Container",
89 | "SnapStartOn.$": "$.Configuration.SnapStart.ApplyOn",
90 | "SnapStartOptimizationStatus.$": "$.Configuration.SnapStart.OptimizationStatus",
91 | "Timeout.$": "$.Configuration.Timeout",
92 | "TracingConfig.$": "$.Configuration.TracingConfig.Mode"
93 | }
94 | },
95 | "IsRuntimePresentVpcEnabledSelected": {
96 | "Type": "Choice",
97 | "Choices": [
98 | {
99 | "Not": {
100 | "Variable": "$.Configuration.Runtime",
101 | "IsPresent": true
102 | },
103 | "Next": "VpcEnabledSelectedAddRuntime"
104 | }
105 | ],
106 | "Default": "IsVpcEnabledSelected"
107 | },
108 | "VpcEnabledSelectedAddRuntime": {
109 | "Type": "Pass",
110 | "End": true,
111 | "Parameters": {
112 | "Architectures.$": "States.ArrayGetItem($.Configuration.Architectures, 0)",
113 | "CodeSize.$": "$.Configuration.CodeSize",
114 | "EphemeralStorage.$": "$.Configuration.EphemeralStorage.Size",
115 | "FunctionArn.$": "$.Configuration.FunctionArn",
116 | "FunctionName.$": "$.Configuration.FunctionName",
117 | "MemorySize.$": "$.Configuration.MemorySize",
118 | "PackageType.$": "$.Configuration.PackageType",
119 | "ProvisionedConcurrencyConfigs.$": "$.Configuration.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
120 | "ReservedConcurrency.$": "$.Configuration.FunctionReservedConcurrency",
121 | "Role.$": "$.Configuration.Role",
122 | "Runtime": "Container",
123 | "SecurityGroupIds.$": "$.Configuration.VpcConfig.SecurityGroupIds",
124 | "SnapStartOn.$": "$.Configuration.SnapStart.ApplyOn",
125 | "SnapStartOptimizationStatus.$": "$.Configuration.SnapStart.OptimizationStatus",
126 | "SubnetIds.$": "$.Configuration.VpcConfig.SubnetIds",
127 | "Timeout.$": "$.Configuration.Timeout",
128 | "TracingConfig.$": "$.Configuration.TracingConfig.Mode",
129 | "VpcId.$": "$.Configuration.VpcConfig.VpcId"
130 | }
131 | },
132 | "IsVpcEnabledSelected": {
133 | "End": true,
134 | "Parameters": {
135 | "Architectures.$": "States.ArrayGetItem($.Configuration.Architectures, 0)",
136 | "CodeSize.$": "$.Configuration.CodeSize",
137 | "EphemeralStorage.$": "$.Configuration.EphemeralStorage.Size",
138 | "FunctionArn.$": "$.Configuration.FunctionArn",
139 | "FunctionName.$": "$.Configuration.FunctionName",
140 | "MemorySize.$": "$.Configuration.MemorySize",
141 | "PackageType.$": "$.Configuration.PackageType",
142 | "ProvisionedConcurrencyConfigs.$": "$.Configuration.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
143 | "ReservedConcurrency.$": "$.Configuration.FunctionReservedConcurrency",
144 | "Role.$": "$.Configuration.Role",
145 | "Runtime.$": "$.Configuration.Runtime",
146 | "SecurityGroupIds.$": "$.Configuration.VpcConfig.SecurityGroupIds",
147 | "SnapStartOn.$": "$.Configuration.SnapStart.ApplyOn",
148 | "SnapStartOptimizationStatus.$": "$.Configuration.SnapStart.OptimizationStatus",
149 | "SubnetIds.$": "$.Configuration.VpcConfig.SubnetIds",
150 | "Timeout.$": "$.Configuration.Timeout",
151 | "TracingConfig.$": "$.Configuration.TracingConfig.Mode",
152 | "VpcId.$": "$.Configuration.VpcConfig.VpcId"
153 | },
154 | "Type": "Pass"
155 | },
156 | "ListProvisionedConcurrencyConfigs": {
157 | "Next": "IsVpcEnabledChoiceSelected",
158 | "Parameters": {
159 | "FunctionName.$": "$.Configuration.FunctionName"
160 | },
161 | "Resource": "arn:aws:states:::aws-sdk:lambda:listProvisionedConcurrencyConfigs",
162 | "ResultPath": "$.Configuration.FunctionProvisionedConcurrency",
163 | "Type": "Task"
164 | },
165 | "NoVpcEnabledSelected": {
166 | "End": true,
167 | "Parameters": {
168 | "Architectures.$": "States.ArrayGetItem($.Configuration.Architectures, 0)",
169 | "CodeSize.$": "$.Configuration.CodeSize",
170 | "EphemeralStorage.$": "$.Configuration.EphemeralStorage.Size",
171 | "FunctionArn.$": "$.Configuration.FunctionArn",
172 | "FunctionName.$": "$.Configuration.FunctionName",
173 | "MemorySize.$": "$.Configuration.MemorySize",
174 | "PackageType.$": "$.Configuration.PackageType",
175 | "ProvisionedConcurrencyConfigs.$": "$.Configuration.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
176 | "ReservedConcurrency.$": "$.Configuration.FunctionReservedConcurrency",
177 | "Role.$": "$.Configuration.Role",
178 | "Runtime.$": "$.Configuration.Runtime",
179 | "SnapStartOn.$": "$.Configuration.SnapStart.ApplyOn",
180 | "SnapStartOptimizationStatus.$": "$.Configuration.SnapStart.OptimizationStatus",
181 | "Timeout.$": "$.Configuration.Timeout",
182 | "TracingConfig.$": "$.Configuration.TracingConfig.Mode"
183 | },
184 | "Type": "Pass"
185 | },
186 | "SelectedGetFunctionConcurrency": {
187 | "Next": "ListProvisionedConcurrencyConfigs",
188 | "Parameters": {
189 | "FunctionName.$": "$.Configuration.FunctionName"
190 | },
191 | "Resource": "arn:aws:states:::aws-sdk:lambda:getFunctionConcurrency",
192 | "ResultPath": "$.Configuration.FunctionReservedConcurrency",
193 | "Type": "Task"
194 | }
195 | }
196 | },
197 | "ItemsPath": "$.Functions",
198 | "Next": "S3PutSelected",
199 | "Type": "Map"
200 | },
201 | "S3PutSelected": {
202 | "Next": "GetRecommendations",
203 | "Parameters": {
204 | "Body.$": "$",
205 | "Bucket": "${ReportBucketName}",
206 | "Key.$": "States.Format('review-{}/selected-functions.json', $$.State.EnteredTime)"
207 | },
208 | "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
209 | "Type": "Task",
210 | "ResultSelector": {
211 | "Bucket": "${ReportBucketName}",
212 | "Prefix.$": "States.Format('review-{}', $$.State.EnteredTime)",
213 | "FunctionsObject": "selected-functions.json"
214 | }
215 | },
216 | "GetRecommendations": {
217 | "Type": "Task",
218 | "Parameters": {},
219 | "Resource": "arn:aws:states:::aws-sdk:computeoptimizer:getLambdaFunctionRecommendations",
220 | "ResultPath": "$.Recommendations",
221 | "Next": "RecommendationsToS3"
222 | },
223 | "RecommendationsToS3": {
224 | "Type": "Task",
225 | "Parameters": {
226 | "Body.$": "$",
227 | "Bucket.$": "$.Bucket",
228 | "Key.$": "States.Format('{}/recommendations.json', $.Prefix)"
229 | },
230 | "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
231 | "Next": "FilterRecommendationsOutput",
232 | "ResultPath": null
233 | },
234 | "FilterRecommendationsOutput": {
235 | "Type": "Pass",
236 | "Next": "ListEventSourceMappings",
237 | "Parameters": {
238 | "Bucket.$": "$.Bucket",
239 | "FunctionsObject.$": "$.FunctionsObject",
240 | "Prefix.$": "$.Prefix"
241 | }
242 | },
243 | "ListEventSourceMappings": {
244 | "Type": "Task",
245 | "Next": "EventSourceMappingsToS3",
246 | "Parameters": {},
247 | "Resource": "arn:aws:states:::aws-sdk:lambda:listEventSourceMappings",
248 | "ResultPath": "$.EventSourceMappings",
249 | "Retry": [
250 | {
251 | "ErrorEquals": [
252 | "States.ALL"
253 | ],
254 | "IntervalSeconds": 1,
255 | "MaxAttempts": 5,
256 | "BackoffRate": 1.5
257 | }
258 | ]
259 | },
260 | "EventSourceMappingsToS3": {
261 | "Type": "Task",
262 | "Next": "RemoveMappingsFromInput",
263 | "Parameters": {
264 | "Body.$": "$",
265 | "Bucket.$": "$.Bucket",
266 | "Key.$": "States.Format('{}/esms.json', $.Prefix)"
267 | },
268 | "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
269 | "ResultPath": null
270 | },
271 | "RemoveMappingsFromInput": {
272 | "Type": "Pass",
273 | "Next": "GenerateReport",
274 | "Parameters": {
275 | "Bucket.$": "$.Bucket",
276 | "FunctionsObject.$": "$.FunctionsObject",
277 | "Prefix.$": "$.Prefix"
278 | }
279 | },
280 | "GenerateReport": {
281 | "Type": "Task",
282 | "End": true,
283 | "Resource": "arn:aws:states:::lambda:invoke",
284 | "Parameters": {
285 | "Payload.$": "$",
286 | "FunctionName": "${GenerateLambda}"
287 | },
288 | "Retry": [
289 | {
290 | "ErrorEquals": [
291 | "Lambda.ServiceException",
292 | "Lambda.AWSLambdaException",
293 | "Lambda.SdkClientException",
294 | "Lambda.TooManyRequestsException"
295 | ],
296 | "IntervalSeconds": 2,
297 | "MaxAttempts": 6,
298 | "BackoffRate": 2
299 | }
300 | ],
301 | "OutputPath": "$.Payload"
302 | }
303 | }
304 |
305 | }
--------------------------------------------------------------------------------
/template.yaml:
--------------------------------------------------------------------------------
1 | # Serverless Ops Review
2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | # SPDX-License-Identifier: MIT-0
4 |
5 | Transform: AWS::Serverless-2016-10-31
6 |
7 | Globals:
8 | Function:
9 | Runtime: python3.11
10 |
11 | Parameters:
12 | IsTrustedAdvisorEnabled:
13 | AllowedValues:
14 | - true
15 | - false
16 | Type: String
17 | Default: true
18 |
19 | Functions:
20 | Type: CommaDelimitedList
21 | Default: All
22 | Description: If you want to check only specific functions, please insert their names here in the format of function1,function2,function3, etc. If left with default value, the check will run on all functions in the region.
23 |
24 | Conditions:
25 | FunctionsNotSet: !Equals [!Select [0, !Ref Functions], "All"]
26 | FunctionsSet: !Not [!Equals [!Select [0, !Ref Functions], "All"]]
27 |
28 | Mappings:
29 | ActRuntimes:
30 | AllRuntimes:
31 | Runtimes: 'python3.10,python3.11,python3.12,nodejs20.x,nodejs18.x,java21,java17,dotnet7,ruby3.2,provided.al2,provided.al2023,Container'
32 |
33 | Resources:
34 | ############################################### Resources for All functions reporting ###############################################
35 | MainStateMachineAll:
36 | Condition: FunctionsNotSet
37 | Type: AWS::Serverless::StateMachine
38 | Properties:
39 | Type: EXPRESS
40 | DefinitionUri: src/sfn/state-machine.asl.json
41 | Role: !GetAtt MainStateMachineRole.Arn
42 | DefinitionSubstitutions:
43 | ReportBucketName: !Ref ReportBucket
44 | GenerateLambda: !GetAtt GenerateReportFunctionAll.Arn
45 | EsmLambda: !GetAtt FetchESMsFunctionAll.Arn
46 | Logging:
47 | Level: ALL
48 | IncludeExecutionData: true
49 | Destinations:
50 | - CloudWatchLogsLogGroup:
51 | LogGroupArn: !GetAtt SfnLogGroup.Arn
52 | Events:
53 | CfnCompleteTrigger:
54 | Type: EventBridgeRule
55 | Properties:
56 | Pattern:
57 | source: [aws.cloudformation]
58 | detail-type: [CloudFormation Stack Status Change]
59 | resources: [!Ref AWS::StackId]
60 | detail:
61 | stack-id: [!Ref AWS::StackId]
62 | status-details:
63 | status: [CREATE_COMPLETE, UPDATE_COMPLETE]
64 |
65 | GenerateReportFunctionAll:
66 | Condition: FunctionsNotSet
67 | Type: AWS::Serverless::Function
68 | Properties:
69 | MemorySize: 256
70 | Architectures:
71 | - arm64
72 | Handler: app.handler
73 | Timeout: 10
74 | CodeUri: src/lambda/function-generate
75 | Environment:
76 | Variables:
77 | S3_BUCKET: !Ref ReportBucket
78 | REGION: !Ref AWS::Region
79 | ACCOUNT: !Ref AWS::AccountId
80 | ACT_RUNTIMES: !FindInMap [ ActRuntimes, AllRuntimes, Runtimes ]
81 | STACK_NAME: !Ref AWS::StackName
82 | TA_ENABLED: !Ref IsTrustedAdvisorEnabled
83 | Policies:
84 | - Version: '2012-10-17'
85 | Statement:
86 | - Effect: Allow
87 | Action:
88 | - 's3:GetObject'
89 | - 's3:PutObject'
90 | Resource: !Sub ${ReportBucket.Arn}/*
91 | - Effect: Allow
92 | Action:
93 | - 's3:ListBucket'
94 | Resource: !Sub ${ReportBucket.Arn}
95 | - Effect: Allow
96 | Action:
97 | - 'ec2:DescribeSubnets'
98 | Resource: '*'
99 | - Effect: Allow
100 | Action:
101 | - 'support:DescribeTrustedAdvisorCheckResult'
102 | Resource: '*'
103 | FetchESMsFunctionAll:
104 | Condition: FunctionsNotSet
105 | Type: AWS::Serverless::Function
106 | Properties:
107 | MemorySize: 256
108 | Architectures:
109 | - arm64
110 | Handler: app.handler
111 | Timeout: 10
112 | CodeUri: src/lambda/function-fetch-esms
113 | Environment:
114 | Variables:
115 | S3_BUCKET: !Ref ReportBucket
116 | Policies:
117 | - Version: '2012-10-17'
118 | Statement:
119 | - Effect: Allow
120 | Action:
121 | - 's3:GetObject'
122 | - 's3:PutObject'
123 | Resource: !Sub ${ReportBucket.Arn}/*
124 | - Effect: Allow
125 | Action:
126 | - 'lambda:ListEventSourceMappings'
127 | Resource: '*'
128 |
129 |
130 | GenerateReportFunctionAllLogGroup:
131 | Condition: FunctionsNotSet
132 | Type: AWS::Logs::LogGroup
133 | Properties:
134 | LogGroupName: !Sub /aws/lambda/${GenerateReportFunctionAll}
135 | RetentionInDays: 14
136 |
137 | FetchESMsFunctionAllLogGroup:
138 | Condition: FunctionsNotSet
139 | Type: AWS::Logs::LogGroup
140 | Properties:
141 | LogGroupName: !Sub /aws/lambda/${FetchESMsFunctionAll}
142 | RetentionInDays: 14
143 |
144 |
145 |
146 |
147 |
148 | #############################################################################################################################################
149 |
150 | MainStateMachineSelected:
151 | Condition: FunctionsSet
152 | Type: AWS::Serverless::StateMachine
153 | Properties:
154 | Type: EXPRESS
155 | DefinitionUri: src/sfn/state-machine-selected.asl.json
156 | Role: !GetAtt MainStateMachineRole.Arn
157 | DefinitionSubstitutions:
158 | ReportBucketName: !Ref ReportBucket
159 | GenerateLambda: !GetAtt GenerateReportFunctionSelected.Arn
160 | Logging:
161 | Level: ALL
162 | IncludeExecutionData: true
163 | Destinations:
164 | - CloudWatchLogsLogGroup:
165 | LogGroupArn: !GetAtt SfnLogGroup.Arn
166 |
167 | FunctionSelected:
168 | Condition: FunctionsSet
169 | Type: AWS::Serverless::Function
170 | Properties:
171 | Architectures:
172 | - arm64
173 | Handler: app.handler
174 | CodeUri: src/lambda/function-selected
175 | Environment:
176 | Variables:
177 | SFN_SELECTED: !Ref MainStateMachineSelected
178 | Policies:
179 | - Version: '2012-10-17'
180 | Statement:
181 | - Effect: Allow
182 | Action:
183 | - 'states:StartExecution'
184 | Resource: !Ref MainStateMachineSelected
185 | - Version: '2012-10-17'
186 | Statement:
187 | - Effect: Allow
188 | Action:
189 | - 'cloudformation:DescribeStacks'
190 | Resource: !Ref AWS::StackId
191 | Events:
192 | CfnCompleteTrigger:
193 | Type: EventBridgeRule
194 | Properties:
195 | Pattern:
196 | source: [aws.cloudformation]
197 | detail-type: [CloudFormation Stack Status Change]
198 | resources: [!Ref AWS::StackId]
199 | detail:
200 | stack-id: [!Ref AWS::StackId]
201 | status-details:
202 | status: [CREATE_COMPLETE, UPDATE_COMPLETE]
203 |
204 | FunctionSelectedLogGroup:
205 | Condition: FunctionsSet
206 | Type: AWS::Logs::LogGroup
207 | Properties:
208 | LogGroupName: !Sub /aws/lambda/${FunctionSelected}
209 | RetentionInDays: 14
210 |
211 | GenerateReportFunctionSelected:
212 | Condition: FunctionsSet
213 | Type: AWS::Serverless::Function
214 | Properties:
215 | MemorySize: 256
216 | Architectures:
217 | - arm64
218 | Handler: app.handler
219 | Timeout: 10
220 | CodeUri: src/lambda/function-generate-selected
221 | Environment:
222 | Variables:
223 | S3_BUCKET: !Ref ReportBucket
224 | REGION: !Ref AWS::Region
225 | ACCOUNT: !Ref AWS::AccountId
226 | ACT_RUNTIMES: !FindInMap [ ActRuntimes, AllRuntimes, Runtimes ]
227 | STACK_NAME: !Ref AWS::StackName
228 | TA_ENABLED: !Ref IsTrustedAdvisorEnabled
229 | Policies:
230 | - Version: '2012-10-17'
231 | Statement:
232 | - Effect: Allow
233 | Action:
234 | - 's3:GetObject'
235 | - 's3:PutObject'
236 | Resource: !Sub ${ReportBucket.Arn}/*
237 | - Effect: Allow
238 | Action:
239 | - 's3:ListBucket'
240 | Resource: !Sub ${ReportBucket.Arn}
241 | - Effect: Allow
242 | Action:
243 | - 'ec2:DescribeSubnets'
244 | Resource: '*'
245 | - Effect: Allow
246 | Action:
247 | - 'support:DescribeTrustedAdvisorCheckResult'
248 | Resource: '*'
249 |
250 | GenerateReportFunctionSelectedLogGroup:
251 | Condition: FunctionsSet
252 | Type: AWS::Logs::LogGroup
253 | Properties:
254 | LogGroupName: !Sub /aws/lambda/${GenerateReportFunctionSelected}
255 | RetentionInDays: 14
256 | #############################################################################################################################################
257 |
258 |
259 |
260 | ############################################### Shared Resources ###############################################
261 |
262 |
263 |
264 | SfnLogGroup:
265 | Type: AWS::Logs::LogGroup
266 | Properties:
267 | LogGroupName: /aws/vendedlogs/states/Ops-Review-Sfn
268 | RetentionInDays: 14
269 |
270 | ReportBucket:
271 | Type: AWS::S3::Bucket
272 | Properties:
273 | BucketName: !Sub reports-${AWS::Region}-${AWS::AccountId}
274 | LifecycleConfiguration:
275 | Rules:
276 | - Id: DeleteOldObjects
277 | Status: Enabled
278 | ExpirationInDays: 30
279 |
280 | MainStateMachineRole:
281 | Type: 'AWS::IAM::Role'
282 | Properties:
283 | AssumeRolePolicyDocument:
284 | Version: "2012-10-17"
285 | Statement:
286 | - Effect: Allow
287 | Principal:
288 | Service:
289 | - states.amazonaws.com
290 | Action:
291 | - 'sts:AssumeRole'
292 | Path: /
293 | Policies:
294 | - PolicyName: CloudWatchLogsDeliveryFullAccessPolicy
295 | PolicyDocument:
296 | Version: "2012-10-17"
297 | Statement:
298 | - Effect: Allow
299 | Action:
300 | - 'logs:CreateLogDelivery'
301 | - 'logs:GetLogDelivery'
302 | - 'logs:UpdateLogDelivery'
303 | - 'logs:DeleteLogDelivery'
304 | - 'logs:ListLogDeliveries'
305 | - 'logs:PutResourcePolicy'
306 | - 'logs:DescribeResourcePolicies'
307 | - 'logs:DescribeLogGroups'
308 | - 'logs:CreateLogGroup'
309 | - 'logs:CreateLogStream'
310 | Resource: '*'
311 | - PolicyName: S3Put
312 | PolicyDocument:
313 | Version: "2012-10-17"
314 | Statement:
315 | - Effect: Allow
316 | Action:
317 | - 's3:PutObject'
318 | Resource: !Sub arn:aws:s3:::${ReportBucket}/*
319 | - PolicyName: LambdaRead
320 | PolicyDocument:
321 | Version: "2012-10-17"
322 | Statement:
323 | -
324 | Effect: Allow
325 | Action:
326 | - 'lambda:ListFunctions'
327 | - 'lambda:GetFunction'
328 | - 'lambda:GetFunctionConcurrency'
329 | - 'lambda:ListProvisionedConcurrencyConfigs'
330 | - 'lambda:ListEventSourceMappings'
331 | Resource: '*'
332 | - PolicyName: InvokeFunctions
333 | PolicyDocument:
334 | Version: "2012-10-17"
335 | Statement:
336 | -
337 | Effect: Allow
338 | Action:
339 | - 'lambda:InvokeFunction'
340 | Resource: '*'
341 | - PolicyName: ComputeOptimizerReadLambda
342 | PolicyDocument:
343 | Version: "2012-10-17"
344 | Statement:
345 | -
346 | Effect: Allow
347 | Action:
348 | - 'compute-optimizer:GetLambdaFunctionRecommendations'
349 | Resource: '*'
350 | - PolicyName: ServiceQuotas
351 | PolicyDocument:
352 | Version: "2012-10-17"
353 | Statement:
354 | -
355 | Effect: Allow
356 | Action:
357 | - 'servicequotas:ListServiceQuotas'
358 | - 'servicequotas:GetServiceQuotas'
359 | Resource: '*'
360 |
361 |
362 | #############################################################################################################################################
363 |
364 | Outputs:
365 | S3Bucket:
366 | Value: !Ref ReportBucket
367 | Description: S3 bucket that contains output files from processing of the Operational Review Tool. Each execution of the tool stores information inside of a timestamped prefix.
368 |
369 | StateMachineAllFunctions:
370 | Value: !GetAtt MainStateMachineAll.Name
371 | Description: Step Functions State Machine executing the processing of the review either on selected functions or on all functions in the region.
372 | Condition: FunctionsNotSet
373 |
374 | StateMachineSelectedFunctions:
375 | Value: !GetAtt MainStateMachineSelected.Name
376 | Description: Step Functions State Machine executing the processing of the review either on selected functions or on all functions in the region.
377 | Condition: FunctionsSet
--------------------------------------------------------------------------------
/src/sfn/state-machine.asl.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "This StateMachine fetches information related to Lambda functions for the purpose of AWS provided Operational Review.",
3 | "StartAt": "GenerateFolder",
4 | "States": {
5 | "GenerateFolder": {
6 | "Type": "Pass",
7 | "Next": "ListFunctions",
8 | "Parameters": {
9 | "FolderKey.$": "States.Format('review-{}', $$.State.EnteredTime)"
10 | }
11 | },
12 | "ListFunctions": {
13 | "Next": "IsNextMarkerPresent",
14 | "Parameters": {
15 | "FunctionVersion": "ALL"
16 | },
17 | "Resource": "arn:aws:states:::aws-sdk:lambda:listFunctions",
18 | "Type": "Task",
19 | "ResultPath": "$.SetOfFunctions",
20 | "Retry": [
21 | {
22 | "ErrorEquals": [
23 | "States.ALL"
24 | ],
25 | "BackoffRate": 2,
26 | "IntervalSeconds": 1,
27 | "MaxAttempts": 3
28 | }
29 | ]
30 | },
31 | "IsNextMarkerPresent": {
32 | "Type": "Choice",
33 | "Choices": [
34 | {
35 | "Not": {
36 | "Variable": "$.SetOfFunctions.NextMarker",
37 | "IsPresent": true
38 | },
39 | "Next": "MarkerNotPresent"
40 | }
41 | ],
42 | "Default": "MarkerPresent"
43 | },
44 | "MarkerPresent": {
45 | "Type": "Pass",
46 | "Parameters": {
47 | "FolderKey.$": "$.FolderKey",
48 | "NextMarker.$": "$.SetOfFunctions.NextMarker",
49 | "Functions.$": "$.SetOfFunctions.Functions"
50 | },
51 | "Next": "MapAllFunctions"
52 | },
53 | "MarkerNotPresent": {
54 | "Type": "Pass",
55 | "Parameters": {
56 | "FolderKey.$": "$.FolderKey",
57 | "NextMarker": "None",
58 | "Functions.$": "$.SetOfFunctions.Functions"
59 | },
60 | "Next": "MapAllFunctions"
61 | },
62 | "MapAllFunctions": {
63 | "ItemProcessor": {
64 | "ProcessorConfig": {
65 | "Mode": "INLINE"
66 | },
67 | "StartAt": "AllFunctionsListProvisionedConcurrency",
68 | "States": {
69 | "AllFunctionsGetFunctionConcurrency": {
70 | "Next": "IsVpcEnabledChoice",
71 | "Parameters": {
72 | "FunctionName.$": "$.FunctionName"
73 | },
74 | "Resource": "arn:aws:states:::aws-sdk:lambda:getFunctionConcurrency",
75 | "ResultPath": "$.FunctionReservedConcurrency",
76 | "Type": "Task"
77 | },
78 | "AllFunctionsListProvisionedConcurrency": {
79 | "Next": "AllFunctionsGetFunctionConcurrency",
80 | "Parameters": {
81 | "FunctionName.$": "$.FunctionName"
82 | },
83 | "Resource": "arn:aws:states:::aws-sdk:lambda:listProvisionedConcurrencyConfigs",
84 | "ResultPath": "$.FunctionProvisionedConcurrency",
85 | "Type": "Task"
86 | },
87 | "IsVpcEnabled": {
88 | "End": true,
89 | "Parameters": {
90 | "Architectures.$": "States.ArrayGetItem($.Architectures, 0)",
91 | "CodeSize.$": "$.CodeSize",
92 | "EphemeralStorage.$": "$.EphemeralStorage.Size",
93 | "FunctionArn.$": "$.FunctionArn",
94 | "FunctionName.$": "$.FunctionName",
95 | "MemorySize.$": "$.MemorySize",
96 | "PackageType.$": "$.PackageType",
97 | "ProvisionedConcurrencyConfigs.$": "$.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
98 | "ReservedConcurrency.$": "$.FunctionReservedConcurrency",
99 | "Role.$": "$.Role",
100 | "Runtime.$": "$.Runtime",
101 | "SecurityGroupIds.$": "$.VpcConfig.SecurityGroupIds",
102 | "SnapStartOn.$": "$.SnapStart.ApplyOn",
103 | "SnapStartOptimizationStatus.$": "$.SnapStart.OptimizationStatus",
104 | "SubnetIds.$": "$.VpcConfig.SubnetIds",
105 | "Timeout.$": "$.Timeout",
106 | "TracingConfig.$": "$.TracingConfig.Mode",
107 | "VpcId.$": "$.VpcConfig.VpcId"
108 | },
109 | "Type": "Pass"
110 | },
111 | "IsVpcEnabledChoice": {
112 | "Choices": [
113 | {
114 | "And": [
115 | {
116 | "Variable": "$.VpcConfig.VpcId",
117 | "IsPresent": true
118 | },
119 | {
120 | "Not": {
121 | "Variable": "$.VpcConfig.VpcId",
122 | "StringMatches": ""
123 | }
124 | }
125 | ],
126 | "Next": "IsRuntimePresentVpcEnabled"
127 | }
128 | ],
129 | "Default": "IsRuntimePresentNoVpcEnabled",
130 | "Type": "Choice"
131 | },
132 | "IsRuntimePresentNoVpcEnabled": {
133 | "Type": "Choice",
134 | "Choices": [
135 | {
136 | "Not": {
137 | "Variable": "$.Runtime",
138 | "IsPresent": true
139 | },
140 | "Next": "NoVpcEnaledAddRuntime"
141 | }
142 | ],
143 | "Default": "NoVpcEnabled"
144 | },
145 | "NoVpcEnaledAddRuntime": {
146 | "Type": "Pass",
147 | "End": true,
148 | "Parameters": {
149 | "Architectures.$": "States.ArrayGetItem($.Architectures, 0)",
150 | "CodeSize.$": "$.CodeSize",
151 | "EphemeralStorage.$": "$.EphemeralStorage.Size",
152 | "FunctionArn.$": "$.FunctionArn",
153 | "FunctionName.$": "$.FunctionName",
154 | "MemorySize.$": "$.MemorySize",
155 | "PackageType.$": "$.PackageType",
156 | "ProvisionedConcurrencyConfigs.$": "$.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
157 | "ReservedConcurrency.$": "$.FunctionReservedConcurrency",
158 | "Role.$": "$.Role",
159 | "Runtime": "Container",
160 | "SnapStartOn.$": "$.SnapStart.ApplyOn",
161 | "SnapStartOptimizationStatus.$": "$.SnapStart.OptimizationStatus",
162 | "Timeout.$": "$.Timeout",
163 | "TracingConfig.$": "$.TracingConfig.Mode"
164 | }
165 | },
166 | "IsRuntimePresentVpcEnabled": {
167 | "Type": "Choice",
168 | "Choices": [
169 | {
170 | "Not": {
171 | "Variable": "$.Runtime",
172 | "IsPresent": true
173 | },
174 | "Next": "IsVpcEnaledAddRuntime"
175 | }
176 | ],
177 | "Default": "IsVpcEnabled"
178 | },
179 | "IsVpcEnaledAddRuntime": {
180 | "Type": "Pass",
181 | "End": true,
182 | "Parameters": {
183 | "Architectures.$": "States.ArrayGetItem($.Architectures, 0)",
184 | "CodeSize.$": "$.CodeSize",
185 | "EphemeralStorage.$": "$.EphemeralStorage.Size",
186 | "FunctionArn.$": "$.FunctionArn",
187 | "FunctionName.$": "$.FunctionName",
188 | "MemorySize.$": "$.MemorySize",
189 | "PackageType.$": "$.PackageType",
190 | "ProvisionedConcurrencyConfigs.$": "$.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
191 | "ReservedConcurrency.$": "$.FunctionReservedConcurrency",
192 | "Role.$": "$.Role",
193 | "Runtime": "Container",
194 | "SecurityGroupIds.$": "$.VpcConfig.SecurityGroupIds",
195 | "SnapStartOn.$": "$.SnapStart.ApplyOn",
196 | "SnapStartOptimizationStatus.$": "$.SnapStart.OptimizationStatus",
197 | "SubnetIds.$": "$.VpcConfig.SubnetIds",
198 | "Timeout.$": "$.Timeout",
199 | "TracingConfig.$": "$.TracingConfig.Mode",
200 | "VpcId.$": "$.VpcConfig.VpcId"
201 | }
202 | },
203 | "NoVpcEnabled": {
204 | "End": true,
205 | "Parameters": {
206 | "Architectures.$": "States.ArrayGetItem($.Architectures, 0)",
207 | "CodeSize.$": "$.CodeSize",
208 | "EphemeralStorage.$": "$.EphemeralStorage.Size",
209 | "FunctionArn.$": "$.FunctionArn",
210 | "FunctionName.$": "$.FunctionName",
211 | "MemorySize.$": "$.MemorySize",
212 | "PackageType.$": "$.PackageType",
213 | "ProvisionedConcurrencyConfigs.$": "$.FunctionProvisionedConcurrency.ProvisionedConcurrencyConfigs",
214 | "ReservedConcurrency.$": "$.FunctionReservedConcurrency",
215 | "Role.$": "$.Role",
216 | "Runtime.$": "$.Runtime",
217 | "SnapStartOn.$": "$.SnapStart.ApplyOn",
218 | "SnapStartOptimizationStatus.$": "$.SnapStart.OptimizationStatus",
219 | "Timeout.$": "$.Timeout",
220 | "TracingConfig.$": "$.TracingConfig.Mode"
221 | },
222 | "Type": "Pass"
223 | }
224 | }
225 | },
226 | "ItemsPath": "$.Functions",
227 | "Type": "Map",
228 | "Next": "RemoveDuplicates",
229 | "Retry": [
230 | {
231 | "ErrorEquals": [
232 | "States.ALL"
233 | ],
234 | "BackoffRate": 2,
235 | "IntervalSeconds": 0,
236 | "MaxAttempts": 3
237 | }
238 | ],
239 | "ResultPath": "$.ProcessedFunctions"
240 | },
241 | "RemoveDuplicates": {
242 | "Type": "Pass",
243 | "Next": "S3PutAllFunctions",
244 | "Parameters": {
245 | "FolderKey.$": "$.FolderKey",
246 | "NextMarker.$": "$.NextMarker",
247 | "Functions.$": "$.ProcessedFunctions"
248 | }
249 | },
250 | "ListFunctionsCntd": {
251 | "Next": "IsNextMarkerPresent",
252 | "Parameters": {
253 | "FunctionVersion": "ALL",
254 | "Marker.$": "$.NextMarker"
255 | },
256 | "Resource": "arn:aws:states:::aws-sdk:lambda:listFunctions",
257 | "Type": "Task",
258 | "ResultPath": "$.SetOfFunctions",
259 | "Retry": [
260 | {
261 | "ErrorEquals": [
262 | "States.ALL"
263 | ],
264 | "BackoffRate": 2,
265 | "IntervalSeconds": 2,
266 | "MaxAttempts": 4
267 | }
268 | ]
269 | },
270 | "S3PutAllFunctions": {
271 | "Next": "FilterParams",
272 | "Parameters": {
273 | "Body.$": "$.Functions",
274 | "Bucket": "${ReportBucketName}",
275 | "Key.$": "States.Format('{}/functions/{}.json', $.FolderKey, States.UUID())"
276 | },
277 | "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
278 | "Type": "Task",
279 | "ResultPath": "$.Output",
280 | "Retry": [
281 | {
282 | "ErrorEquals": [
283 | "States.ALL"
284 | ],
285 | "BackoffRate": 2,
286 | "IntervalSeconds": 1,
287 | "MaxAttempts": 2
288 | }
289 | ]
290 | },
291 | "FilterParams": {
292 | "Type": "Pass",
293 | "Next": "RerunCheck",
294 | "Parameters": {
295 | "Bucket": "${ReportBucketName}",
296 | "FolderKey.$": "$.FolderKey",
297 | "NextMarker.$": "$.NextMarker"
298 | }
299 | },
300 | "RerunCheck": {
301 | "Type": "Choice",
302 | "Choices": [
303 | {
304 | "Not": {
305 | "Variable": "$.NextMarker",
306 | "StringEquals": "None"
307 | },
308 | "Next": "Wait"
309 | }
310 | ],
311 | "Default": "GetRecommendations"
312 | },
313 | "Wait": {
314 | "Type": "Wait",
315 | "Seconds": 3,
316 | "Next": "ListFunctionsCntd"
317 | },
318 | "GetRecommendations": {
319 | "Type": "Task",
320 | "Parameters": {},
321 | "Resource": "arn:aws:states:::aws-sdk:computeoptimizer:getLambdaFunctionRecommendations",
322 | "ResultPath": "$.Recommendations",
323 | "Next": "RecommendationsToS3",
324 | "Retry": [
325 | {
326 | "ErrorEquals": [
327 | "States.TaskFailed"
328 | ],
329 | "BackoffRate": 2,
330 | "IntervalSeconds": 1,
331 | "MaxAttempts": 2
332 | }
333 | ]
334 | },
335 | "RecommendationsToS3": {
336 | "Type": "Task",
337 | "Parameters": {
338 | "Body.$": "$.Recommendations.LambdaFunctionRecommendations",
339 | "Bucket": "${ReportBucketName}",
340 | "Key.$": "States.Format('{}/recommendations.json', $.FolderKey)"
341 | },
342 | "Resource": "arn:aws:states:::aws-sdk:s3:putObject",
343 | "Next": "FilterRecommendationsOutput",
344 | "ResultPath": null,
345 | "Retry": [
346 | {
347 | "ErrorEquals": [
348 | "States.ALL"
349 | ],
350 | "BackoffRate": 2,
351 | "IntervalSeconds": 1,
352 | "MaxAttempts": 2
353 | }
354 | ]
355 | },
356 | "FilterRecommendationsOutput": {
357 | "Type": "Pass",
358 | "Next": "ListESMsAndSaveToS3",
359 | "Parameters": {
360 | "FolderKey.$": "$.FolderKey"
361 | }
362 | },
363 | "ListESMsAndSaveToS3": {
364 | "Type": "Task",
365 | "Resource": "arn:aws:states:::lambda:invoke",
366 | "OutputPath": "$.Payload",
367 | "Parameters": {
368 | "Payload.$": "$",
369 | "FunctionName": "${EsmLambda}"
370 | },
371 | "Retry": [
372 | {
373 | "ErrorEquals": [
374 | "Lambda.ServiceException",
375 | "Lambda.AWSLambdaException",
376 | "Lambda.SdkClientException",
377 | "Lambda.TooManyRequestsException"
378 | ],
379 | "IntervalSeconds": 2,
380 | "MaxAttempts": 6,
381 | "BackoffRate": 2
382 | }
383 | ],
384 | "Next": "GenerateReport"
385 | },
386 | "GenerateReport": {
387 | "Type": "Task",
388 | "Resource": "arn:aws:states:::lambda:invoke",
389 | "Parameters": {
390 | "Payload.$": "$",
391 | "FunctionName": "${GenerateLambda}"
392 | },
393 | "Retry": [
394 | {
395 | "ErrorEquals": [
396 | "Lambda.ServiceException",
397 | "Lambda.AWSLambdaException",
398 | "Lambda.SdkClientException",
399 | "Lambda.TooManyRequestsException"
400 | ],
401 | "IntervalSeconds": 2,
402 | "MaxAttempts": 6,
403 | "BackoffRate": 2
404 | }
405 | ],
406 | "OutputPath": "$.Payload",
407 | "End": true
408 | }
409 | }
410 | }
--------------------------------------------------------------------------------
/src/lambda/function-generate/app.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | import json, os, copy
5 | import boto3, requests
6 | from jinja2 import Template
7 | from bs4 import BeautifulSoup
8 |
9 | s3 = boto3.client('s3')
10 | s3_r = boto3.resource('s3')
11 | ec2 = boto3.client('ec2')
12 | ssm = boto3.client('ssm')
13 | supp = boto3.client('support')
14 |
15 | #env var load
16 | S3_BUCKET = os.environ['S3_BUCKET']
17 | AWS_REGION = os.environ['REGION']
18 | AWS_ACCOUNT = os.environ['ACCOUNT']
19 | ACT_RUNTIMES = os.environ['ACT_RUNTIMES']
20 | STACK_NAME = os.environ['STACK_NAME']
21 | TA_ENABLED = os.environ['TA_ENABLED']
22 | DOC_URL = "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"
23 |
24 |
25 | ###################################################################################################################################
26 | # Runtime evaluation code functionality
27 | ###################################################################################################################################
28 | def check_dep_runtime(fns,dep_runs):
29 | # Create a dictionary to map Identifiers to runtime details
30 | runtime_map = {item['Identifier']: {
31 | 'Deprecation date': item['Deprecation date'],
32 | 'Block function create': item['Block function create'],
33 | 'Block function update': item['Block function update']
34 | } for item in dep_runs['Deprecated Runtimes']}
35 |
36 | # Iterate through the function data and append the runtime details
37 | for function in fns:
38 | runtime = function['Runtime']
39 | for identifier, details in runtime_map.items():
40 | if runtime == identifier:
41 | function['Deprecation date'] = details['Deprecation date']
42 | function['Block function create'] = details['Block function create']
43 | function['Block function update'] = details['Block function update']
44 | break
45 |
46 | return fns
47 |
48 | def check_sup_runtime(fns,sup_runs):
49 | # Create a dictionary to map Identifiers to runtime details
50 | print(sup_runs)
51 | runtime_map = {item['Identifier']: {
52 | 'Deprecation date': item['Deprecation date'],
53 | 'Block function create': item['Block function create'],
54 | 'Block function update': item['Block function update']
55 | } for item in sup_runs['Supported Runtimes']}
56 |
57 | # Iterate through the function data and append the runtime details
58 | for function in fns:
59 | runtime = function['Runtime']
60 | for identifier, details in runtime_map.items():
61 | if runtime == identifier:
62 | function['Deprecation date'] = details['Deprecation date']
63 | function['Block function create'] = details['Block function create']
64 | function['Block function update'] = details['Block function update']
65 | break
66 |
67 | return fns
68 |
69 |
70 | ###################################################################################################################################
71 |
72 | ###################################################################################################################################
73 | #Deprecated runtime fetch:
74 | ###################################################################################################################################
75 | def fetch_deprecated_runtimes(dep_run_soup):
76 | """
77 | Fetch the list of deprecated runtimes from the AWS Lambda docs
78 | """
79 | # Find the Deprecated Runtime table
80 | element = dep_run_soup.find(id="runtimes-deprecated")
81 | table = element.find_next("table")
82 |
83 | # Extract header row
84 | headers = [header.text for header in table.find('thead').find_all('tr')]
85 | headers = headers[1]
86 | headers = headers.split('\n')
87 | headers = headers[1:-1]
88 |
89 | rows = []
90 |
91 | for row in table.find_all('tr')[1:]:
92 | cells = row.find_all('td')
93 | data = [cell.text for cell in cells]
94 | data = [line.strip() for line in data]
95 | rows.append(dict(zip(headers, data)))
96 | rows = rows[1:]
97 |
98 |
99 | return rows
100 | ###################################################################################################################################
101 |
102 | ###################################################################################################################################
103 | #Supported runtime fetch:
104 | ###################################################################################################################################
105 |
106 | def fetch_supported_runtimes(sup_run_soup):
107 | """
108 | Fetch the list of supported runtimes from the AWS Lambda docs
109 | """
110 | # Find the Supported Runtime table
111 | element = sup_run_soup.find(id="runtimes-supported")
112 | table = element.find_next("table")
113 |
114 | # Extract header row
115 | headers = [header.text for header in table.find('thead').find_all('tr')]
116 | headers = headers[1]
117 | headers = headers.split('\n')
118 | headers = headers[1:-1]
119 |
120 | rows = []
121 |
122 | for row in table.find_all('tr')[1:]:
123 | cells = row.find_all('td')
124 | data = [cell.text for cell in cells]
125 | data = [line.strip() for line in data]
126 | rows.append(dict(zip(headers, data)))
127 | rows = rows[1:]
128 |
129 |
130 | return rows
131 |
132 | ###################################################################################################################################
133 |
134 | ###################################################################################################################################
135 | #TA - High Error Rates
136 | ###################################################################################################################################
137 | def check_ta_high_errors(fns):
138 | ta_he = supp.describe_trusted_advisor_check_result(checkId='L4dfs2Q3C2')
139 | ta_he_flagged = ta_he['result']['flaggedResources']
140 | ta_he_flagged_list = []
141 | ta_he_flagged_dict = dict
142 | checked_fns = fns
143 |
144 | ### ADD cross check for functions
145 |
146 | #data cleanup
147 | for f in ta_he_flagged:
148 | f_arn = f['metadata'][2].removesuffix(f['metadata'][2].split(':')[-1])
149 | f_arn = f_arn.rstrip(f_arn[-1])
150 |
151 | for fn in checked_fns:
152 | if f_arn == fn['FunctionArn']:
153 | ta_he_flagged_dict = {
154 | 'Status': f['metadata'][0],
155 | 'Region': f['metadata'][1],
156 | 'FunctionArn': f['metadata'][2],
157 | 'MaxDailyErrorRatePerc': f['metadata'][3],
158 | 'DateOfMaxErrorRate': f['metadata'][4],
159 | 'AverageDailyErrorRatePerc': f['metadata'][5]
160 | }
161 | ta_he_flagged_list.append(ta_he_flagged_dict)
162 | break
163 |
164 | return ta_he_flagged_list
165 |
166 | ###################################################################################################################################
167 |
168 | ###################################################################################################################################
169 | #TA - Excessive Timeouts
170 | ###################################################################################################################################
171 | def check_ta_excessive_timeout(fns):
172 | ta_et = supp.describe_trusted_advisor_check_result(checkId='L4dfs2Q3C3')
173 | ta_et_flagged = ta_et['result']['flaggedResources']
174 | ta_et_flagged_list = []
175 | ta_et_flagged_dict = dict
176 | checked_fns = fns
177 |
178 | ### ADD cross check for functions
179 |
180 | #data cleanup
181 | for f in ta_et_flagged:
182 | f_arn = f['metadata'][2].removesuffix(f['metadata'][2].split(':')[-1])
183 | f_arn = f_arn.rstrip(f_arn[-1])
184 |
185 | for fn in checked_fns:
186 | if f_arn == fn['FunctionArn']:
187 | ta_et_flagged_dict = {
188 | 'Status': f['metadata'][0],
189 | 'Region': f['metadata'][1],
190 | 'FunctionArn': f['metadata'][2],
191 | 'MaxDailyTimeoutRatePerc': f['metadata'][3],
192 | 'DateOfMaxTimeoutRate': f['metadata'][4],
193 | 'AverageDailyTimeoutRatePerc': f['metadata'][5],
194 | 'FunctionTimeoutSettings': f['metadata'][6]
195 | }
196 | ta_et_flagged_list.append(ta_et_flagged_dict)
197 | break
198 |
199 | return ta_et_flagged_list
200 | ###################################################################################################################################
201 |
202 | ###################################################################################################################################
203 | #multi-az evaluation code functionality
204 | ###################################################################################################################################
205 | def check_multi_az(fns):
206 |
207 | vpc_fns = []
208 | sublist = []
209 |
210 | for fn in fns:
211 | if 'SubnetIds' in fn.keys() and fn['SubnetIds'] != []:
212 | vpc_fns.append(fn)
213 | sublist.append(fn['SubnetIds'])
214 | sublist = [item for row in sublist for item in row]
215 | sublist = list(dict.fromkeys(sublist))
216 | subnets = ec2.describe_subnets(SubnetIds=sublist)['Subnets']
217 | sub_az_list = []
218 | for sub in sublist:
219 | for s in subnets:
220 | if sub == s['SubnetId']:
221 | subaz = {
222 | 'SubnetId':s['SubnetId'],
223 | 'AZ': s['AvailabilityZone']
224 | }
225 | sub_az_list.append(subaz)
226 | vpc_functions = []
227 | for fn in vpc_fns:
228 | s_list = []
229 | for s in fn['SubnetIds']:
230 | for saz in sub_az_list:
231 | if s == saz['SubnetId']:
232 | s = {
233 | 'SubnetId':saz['SubnetId'],
234 | 'AZ': saz['AZ']
235 | }
236 | s_list.append(s)
237 | vpc_function = {
238 | 'FunctionArn':fn['FunctionArn'],
239 | 'Subnets':s_list
240 | }
241 | vpc_functions.append(vpc_function)
242 |
243 | non_multiaz_functions = []
244 | for f in vpc_functions:
245 | f_azs = []
246 | for az in f['Subnets']:
247 | f_azs.append(az['AZ'])
248 | if len(f_azs) != len(set(f_azs)) or len(f_azs) == 1:
249 | non_multiaz_functions.append(f)
250 | return non_multiaz_functions
251 |
252 | ###################################################################################################################################
253 |
254 | def handler(event, context):
255 | #List function config objects from S3:
256 | obj_functions = []
257 | s3_prefix = event['FolderKey']
258 | s3_f_folder = event['FolderKey'] + '/functions/'
259 | bucket = s3_r.Bucket(S3_BUCKET)
260 | for objects in bucket.objects.filter(Prefix=s3_f_folder):
261 | obj_functions.append(objects.key)
262 |
263 | #Fetch function config objects from S3:
264 | functions = []
265 | count = 0
266 | for obj in obj_functions:
267 | s3_function_set = s3.get_object(Bucket=S3_BUCKET, Key=obj)
268 | s3_function_set = json.loads(s3_function_set['Body'].read())
269 | functions.extend(s3_function_set)
270 | count = count + len(s3_function_set)
271 |
272 | #Remove review functions from list:
273 | for function in functions:
274 | if function['FunctionName'].startswith(STACK_NAME):
275 | functions.remove(function)
276 | #Set versions:
277 | for function in functions:
278 | function['Version'] = function['FunctionArn'].split(':')[-1]
279 |
280 | #get recommendations.json and event source mappings list from S3
281 | s3_recommendations = s3.get_object(Bucket=S3_BUCKET, Key=s3_prefix+'/recommendations.json')
282 | s3_event_source_mappings = s3.get_object(Bucket=S3_BUCKET, Key=s3_prefix+'/esms.json')
283 |
284 | #convert json to python dict
285 | recommendations = json.loads(s3_recommendations['Body'].read())
286 | #recommendations = recommendations['Recommendations']['LambdaFunctionRecommendations']
287 | esms = json.loads(s3_event_source_mappings['Body'].read())
288 |
289 |
290 |
291 | #################################################################################
292 | #Runtime evaluation method pointer:
293 | #################################################################################
294 |
295 | html = requests.get(DOC_URL)
296 | html = html.text
297 |
298 | soup = BeautifulSoup(html, 'html.parser')
299 |
300 | supported_runtimes = {'Supported Runtimes': fetch_supported_runtimes(soup)}
301 |
302 | deprecated_runtimes = {'Deprecated Runtimes': fetch_deprecated_runtimes(soup)}
303 |
304 | dep_fns = copy.deepcopy(functions)
305 | dep_runtime_fns = check_dep_runtime(dep_fns,deprecated_runtimes)
306 | sup_fns = copy.deepcopy(functions)
307 | sup_runtime_fns = check_sup_runtime(sup_fns,supported_runtimes)
308 |
309 | # Separate deprecated functions by runtime:
310 | py_dep_run = []
311 | for function in dep_runtime_fns:
312 | runtime = function['Runtime']
313 | if runtime.startswith('python') and 'Deprecation date' in function and function['Deprecation date'] != '':
314 | py_dep_run.append(function)
315 |
316 | no_dep_run = []
317 | for function in dep_runtime_fns:
318 | runtime = function['Runtime']
319 | if runtime.startswith('nodejs') and 'Deprecation date' in function and function['Deprecation date'] != '':
320 | no_dep_run.append(function)
321 |
322 | ja_dep_run = []
323 | for function in dep_runtime_fns:
324 | runtime = function['Runtime']
325 | if runtime.startswith('java') and 'Deprecation date' in function and function['Deprecation date'] != '':
326 | ja_dep_run.append(function)
327 |
328 | do_dep_run = []
329 | for function in dep_runtime_fns:
330 | runtime = function['Runtime']
331 | if runtime.startswith('dotnet') and 'Deprecation date' in function and function['Deprecation date'] != '':
332 | do_dep_run.append(function)
333 |
334 | ru_dep_run = []
335 | for function in dep_runtime_fns:
336 | runtime = function['Runtime']
337 | if runtime.startswith('ruby') and 'Deprecation date' in function and function['Deprecation date'] != '':
338 | ru_dep_run.append(function)
339 |
340 | go_dep_run = []
341 | for function in dep_runtime_fns:
342 | runtime = function['Runtime']
343 | if runtime.startswith('golang') and 'Deprecation date' in function and function['Deprecation date'] != '':
344 | go_dep_run.append(function)
345 |
346 | cu_dep_run = []
347 | for function in dep_runtime_fns:
348 | runtime = function['Runtime']
349 | if runtime.startswith('provided') and 'Deprecation date' in function and function['Deprecation date'] != '':
350 | cu_dep_run.append(function)
351 |
352 |
353 | # Separate supported functions by runtime:
354 | py_sup_run = []
355 | for function in sup_runtime_fns:
356 | runtime = function['Runtime']
357 | if runtime.startswith('python') and 'Deprecation date' in function and function['Deprecation date'] != '':
358 | py_sup_run.append(function)
359 |
360 | no_sup_run = []
361 | for function in sup_runtime_fns:
362 | runtime = function['Runtime']
363 | if runtime.startswith('nodejs') and 'Deprecation date' in function and function['Deprecation date'] != '':
364 | no_sup_run.append(function)
365 |
366 | ja_sup_run = []
367 | for function in sup_runtime_fns:
368 | runtime = function['Runtime']
369 | if runtime.startswith('java') and 'Deprecation date' in function and function['Deprecation date'] != '':
370 | ja_sup_run.append(function)
371 |
372 | do_sup_run = []
373 | for function in sup_runtime_fns:
374 | runtime = function['Runtime']
375 | if runtime.startswith('dotnet') and 'Deprecation date' in function and function['Deprecation date'] != '':
376 | do_sup_run.append(function)
377 |
378 | ru_sup_run = []
379 | for function in sup_runtime_fns:
380 | runtime = function['Runtime']
381 | if runtime.startswith('ruby') and 'Deprecation date' in function and function['Deprecation date'] != '':
382 | ru_sup_run.append(function)
383 |
384 | go_sup_run = []
385 | for function in sup_runtime_fns:
386 | runtime = function['Runtime']
387 | if runtime.startswith('golang') and 'Deprecation date' in function and function['Deprecation date'] != '':
388 | go_sup_run.append(function)
389 |
390 | cu_sup_run = []
391 | for function in sup_runtime_fns:
392 | runtime = function['Runtime']
393 | if runtime.startswith('provided') and 'Deprecation date' in function and function['Deprecation date'] != '':
394 | cu_sup_run.append(function)
395 |
396 |
397 | #################################################################################
398 | #Trusted Advisor evaluations
399 | #################################################################################
400 | if TA_ENABLED == 'true':
401 | ta_fns = copy.deepcopy(functions)
402 | # Get HighErrorRate Check result
403 | warnings_ta_high_errors = check_ta_high_errors(ta_fns)
404 |
405 | #Get ExcessiveTimeout Check result
406 | warnings_ta_excessive_timeout = check_ta_excessive_timeout(ta_fns)
407 |
408 |
409 | #################################################################################
410 | #Mutli-az evaluation:
411 | #################################################################################
412 |
413 | vpc_fns = copy.deepcopy(functions)
414 |
415 | warnings_vpc_functions = check_multi_az(vpc_fns)
416 |
417 | #################################################################################
418 |
419 | #################################################################################
420 | #Recommendations evaluations:
421 | #################################################################################
422 | #Match Compute Optimizer recommendations to functions
423 |
424 | not_optimized_functions = []
425 | unavailable_functions = []
426 | savings_not_present = {"EstimatedMonthlySavings":{"Currency": "USD","Value": "N/A"},"SavingsOpportunityPercentage": "N/A"}
427 | for recommendation in recommendations:
428 | rec_function_arn = recommendation['FunctionArn']
429 | rec_function_name = recommendation['FunctionArn'].split(':')
430 | rec_function_name = rec_function_name[6]+':'+rec_function_name[7]
431 |
432 | for function in functions:
433 |
434 | if rec_function_arn == function['FunctionArn']:
435 | recommendation['FunctionName'] = rec_function_name
436 | if recommendation['Finding'] == 'NotOptimized':
437 | if not 'SavingsOpportunity' in recommendation['MemorySizeRecommendationOptions'][0]:
438 | recommendation['MemorySizeRecommendationOptions'][0]['SavingsOpportunity'] = savings_not_present
439 | not_optimized_functions.append(recommendation)
440 |
441 |
442 | else:
443 | unavailable_functions.append(recommendation)
444 |
445 | #Check functions with x86 architecture
446 |
447 | old_arch_fns = []
448 | for function in functions:
449 | if 'x86_64' in function['Architectures']:
450 | old_arch_fns.append(function['FunctionArn'])
451 |
452 | #################################################################################
453 |
454 |
455 |
456 | #################################################################################
457 | #Create a dict wilh all data that will populate the template
458 | #################################################################################
459 | data = {}
460 | data['account'] = AWS_ACCOUNT
461 | data['region'] = AWS_REGION
462 | data['reviewed_functions'] = functions
463 | data['dep_python_functions'] = py_dep_run
464 | data['dep_nodejs_functions'] = no_dep_run
465 | data['dep_java_functions'] = ja_dep_run
466 | data['dep_dotnet_functions'] = do_dep_run
467 | data['dep_ruby_functions'] = ru_dep_run
468 | data['dep_golang_functions'] = go_dep_run
469 | data['dep_custom_functions'] = cu_dep_run
470 | data['sup_python_functions'] = py_sup_run
471 | data['sup_nodejs_functions'] = no_sup_run
472 | data['sup_java_functions'] = ja_sup_run
473 | data['sup_dotnet_functions'] = do_sup_run
474 | data['sup_ruby_functions'] = ru_sup_run
475 | data['sup_golang_functions'] = go_sup_run
476 | data['sup_custom_functions'] = cu_sup_run
477 | if TA_ENABLED == 'true':
478 | data['warnings_ta_high_errors'] = warnings_ta_high_errors
479 | data['warnings_ta_excessive_timeouts'] = warnings_ta_excessive_timeout
480 | data['warnings_vpc'] = warnings_vpc_functions
481 | data['recfunctions'] = not_optimized_functions
482 | data['recarch'] = old_arch_fns
483 | data['esms'] = esms
484 |
485 |
486 | #render the template
487 | with open('template.html', 'r') as file:
488 | template = Template(file.read(),trim_blocks=True)
489 | rendered_file = template.render(data=data)
490 |
491 | s3.put_object(Body=rendered_file, Bucket=S3_BUCKET, Key=s3_prefix+'/report.html')
492 |
493 | presign = s3.generate_presigned_url(ClientMethod='get_object',Params={'Bucket': S3_BUCKET,'Key': s3_prefix+'/report.html'}, ExpiresIn=3600)
494 |
495 | return {
496 | "statusCode": 200,
497 | "ReportUrl": presign
498 | }
499 |
500 |
501 |
502 |
--------------------------------------------------------------------------------
/src/lambda/function-generate-selected/app.py:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | import json, os, copy
5 | import boto3, requests
6 | from jinja2 import Template
7 | from bs4 import BeautifulSoup
8 |
9 | s3 = boto3.client('s3')
10 | s3_r = boto3.resource('s3')
11 | ec2 = boto3.client('ec2')
12 | ssm = boto3.client('ssm')
13 | supp = boto3.client('support')
14 |
15 | #env var load
16 | S3_BUCKET = os.environ['S3_BUCKET']
17 | AWS_REGION = os.environ['REGION']
18 | AWS_ACCOUNT = os.environ['ACCOUNT']
19 | ACT_RUNTIMES = os.environ['ACT_RUNTIMES']
20 | STACK_NAME = os.environ['STACK_NAME']
21 | TA_ENABLED = os.environ['TA_ENABLED']
22 | DOC_URL = "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"
23 |
24 |
25 | ###################################################################################################################################
26 | # Runtime evaluation code functionality
27 | ###################################################################################################################################
28 | def check_dep_runtime(fns,dep_runs):
29 | # Create a dictionary to map Identifiers to runtime details
30 | runtime_map = {item['Identifier']: {
31 | 'Deprecation date': item['Deprecation date'],
32 | 'Block function create': item['Block function create'],
33 | 'Block function update': item['Block function update']
34 | } for item in dep_runs['Deprecated Runtimes']}
35 |
36 | # Iterate through the function data and append the runtime details
37 | for function in fns:
38 | runtime = function['Runtime']
39 | for identifier, details in runtime_map.items():
40 | if runtime == identifier:
41 | function['Deprecation date'] = details['Deprecation date']
42 | function['Block function create'] = details['Block function create']
43 | function['Block function update'] = details['Block function update']
44 | break
45 |
46 | return fns
47 |
48 | def check_sup_runtime(fns,sup_runs):
49 | # Create a dictionary to map Identifiers to runtime details
50 | runtime_map = {item['Identifier']: {
51 | 'Deprecation date': item['Deprecation date'],
52 | 'Block function create': item['Block function create'],
53 | 'Block function update': item['Block function update']
54 | } for item in sup_runs['Supported Runtimes']}
55 |
56 | # Iterate through the function data and append the runtime details
57 | for function in fns:
58 | runtime = function['Runtime']
59 | for identifier, details in runtime_map.items():
60 | if runtime == identifier:
61 | function['Deprecation date'] = details['Deprecation date']
62 | function['Block function create'] = details['Block function create']
63 | function['Block function update'] = details['Block function update']
64 | break
65 |
66 | return fns
67 |
68 |
69 | ###################################################################################################################################
70 |
71 | ###################################################################################################################################
72 | #TA - High Error Rates
73 | ###################################################################################################################################
74 | def check_ta_high_errors(fns):
75 | ta_he = supp.describe_trusted_advisor_check_result(checkId='L4dfs2Q3C2')
76 | ta_he_flagged = ta_he['result']['flaggedResources']
77 | ta_he_flagged_list = []
78 | ta_he_flagged_dict = dict
79 | checked_fns = fns
80 |
81 | ### ADD cross check for functions
82 |
83 | #data cleanup
84 | for f in ta_he_flagged:
85 | f_arn = f['metadata'][2].removesuffix(f['metadata'][2].split(':')[-1])
86 | f_arn = f_arn.rstrip(f_arn[-1])
87 |
88 | for fn in checked_fns:
89 | if f_arn == fn['FunctionArn']:
90 | ta_he_flagged_dict = {
91 | 'Status': f['metadata'][0],
92 | 'Region': f['metadata'][1],
93 | 'FunctionArn': f['metadata'][2],
94 | 'MaxDailyErrorRatePerc': f['metadata'][3],
95 | 'DateOfMaxErrorRate': f['metadata'][4],
96 | 'AverageDailyErrorRatePerc': f['metadata'][5]
97 | }
98 | ta_he_flagged_list.append(ta_he_flagged_dict)
99 | break
100 |
101 | return ta_he_flagged_list
102 |
103 | ###################################################################################################################################
104 |
105 | ###################################################################################################################################
106 | #TA - Excessive Timeouts
107 | ###################################################################################################################################
108 | def check_ta_excessive_timeout(fns):
109 | ta_et = supp.describe_trusted_advisor_check_result(checkId='L4dfs2Q3C3')
110 | ta_et_flagged = ta_et['result']['flaggedResources']
111 | ta_et_flagged_list = []
112 | ta_et_flagged_dict = dict
113 | checked_fns = fns
114 |
115 | ### ADD cross check for functions
116 |
117 | #data cleanup
118 | for f in ta_et_flagged:
119 | f_arn = f['metadata'][2].removesuffix(f['metadata'][2].split(':')[-1])
120 | f_arn = f_arn.rstrip(f_arn[-1])
121 |
122 | for fn in checked_fns:
123 | if f_arn == fn['FunctionArn']:
124 | ta_et_flagged_dict = {
125 | 'Status': f['metadata'][0],
126 | 'Region': f['metadata'][1],
127 | 'FunctionArn': f['metadata'][2],
128 | 'MaxDailyTimeoutRatePerc': f['metadata'][3],
129 | 'DateOfMaxTimeoutRate': f['metadata'][4],
130 | 'AverageDailyTimeoutRatePerc': f['metadata'][5],
131 | 'FunctionTimeoutSettings': f['metadata'][6]
132 | }
133 | ta_et_flagged_list.append(ta_et_flagged_dict)
134 | break
135 |
136 | return ta_et_flagged_list
137 | ###################################################################################################################################
138 |
139 | ###################################################################################################################################
140 | #multi-az evaluation code functionality
141 | ###################################################################################################################################
142 | def check_multi_az(fns):
143 |
144 | vpc_fns = []
145 | sublist = []
146 |
147 | for fn in fns:
148 | if 'SubnetIds' in fn.keys() and fn['SubnetIds'] != []:
149 | vpc_fns.append(fn)
150 | sublist.append(fn['SubnetIds'])
151 | sublist = [item for row in sublist for item in row]
152 | sublist = list(dict.fromkeys(sublist))
153 | subnets = ec2.describe_subnets(SubnetIds=sublist)['Subnets']
154 | sub_az_list = []
155 | for sub in sublist:
156 | for s in subnets:
157 | if sub == s['SubnetId']:
158 | subaz = {
159 | 'SubnetId':s['SubnetId'],
160 | 'AZ': s['AvailabilityZone']
161 | }
162 | sub_az_list.append(subaz)
163 | vpc_functions = []
164 | for fn in vpc_fns:
165 | s_list = []
166 | for s in fn['SubnetIds']:
167 | for saz in sub_az_list:
168 | if s == saz['SubnetId']:
169 | s = {
170 | 'SubnetId':saz['SubnetId'],
171 | 'AZ': saz['AZ']
172 | }
173 | s_list.append(s)
174 | vpc_function = {
175 | 'FunctionArn':fn['FunctionArn'],
176 | 'Subnets':s_list
177 | }
178 | vpc_functions.append(vpc_function)
179 |
180 | non_multiaz_functions = []
181 | for f in vpc_functions:
182 | f_azs = []
183 | for az in f['Subnets']:
184 | f_azs.append(az['AZ'])
185 | if len(f_azs) != len(set(f_azs)) or len(f_azs) == 1:
186 | non_multiaz_functions.append(f)
187 | return non_multiaz_functions
188 |
189 | ###################################################################################################################################
190 |
191 | ###################################################################################################################################
192 | #Deprecated runtime fetch:
193 | ###################################################################################################################################
194 | def fetch_deprecated_runtimes(dep_run_soup):
195 | """
196 | Fetch the list of deprecated runtimes from the AWS Lambda docs
197 | """
198 | # Find the Deprecated Runtime table
199 | element = dep_run_soup.find(id="runtimes-deprecated")
200 | table = element.find_next("table")
201 |
202 | # Extract header row
203 | headers = [header.text for header in table.find('thead').find_all('tr')]
204 | headers = headers[1]
205 | headers = headers.split('\n')
206 | headers = headers[1:-1]
207 |
208 | rows = []
209 |
210 | for row in table.find_all('tr')[1:]:
211 | cells = row.find_all('td')
212 | data = [cell.text for cell in cells]
213 | data = [line.strip() for line in data]
214 | rows.append(dict(zip(headers, data)))
215 | rows = rows[1:]
216 |
217 |
218 | return rows
219 | ###################################################################################################################################
220 |
221 | ###################################################################################################################################
222 | #Supported runtime fetch:
223 | ###################################################################################################################################
224 |
225 | def fetch_supported_runtimes(sup_run_soup):
226 | """
227 | Fetch the list of supported runtimes from the AWS Lambda docs
228 | """
229 | # Find the Supported Runtime table
230 | element = sup_run_soup.find(id="runtimes-supported")
231 | table = element.find_next("table")
232 |
233 | # Extract header row
234 | headers = [header.text for header in table.find('thead').find_all('tr')]
235 | headers = headers[1]
236 | headers = headers.split('\n')
237 | headers = headers[1:-1]
238 |
239 | rows = []
240 |
241 | for row in table.find_all('tr')[1:]:
242 | cells = row.find_all('td')
243 | data = [cell.text for cell in cells]
244 | data = [line.strip() for line in data]
245 | rows.append(dict(zip(headers, data)))
246 | rows = rows[1:]
247 |
248 |
249 | return rows
250 |
251 | ###################################################################################################################################
252 |
253 | def handler(event, context):
254 | #List function config objects from S3:
255 | s3_prefix = event['Prefix']
256 |
257 | #Fetch function config objects from S3:
258 |
259 |
260 | functions = s3.get_object(Bucket=S3_BUCKET, Key=s3_prefix+'/'+event['FunctionsObject'])
261 | functions = json.loads(functions['Body'].read())
262 |
263 | #Remove review functions from list:
264 | for function in functions:
265 | if function['FunctionName'].startswith(STACK_NAME):
266 | functions.remove(function)
267 | #Set versions:
268 | for function in functions:
269 | function['Version'] = function['FunctionArn'].split(':')[-1]
270 |
271 | #get recommendations.json and event source mappings list from S3
272 | s3_recommendations = s3.get_object(Bucket=S3_BUCKET, Key=s3_prefix+'/recommendations.json')
273 | s3_event_source_mappings = s3.get_object(Bucket=S3_BUCKET, Key=s3_prefix+'/esms.json')
274 |
275 | #convert json to python dict
276 | recommendations = json.loads(s3_recommendations['Body'].read())
277 | recommendations = recommendations['Recommendations']['LambdaFunctionRecommendations']
278 | esms = json.loads(s3_event_source_mappings['Body'].read())
279 | esms = esms['EventSourceMappings']['EventSourceMappings']
280 |
281 |
282 |
283 | #################################################################################
284 | #Runtime evaluation method pointer:
285 | #################################################################################
286 |
287 | html = requests.get(DOC_URL)
288 | html = html.text
289 |
290 | soup = BeautifulSoup(html, 'html.parser')
291 |
292 | supported_runtimes = {'Supported Runtimes': fetch_supported_runtimes(soup)}
293 |
294 | deprecated_runtimes = {'Deprecated Runtimes': fetch_deprecated_runtimes(soup)}
295 |
296 | dep_fns = copy.deepcopy(functions)
297 | dep_runtime_fns = check_dep_runtime(dep_fns,deprecated_runtimes)
298 | sup_fns = copy.deepcopy(functions)
299 | sup_runtime_fns = check_sup_runtime(sup_fns,supported_runtimes)
300 |
301 | # Separate deprecated functions by runtime:
302 | py_dep_run = []
303 | for function in dep_runtime_fns:
304 | runtime = function['Runtime']
305 | if runtime.startswith('python') and 'Deprecation date' in function and function['Deprecation date'] != '':
306 | py_dep_run.append(function)
307 |
308 | no_dep_run = []
309 | for function in dep_runtime_fns:
310 | runtime = function['Runtime']
311 | if runtime.startswith('nodejs') and 'Deprecation date' in function and function['Deprecation date'] != '':
312 | no_dep_run.append(function)
313 |
314 | ja_dep_run = []
315 | for function in dep_runtime_fns:
316 | runtime = function['Runtime']
317 | if runtime.startswith('java') and 'Deprecation date' in function and function['Deprecation date'] != '':
318 | ja_dep_run.append(function)
319 |
320 | do_dep_run = []
321 | for function in dep_runtime_fns:
322 | runtime = function['Runtime']
323 | if runtime.startswith('dotnet') and 'Deprecation date' in function and function['Deprecation date'] != '':
324 | do_dep_run.append(function)
325 |
326 | ru_dep_run = []
327 | for function in dep_runtime_fns:
328 | runtime = function['Runtime']
329 | if runtime.startswith('ruby') and 'Deprecation date' in function and function['Deprecation date'] != '':
330 | ru_dep_run.append(function)
331 |
332 | go_dep_run = []
333 | for function in dep_runtime_fns:
334 | runtime = function['Runtime']
335 | if runtime.startswith('golang') and 'Deprecation date' in function and function['Deprecation date'] != '':
336 | go_dep_run.append(function)
337 |
338 | cu_dep_run = []
339 | for function in dep_runtime_fns:
340 | runtime = function['Runtime']
341 | if runtime.startswith('provided') and 'Deprecation date' in function and function['Deprecation date'] != '':
342 | cu_dep_run.append(function)
343 |
344 |
345 | # Separate supported functions by runtime:
346 | py_sup_run = []
347 | for function in sup_runtime_fns:
348 | runtime = function['Runtime']
349 | if runtime.startswith('python') and 'Deprecation date' in function and function['Deprecation date'] != '':
350 | py_sup_run.append(function)
351 |
352 | no_sup_run = []
353 | for function in sup_runtime_fns:
354 | runtime = function['Runtime']
355 | if runtime.startswith('nodejs') and 'Deprecation date' in function and function['Deprecation date'] != '':
356 | no_sup_run.append(function)
357 |
358 | ja_sup_run = []
359 | for function in sup_runtime_fns:
360 | runtime = function['Runtime']
361 | if runtime.startswith('java') and 'Deprecation date' in function and function['Deprecation date'] != '':
362 | ja_sup_run.append(function)
363 |
364 | do_sup_run = []
365 | for function in sup_runtime_fns:
366 | runtime = function['Runtime']
367 | if runtime.startswith('dotnet') and 'Deprecation date' in function and function['Deprecation date'] != '':
368 | do_sup_run.append(function)
369 |
370 | ru_sup_run = []
371 | for function in sup_runtime_fns:
372 | runtime = function['Runtime']
373 | if runtime.startswith('ruby') and 'Deprecation date' in function and function['Deprecation date'] != '':
374 | ru_sup_run.append(function)
375 |
376 | go_sup_run = []
377 | for function in sup_runtime_fns:
378 | runtime = function['Runtime']
379 | if runtime.startswith('golang') and 'Deprecation date' in function and function['Deprecation date'] != '':
380 | go_sup_run.append(function)
381 |
382 | cu_sup_run = []
383 | for function in sup_runtime_fns:
384 | runtime = function['Runtime']
385 | if runtime.startswith('provided') and 'Deprecation date' in function and function['Deprecation date'] != '':
386 | cu_sup_run.append(function)
387 |
388 |
389 | #################################################################################
390 | #Trusted Advisor evaluations
391 | #################################################################################
392 | if TA_ENABLED == 'true':
393 | ta_fns = copy.deepcopy(functions)
394 | # Get HighErrorRate Check result
395 | warnings_ta_high_errors = check_ta_high_errors(ta_fns)
396 |
397 | #Get ExcessiveTimeout Check result
398 | warnings_ta_excessive_timeout = check_ta_excessive_timeout(ta_fns)
399 |
400 |
401 | #################################################################################
402 | #Mutli-az evaluation:
403 | #################################################################################
404 |
405 | vpc_fns = copy.deepcopy(functions)
406 |
407 | warnings_vpc_functions = check_multi_az(vpc_fns)
408 |
409 | #################################################################################
410 |
411 | #################################################################################
412 | #Recommendations evaluations:
413 | #################################################################################
414 | #Match Compute Optimizer recommendations to functions
415 |
416 | not_optimized_functions = []
417 | unavailable_functions = []
418 | savings_not_present = {"EstimatedMonthlySavings":{"Currency": "USD","Value": "N/A"},"SavingsOpportunityPercentage": "N/A"}
419 | for recommendation in recommendations:
420 | rec_function_arn = recommendation['FunctionArn']
421 | rec_function_name = recommendation['FunctionArn'].split(':')
422 | rec_function_name = rec_function_name[6]+':'+rec_function_name[7]
423 |
424 | for function in functions:
425 |
426 | if rec_function_arn == function['FunctionArn']:
427 | recommendation['FunctionName'] = rec_function_name
428 | if recommendation['Finding'] == 'NotOptimized':
429 | if not 'SavingsOpportunity' in recommendation['MemorySizeRecommendationOptions'][0]:
430 | recommendation['MemorySizeRecommendationOptions'][0]['SavingsOpportunity'] = savings_not_present
431 | not_optimized_functions.append(recommendation)
432 |
433 |
434 | else:
435 | unavailable_functions.append(recommendation)
436 |
437 | #Check functions with x86 architecture
438 |
439 | old_arch_fns = []
440 | for function in functions:
441 | if 'x86_64' in function['Architectures']:
442 | old_arch_fns.append(function['FunctionArn'])
443 |
444 | #################################################################################
445 |
446 | #################################################################################
447 | #Functions configuration compilation:
448 | #################################################################################
449 |
450 | #cleanup event source mappings
451 |
452 | filtered_esms = []
453 | for fn in functions:
454 | for esm in esms:
455 | if fn['FunctionArn'] == esm['FunctionArn']:
456 | filtered_esms.append(esm)
457 |
458 |
459 | #################################################################################
460 |
461 | #################################################################################
462 | #Create a dict with all data that will populate the template
463 | #################################################################################
464 | data = {}
465 | data['account'] = AWS_ACCOUNT
466 | data['region'] = AWS_REGION
467 | data['reviewed_functions'] = functions
468 | data['dep_python_functions'] = py_dep_run
469 | data['dep_nodejs_functions'] = no_dep_run
470 | data['dep_java_functions'] = ja_dep_run
471 | data['dep_dotnet_functions'] = do_dep_run
472 | data['dep_ruby_functions'] = ru_dep_run
473 | data['dep_golang_functions'] = go_dep_run
474 | data['dep_custom_functions'] = cu_dep_run
475 | data['sup_python_functions'] = py_sup_run
476 | data['sup_nodejs_functions'] = no_sup_run
477 | data['sup_java_functions'] = ja_sup_run
478 | data['sup_dotnet_functions'] = do_sup_run
479 | data['sup_ruby_functions'] = ru_sup_run
480 | data['sup_golang_functions'] = go_sup_run
481 | data['sup_custom_functions'] = cu_sup_run
482 | if TA_ENABLED == 'true':
483 | data['warnings_ta_high_errors'] = warnings_ta_high_errors
484 | data['warnings_ta_excessive_timeouts'] = warnings_ta_excessive_timeout
485 | data['warnings_vpc'] = warnings_vpc_functions
486 | data['recfunctions'] = not_optimized_functions
487 | data['recarch'] = old_arch_fns
488 | data['esms'] = filtered_esms
489 |
490 |
491 | #render the template
492 | with open('template.html', 'r') as file:
493 | template = Template(file.read(),trim_blocks=True)
494 | rendered_file = template.render(data=data)
495 |
496 | s3.put_object(Body=rendered_file, Bucket=S3_BUCKET, Key=s3_prefix+'/report.html')
497 |
498 | presign = s3.generate_presigned_url(ClientMethod='get_object',Params={'Bucket': S3_BUCKET,'Key': s3_prefix+'/report.html'}, ExpiresIn=3600)
499 |
500 | return {
501 | "statusCode": 200,
502 | "ReportUrl": presign
503 | }
504 |
505 |
506 |
507 |
--------------------------------------------------------------------------------
/src/lambda/function-generate/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Serverless Operational Review
8 |
9 |
43 |
44 |
45 |
46 |
Serverless Operational Review
47 |
48 |
49 | AWS Account:
50 | {{data.account}}
51 |
52 |
53 | Region:
54 | {{data.region}}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
AWS Lambda Review
62 |
63 |
64 |
65 |
66 |
67 |
RED Danger Zone - Risks
68 |
69 | This section outlines areas that have a high risk of impacting existing workloads!
70 | Details about specific Lambda functions can be found in Reviewed Functions - Configurations section of this report.
71 |
72 |
73 |
74 |
75 |
76 |
Deprecated Function Runtime Versions!
77 |
80 |
81 |
82 |
83 | {% if data.dep_python_functions != [] %}
84 |
85 |
86 |
87 |
88 | Function Name
89 | Function Version
90 | Runtime Version
91 | Deprecation Date
92 | Update Blocked Date
93 | Creation Block Date
94 |
95 |
96 |
97 | {% for fn in data.dep_python_functions %}
98 |
99 | {{fn['FunctionName']}}
100 | {{fn['Version']}}
101 | {{fn['Runtime']}}
102 | {{fn['Deprecation date']}}
103 | {{fn['Block function update']}}
104 | {{fn['Block function create']}}
105 |
106 | {% endfor %}
107 |
108 |
109 |
110 | {% else %}
111 |
112 |
113 |
114 | {% endif %}
115 |
116 |
117 |
118 |
119 | {% if data.dep_nodejs_functions != [] %}
120 |
121 |
122 |
123 |
124 | Function Name
125 | Function Version
126 | Runtime Version
127 | Deprecation Date
128 | Update Blocked Date
129 | Creation Block Date
130 |
131 |
132 |
133 | {% for fn in data.dep_nodejs_functions %}
134 |
135 | {{fn['FunctionName']}}
136 | {{fn['Version']}}
137 | {{fn['Runtime']}}
138 | {{fn['Deprecation date']}}
139 | {{fn['Block function update']}}
140 | {{fn['Block function create']}}
141 |
142 | {% endfor %}
143 |
144 |
145 |
146 | {% else %}
147 |
148 |
149 |
150 | {% endif %}
151 |
152 |
153 |
154 |
155 | {% if data.dep_java_functions != [] %}
156 |
157 |
158 |
159 |
160 | Function Name
161 | Function Version
162 | Runtime Version
163 | Deprecation Date
164 | Update Blocked Date
165 | Creation Block Date
166 |
167 |
168 |
169 | {% for fn in data.dep_java_functions %}
170 |
171 | Function Name
172 | Function Version
173 | Runtime Version
174 | Deprecation Date
175 | Update Blocked Date
176 | Creation Block Date
177 |
178 | {% endfor %}
179 |
180 |
181 |
182 | {% else %}
183 |
184 |
185 |
186 | {% endif %}
187 |
188 |
189 |
190 |
191 | {% if data.dep_dotnet_functions != [] %}
192 |
193 |
194 |
195 |
196 | Function Name
197 | Function Version
198 | Runtime Version
199 | Deprecation Date
200 | Update Blocked Date
201 | Creation Block Date
202 |
203 |
204 |
205 | {% for fn in data.dep_dotnet_functions %}
206 |
207 | {{fn['FunctionName']}}
208 | {{fn['Version']}}
209 | {{fn['Runtime']}}
210 | {{fn['Deprecation date']}}
211 | {{fn['Block function update']}}
212 | {{fn['Block function create']}}
213 |
214 | {% endfor %}
215 |
216 |
217 |
218 | {% else %}
219 |
220 |
221 |
222 | {% endif %}
223 |
224 |
225 |
226 |
227 | {% if data.dep_ruby_functions != [] %}
228 |
229 |
230 |
231 |
232 | Function Name
233 | Function Version
234 | Runtime Version
235 | Deprecation Date
236 | Update Blocked Date
237 | Creation Block Date
238 |
239 |
240 |
241 | {% for fn in data.dep_ruby_functions %}
242 |
243 | {{fn['FunctionName']}}
244 | {{fn['Version']}}
245 | {{fn['Runtime']}}
246 | {{fn['Deprecation date']}}
247 | {{fn['Block function update']}}
248 | {{fn['Block function create']}}
249 |
250 | {% endfor %}
251 |
252 |
253 |
254 | {% else %}
255 |
256 |
257 |
258 | {% endif %}
259 |
260 |
261 |
262 |
263 | {% if data.dep_golang_functions != [] %}
264 |
265 |
266 |
267 |
268 | Function Name
269 | Function Version
270 | Runtime Version
271 | Deprecation Date
272 | Update Blocked Date
273 | Creation Block Date
274 |
275 |
276 |
277 | {% for fn in data.dep_golang_functions %}
278 |
279 | {{fn['FunctionName']}}
280 | {{fn['Version']}}
281 | {{fn['Runtime']}}
282 | {{fn['Deprecation date']}}
283 | {{fn['Block function update']}}
284 | {{fn['Block function create']}}
285 |
286 | {% endfor %}
287 |
288 |
289 |
290 | {% else %}
291 |
292 |
293 |
294 | {% endif %}
295 |
296 |
297 |
298 |
299 | {% if data.dep_custom_functions != [] %}
300 |
301 |
302 |
303 |
304 | Function Name
305 | Function Version
306 | Runtime Version
307 | Deprecation Date
308 | Update Blocked Date
309 | Creation Block Date
310 |
311 |
312 |
313 | {% for fn in data.dep_custom_functions %}
314 |
315 | {{fn['FunctionName']}}
316 | {{fn['Version']}}
317 | {{fn['Runtime']}}
318 | {{fn['Deprecation date']}}
319 | {{fn['Block function update']}}
320 | {{fn['Block function create']}}
321 |
322 | {% endfor %}
323 |
324 |
325 |
326 | {% else %}
327 |
328 |
329 |
330 | {% endif %}
331 |
332 |
333 |
334 | {% if data.warnings_ta_high_errors != [] %}
335 |
336 |
Functions with High Error Rates
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 | Function ARN
345 | Status
346 | Average Daily Errors %
347 | Maximum Daily Errors %
348 | Date of Max Error Rate
349 |
350 |
351 |
352 | {% for warnings_ta_high_errors in data.warnings_ta_high_errors %}
353 |
354 | {{warnings_ta_high_errors['FunctionArn']}}
355 | {{warnings_ta_high_errors['Status']}}
356 | {{warnings_ta_high_errors['AverageDailyErrorRatePerc']}}
357 | {{warnings_ta_high_errors['MaxDailyErrorRatePerc']}}
358 | {{warnings_ta_high_errors['DateOfMaxErrorRate']}}
359 |
360 | {% endfor %}
361 |
362 |
363 |
364 |
365 | {% endif %}
366 |
367 |
368 |
369 | {% if data.warnings_ta_excessive_timeouts != [] %}
370 |
371 |
Functions with Excessive Timeouts
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 | Function ARN
380 | Status
381 | Average Daily Timeouts %
382 | Maximum Daily Timeouts %
383 | Date of Max Timeouts
384 | Function Timeout Config
385 |
386 |
387 |
388 | {% for warnings_ta_excessive_timeout in data.warnings_ta_excessive_timeouts %}
389 |
390 | {{warnings_ta_excessive_timeout['FunctionArn']}}
391 | {{warnings_ta_excessive_timeout['Status']}}
392 | {{warnings_ta_excessive_timeout['AverageDailyTimeoutRatePerc']}}
393 | {{warnings_ta_excessive_timeout['MaxDailyTimeoutRatePerc']}}
394 | {{warnings_ta_excessive_timeout['DateOfMaxTimeoutRate']}}
395 | {{warnings_ta_excessive_timeout['FunctionTimeoutSettings']}}
396 |
397 | {% endfor %}
398 |
399 |
400 |
401 |
402 | {% endif %}
403 |
404 |
405 |
406 | {% if data.warnings_vpc != [] %}
407 |
408 |
VPC-enabled Functions - Incorrect multi-AZ configuration
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 | Function ARN
417 | SubnetID
418 | Avalabitlity Zone
419 |
420 |
421 |
422 | {% for warnings_vpc in data.warnings_vpc %}
423 |
424 | {{warnings_vpc['FunctionArn']}}
425 | {% for subaz in warnings_vpc['Subnets'] %}
426 |
427 |
428 | {{subaz['SubnetId']}}
429 | {{subaz['AZ']}}
430 |
431 | {% endfor %}
432 |
433 | {% endfor %}
434 |
435 |
436 |
437 | {% endif %}
438 |
439 |
440 |
441 |
442 |
443 |
444 |
YELLOW Warnings Zone
445 |
446 | This section outlines areas that have a low risk of impacting existing workloads but should be considered for the future.
447 | Details about specific Lambda functions can be found in Reviewed Functions - Configurations section of this report.
448 |
449 |
450 |
451 |
452 |
453 | {% if data.sup_python_functions != [] %}
454 |
455 |
456 |
457 |
458 | Function Name
459 | Function Version
460 | Runtime Version
461 | Deprecation Date
462 | Update Blocked Date
463 | Creation Block Date
464 |
465 |
466 |
467 | {% for fn in data.sup_python_functions %}
468 |
469 | {{fn['FunctionName']}}
470 | {{fn['Version']}}
471 | {{fn['Runtime']}}
472 | {{fn['Deprecation date']}}
473 | {{fn['Block function update']}}
474 | {{fn['Block function create']}}
475 |
476 | {% endfor %}
477 |
478 |
479 |
480 | {% else %}
481 |
482 |
483 |
484 | {% endif %}
485 |
486 |
487 |
488 |
489 | {% if data.sup_nodejs_functions != [] %}
490 |
491 |
492 |
493 |
494 | Function Name
495 | Function Version
496 | Runtime Version
497 | Deprecation Date
498 | Update Blocked Date
499 | Creation Block Date
500 |
501 |
502 |
503 | {% for fn in data.sup_nodejs_functions %}
504 |
505 | {{fn['FunctionName']}}
506 | {{fn['Version']}}
507 | {{fn['Runtime']}}
508 | {{fn['Deprecation date']}}
509 | {{fn['Block function update']}}
510 | {{fn['Block function create']}}
511 |
512 | {% endfor %}
513 |
514 |
515 |
516 | {% else %}
517 |
518 |
519 |
520 | {% endif %}
521 |
522 |
523 |
524 |
525 | {% if data.sup_java_functions != [] %}
526 |
527 |
528 |
529 |
530 | Function Name
531 | Function Version
532 | Runtime Version
533 | Deprecation Date
534 | Update Blocked Date
535 | Creation Block Date
536 |
537 |
538 |
539 | {% for fn in data.sup_java_functions %}
540 |
541 | Function Name
542 | Function Version
543 | Runtime Version
544 | Deprecation Date
545 | Update Blocked Date
546 | Creation Block Date
547 |
548 | {% endfor %}
549 |
550 |
551 |
552 | {% else %}
553 |
554 |
555 |
556 | {% endif %}
557 |
558 |
559 |
560 |
561 | {% if data.sup_dotnet_functions != [] %}
562 |
563 |
564 |
565 |
566 | Function Name
567 | Function Version
568 | Runtime Version
569 | Deprecation Date
570 | Update Blocked Date
571 | Creation Block Date
572 |
573 |
574 |
575 | {% for fn in data.sup_dotnet_functions %}
576 |
577 | {{fn['FunctionName']}}
578 | {{fn['Version']}}
579 | {{fn['Runtime']}}
580 | {{fn['Deprecation date']}}
581 | {{fn['Block function update']}}
582 | {{fn['Block function create']}}
583 |
584 | {% endfor %}
585 |
586 |
587 |
588 | {% else %}
589 |
590 |
591 |
592 | {% endif %}
593 |
594 |
595 |
596 |
597 | {% if data.sup_ruby_functions != [] %}
598 |
599 |
600 |
601 |
602 | Function Name
603 | Function Version
604 | Runtime Version
605 | Deprecation Date
606 | Update Blocked Date
607 | Creation Block Date
608 |
609 |
610 |
611 | {% for fn in data.sup_ruby_functions %}
612 |
613 | {{fn['FunctionName']}}
614 | {{fn['Version']}}
615 | {{fn['Runtime']}}
616 | {{fn['Deprecation date']}}
617 | {{fn['Block function update']}}
618 | {{fn['Block function create']}}
619 |
620 | {% endfor %}
621 |
622 |
623 |
624 | {% else %}
625 |
626 |
627 |
628 | {% endif %}
629 |
630 |
631 |
632 |
633 | {% if data.sup_golang_functions != [] %}
634 |
635 |
636 |
637 |
638 | Function Name
639 | Function Version
640 | Runtime Version
641 | Deprecation Date
642 | Update Blocked Date
643 | Creation Block Date
644 |
645 |
646 |
647 | {% for fn in data.sup_golang_functions %}
648 |
649 | {{fn['FunctionName']}}
650 | {{fn['Version']}}
651 | {{fn['Runtime']}}
652 | {{fn['Deprecation date']}}
653 | {{fn['Block function update']}}
654 | {{fn['Block function create']}}
655 |
656 | {% endfor %}
657 |
658 |
659 |
660 | {% else %}
661 |
662 |
663 |
664 | {% endif %}
665 |
666 |
667 |
668 |
669 | {% if data.sup_custom_functions != [] %}
670 |
671 |
672 |
673 |
674 | Function Name
675 | Function Version
676 | Runtime Version
677 | Deprecation Date
678 | Update Blocked Date
679 | Creation Block Date
680 |
681 |
682 |
683 | {% for fn in data.sup_custom_functions %}
684 |
685 | {{fn['FunctionName']}}
686 | {{fn['Version']}}
687 | {{fn['Runtime']}}
688 | {{fn['Deprecation date']}}
689 | {{fn['Block function update']}}
690 | {{fn['Block function create']}}
691 |
692 | {% endfor %}
693 |
694 |
695 |
696 | {% else %}
697 |
698 |
699 |
700 | {% endif %}
701 |
702 |
703 |
704 |
705 |
706 |
BLUE Optimization Zone - Recommendations
707 |
708 | To balance memory and CPU of your Lambda functions, we recommend using Lambda Power Tuning .
709 |
710 |
711 |
712 |
713 |
714 | {% if data.recfunctions != [] %}
715 |
716 |
Memory Optimization Recommendations
717 |
718 | The recommendations below are sourced from AWS Compute Optimizer .
719 | The loopback period is 14 days.
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 | Function ARN
730 | Finding
731 | Number of Invocations
732 | Current Memory Size
733 | Recommended Memory Size
734 | Est. Montly Saving (USD)
735 | Est. Montly Saving %
736 | Duration Current - Projected (ms)
737 |
738 |
739 |
740 | {% for rec in data.recfunctions %}
741 |
742 | {{rec['FunctionArn']}}
743 | {{rec['FindingReasonCodes'][0]}}
744 | {{rec['NumberOfInvocations']}}
745 | {{rec['CurrentMemorySize']}}
746 | {{rec['MemorySizeRecommendationOptions'][0]['MemorySize']}}
747 | {{rec['MemorySizeRecommendationOptions'][0]['SavingsOpportunity']['EstimatedMonthlySavings']['Value']}}
748 | {{rec['MemorySizeRecommendationOptions'][0]['SavingsOpportunity']['SavingsOpportunityPercentage']}}
749 | {{rec['UtilizationMetrics'][0]['Value']}} - {{rec['MemorySizeRecommendationOptions'][0]['ProjectedUtilizationMetrics'][1]['Value']}}
750 |
751 | {% endfor %}
752 |
753 |
754 |
755 | {% endif %}
756 |
757 |
758 |
759 |
760 | {% if data.recarch != [] %}
761 |
762 |
Functions running on x86 Architecture Type
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 | Functions running x86 architecture, consider upgrade to arm64
772 |
773 |
774 |
775 | {% for f in data.recarch %}
776 |
777 | {{f}}
778 |
779 | {% endfor %}
780 |
781 |
782 |
783 | {% endif %}
784 |
785 |
786 |
787 |
788 |
789 |
790 |
Reviewed Functions - Configurations
791 |
The below information contains all reviewed Lambda functions, function versions and their configurations including event source mappings configurations.
792 |
793 |
794 |
795 | {% if data.reviewed_functions != [] %}
796 |
797 |
798 |
799 |
800 |
801 | Reviewed functions configurations
802 |
803 |
804 |
805 | {% for f in data.reviewed_functions %}
806 |
807 | Function ARN:
808 | {{f['FunctionArn']}}
809 |
810 |
811 | Memory (MB):
812 | <{{f['MemorySize']}}
813 |
814 |
815 | Timeout (S):
816 | {{f['Timeout']}}
817 |
818 |
819 | Runtime:
820 | {{f['Runtime']}}
821 |
822 |
823 | Architecture:
824 | {{f['Architectures']}}
825 |
826 |
827 | Package Type:
828 | {{f['PackageType']}}
829 |
830 |
831 | Reserved Concurrency:
832 | {% if f.ReservedConcurrency != {} %}
833 | {{f.ReservedConcurrency.ReservedConcurrentExecutions}}
834 | {% else %}
835 | 0
836 | {% endif %}
837 |
838 |
839 | Provisioned Concurrency Config:
840 | {% if f.ProvisionedConcurrencyConfigs != [] %}
841 | {% for c in f.ProvisionedConcurrencyConfigs %}
842 | {% if f.FunctionArn == c.FunctionArn %}
843 |
844 |
845 | Status: {{c.Status}}
846 |
847 |
848 |
849 | Requested PC Executions: {{c.RequestedProvisionedConcurrentExecutions}}
850 |
851 |
852 |
853 | Allocated PC Executions: {{c.AllocatedProvisionedConcurrentExecutions}}
854 |
855 |
856 |
857 | Available PC Executions: {{c.AvailableProvisionedConcurrentExecutions}}
858 |
859 |
860 |
861 | Last Modified: {{c.LastModified}}
862 | {% endif %}
863 | {% endfor %}
864 | {% else %}
865 | Not Set
866 | {% endif %}
867 |
868 |
869 | Execution Role:
870 | {{f['Role']}}
871 |
872 |
873 | Code Size (B):
874 | {{f['CodeSize']}}
875 |
876 |
877 | Ephemeral Storage Size (MB):
878 | {{f['EphemeralStorage']}}
879 |
880 |
881 | X-Ray tracing:
882 | {{f['TracingConfig']}}
883 |
884 |
885 | SnapStart Status (only Java):
886 | {{f['SnapStartOptimizationStatus']}}
887 |
888 |
889 |
890 |
891 | {% endfor %}
892 |
893 |
894 |
895 | {% endif %}
896 |
897 |
898 |
899 | {% if data.esms != [] %}
900 |
901 |
902 |
903 |
904 |
905 |
906 | Event Source Mapping Configuration Details
907 |
908 |
909 |
910 | {% for e in data.esms %}
911 |
912 | Event Source ARN :
913 | {{e.EventSourceArn}}
914 |
915 |
916 | Linked Function ARN :
917 | {{e.FunctionArn}}
918 |
919 |
920 | State of Event Source Mapping:
921 | {{e.State}}
922 |
923 |
924 | Batch Size:
925 | {{e.BatchSize}}
926 |
927 |
928 | Last Modified:
929 | {{e.LastModified}}
930 |
931 |
932 | Last Processing Result:
933 | {{e.LastProcessingResult}}
934 |
935 | {% if e.StartingPosition is defined %}
936 |
937 | Starting Position:
938 | {{e.StartingPosition}}
939 |
940 | {% endif %}
941 | {% if e.StartingPositionTimestamp is defined %}
942 |
943 | Starting Position Timestamp:
944 | {{e.StartingPositionTimestamp}}
945 |
946 | {% endif %}
947 | {% if e.StateTransitionReason is defined %}
948 |
949 | State Transition Reason:
950 | {{e.StateTransitionReason}}
951 |
952 | {% endif %}
953 | {% if e.AmazonManagedKafkaEventSourceConfig is defined %}
954 |
955 | Kafka Consumer Group Id:
956 | {{e.AmazonManagedKafkaEventSourceConfig.ConsumerGroupId}}
957 |
958 | {% endif %}
959 | {% if e.BisectBatchOnFunctionError is defined %}
960 |
961 | Bisect Batch on Function Error Config:
962 | {{e.BisectBatchOnFunctionError}}
963 |
964 | {% endif %}
965 | {% if e.TumblingWindowInSeconds is defined %}
966 |
967 | Tumbling Window (s):
968 | {{e.TumblingWindowInSeconds}}
969 |
970 | {% endif %}
971 | {% if e.DestinationConfig is defined %}
972 | {% if e.DestinationConfig.OnFailure is defined %}
973 |
974 | Destination On Failure:
975 | {{e.DestinationConfig.OnFailure.Destination}}
976 |
977 | {% endif %}
978 | {% if e.DestinationConfig.OnSuccess is defined %}
979 |
980 | Destination On Success:
981 | {{e.DestinationConfig.OnSuccess.Destination}}
982 |
983 | {% endif %}
984 | {% endif %}
985 | {% if e.FunctionResponseTypes is defined %}
986 |
987 | Function Response Types:
988 | {{e.FunctionResponseTypes}}
989 |
990 | {% endif %}
991 | {% if e.DocumentDBEventSourceConfig is defined %}
992 |
993 | Collection Name:
994 | {{e.DocumentDBEventSourceConfig.CollectionName}}
995 |
996 |
997 | Database Name:
998 | {{e.DocumentDBEventSourceConfig.DatabaseName}}
999 |
1000 |
1001 | Full Document:
1002 | {{e.DocumentDBEventSourceConfig.FullDocument}}
1003 |
1004 | {% endif %}
1005 | {% if e.FilterCriteria is defined %}
1006 |
1007 | Filter Configurations:
1008 |
1009 | {% for f in e.FilterCriteria.Filters %}
1010 |
1011 |
1012 | {{f.Pattern}}
1013 |
1014 | {% endfor %}
1015 | {% endif %}
1016 |
1017 | Max Batching Window (s):
1018 | {{e.MaximumBatchingWindowInSeconds}}
1019 |
1020 | {% if e.MaximumRecordAgeInSeconds is defined %}
1021 |
1022 | Max Record Age (s):
1023 | {{e.MaximumRecordAgeInSeconds}}
1024 |
1025 | {% endif %}
1026 | {% if e.MaximumRetryAttempts is defined %}
1027 |
1028 | Max Retry Attempts:
1029 | {{e.MaximumRetryAttempts}}
1030 |
1031 | {% endif %}
1032 | {% if e.ParallelizationFactor is defined %}
1033 |
1034 | Parallelization Factor:
1035 | {{e.ParallelizationFactor}}
1036 |
1037 | {% endif %}
1038 | {% if e.ScalingConfig is defined %}
1039 |
1040 | Scaling Configuration (Concurrency):
1041 | {{e.ScalingConfig.MaximumConcurrency}}
1042 |
1043 | {% endif %}
1044 | {% if e.SelfManagedEventSource is defined %}
1045 |
1046 | Self Managed Event Source Endpoints:
1047 | {{e.SelfManagedEventSource.Endpoints}}
1048 |
1049 | {% endif %}
1050 | {% if e.SelfManagedKafkaEventSourceConfig is defined %}
1051 |
1052 | Self Managed Kafka Event Source Config:
1053 | Consumer Group ID: {{e.SelfManagedKafkaEventSourceConfig.ConsumerGroupId}}
1054 |
1055 | {% endif %}
1056 |
1057 |
1058 |
1059 | {% endfor %}
1060 |
1061 |
1062 |
1063 | {% endif %}
1064 |
1065 |
1066 |
1067 |
END OF REPORT
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
--------------------------------------------------------------------------------
/src/lambda/function-generate-selected/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Serverless Operational Review
8 |
9 |
43 |
44 |
45 |
46 |
Serverless Operational Review
47 |
48 |
49 | AWS Account:
50 | {{data.account}}
51 |
52 |
53 | Region:
54 | {{data.region}}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
AWS Lambda Review
62 |
63 |
64 |
65 |
66 |
67 |
RED Danger Zone - Risks
68 |
69 | This section outlines areas that have a high risk of impacting existing workloads!
70 | Details about specific Lambda functions can be found in Reviewed Functions - Configurations section of this report.
71 |
72 |
73 |
74 |
75 |
76 |
Deprecated Function Runtime Versions!
77 |
80 |
81 |
82 |
83 | {% if data.dep_python_functions != [] %}
84 |
85 |
86 |
87 |
88 | Function Name
89 | Function Version
90 | Runtime Version
91 | Deprecation Date
92 | Update Blocked Date
93 | Creation Block Date
94 |
95 |
96 |
97 | {% for fn in data.dep_python_functions %}
98 |
99 | {{fn['FunctionName']}}
100 | {{fn['Version']}}
101 | {{fn['Runtime']}}
102 | {{fn['Deprecation date']}}
103 | {{fn['Block function update']}}
104 | {{fn['Block function create']}}
105 |
106 | {% endfor %}
107 |
108 |
109 |
110 | {% else %}
111 |
112 |
113 |
114 | {% endif %}
115 |
116 |
117 |
118 |
119 | {% if data.dep_nodejs_functions != [] %}
120 |
121 |
122 |
123 |
124 | Function Name
125 | Function Version
126 | Runtime Version
127 | Deprecation Date
128 | Update Blocked Date
129 | Creation Block Date
130 |
131 |
132 |
133 | {% for fn in data.dep_nodejs_functions %}
134 |
135 | {{fn['FunctionName']}}
136 | {{fn['Version']}}
137 | {{fn['Runtime']}}
138 | {{fn['Deprecation date']}}
139 | {{fn['Block function update']}}
140 | {{fn['Block function create']}}
141 |
142 | {% endfor %}
143 |
144 |
145 |
146 | {% else %}
147 |
148 |
149 |
150 | {% endif %}
151 |
152 |
153 |
154 |
155 | {% if data.dep_java_functions != [] %}
156 |
157 |
158 |
159 |
160 | Function Name
161 | Function Version
162 | Runtime Version
163 | Deprecation Date
164 | Update Blocked Date
165 | Creation Block Date
166 |
167 |
168 |
169 | {% for fn in data.dep_java_functions %}
170 |
171 | Function Name
172 | Function Version
173 | Runtime Version
174 | Deprecation Date
175 | Update Blocked Date
176 | Creation Block Date
177 |
178 | {% endfor %}
179 |
180 |
181 |
182 | {% else %}
183 |
184 |
185 |
186 | {% endif %}
187 |
188 |
189 |
190 |
191 | {% if data.dep_dotnet_functions != [] %}
192 |
193 |
194 |
195 |
196 | Function Name
197 | Function Version
198 | Runtime Version
199 | Deprecation Date
200 | Update Blocked Date
201 | Creation Block Date
202 |
203 |
204 |
205 | {% for fn in data.dep_dotnet_functions %}
206 |
207 | {{fn['FunctionName']}}
208 | {{fn['Version']}}
209 | {{fn['Runtime']}}
210 | {{fn['Deprecation date']}}
211 | {{fn['Block function update']}}
212 | {{fn['Block function create']}}
213 |
214 | {% endfor %}
215 |
216 |
217 |
218 | {% else %}
219 |
220 |
221 |
222 | {% endif %}
223 |
224 |
225 |
226 |
227 | {% if data.dep_ruby_functions != [] %}
228 |
229 |
230 |
231 |
232 | Function Name
233 | Function Version
234 | Runtime Version
235 | Deprecation Date
236 | Update Blocked Date
237 | Creation Block Date
238 |
239 |
240 |
241 | {% for fn in data.dep_ruby_functions %}
242 |
243 | {{fn['FunctionName']}}
244 | {{fn['Version']}}
245 | {{fn['Runtime']}}
246 | {{fn['Deprecation date']}}
247 | {{fn['Block function update']}}
248 | {{fn['Block function create']}}
249 |
250 | {% endfor %}
251 |
252 |
253 |
254 | {% else %}
255 |
256 |
257 |
258 | {% endif %}
259 |
260 |
261 |
262 |
263 | {% if data.dep_golang_functions != [] %}
264 |
265 |
266 |
267 |
268 | Function Name
269 | Function Version
270 | Runtime Version
271 | Deprecation Date
272 | Update Blocked Date
273 | Creation Block Date
274 |
275 |
276 |
277 | {% for fn in data.dep_golang_functions %}
278 |
279 | {{fn['FunctionName']}}
280 | {{fn['Version']}}
281 | {{fn['Runtime']}}
282 | {{fn['Deprecation date']}}
283 | {{fn['Block function update']}}
284 | {{fn['Block function create']}}
285 |
286 | {% endfor %}
287 |
288 |
289 |
290 | {% else %}
291 |
292 |
293 |
294 | {% endif %}
295 |
296 |
297 |
298 |
299 | {% if data.dep_custom_functions != [] %}
300 |
301 |
302 |
303 |
304 | Function Name
305 | Function Version
306 | Runtime Version
307 | Deprecation Date
308 | Update Blocked Date
309 | Creation Block Date
310 |
311 |
312 |
313 | {% for fn in data.dep_custom_functions %}
314 |
315 | {{fn['FunctionName']}}
316 | {{fn['Version']}}
317 | {{fn['Runtime']}}
318 | {{fn['Deprecation date']}}
319 | {{fn['Block function update']}}
320 | {{fn['Block function create']}}
321 |
322 | {% endfor %}
323 |
324 |
325 |
326 | {% else %}
327 |
328 |
329 |
330 | {% endif %}
331 |
332 |
333 |
334 | {% if data.warnings_ta_high_errors != [] %}
335 |
336 |
Functions with High Error Rates
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 | Function ARN
345 | Status
346 | Average Daily Errors %
347 | Maximum Daily Errors %
348 | Date of Max Error Rate
349 |
350 |
351 |
352 | {% for warnings_ta_high_errors in data.warnings_ta_high_errors %}
353 |
354 | {{warnings_ta_high_errors['FunctionArn']}}
355 | {{warnings_ta_high_errors['Status']}}
356 | {{warnings_ta_high_errors['AverageDailyErrorRatePerc']}}
357 | {{warnings_ta_high_errors['MaxDailyErrorRatePerc']}}
358 | {{warnings_ta_high_errors['DateOfMaxErrorRate']}}
359 |
360 | {% endfor %}
361 |
362 |
363 |
364 |
365 | {% endif %}
366 |
367 |
368 |
369 | {% if data.warnings_ta_excessive_timeouts != [] %}
370 |
371 |
Functions with Excessive Timeouts
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 | Function ARN
380 | Status
381 | Average Daily Timeouts %
382 | Maximum Daily Timeouts %
383 | Date of Max Timeouts
384 | Function Timeout Config
385 |
386 |
387 |
388 | {% for warnings_ta_excessive_timeout in data.warnings_ta_excessive_timeouts %}
389 |
390 | {{warnings_ta_excessive_timeout['FunctionArn']}}
391 | {{warnings_ta_excessive_timeout['Status']}}
392 | {{warnings_ta_excessive_timeout['AverageDailyTimeoutRatePerc']}}
393 | {{warnings_ta_excessive_timeout['MaxDailyTimeoutRatePerc']}}
394 | {{warnings_ta_excessive_timeout['DateOfMaxTimeoutRate']}}
395 | {{warnings_ta_excessive_timeout['FunctionTimeoutSettings']}}
396 |
397 | {% endfor %}
398 |
399 |
400 |
401 |
402 | {% endif %}
403 |
404 |
405 |
406 | {% if data.warnings_vpc != [] %}
407 |
408 |
VPC-enabled Functions - Incorrect multi-AZ configuration
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 | Function ARN
417 | SubnetID
418 | Avalabitlity Zone
419 |
420 |
421 |
422 | {% for warnings_vpc in data.warnings_vpc %}
423 |
424 | {{warnings_vpc['FunctionArn']}}
425 | {% for subaz in warnings_vpc['Subnets'] %}
426 |
427 |
428 | {{subaz['SubnetId']}}
429 | {{subaz['AZ']}}
430 |
431 | {% endfor %}
432 |
433 | {% endfor %}
434 |
435 |
436 |
437 | {% endif %}
438 |
439 |
440 |
441 |
442 |
443 |
444 |
YELLOW Warnings Zone
445 |
446 | This section outlines areas that have a low risk of impacting existing workloads but should be considered for the future.
447 | Details about specific Lambda functions can be found in Reviewed Functions - Configurations section of this report.
448 |
449 |
450 |
451 |
452 |
453 | {% if data.sup_python_functions != [] %}
454 |
455 |
456 |
457 |
458 | Function Name
459 | Function Version
460 | Runtime Version
461 | Deprecation Date
462 | Update Blocked Date
463 | Creation Block Date
464 |
465 |
466 |
467 | {% for fn in data.sup_python_functions %}
468 |
469 | {{fn['FunctionName']}}
470 | {{fn['Version']}}
471 | {{fn['Runtime']}}
472 | {{fn['Deprecation date']}}
473 | {{fn['Block function update']}}
474 | {{fn['Block function create']}}
475 |
476 | {% endfor %}
477 |
478 |
479 |
480 | {% else %}
481 |
482 |
483 |
484 | {% endif %}
485 |
486 |
487 |
488 |
489 | {% if data.sup_nodejs_functions != [] %}
490 |
491 |
492 |
493 |
494 | Function Name
495 | Function Version
496 | Runtime Version
497 | Deprecation Date
498 | Update Blocked Date
499 | Creation Block Date
500 |
501 |
502 |
503 | {% for fn in data.sup_nodejs_functions %}
504 |
505 | {{fn['FunctionName']}}
506 | {{fn['Version']}}
507 | {{fn['Runtime']}}
508 | {{fn['Deprecation date']}}
509 | {{fn['Block function update']}}
510 | {{fn['Block function create']}}
511 |
512 | {% endfor %}
513 |
514 |
515 |
516 | {% else %}
517 |
518 |
519 |
520 | {% endif %}
521 |
522 |
523 |
524 |
525 | {% if data.sup_java_functions != [] %}
526 |
527 |
528 |
529 |
530 | Function Name
531 | Function Version
532 | Runtime Version
533 | Deprecation Date
534 | Update Blocked Date
535 | Creation Block Date
536 |
537 |
538 |
539 | {% for fn in data.sup_java_functions %}
540 |
541 | Function Name
542 | Function Version
543 | Runtime Version
544 | Deprecation Date
545 | Update Blocked Date
546 | Creation Block Date
547 |
548 | {% endfor %}
549 |
550 |
551 |
552 | {% else %}
553 |
554 |
555 |
556 | {% endif %}
557 |
558 |
559 |
560 |
561 | {% if data.sup_dotnet_functions != [] %}
562 |
563 |
564 |
565 |
566 | Function Name
567 | Function Version
568 | Runtime Version
569 | Deprecation Date
570 | Update Blocked Date
571 | Creation Block Date
572 |
573 |
574 |
575 | {% for fn in data.sup_dotnet_functions %}
576 |
577 | {{fn['FunctionName']}}
578 | {{fn['Version']}}
579 | {{fn['Runtime']}}
580 | {{fn['Deprecation date']}}
581 | {{fn['Block function update']}}
582 | {{fn['Block function create']}}
583 |
584 | {% endfor %}
585 |
586 |
587 |
588 | {% else %}
589 |
590 |
591 |
592 | {% endif %}
593 |
594 |
595 |
596 |
597 | {% if data.sup_ruby_functions != [] %}
598 |
599 |
600 |
601 |
602 | Function Name
603 | Function Version
604 | Runtime Version
605 | Deprecation Date
606 | Update Blocked Date
607 | Creation Block Date
608 |
609 |
610 |
611 | {% for fn in data.sup_ruby_functions %}
612 |
613 | {{fn['FunctionName']}}
614 | {{fn['Version']}}
615 | {{fn['Runtime']}}
616 | {{fn['Deprecation date']}}
617 | {{fn['Block function update']}}
618 | {{fn['Block function create']}}
619 |
620 | {% endfor %}
621 |
622 |
623 |
624 | {% else %}
625 |
626 |
627 |
628 | {% endif %}
629 |
630 |
631 |
632 |
633 | {% if data.sup_golang_functions != [] %}
634 |
635 |
636 |
637 |
638 | Function Name
639 | Function Version
640 | Runtime Version
641 | Deprecation Date
642 | Update Blocked Date
643 | Creation Block Date
644 |
645 |
646 |
647 | {% for fn in data.sup_golang_functions %}
648 |
649 | {{fn['FunctionName']}}
650 | {{fn['Version']}}
651 | {{fn['Runtime']}}
652 | {{fn['Deprecation date']}}
653 | {{fn['Block function update']}}
654 | {{fn['Block function create']}}
655 |
656 | {% endfor %}
657 |
658 |
659 |
660 | {% else %}
661 |
662 |
663 |
664 | {% endif %}
665 |
666 |
667 |
668 |
669 | {% if data.sup_custom_functions != [] %}
670 |
671 |
672 |
673 |
674 | Function Name
675 | Function Version
676 | Runtime Version
677 | Deprecation Date
678 | Update Blocked Date
679 | Creation Block Date
680 |
681 |
682 |
683 | {% for fn in data.sup_custom_functions %}
684 |
685 | {{fn['FunctionName']}}
686 | {{fn['Version']}}
687 | {{fn['Runtime']}}
688 | {{fn['Deprecation date']}}
689 | {{fn['Block function update']}}
690 | {{fn['Block function create']}}
691 |
692 | {% endfor %}
693 |
694 |
695 |
696 | {% else %}
697 |
698 |
699 |
700 | {% endif %}
701 |
702 |
703 |
704 |
705 |
706 |
BLUE Optimization Zone - Recommendations
707 |
708 | To balance memory and CPU of your Lambda functions, we recommend using Lambda Power Tuning .
709 |
710 |
711 |
712 |
713 |
714 | {% if data.recfunctions != [] %}
715 |
716 |
Memory Optimization Recommendations
717 |
718 | The recommendations below are sourced from AWS Compute Optimizer .
719 | The loopback period is 14 days.
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 | Function ARN
730 | Finding
731 | Number of Invocations
732 | Current Memory Size
733 | Recommended Memory Size
734 | Est. Montly Saving (USD)
735 | Est. Montly Saving %
736 | Duration Current - Projected (ms)
737 |
738 |
739 |
740 | {% for rec in data.recfunctions %}
741 |
742 | {{rec['FunctionArn']}}
743 | {{rec['FindingReasonCodes'][0]}}
744 | {{rec['NumberOfInvocations']}}
745 | {{rec['CurrentMemorySize']}}
746 | {{rec['MemorySizeRecommendationOptions'][0]['MemorySize']}}
747 | {{rec['MemorySizeRecommendationOptions'][0]['SavingsOpportunity']['EstimatedMonthlySavings']['Value']}}
748 | {{rec['MemorySizeRecommendationOptions'][0]['SavingsOpportunity']['SavingsOpportunityPercentage']}}
749 | {{rec['UtilizationMetrics'][0]['Value']}} - {{rec['MemorySizeRecommendationOptions'][0]['ProjectedUtilizationMetrics'][1]['Value']}}
750 |
751 | {% endfor %}
752 |
753 |
754 |
755 | {% endif %}
756 |
757 |
758 |
759 |
760 | {% if data.recarch != [] %}
761 |
762 |
Functions running on x86 Architecture Type
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 | Functions running x86 architecture, consider upgrade to arm64
772 |
773 |
774 |
775 | {% for f in data.recarch %}
776 |
777 | {{f}}
778 |
779 | {% endfor %}
780 |
781 |
782 |
783 | {% endif %}
784 |
785 |
786 |
787 |
788 |
789 |
790 |
Reviewed Functions - Configurations
791 |
The below information contains all reviewed Lambda functions, function versions and their configurations including event source mappings configurations.
792 |
793 |
794 |
795 | {% if data.reviewed_functions != [] %}
796 |
797 |
798 |
799 |
800 |
801 | Reviewed functions configurations
802 |
803 |
804 |
805 | {% for f in data.reviewed_functions %}
806 |
807 | Function ARN:
808 | {{f['FunctionArn']}}
809 |
810 |
811 | Memory (MB):
812 | <{{f['MemorySize']}}
813 |
814 |
815 | Timeout (S):
816 | {{f['Timeout']}}
817 |
818 |
819 | Runtime:
820 | {{f['Runtime']}}
821 |
822 |
823 | Architecture:
824 | {{f['Architectures']}}
825 |
826 |
827 | Package Type:
828 | {{f['PackageType']}}
829 |
830 |
831 | Reserved Concurrency:
832 | {% if f.ReservedConcurrency != {} %}
833 | {{f.ReservedConcurrency.ReservedConcurrentExecutions}}
834 | {% else %}
835 | 0
836 | {% endif %}
837 |
838 |
839 | Provisioned Concurrency Config:
840 | {% if f.ProvisionedConcurrencyConfigs != [] %}
841 | {% for c in f.ProvisionedConcurrencyConfigs %}
842 | {% if f.FunctionArn == c.FunctionArn %}
843 |
844 |
845 | Status: {{c.Status}}
846 |
847 |
848 |
849 | Requested PC Executions: {{c.RequestedProvisionedConcurrentExecutions}}
850 |
851 |
852 |
853 | Allocated PC Executions: {{c.AllocatedProvisionedConcurrentExecutions}}
854 |
855 |
856 |
857 | Available PC Executions: {{c.AvailableProvisionedConcurrentExecutions}}
858 |
859 |
860 |
861 | Last Modified: {{c.LastModified}}
862 | {% endif %}
863 | {% endfor %}
864 | {% else %}
865 | Not Set
866 | {% endif %}
867 |
868 |
869 | Execution Role:
870 | {{f['Role']}}
871 |
872 |
873 | Code Size (B):
874 | {{f['CodeSize']}}
875 |
876 |
877 | Ephemeral Storage Size (MB):
878 | {{f['EphemeralStorage']}}
879 |
880 |
881 | X-Ray tracing:
882 | {{f['TracingConfig']}}
883 |
884 |
885 | SnapStart Status (only Java):
886 | {{f['SnapStartOptimizationStatus']}}
887 |
888 |
889 |
890 |
891 | {% endfor %}
892 |
893 |
894 |
895 | {% endif %}
896 |
897 |
898 |
899 | {% if data.esms != [] %}
900 |
901 |
902 |
903 |
904 |
905 |
906 | Event Source Mapping Configuration Details
907 |
908 |
909 |
910 | {% for e in data.esms %}
911 |
912 | Event Source ARN :
913 | {{e.EventSourceArn}}
914 |
915 |
916 | Linked Function ARN :
917 | {{e.FunctionArn}}
918 |
919 |
920 | State of Event Source Mapping:
921 | {{e.State}}
922 |
923 |
924 | Batch Size:
925 | {{e.BatchSize}}
926 |
927 |
928 | Last Modified:
929 | {{e.LastModified}}
930 |
931 |
932 | Last Processing Result:
933 | {{e.LastProcessingResult}}
934 |
935 | {% if e.StartingPosition is defined %}
936 |
937 | Starting Position:
938 | {{e.StartingPosition}}
939 |
940 | {% endif %}
941 | {% if e.StartingPositionTimestamp is defined %}
942 |
943 | Starting Position Timestamp:
944 | {{e.StartingPositionTimestamp}}
945 |
946 | {% endif %}
947 | {% if e.StateTransitionReason is defined %}
948 |
949 | State Transition Reason:
950 | {{e.StateTransitionReason}}
951 |
952 | {% endif %}
953 | {% if e.AmazonManagedKafkaEventSourceConfig is defined %}
954 |
955 | Kafka Consumer Group Id:
956 | {{e.AmazonManagedKafkaEventSourceConfig.ConsumerGroupId}}
957 |
958 | {% endif %}
959 | {% if e.BisectBatchOnFunctionError is defined %}
960 |
961 | Bisect Batch on Function Error Config:
962 | {{e.BisectBatchOnFunctionError}}
963 |
964 | {% endif %}
965 | {% if e.TumblingWindowInSeconds is defined %}
966 |
967 | Tumbling Window (s):
968 | {{e.TumblingWindowInSeconds}}
969 |
970 | {% endif %}
971 | {% if e.DestinationConfig is defined %}
972 | {% if e.DestinationConfig.OnFailure is defined %}
973 |
974 | Destination On Failure:
975 | {{e.DestinationConfig.OnFailure.Destination}}
976 |
977 | {% endif %}
978 | {% if e.DestinationConfig.OnSuccess is defined %}
979 |
980 | Destination On Success:
981 | {{e.DestinationConfig.OnSuccess.Destination}}
982 |
983 | {% endif %}
984 | {% endif %}
985 | {% if e.FunctionResponseTypes is defined %}
986 |
987 | Function Response Types:
988 | {{e.FunctionResponseTypes}}
989 |
990 | {% endif %}
991 | {% if e.DocumentDBEventSourceConfig is defined %}
992 |
993 | Collection Name:
994 | {{e.DocumentDBEventSourceConfig.CollectionName}}
995 |
996 |
997 | Database Name:
998 | {{e.DocumentDBEventSourceConfig.DatabaseName}}
999 |
1000 |
1001 | Full Document:
1002 | {{e.DocumentDBEventSourceConfig.FullDocument}}
1003 |
1004 | {% endif %}
1005 | {% if e.FilterCriteria is defined %}
1006 |
1007 | Filter Configurations:
1008 |
1009 | {% for f in e.FilterCriteria.Filters %}
1010 |
1011 |
1012 | {{f.Pattern}}
1013 |
1014 | {% endfor %}
1015 | {% endif %}
1016 |
1017 | Max Batching Window (s):
1018 | {{e.MaximumBatchingWindowInSeconds}}
1019 |
1020 | {% if e.MaximumRecordAgeInSeconds is defined %}
1021 |
1022 | Max Record Age (s):
1023 | {{e.MaximumRecordAgeInSeconds}}
1024 |
1025 | {% endif %}
1026 | {% if e.MaximumRetryAttempts is defined %}
1027 |
1028 | Max Retry Attempts:
1029 | {{e.MaximumRetryAttempts}}
1030 |
1031 | {% endif %}
1032 | {% if e.ParallelizationFactor is defined %}
1033 |
1034 | Parallelization Factor:
1035 | {{e.ParallelizationFactor}}
1036 |
1037 | {% endif %}
1038 | {% if e.ScalingConfig is defined %}
1039 |
1040 | Scaling Configuration (Concurrency):
1041 | {{e.ScalingConfig.MaximumConcurrency}}
1042 |
1043 | {% endif %}
1044 | {% if e.SelfManagedEventSource is defined %}
1045 |
1046 | Self Managed Event Source Endpoints:
1047 | {{e.SelfManagedEventSource.Endpoints}}
1048 |
1049 | {% endif %}
1050 | {% if e.SelfManagedKafkaEventSourceConfig is defined %}
1051 |
1052 | Self Managed Kafka Event Source Config:
1053 | Consumer Group ID: {{e.SelfManagedKafkaEventSourceConfig.ConsumerGroupId}}
1054 |
1055 | {% endif %}
1056 |
1057 |
1058 |
1059 | {% endfor %}
1060 |
1061 |
1062 |
1063 | {% endif %}
1064 |
1065 |
1066 |
1067 |
END OF REPORT
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
--------------------------------------------------------------------------------