# How to keep AMI up-to-date by using Packer, AWS Codebuild, and Terraform | Part 2.

After the Part 1 post, which specifically explaining configuration with packer, on this part, I'll write more about Terraform and AWS Codebuild.

The final state that we'd like to have is something like this.


![ami.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1626412532715/EsuQ9ncty.png)


##  [2. Terraform](https://www.terraform.io/) 

I will build the whole stack using Terraform. Before we start, we need a code repository to store our code. In this case, I am using Github.

Let's start setting up Codebuild !!!


- Create a module for Codebuild

*main.tf*
 ```
resource "aws_codebuild_project" "main" {
  name         = var.name
  description  = var.description
  service_role = var.service_role
  artifacts {
    encryption_disabled = true
    type                = "CODEPIPELINE"
  }
  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "aws/codebuild/standard:4.0"
    type                        = "LINUX_CONTAINER"
    image_pull_credentials_type = "CODEBUILD"
  }
  source {
    git_clone_depth     = 0
    insecure_ssl        = false
    report_build_status = false
    type                = "CODEPIPELINE"
    buildspec = templatefile("${path.module}/buildspec.tpl", { project = var.name })

  }
  tags = {
    ManagedBy = "Terraform"
  }
}
```

*output.tf*
```
output "codebuild_project_name" {
  value = aws_codebuild_project.main.name
}
```

*variables.tf*
```
variable "service_role" {}

variable "name" {}

variable "description" {
  default = ""
}

variable "location" {}
```

buldspec.tpl
```
version: 0.2

phases:
  pre_build:
    commands:
      - curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add -
      - apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
      - apt-get update && apt-get install packer=1.6.6
      - wget https://github.com/rgl/packer-provisioner-windows-update/releases/download/v0.10.1/packer-provisioner-windows-update_0.10.1_linux_amd64.tar.gz
      - tar -xvf packer-provisioner-windows-update_0.10.1_linux_amd64.tar.gz
      - ls -la
      - chmod +x packer-provisioner-windows-update
      - mv packer-provisioner-windows-update /usr/bin/packer-provisioner-windows-update
  build:
    commands:
      - echo Build started on `date.`
      - packer build -var-file variables.json ${project}.json
  post_build:
    commands:
      - echo Build completed on `date.`

```

*iam.tf*

```
locals {
  codebuild_policy_arns = [
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
  ]
}

resource "aws_iam_role_policy_attachment" "codebuild_packer_policy_attachment" {
  count      = length(local.codebuild_policy_arns)
  role       = aws_iam_role.codebuild_packer.name
  policy_arn = local.codebuild_policy_arns[count.index]
}

resource "aws_iam_role" "codebuild_packer" {
  name = "codebuild_packer_role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "codebuild_packer" {
  role = aws_iam_role.codebuild_packer.name

  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Resource": [
        "*"
      ],
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "iam:PassRole",
        "iam:GetInstanceProfile"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "${aws_s3_bucket.src.arn}",
        "${aws_s3_bucket.src.arn}/*"
      ]
    }
  ]
}
POLICY
}

###
# EC2  Role
data "aws_iam_policy_document" "ec2_default_policy" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "cloudwatch:GetMetricData",
      "cloudwatch:ListMetrics",
      "ec2:DescribeRegions",
      "sqs:ListQueues",
      "sts:GetCallerIdentity",
      "iam:ListAccountAliases",
      "autoscaling:CompleteLifecycleAction",
      "autoscaling:RecordLifecycleActionHeartbeat"
    ]
  }
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "s3:ListBucket",
      "s3:GetObject",
      "s3:GetBucketLocation"
    ]
  }
}

resource "aws_iam_role" "ec2_default_role" {
  name               = "packer"
  assume_role_policy = data.aws_iam_policy_document.ec2_default_sts.json
}

data "aws_iam_policy_document" "ec2_default_sts" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

locals {
  ec2_default_policy_arns = [
    "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
    "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
  ]
}

resource "aws_iam_role_policy_attachment" "ec2_default_attachment" {
  count      = length(local.ec2_default_policy_arns)
  role       = aws_iam_role.ec2_default_role.name
  policy_arn = local.ec2_default_policy_arns[count.index]
}
resource "aws_iam_policy" "ec2_default_policy" {
  name   = "Packer-EC2-default-Policy"
  policy = data.aws_iam_policy_document.ec2_default_policy.json
}

resource "aws_iam_role_policy_attachment" "ec2_default_policy_attachment" {
  role       = aws_iam_role.ec2_default_role.name
  policy_arn = aws_iam_policy.ec2_default_policy.arn
}

resource "aws_iam_instance_profile" "ec2_default_instance_profile" {
  name = "packer"
  role = aws_iam_role.ec2_default_role.name
}

resource "aws_iam_role" "codepipeline_role" {
  name = "codepipeline_role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codepipeline.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "codepipeline_policy" {
  name = "codepipeline_policy"
  role = aws_iam_role.codepipeline_role.id

  policy = <<EOF
{
    "Statement": [
        {
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Condition": {
                "StringEqualsIfExists": {
                    "iam:PassedToService": [
                        "cloudformation.amazonaws.com",
                        "elasticbeanstalk.amazonaws.com",
                        "ec2.amazonaws.com",
                        "ecs-tasks.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Action": [
                "codecommit:CancelUploadArchive",
                "codecommit:GetBranch",
                "codecommit:GetCommit",
                "codecommit:GetRepository",
                "codecommit:GetUploadArchiveStatus",
                "codecommit:UploadArchive"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codedeploy:CreateDeployment",
                "codedeploy:GetApplication",
                "codedeploy:GetApplicationRevision",
                "codedeploy:GetDeployment",
                "codedeploy:GetDeploymentConfig",
                "codedeploy:RegisterApplicationRevision"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codestar-connections:UseConnection"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "elasticbeanstalk:*",
                "ec2:*",
                "elasticloadbalancing:*",
                "autoscaling:*",
                "cloudwatch:*",
                "s3:*",
                "sns:*",
                "cloudformation:*",
                "rds:*",
                "sqs:*",
                "ecs:*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "lambda:InvokeFunction",
                "lambda:ListFunctions"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "opsworks:CreateDeployment",
                "opsworks:DescribeApps",
                "opsworks:DescribeCommands",
                "opsworks:DescribeDeployments",
                "opsworks:DescribeInstances",
                "opsworks:DescribeStacks",
                "opsworks:UpdateApp",
                "opsworks:UpdateStack"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "cloudformation:CreateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStacks",
                "cloudformation:UpdateStack",
                "cloudformation:CreateChangeSet",
                "cloudformation:DeleteChangeSet",
                "cloudformation:DescribeChangeSet",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:SetStackPolicy",
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codebuild:BatchGetBuilds",
                "codebuild:StartBuild",
                "codebuild:BatchGetBuildBatches",
                "codebuild:StartBuildBatch"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Effect": "Allow",
            "Action": [
                "devicefarm:ListProjects",
                "devicefarm:ListDevicePools",
                "devicefarm:GetRun",
                "devicefarm:GetUpload",
                "devicefarm:CreateUpload",
                "devicefarm:ScheduleRun"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "servicecatalog:ListProvisioningArtifacts",
                "servicecatalog:CreateProvisioningArtifact",
                "servicecatalog:DescribeProvisioningArtifact",
                "servicecatalog:DeleteProvisioningArtifact",
                "servicecatalog:UpdateProduct"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecr:DescribeImages"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "states:DescribeExecution",
                "states:DescribeStateMachine",
                "states:StartExecution"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "appconfig:StartDeployment",
                "appconfig:StopDeployment",
                "appconfig:GetDeployment"
            ],
            "Resource": "*"
        }
    ],
    "Version": "2012-10-17"
}
EOF
}

```

