How to use Terraform modules for multiple lambdas in Github?

Hi guys,

I use Terraform with Github to deploy changes in AWS lambda functions with Jenkins.

What I did so far is below in Github:

repository-folder
./modules/ #My folder with Terraform module for Lambda function
./modules/lambda/ # Here is the folder where I have my main.tf and variables.tf file
./lambda-function-1/ #In each lambda folder I have main.tf file with the module block
./lambda-function-2/
./lambda-function-3/

With Github webhook I keep track for any changes in the folders. Once any change is detected Terraform run initialize and apply the changes as part of the Jenkins pipeline.
My module is running correctly and I don’t see any errors but Terraform cannot detect any changes and my lambdas are not rebuild.

I already read all the documentation in Terraform from the modules section but I can’t find the way to complete this. My question is how can I use Terraform to detect any changes in my lambda folders and apply them via Jenkins?

Thank you in advance!
Ivo

I forgot to share my Terraform version which is 1.4.2 but I’m going to install later the latest one.

What happens when you apply each lambda root module manually?
I’m assuming they are all independent from each other(i.e. different state files), let me know if not.

Were the lambdas created by the same pipeline or are you implementing this now? If so, how did you apply the changes before?

Out of curiosity, what storage backend are you using for the state file?

Hi macmiranda,

Thanks for your reply.

If I go the folder and run Terraform everything is fine and the lambdas are build smoothly.
Yes, they are independent and separated to folders as described in my first message.

I’m building this now and the lambdas have to be included as step in big pipeline with frontend/backend test and deployment.

Currently the state files are local but when I’m ready the state files will be uploaded to S3 bucket with DynamoDB key.

Please share your Jenkinsfile and all your Terraform code - otherwise there’s just so much unknown, that it’s not really possible to help. Doing this via a link to GitHub is probably easier than pasting things into the discussion forum.

Hi maxb,

Thanks for your reply. :slight_smile:

I’m doing this in private repo.
My configuration for now is very basic as below:

./lambda-function-1/main.tf

provider “aws” {
region = “us-west-2”
}

module “lambda” {
source = “…/modules/lambda”

iam_role_name = “tf-lambda-role-name”
iam_policy_name = “tf-lambda-policy-name”
function_name = “tf-lambda-fucntion-name”
lambda_handler = “index_handler”
compatible_runtimes = “nodejs14.x”

publish = true
}

If I run Terrafor init and apply manually from the lambdas folder from the Jenkins controller server everything is fine and my resources are created successfully. Unfortunately I can’t share with you the pipeline because there are lots of configuration in the block with .env variables.

Regards,
Ivo

So at this point I have to assume that you checked in your state file to the git repo (which is a bad idea by the way).

If I got this right, you’re using Github webhooks to trigger the Jenkins pipeline.
But you didn’t say which events specifically and what branch your pipeline is running on

Hi macmiranda,

Thanks again for your reply.

I have .gitignore with the following lines:

.terraform
*.tfstate
*.tfstate.backup

Yes, you are right about Github webhooks and Jenkins pipeline.

My webhook have the following events enabled in it.

“Pull requests” and “Pushes”.

Currently the branch is “dev” but later on the idea is to use it for “prod” too because the pipelines don’t differ the steps except the branch.

Regards,
Ivo

How’s Jenkins supposed to know the previous state of your module then?

Hi macmiranda,

My mistake now it’s removed from the root folder of the repository but there aren’t any changes. I just re-check the module folder configurations if they can be in any help:

/var/jenkins_home/workspace/tf-projects/modules/lambda# ls -lahtr
total 12K
drwxr-xr-x. 3 jenkins jenkins   20 Mar 30 12:37 ..
-rw-r--r--. 1 root    root    3.4K Mar 31 08:29 .terraform.lock.hcl
drwxr-xr-x. 4 root    root      38 Apr  3 05:58 .terraform
drwxr-xr-x. 3 jenkins jenkins   86 Apr  3 06:09 .
-rw-r--r--. 1 jenkins jenkins  690 Apr  3 06:10 variables.tf
-rw-r--r--. 1 jenkins jenkins 1.9K Apr  3 06:10 main.tf

and this is for the one of the lambda functions:

/var/jenkins_home/workspace/tf-projects/lambda-function-1/# ls -lahtr 
total 28K
drwxr-xr-x. 4 jenkins jenkins   38 Mar 30 05:40 .terraform
-rw-r--r--. 1 jenkins jenkins 3.5K Mar 30 05:40 .terraform.lock.hcl
-rw-r--r--. 1 jenkins jenkins  320 Mar 31 09:15 main.tf
-rw-r--r--. 1 jenkins jenkins  397 Mar 31 09:15 index.js
drwxr-xr-x. 3 jenkins jenkins   82 Apr  3 06:05 .
drwxr-xr-x. 8 jenkins jenkins  16K Apr  3 06:09 ..

