Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EPMGCIP-177-Terraform Init terraform configuration #6

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/destroy-resources.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Destroy Azure Resources with Terraform

on:
workflow_dispatch:
inputs:
environment:
description: "Specify the environment to clean up (DEV, STAGING, PROD)"
required: true
default: DEV

jobs:
terraform-destroy:
name: Destroy terraform resources
runs-on: ubuntu-latest

defaults:
run:
working-directory: infrastructure/terraform

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set Environment and Workspace
run: |
BASE_NAME=${{ secrets.TF_CLOUD_WORKSPACE_BASE_NAME }}

# Set environment from input
ENVIRONMENT=${{ github.event.inputs.environment }}

# Save environment and workspace to GitHub environment variables
echo "ENVIRONMENT=$ENVIRONMENT" >> $GITHUB_ENV
TF_WORKSPACE="${BASE_NAME}-${ENVIRONMENT,,}"
echo "TF_WORKSPACE=$TF_WORKSPACE" >> $GITHUB_ENV

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

- name: Terraform Init
env:
TF_CLOUD_ORGANIZATION: ${{ secrets.TF_CLOUD_ORGANIZATION }}
TF_WORKSPACE: ${{ env.TF_WORKSPACE }}
run: terraform init

- name: Terraform Destroy Plan
env:
TF_CLOUD_ORGANIZATION: ${{ secrets.TF_CLOUD_ORGANIZATION }}
TF_WORKSPACE: ${{ env.TF_WORKSPACE }}
run: terraform plan -destroy -out=tfplan-destroy

- name: Confirm and Destroy Resources
env:
TF_CLOUD_ORGANIZATION: ${{ secrets.TF_CLOUD_ORGANIZATION }}
TF_WORKSPACE: ${{ env.TF_WORKSPACE }}
run: terraform apply -auto-approve tfplan-destroy
90 changes: 90 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Deploy to Azure with Terraform

on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
workflow_dispatch: # Allow manual workflow dispatch
inputs:
environment:
description: "Specify the environment (DEV, STAGING, PROD)" # Input for manual trigger
required: true
default: DEV

jobs:
terraform:
name: Apply terraform resources
runs-on: ubuntu-latest

defaults:
run:
working-directory: infrastructure/terraform # Set the default working directory

steps:
# Step 1: Checkout the code repository
- name: Checkout Code
uses: actions/checkout@v4

# Step 2: Set Environment and Workspace
- name: Set Environment and Workspace
run: |
# Retrieve the base name for the Terraform workspace
BASE_NAME=${{ secrets.TF_CLOUD_WORKSPACE_BASE_NAME }}

# Determine the environment based on the event type
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
# Use the environment input for manual workflow dispatch
ENVIRONMENT=${{ github.event.inputs.environment }}
elif [[ "${{ github.event_name }}" == "push" ]] && [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master" ]]; then
# Set environment to STAGING for push to main/master
ENVIRONMENT="STAGING"
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Set environment to DEV for pull request
ENVIRONMENT="DEV"
else
echo "Unknown environment. Exiting."
exit 1
fi

# Save the environment and workspace to GitHub environment variables
echo "ENVIRONMENT=$ENVIRONMENT" >> $GITHUB_ENV
TF_WORKSPACE="${BASE_NAME}-${ENVIRONMENT,,}" # Generate workspace name in lowercase
echo "TF_WORKSPACE=$TF_WORKSPACE" >> $GITHUB_ENV

# Step 3: Setup Terraform
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.9.8
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

# Step 4: Initialize Terraform
- name: Terraform Init
env:
TF_CLOUD_ORGANIZATION: ${{ secrets.TF_CLOUD_ORGANIZATION }}
TF_WORKSPACE: ${{ env.TF_WORKSPACE }}
run: terraform init

# Step 5: Check Terraform format
- name: Terraform Format
run: terraform fmt -check

# Step 6: Plan Terraform changes
- name: Terraform Plan
env:
TF_CLOUD_ORGANIZATION: ${{ secrets.TF_CLOUD_ORGANIZATION }}
TF_WORKSPACE: ${{ env.TF_WORKSPACE }}
run: terraform plan

# Step 7: Apply Terraform changes
- name: Terraform Apply
if: ${{ github.event_name == 'workflow_dispatch' || env.ENVIRONMENT != 'DEV' }}
env:
TF_CLOUD_ORGANIZATION: ${{ secrets.TF_CLOUD_ORGANIZATION }}
TF_WORKSPACE: ${{ env.TF_WORKSPACE }}
run: terraform apply -auto-approve -input=false
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc
Binary file added assets/github-actions-secrets.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 154 additions & 0 deletions docs/infrastructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Azure Setup Instructions

This document provides step-by-step instructions to set up Azure for the project and obtain the necessary data for GitHub Actions secrets.

---

## **Step 1: Log in to Azure**

1. Install the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli).
2. Log in to an Azure account:
```bash
az login
```
- A browser window will open for authentication.
- After successful login, a JSON output of account details will be displayed.

---

## **Step 2: Check the Subscription**

1. List all available subscriptions:
```bash
az account list --output table
```
2. Set the subscription to use:
```bash
az account set --subscription <subscription-id>
```
3. Verify the active subscription:
```bash
az account show
```
Output:
```json
{
"id": "<subscription-id>",
"name": "<subscription-name>",
"tenantId": "<tenant-id>",
"user": {
"name": "[email protected]",
"type": "user"
}
}
```