- Create Pipeline with CodePipeline

Store the whole packer config into a zip file, and store it in an S3 bucket.
Please change the GitHub config with yours.

```
locals {
  codebuild_project = {
    dotnet-hosting = {
      name = "dotnet-hosting"
    }
  }
}
module "ami-codebuild" {
  for_each     = local.codebuild_project
  source       = "./modules/codebuild"
  name         = local.codebuild_project[each.key].name
  service_role = aws_iam_role.codebuild_packer.arn
  location     = "${aws_s3_bucket.src.bucket}/packer/packer.zip"
}

resource "aws_codepipeline" "ami" {
  name     = "ami"
  role_arn = aws_iam_role.codepipeline_role.arn
  artifact_store {
    location = aws_s3_bucket.src.bucket
    type     = "S3"
  }
  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["source"]

      configuration = {
        OAuthToken = var.o_auth_token
        Owner      = "uje-m"
        Repo       = "packer"
        Branch     = "main"
      }
    }
  }

  stage {
    name = "Build"

    dynamic "action" {
      for_each = local.codebuild_project
      content {
        name            = action.key
        category        = "Build"
        owner           = "AWS"
        provider        = "CodeBuild"
        version         = "1"
        input_artifacts = ["source"]
        run_order       = 1
        configuration = {
          ProjectName = module.ami-codebuild[action.key].codebuild_project_name
        }
      }
    }
  }

}

resource "aws_cloudwatch_event_rule" "every_last_sun_of_month" {
  name                = "every-last-sunday-month"
  description         = "Fires every last Sunday of the month"
  schedule_expression = "cron(0 16 ? * 1L *)"
}

resource "aws_cloudwatch_event_target" "cloudwatch_triggers_pipeline" {
  target_id = "${var.name}-commits-trigger-pipeline"
  rule      = aws_cloudwatch_event_rule.every_last_sun_of_month.name
  arn       = aws_codepipeline.ami.arn
  role_arn  = aws_iam_role.cloudwatch_ci_role.arn
}

# Allows the CloudWatch event to assume roles
resource "aws_iam_role" "cloudwatch_ci_role" {
  name_prefix = "${var.name}-cloudwatch-ci-"

  assume_role_policy = <<DOC
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
DOC
}
data "aws_iam_policy_document" "cloudwatch_ci_iam_policy" {
  statement {
    actions = [
      "iam:PassRole"
    ]
    resources = [
      "*"
    ]
  }
  statement {
    # Allow CloudWatch to start the Pipeline
    actions = [
      "codepipeline:StartPipelineExecution"
    ]
    resources = [
      aws_codepipeline.ami.arn
    ]
  }
}
resource "aws_iam_policy" "cloudwatch_ci_iam_policy" {
  name_prefix = "${var.name}-cloudwatch-ci-"
  policy      = data.aws_iam_policy_document.cloudwatch_ci_iam_policy.json
}
resource "aws_iam_role_policy_attachment" "cloudwatch_ci_iam" {
  policy_arn = aws_iam_policy.cloudwatch_ci_iam_policy.arn
  role       = aws_iam_role.cloudwatch_ci_role.name
}

```

I am using *for_each * in the module, so I would only need to add another dictionary to have more images in the pipeline.


At this stage, all configuration is complete, what I need to do is deploying it using Terraform.

Instead of using Terraform locally and managing your state somewhere, I am using Terraform Cloud.

Sign up here https://app.terraform.io/signup/account, configure the required variables and the AWS access key.


![test.JPG](https://cdn.hashnode.com/res/hashnode/image/upload/v1626415910265/uwnK01D9h.jpeg)