When I do manually terraform plan from the lambda function folder I have output below:

Terraform will perform the following actions:

  # module.lambda.data.archive_file.task_payload_zip will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "archive_file" "task_payload_zip" {
      + id                  = (known after apply)
      + output_base64sha256 = (known after apply)
      + output_md5          = (known after apply)
      + output_path         = "./task_payload.zip"
      + output_sha          = (known after apply)
      + output_size         = (known after apply)
      + source_dir          = "./lambda"
      + type                = "zip"
    }

  # module.lambda.aws_iam_policy.AWSLambdaBasicExecutionRole-f8 will be created
  + resource "aws_iam_policy" "AWSLambdaBasicExecutionRole-f8" {
      + arn         = (known after apply)
      + description = "AWS IAM Policy for managing aws lambda role"
      + id          = (known after apply)
      + name        = "tf-lambda-policy-name"
      + path        = "/"
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "logs:CreateLogGroup",
                          + "logs:CreateLogStream",
                          + "logs:PutLogEvents",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:logs:*:*:*"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + policy_id   = (known after apply)
      + tags_all    = (known after apply)
    }

  # module.lambda.aws_iam_role.task_payload will be created
  + resource "aws_iam_role" "task_payload" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "lambda.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "tf-lambda-role"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)
    }

  # module.lambda.aws_iam_role_policy_attachment.attach_iam_policy_to_iam_role will be created
  + resource "aws_iam_role_policy_attachment" "attach_iam_policy_to_iam_role" {
      + id         = (known after apply)
      + policy_arn = (known after apply)
      + role       = "tf-lambda-role"
    }

  # module.lambda.aws_lambda_function.task_payload will be created
  + resource "aws_lambda_function" "task_payload" {
      + architectures                  = (known after apply)
      + arn                            = (known after apply)
      + filename                       = "./task_payload.zip"
      + function_name                  = "tf-lambda"
      + handler                        = "index_handler"
      + id                             = (known after apply)
      + invoke_arn                     = (known after apply)
      + last_modified                  = (known after apply)
      + memory_size                    = 128
      + package_type                   = "Zip"
      + publish                        = true
      + qualified_arn                  = (known after apply)
      + qualified_invoke_arn           = (known after apply)
      + reserved_concurrent_executions = -1
      + role                           = (known after apply)
      + runtime                        = "nodejs14.x"
      + signing_job_arn                = (known after apply)
      + signing_profile_version_arn    = (known after apply)
      + skip_destroy                   = false
      + source_code_hash               = (known after apply)
      + source_code_size               = (known after apply)
      + tags_all                       = (known after apply)
      + timeout                        = 3
      + version                        = (known after apply)
    }

  # module.lambda.null_resource.lambda_dependencies will be created
  + resource "null_resource" "lambda_dependencies" {
      + id = (known after apply)
    }

Plan: 5 to add, 0 to change, 0 to destroy.

Regards,
Ivo

Now you lost me completely.

Are you running the manual terraform commands from your Jenkins server? If so, is it just for convenience or is there another reason for that?

Note: Please use pre-formatted text when you share source code or terminal output here (for readability)

I can see in the plan above that Terraform will attempt to create all those resources, not update them. So you actually removed the state files from your modules? How are you going to update them now?

Besides, I really need to look at your Jenkinsfile pipeline declaration and your Github Webhook configuration to understand what’s going on.

Hi macmiranda,

Don’t get lost, please, I need you. :slight_smile:

Yes, this is from the test server with Jenkins and the module is not installed yet because I want to be convenient everything is fine before that.

I apologies, the code is now formatted in the previous post.

Can I share with you my repo and Jenkins pipeline for the test server in private message or this is against the rules here? Basically the file is very very simple with the 3 main steps for Jenkins to run Terraform.

Yesterday I provided you my webhook configuration settings.

You can share anything privately with me as long as you trust me :wink:

Just remember this is a community forum, you shouldn’t trust every one. The best practice in this case is to make sure that whatever you want to share does not contain sensitive data or personal information.

1 Like

Something else to bear in mind: The best way to attract help is to make it easy for people to help you, which means providing as much relevant information as you can, in forms that are easy to read. (Redact truly secret information, but try to do so only when genuinely necessary.)

Right now, you’re making it pretty hard, instead, and macmiranda has been patiently persevering at trying to drag out further details - whereas I mostly just gave up on interacting in this thread once you declined to share your pipeline.

2 Likes