@@ -26,12 +26,16 @@ import (
2626 internal "github.com/karl-cardenas-coding/go-lambda-cleanup/v2/internal"
2727 log "github.com/sirupsen/logrus"
2828 "github.com/spf13/cobra"
29+ "golang.org/x/time/rate"
2930)
3031
3132const (
3233 // Per AWS API Valid Range: Minimum value of 1. Maximum value of 10000.
3334 maxItems int32 = 10000
3435 regionFile string = "aws-regions.txt"
36+ // AWS Lambda API rate limit is ~15 requests/second per account per region.
37+ // Default to 10 rps to leave headroom for other callers in the same account.
38+ defaultAPIRPS rate.Limit = 10
3539)
3640
3741var (
@@ -143,7 +147,10 @@ var cleanCmd = &cobra.Command{
143147 o .APIOptions = append (o .APIOptions , middleware .AddUserAgentKeyValue ("go-lambda-cleanup" , VersionString ))
144148 })
145149
146- err = executeClean (ctx , & config , initSvc , customeDeleteList )
150+ limiter := rate .NewLimiter (defaultAPIRPS , 1 )
151+ log .Debugf ("API rate limiting: %.0f requests/second" , float64 (defaultAPIRPS ))
152+
153+ err = executeClean (ctx , & config , initSvc , customeDeleteList , limiter )
147154 if err != nil {
148155 return err
149156 }
@@ -157,7 +164,7 @@ executeClean is the main function that executes the clean-up process
157164It takes a context, a pointer to a cliConfig struct, a pointer to a lambda client, and a list of custom lambdas to delete
158165An error is returned if the function fails to execute.
159166*/
160- func executeClean (ctx context.Context , config * cliConfig , svc * lambda.Client , customList []string ) error {
167+ func executeClean (ctx context.Context , config * cliConfig , svc * lambda.Client , customList []string , limiter * rate. Limiter ) error {
161168 startTime := time .Now ()
162169
163170 var (
@@ -170,7 +177,7 @@ func executeClean(ctx context.Context, config *cliConfig, svc *lambda.Client, cu
170177
171178 log .Info ("Scanning AWS environment in " + * config .RegionFlag )
172179
173- lambdaList , err := getAllLambdas (ctx , svc , customList )
180+ lambdaList , err := getAllLambdas (ctx , svc , customList , limiter )
174181 if err != nil {
175182 log .Error ("ERROR: " , err )
176183 log .Fatal ("ERROR: Failed to retrieve Lambda list." )
@@ -184,7 +191,7 @@ func executeClean(ctx context.Context, config *cliConfig, svc *lambda.Client, cu
184191 for _ , lambda := range lambdaList {
185192 lambdaItem := lambda
186193
187- lambdaVersionsList , err := getAllLambdaVersion (ctx , svc , lambdaItem , * config )
194+ lambdaVersionsList , err := getAllLambdaVersion (ctx , svc , lambdaItem , * config , limiter )
188195 if err != nil {
189196 log .Error ("ERROR: " , err )
190197 log .Fatal ("ERROR: Failed to retrieve Lambda version list." )
@@ -241,14 +248,14 @@ func executeClean(ctx context.Context, config *cliConfig, svc *lambda.Client, cu
241248 return returnError
242249 }
243250
244- err = deleteLambdaVersion (ctx , svc , globalLambdaDeleteInputStructs ... )
251+ err = deleteLambdaVersion (ctx , svc , limiter , globalLambdaDeleteInputStructs ... )
245252 if err != nil {
246253 log .Error ("ERROR: " , err )
247254 log .Fatal ("ERROR: Failed to delete Lambda versions." )
248255 }
249256
250257 // Recalculate storage size
251- updatedLambdaList , err := getAllLambdas (ctx , svc , customList )
258+ updatedLambdaList , err := getAllLambdas (ctx , svc , customList , limiter )
252259 if err != nil {
253260 log .Error ("ERROR: " , err )
254261 log .Fatal ("ERROR: Failed to retrieve Lambda list." )
@@ -257,7 +264,7 @@ func executeClean(ctx context.Context, config *cliConfig, svc *lambda.Client, cu
257264 log .Info ("............" )
258265
259266 for _ , lambda := range updatedLambdaList {
260- updatededlambdaVersionsList , err := getAllLambdaVersion (ctx , svc , lambda , * config )
267+ updatededlambdaVersionsList , err := getAllLambdaVersion (ctx , svc , lambda , * config , limiter )
261268 if err != nil {
262269 log .Error ("ERROR: " , err )
263270 log .Fatal ("ERROR: Failed to retrieve Lambda version list." )
@@ -388,7 +395,7 @@ func countDeleteVersions(deleteList [][]lambda.DeleteFunctionInput) int {
388395// deleteLambdaVersion takes a list of lambda.DeleteFunctionInput and deletes all the versions in the list
389396// The function takes a context, a pointer to a lambda client, and a list of lambda.DeleteFunctionInput. A variadic operator is used to allow the user to pass in multiple lists of lambda.DeleteFunctionInput
390397// Use this function with caution as it will delete all the versions in the list.
391- func deleteLambdaVersion (ctx context.Context , svc * lambda.Client , deleteList ... []lambda.DeleteFunctionInput ) error {
398+ func deleteLambdaVersion (ctx context.Context , svc * lambda.Client , limiter * rate. Limiter , deleteList ... []lambda.DeleteFunctionInput ) error {
392399 var (
393400 returnError error
394401 wg sync.WaitGroup
@@ -400,6 +407,11 @@ func deleteLambdaVersion(ctx context.Context, svc *lambda.Client, deleteList ...
400407 func () {
401408 defer wg .Done ()
402409
410+ if err := limiter .Wait (ctx ); err != nil {
411+ returnError = fmt .Errorf ("rate limiter interrupted: %w" , err )
412+ return
413+ }
414+
403415 _ , err := svc .DeleteFunction (ctx , & version )
404416 if err != nil {
405417 err = errors .New ("Failed to delete version " + * version .Qualifier + " of " + * version .FunctionName + ". \n Additional details: " + err .Error ())
@@ -436,7 +448,7 @@ func getLambdasToDeleteList(list []types.FunctionConfiguration, retainCount int8
436448}
437449
438450// getAllLambdas returns a list of all available lambdas in the AWS environment. The function takes a context, a pointer to a lambda client, and a list of custom lambdas function names to delete.
439- func getAllLambdas (ctx context.Context , svc * lambda.Client , customList []string ) ([]types.FunctionConfiguration , error ) {
451+ func getAllLambdas (ctx context.Context , svc * lambda.Client , customList []string , limiter * rate. Limiter ) ([]types.FunctionConfiguration , error ) {
440452 var (
441453 lambdasListOutput []types.FunctionConfiguration
442454 returnError error
@@ -463,6 +475,10 @@ func getAllLambdas(ctx context.Context, svc *lambda.Client, customList []string)
463475
464476 if len (customList ) > 0 {
465477 for _ , item := range customList {
478+ if err := limiter .Wait (ctx ); err != nil {
479+ return lambdasListOutput , fmt .Errorf ("rate limiter interrupted: %w" , err )
480+ }
481+
466482 input := & lambda.GetFunctionInput {
467483 FunctionName : aws .String (item ),
468484 }
@@ -495,6 +511,7 @@ func getAllLambdaVersion(
495511 svc * lambda.Client ,
496512 item types.FunctionConfiguration ,
497513 flags cliConfig ,
514+ limiter * rate.Limiter ,
498515) ([]types.FunctionConfiguration , error ) {
499516 var (
500517 lambdasLisOutput []types.FunctionConfiguration
@@ -509,6 +526,10 @@ func getAllLambdaVersion(
509526
510527 p := lambda .NewListVersionsByFunctionPaginator (svc , input )
511528 for p .HasMorePages () {
529+ if err := limiter .Wait (ctx ); err != nil {
530+ return lambdasLisOutput , fmt .Errorf ("rate limiter interrupted: %w" , err )
531+ }
532+
512533 page , err := p .NextPage (ctx )
513534 if err != nil {
514535 log .Error (err )
@@ -529,6 +550,10 @@ func getAllLambdaVersion(
529550 var aliasesOut []types.AliasConfiguration
530551
531552 for pg .HasMorePages () {
553+ if err := limiter .Wait (ctx ); err != nil {
554+ return lambdasLisOutput , fmt .Errorf ("rate limiter interrupted: %w" , err )
555+ }
556+
532557 page , err := pg .NextPage (ctx )
533558 if err != nil {
534559 log .Error (err )
0 commit comments