- **`id`**: This is the `subscriptionId`.
- **`tenantId`**: This is the `tenantId`.

---

## **Step 3: Manually Create a Service Principal**

Service Principals are essential for authenticating GitHub Actions with Azure. For security and control, it is recommended to create Service Principals manually, especially for Production environments.

### **Why Configure Manually?**
1. **Security**: Manual creation ensures that secrets (e.g., client secrets) are not exposed in logs or Terraform state files.
2. **Control**: Allows verification of roles and permissions before integration with automation.
3. **Best Practices**: Minimizes the risk of misconfiguration during automated deployments for Production environments.
4. **Terraform Limitation**: Terraform stores sensitive data like client secrets in its state file, which can pose a security risk.

### **How to Create a Service Principal Manually**

1. Create a Service Principal for the project environment (e.g., `DEV`, `STAGING`, `PROD`):
```bash
az ad sp create-for-rbac --name "<MyServicePrincipal>-DEV" --role="Contributor" --scopes="/subscriptions/<subscription-id>" --sdk-auth
```
Example output:
```json
{
"clientId": "<client-id>",
"clientSecret": "<client-secret>",
"subscriptionId": "<subscription-id>",
"tenantId": "<tenant-id>",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
```
2. Save the output securely. The following fields will be needed for GitHub Actions secrets:
- `clientId` → `AZURE_CLIENT_ID`
- `clientSecret` → `AZURE_CLIENT_SECRET`
- `subscriptionId` → `AZURE_SUBSCRIPTION_ID`
- `tenantId` → `AZURE_TENANT_ID`

3. Repeat this process for each environment (`DEV`, `STAGING`, `PROD`) with unique names like `MyServicePrincipal-STAGING` and `MyServicePrincipal-PROD`.

---

## **Step 4: Add Secrets to GitHub Actions**

1. Go to the GitHub repository.
2. Navigate to **Settings** → **Secrets and variables** → **Actions**.
3. Add the following secrets for each environment (e.g., `DEV`, `STAGING`, `PROD`):
- **`AZURE_CLIENT_ID`**: From the `clientId` field.
- **`AZURE_CLIENT_SECRET`**: From the `clientSecret` field.
- **`AZURE_SUBSCRIPTION_ID`**: From the `subscriptionId` field.
- **`AZURE_TENANT_ID`**: From the `tenantId` field.

For example:
- `DEV_AZURE_CLIENT_ID`
- `DEV_AZURE_CLIENT_SECRET`
- `DEV_AZURE_SUBSCRIPTION_ID`
- `DEV_AZURE_TENANT_ID`

Repeat the process for `STAGING` and `PROD`.

![alt text](/assets/github-actions-secrets.png)

---

## **Step 5: (Optional) Reset the Client Secret**

If the `clientSecret` is lost, reset it with the following command:
```bash
az ad sp credential reset --name "MyServicePrincipal-DEV"
```
Example output:
```json
{
"appId": "<client-id>",
"password": "<new-client-secret>",
"tenant": "<tenant-id>"
}
```
- Use the new `password` as `AZURE_CLIENT_SECRET`.

---

## **Step 6: Obtain OpenAI API Keys (if applicable)**

1. Log in to the [OpenAI Dashboard](https://platform.openai.com/).
2. Navigate to **API Keys**.
3. Create API keys for each environment (`DEV`, `STAGING`, `PROD`).
4. Add the keys to GitHub Actions secrets:
- `DEV_OPENAI_API_KEY`
- `STAGING_OPENAI_API_KEY`
- `PROD_OPENAI_API_KEY`

---

## **Verification**

1. Ensure all secrets are added to GitHub:
- Go to **Settings** → **Secrets and variables** → **Actions**.
- Verify the presence of all required secrets.
2. Run the GitHub Actions workflow and confirm it uses the correct secrets.

---

The Azure setup is now complete, and the necessary data has been added to GitHub Actions secrets.

49 changes: 49 additions & 0 deletions infrastructure/terraform/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
terraform {
cloud {}
}

# Module for creating a Resource Group
module "resource_group" {
source = "./modules/resource_group"
environment = var.environment
resource_group_name = var.resource_group_name
location = var.location
depends_on = []
}

# Module for creating a Storage Account
module "storage_account" {
source = "./modules/storage_account"
environment = var.environment
storage_account_name = var.storage_account_name
resource_group_name = module.resource_group.name
location = module.resource_group.location
depends_on = [module.resource_group]
}

module "app_service_plan" {
source = "./modules/app_service_plan"
environment = var.environment
service_plan_name = var.service_plan_name
resource_group_name = module.resource_group.name
location = module.resource_group.location
depends_on = [module.resource_group]
}

# Module for creating an Azure Function App
module "function_app" {
source = "./modules/function_app"
environment = var.environment
function_app_name = var.function_app_name
resource_group_name = module.resource_group.name
location = module.resource_group.location
service_plan_id = module.app_service_plan.id
storage_account_name = module.storage_account.name
storage_account_access_key = module.storage_account.primary_access_key
depends_on = [
module.resource_group,
module.storage_account,
module.app_service_plan,
]
OPENAI_API_KEY = var.OPENAI_API_KEY
}
Loading
Loading