Provisioning and Accessing Cloud9 Development Environment with a Public-Facing URL using Terraform and Github actions
Tags: aws, terraform, githubactions, iac
Published: 2023-07-07 19:35:33

AWS Cloud9 IDE provides a powerful and collaborative environment for software development. Provisioning Cloud9 with Terraform and automating the process with GitHub Actions can streamline the setup, configuration, and maintenance of your development environment. In this blog post, we will explore how to leverage Terraform and GitHub Actions to provision and manage Cloud9 IDE, enabling faster and more efficient development workflows.



## Setting up Terraform for Cloud9 provisioning

Follow the Terraforms official guide [**_Get Started - AWS _**](https://developer.hashicorp.com/terraform/tutorials/aws-get-started)for quick start and installation process for Terraform, configuring it to link your AWS account. 

Once, you have the required prerequisites - Lets start writing the terraform code to provision Cloud9 instance. 

```tf
resource "aws_cloud9_environment_ec2" "cloudysky_cloud9_instance" {
  name                        = "cloudysky_cloud9_instance"
  instance_type               = "t2.medium"
  automatic_stop_time_minutes = 30
  subnet_id                   = data.aws_subnet.existing_subnet.id

  tags = {
    Environment = "dev"
    Owner       = "CloudySky"
  }
}
```

Check out the official Terraform documentation for the available arguments for this resource  [**_aws_cloud9_environment_ec2_**](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloud9_environment_ec2.html)

In my example above, I have included name, instance type, automatic stop time minutes to shut down the instance after 30 minutes since last used and fetch subnet id dynamically. Also, i have included couple of tags for this resource. 



To get the subnet ID you can use various approaches and for easier understanding, I have used data block so that terraform reads from the given source which can be exported and referred with the local name. 

```tf
data "aws_subnet" "existing_subnet" {
  filter {
    name   = "vpc-id"
    values = ["vpc-048c23df4f54370a4"] # Replace with the ID of the VPC
  }

  filter {
    name   = "tag:Name"
    values = ["project-subnet-public1-us-east-1a"] # Replace with the desired subnet name
  }
}
```



 In addition to this, if you would like to make the Cloud9 URL with public access - please add the following ingress SG rule as well. You could also use AWS Cognito user pool to authenticate users. (Watch out for a blog in that soon :) )



To allow public access, first get the security group associated with the EC2 instance dynamically and then add a SG rule to allow traffic from public internet. 

```tf
data "aws_security_group" "cloud9_secgroup" {
  filter {
    name = "tag:aws:cloud9:environment"
    values = [
      aws_cloud9_environment_ec2.cloudysky_cloud9_instance.id
    ]
  }
}
# Allow public access to port 8080
resource "aws_security_group_rule" "cloud9_inbound" {
  type              = "ingress"
  from_port         = 8080
  to_port           = 8080
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = data.aws_security_group.cloud9_secgroup.id
}
```

In addition to the above, define the following output variables for EC2 ID, ARN and Cloud9 URL.

```tf
# EC2 ID
output "id" {
  value = aws_cloud9_environment_ec2.cloudysky_cloud9_instance.id
}

# EC2 ARN
output "arn" {
  value = aws_cloud9_environment_ec2.cloudysky_cloud9_instance.arn
}

# cloud9 url
output "cloud9_url" {
  value = "https://${var.region}.console.aws.amazon.com/cloud9/ide/${aws_cloud9_environment_ec2.cloudysky_cloud9_instance.id}"
}
```

## Automating Cloud9 Provisioning with GitHub Actions

GitHub Actions provides a powerful automation platform that integrates seamlessly with your GitHub repositories. By leveraging GitHub Actions, you can automate the provisioning of Cloud9 environments, ensuring consistent and reliable setups for your development workflows. Here's how you can automate Cloud9 provisioning using GitHub Actions.



**Create a Workflow File:**

In your GitHub repository, create a new workflow file in the .github/workflows directory. You can name it something like cloud9-provisioning.yml. This file will define the steps and triggers for the Cloud9 provisioning workflow. **_ _**



**Define Workflow Triggers:**

The idea here is to run the **_terraform plan for any PR request created and update the pull request comment with the plan details and terraform apply --auto-approve when the pull request is merged._**



**Configure Workflow Steps:**

Inside the workflow file, define the steps required to provision the Cloud9 environment. Here is a quick snippet on how I have done this. 



```yaml
name: "Terraform Cloud9 Provisioning"

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  terraform:
    name: "Terraform Cloud9 Provisioning"
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform Format
        id: fmt
        run: terraform fmt -check

      - name: Terraform Init
        id: init
        run: terraform init
      
      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform Plan
        id: plan
        if: github.event_name == 'pull_request'
        run: terraform plan -no-color -input=false
        continue-on-error: true

      - name: Update Pull Request
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: ${{ steps.plan.outputs.stdout }}
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

            <details><summary>Show Plan</summary>

            \`\`\`terraform\n
            ${process.env.PLAN}
            \`\`\`

            </details>

            *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      - name: Terraform Plan Status
        if: steps.plan.outcome == 'failure'
        run: exit 1

      - name: Terraform Apply
        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
        run: terraform apply -auto-approve -input=false
```



> Please make sure to configure **_TF_API_TOKEN _**in github secrets and following the steps mentioned on official documentation for Terraform and Github. 



**Test and Deploy Workflow:**

Commit and push the workflow file to your repository. GitHub Actions will automatically pick up the workflow configuration and start running it based on the specified triggers. Monitor the workflow runs and verify that the Cloud9 environment is provisioned successfully.

**Check out the source code here : **https://github.com/SandeepKumarYaramchitti/terraform-ec2-cloud9ide


By automating the Cloud9 provisioning process with GitHub Actions, you can ensure consistent and reproducible setups for your development environments. This automation saves time and effort, enabling developers to focus on writing code rather than manually setting up environments.